util: Improvements of string helpers

Improvements include:
- test cases for every string helper
- a new `printHex` helper to output hex conversion directly to std::ostream
- new `toHex` and `printHex` helpers that accept Buffer as input parameter
- a new `fromHex` helper to convert the supplied string to shared_ptr<const Buffer>
- replaced uses of CryptoPP routines with `toHex` and `fromHex` helpers where applicable

Change-Id: I092c9fa8fd21c413b53ea5624b82f769287bb42c
Refs: #3006
diff --git a/src/util/digest.cpp b/src/util/digest.cpp
index 664df2b..d5bb312 100644
--- a/src/util/digest.cpp
+++ b/src/util/digest.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "digest.hpp"
+#include "string-helper.hpp"
 #include <sstream>
 
 namespace ndn {
@@ -158,11 +159,8 @@
 std::ostream&
 operator<<(std::ostream& os, Digest<Hash>& digest)
 {
-  using namespace CryptoPP;
-
-  std::string output;
   ConstBufferPtr buffer = digest.computeDigest();
-  StringSource(buffer->buf(), buffer->size(), true, new HexEncoder(new FileSink(os)));
+  printHex(os, buffer->buf(), buffer->size());
 
   return os;
 }
diff --git a/src/util/string-helper.cpp b/src/util/string-helper.cpp
new file mode 100644
index 0000000..fc0e1a1
--- /dev/null
+++ b/src/util/string-helper.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "string-helper.hpp"
+#include "../encoding/buffer-stream.hpp"
+#include "../security/cryptopp.hpp"
+
+#include <sstream>
+#include <iomanip>
+
+#include <boost/algorithm/string/trim.hpp>
+
+namespace ndn {
+
+void
+printHex(std::ostream& os, const uint8_t* buffer, size_t length, bool isUpperCase/* = true*/)
+{
+  if (buffer == nullptr || length == 0)
+    return;
+
+  auto newFlags = std::ios::hex;
+  if (isUpperCase) {
+    newFlags |= std::ios::uppercase;
+  }
+  auto oldFlags = os.flags(newFlags);
+  auto oldFill = os.fill('0');
+  for (size_t i = 0; i < length; ++i) {
+    os << std::setw(2) << static_cast<unsigned int>(buffer[i]);
+  }
+  os.fill(oldFill);
+  os.flags(oldFlags);
+}
+
+void
+printHex(std::ostream& os, const Buffer& buffer, bool isUpperCase/* = true*/)
+{
+  return printHex(os, buffer.buf(), buffer.size(), isUpperCase);
+}
+
+std::string
+toHex(const uint8_t* buffer, size_t length, bool isUpperCase/* = true*/)
+{
+  if (buffer == nullptr || length == 0)
+    return "";
+
+  std::ostringstream result;
+  printHex(result, buffer, length, isUpperCase);
+  return result.str();
+}
+
+std::string
+toHex(const Buffer& buffer, bool isUpperCase/* = true*/)
+{
+  return toHex(buffer.buf(), buffer.size(), isUpperCase);
+}
+
+int
+fromHexChar(uint8_t c)
+{
+  if (c >= '0' && c <= '9')
+    return c - '0';
+  else if (c >= 'A' && c <= 'F')
+    return c - 'A' + 0xA;
+  else if (c >= 'a' && c <= 'f')
+    return c - 'a' + 0xA;
+  else
+    return -1;
+}
+
+shared_ptr<const Buffer>
+fromHex(const std::string& hexString)
+{
+  if (hexString.size() % 2 != 0) {
+    throw StringHelperError("Invalid number of characters in the supplied hex string");
+  }
+
+  using namespace CryptoPP;
+
+  OBufferStream os;
+  StringSource(hexString, true, new HexDecoder(new FileSink(os)));
+  shared_ptr<const Buffer> buffer = os.buf();
+
+  if (buffer->size() * 2 != hexString.size()) {
+    throw StringHelperError("The supplied hex string contains non-hex characters");
+  }
+
+  return buffer;
+}
+
+void
+trimLeft(std::string& str)
+{
+  boost::algorithm::trim_left(str);
+}
+
+void
+trimRight(std::string& str)
+{
+  boost::algorithm::trim_right(str);
+}
+
+void
+trim(std::string& str)
+{
+  boost::algorithm::trim(str);
+}
+
+std::string
+unescape(const std::string& str)
+{
+  std::ostringstream result;
+
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (str[i] == '%' && i + 2 < str.size()) {
+      int hi = fromHexChar(str[i + 1]);
+      int lo = fromHexChar(str[i + 2]);
+
+      if (hi < 0 || lo < 0)
+        // Invalid hex characters, so just keep the escaped string.
+        result << str[i] << str[i + 1] << str[i + 2];
+      else
+        result << static_cast<char>((hi << 4) | lo);
+
+      // Skip ahead past the escaped value.
+      i += 2;
+    }
+    else
+      // Just copy through.
+      result << str[i];
+  }
+
+  return result.str();
+}
+
+} // namespace ndn
diff --git a/src/util/string-helper.hpp b/src/util/string-helper.hpp
index f9f37e4..e0ba8a6 100644
--- a/src/util/string-helper.hpp
+++ b/src/util/string-helper.hpp
@@ -17,131 +17,126 @@
  * <http://www.gnu.org/licenses/>.
  *
  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- *
- * @author Jeff Thompson <jefft0@remap.ucla.edu>
  */
 
 #ifndef NDN_STRING_HELPER_HPP
 #define NDN_STRING_HELPER_HPP
 
