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/name-component.cpp b/src/name-component.cpp
index 576fac4..dbe9998 100644
--- a/src/name-component.cpp
+++ b/src/name-component.cpp
@@ -102,16 +102,10 @@
"(expected sha256 in hex encoding)");
try {
- std::string value;
- CryptoPP::StringSource(reinterpret_cast<const uint8_t*>(trimmedString.c_str()) +
- getSha256DigestUriPrefix().size(),
- trimmedString.size () - getSha256DigestUriPrefix().size(), true,
- new CryptoPP::HexDecoder(new CryptoPP::StringSink(value)));
-
- return fromImplicitSha256Digest(reinterpret_cast<const uint8_t*>(value.c_str()),
- value.size());
+ trimmedString.erase(0, getSha256DigestUriPrefix().size());
+ return fromImplicitSha256Digest(fromHex(trimmedString));
}
- catch (CryptoPP::Exception& e) {
+ catch (StringHelperError& e) {
throw Error("Cannot convert to a ImplicitSha256DigestComponent (invalid hex encoding)");
}
}
@@ -139,8 +133,7 @@
if (type() == tlv::ImplicitSha256DigestComponent) {
result << getSha256DigestUriPrefix();
- CryptoPP::StringSource(value(), value_size(), true,
- new CryptoPP::HexEncoder(new CryptoPP::FileSink(result), false));
+ printHex(result, value(), value_size(), false);
}
else {
const uint8_t* value = this->value();
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