-#include <string>
-#include <sstream>
+#include "../common.hpp"
+#include "../encoding/buffer.hpp"
 
 namespace ndn {
 
-static const char* WHITESPACE_CHARS = " \n\r\t";
+class StringHelperError : public std::invalid_argument
+{
+public:
+  explicit
+  StringHelperError(const std::string& what)
+    : std::invalid_argument(what)
+  {
+  }
+};
+
+/**
+ * @brief Output the hex representation of the bytes in array to the output stream @p os
+ *
+ * @param os Output stream
+ * @param buffer The array of bytes
+ * @param length Size of the array
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ *
+ * @example
+ *     printHex(std::cout, "Hello, World!") outputs "48656C6C6F2C20776F726C6421"
+ *     printHex(std::cout, "Hello, World!", false) outputs "48656c6c6f2c20776f726c6421"
+ *
+ * Each octet is always represented as two hex characters ("00" for octet==0).
+ *
+ * The output string is a continuous sequence of hex characters without any whitespace separators.
+ */
+void
+printHex(std::ostream& os, const uint8_t* buffer, size_t length, bool isUpperCase = true);
+
+/**
+ * @brief Output the hex representation of the bytes in the @p buffer to the output stream @p os
+ *
+ * @param os Output stream
+ * @param buffer The array of bytes
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ */
+void
+printHex(std::ostream& os, const Buffer& buffer, bool isUpperCase = true);
 
 /**
  * @brief Return the hex representation of the bytes in array
  *
- * @param array The array of bytes
- * @param arraySize Size of the array
+ * @param buffer The array of bytes
+ * @param length Size of the array
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ *
+ * @example
+ *     toHex("Hello, World!") == "48656C6C6F2C20776F726C6421"
+ *     toHex("Hello, World!", false) == "48656c6c6f2c20776f726c6421"
+ *
+ * Each octet is always represented as two hex characters ("00" for octet==0).
+ *
+ * The output string is a continuous sequence of hex characters without any whitespace separators.
  */
-inline std::string
-toHex(const uint8_t* array, size_t arraySize)
-{
-  if (array == 0 || arraySize == 0)
-    return "";
+std::string
+toHex(const uint8_t* buffer, size_t length, bool isUpperCase = true);
 
-  std::ostringstream result;
-  result.flags(std::ios::hex | std::ios::uppercase);
-  for (size_t i = 0; i < arraySize; ++i) {
-    uint8_t x = array[i];
-    if (x < 16)
-      result << '0';
-    result << (unsigned int)x;
-  }
+/**
+ * @brief Return the hex representation of the bytes in the @p buffer to the output stream @p os
+ *
+ * @param buffer The array of bytes
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ */
+std::string
+toHex(const Buffer& buffer, bool isUpperCase = true);
 
-  return result.str();
-}
+/**
+ * @brief Convert the hex string to buffer
+ * @param hexString sequence of pairs of hex numbers (lower and upper case can be mixed)
+ *        without any whitespace separators (e.g., "48656C6C6F2C20776F726C6421")
+ * @throw StringHelperError if input is invalid
+ */
+shared_ptr<const Buffer>
+fromHex(const std::string& hexString);
 
 /**
  * @brief Modify str in place to erase whitespace on the left
  */
-inline void
-trimLeft(std::string& str)
-{
-  size_t found = str.find_first_not_of(WHITESPACE_CHARS);
-  if (found != std::string::npos) {
-    if (found > 0)
-      str.erase(0, found);
-  }
-  else
-    // All whitespace
-    str.clear();
-}
+void
+trimLeft(std::string& str);
 
 /**
  * @brief Modify str in place to erase whitespace on the right
  */
-inline void
-trimRight(std::string& str)
-{
-  size_t found = str.find_last_not_of(WHITESPACE_CHARS);
-  if (found != std::string::npos) {
-    if (found + 1 < str.size())
-      str.erase(found + 1);
-  }
-  else
-    // All whitespace
-    str.clear();
-}
+void
+trimRight(std::string& str);
 
 /**
  * @brief Modify str in place to erase whitespace on the left and right
  */
-inline void
-trim(std::string& str)
-{
-  trimLeft(str);
-  trimRight(str);
-}
+void
+trim(std::string& str);
 
 /**
  * @brief Convert the hex character to an integer from 0 to 15, or -1 if not a hex character
  */
-inline int
-fromHexChar(uint8_t c)
-{
-  if (c >= '0' && c <= '9')
-    return (int)c - (int)'0';
-  else if (c >= 'A' && c <= 'F')
-    return (int)c - (int)'A' + 10;
-  else if (c >= 'a' && c <= 'f')
-    return (int)c - (int)'a' + 10;
-  else
-    return -1;
-}
+int
+fromHexChar(uint8_t c);
 
 /**
- * @brief Return a copy of str, converting each escaped "%XX" to the char value
+ * @brief Decode a percent-encoded string
+ * @see RFC 3986 section 2
+ *
+ * When % is not followed by two hex characters, the output is not transformed.
+ *
+ * @example unescape("hello%20world") == "hello world"
+ * @example unescape("hello%20world%FooBar") == "hello world%FooBar"
  */
-inline std::string
-unescape(const std::string& str)
-{
-  std::ostringstream result;
-
-  for (size_t i = 0; i < str.size(); ++i) {
-    if (str[i] == '%' && i + 2 < str.size()) {
-      int hi = fromHexChar(str[i + 1]);
-      int lo = fromHexChar(str[i + 2]);
-
-      if (hi < 0 || lo < 0)
-        // Invalid hex characters, so just keep the escaped string.
-        result << str[i] << str[i + 1] << str[i + 2];
-      else
-        result << (uint8_t)(16 * hi + lo);
-
-      // Skip ahead past the escaped value.
-      i += 2;
-    }
-    else
-      // Just copy through.
-      result << str[i];
-  }
-
-  return result.str();
-}
+std::string
+unescape(const std::string& str);
 
 } // namespace ndn