diff --git a/lib/inc/drogon/utils/Utilities.h b/lib/inc/drogon/utils/Utilities.h index bcbd0759..45704107 100644 --- a/lib/inc/drogon/utils/Utilities.h +++ b/lib/inc/drogon/utils/Utilities.h @@ -60,6 +60,9 @@ namespace utils /// Determine if the string is an integer DROGON_EXPORT bool isInteger(const std::string &str); +/// Determine if the string is base64 encoded +DROGON_EXPORT bool isBase64(const std::string &str); + /// Generate random a string /** * @param length The string length @@ -98,15 +101,46 @@ DROGON_EXPORT std::set splitStringToSet( /// Get UUID string. DROGON_EXPORT std::string getUuid(); +/// Get the encoded length of base64. +DROGON_EXPORT size_t base64EncodedLength(unsigned int in_len, + bool padded = true); + /// Encode the string to base64 format. DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode, unsigned int in_len, - bool url_safe = false); + bool url_safe = false, + bool padded = true); + +/// Encode the string to base64 format. +inline std::string base64Encode(string_view data, + bool url_safe = false, + bool padded = true) +{ + return base64Encode((unsigned char *)data.data(), + data.size(), + url_safe, + padded); +} + +/// Encode the string to base64 format with no padding. +DROGON_EXPORT std::string base64EncodeUnpadded( + const unsigned char *bytes_to_encode, + unsigned int in_len, + bool url_safe = false); + +/// Encode the string to base64 format with no padding. +inline std::string base64EncodeUnpadded(string_view data, bool url_safe = false) +{ + return base64Encode(data, url_safe, false); +} + +/// Get the decoded length of base64. +DROGON_EXPORT size_t base64DecodedLength(unsigned int in_len); /// Decode the base64 format string. -DROGON_EXPORT std::string base64Decode(const std::string &encoded_string); +DROGON_EXPORT std::string base64Decode(string_view encoded_string); DROGON_EXPORT std::vector base64DecodeToVector( - const std::string &encoded_string); + string_view encoded_string); /// Check if the string need decoding DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end); diff --git a/lib/src/Utilities.cc b/lib/src/Utilities.cc index 56f08ba6..b6630a82 100644 --- a/lib/src/Utilities.cc +++ b/lib/src/Utilities.cc @@ -149,6 +149,14 @@ bool isInteger(const std::string &str) return true; } +bool isBase64(const std::string &str) +{ + for (auto c : str) + if (!isBase64(c)) + return false; + return true; +} + std::string genRandomString(int length) { static const char char_space[] = @@ -387,11 +395,18 @@ std::string getUuid() #endif } +size_t base64EncodedLength(unsigned int in_len, bool padded) +{ + return padded ? ((in_len + 3 - 1) / 3) * 4 : (in_len * 8 + 6 - 1) / 6; +} + std::string base64Encode(const unsigned char *bytes_to_encode, unsigned int in_len, - bool url_safe) + bool url_safe, + bool padded) { std::string ret; + ret.reserve(base64EncodedLength(in_len, padded)); int i = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; @@ -428,27 +443,45 @@ std::string base64Encode(const unsigned char *bytes_to_encode, ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; - for (int j = 0; (j < i + 1); ++j) + for (int j = 0; (j <= i); ++j) ret += charSet[char_array_4[j]]; - while ((i++ < 3)) - ret += '='; + if (padded) + while ((++i < 4)) + ret += '='; } return ret; } -std::vector base64DecodeToVector(const std::string &encoded_string) +std::string base64EncodeUnpadded(const unsigned char *bytes_to_encode, + unsigned int in_len, + bool url_safe) +{ + return base64Encode(bytes_to_encode, in_len, url_safe, false); +} + +size_t base64DecodedLength(unsigned int in_len) +{ + return (in_len * 3) / 4; +} + +std::vector base64DecodeToVector(string_view encoded_string) { auto in_len = encoded_string.size(); int i = 0; int in_{0}; char char_array_4[4], char_array_3[3]; std::vector ret; - ret.reserve(in_len); + ret.reserve(base64DecodedLength(in_len)); - while (in_len-- && (encoded_string[in_] != '=') && - isBase64(encoded_string[in_])) + while (in_len-- && (encoded_string[in_] != '=')) { + if (!isBase64(encoded_string[in_])) + { + ++in_; + continue; + } + char_array_4[i++] = encoded_string[in_]; ++in_; if (i == 4) @@ -486,24 +519,31 @@ std::vector base64DecodeToVector(const std::string &encoded_string) ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (int j = 0; (j < i - 1); ++j) + --i; + for (int j = 0; (j < i); ++j) ret.push_back(char_array_3[j]); } return ret; } -std::string base64Decode(const std::string &encoded_string) +std::string base64Decode(string_view encoded_string) { auto in_len = encoded_string.size(); int i = 0; int in_{0}; unsigned char char_array_4[4], char_array_3[3]; std::string ret; + ret.reserve(base64DecodedLength(in_len)); - while (in_len-- && (encoded_string[in_] != '=') && - isBase64(encoded_string[in_])) + while (in_len-- && (encoded_string[in_] != '=')) { + if (!isBase64(encoded_string[in_])) + { + ++in_; + continue; + } + char_array_4[i++] = encoded_string[in_]; ++in_; if (i == 4) @@ -540,7 +580,8 @@ std::string base64Decode(const std::string &encoded_string) ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - for (int j = 0; (j < i - 1); ++j) + --i; + for (int j = 0; (j < i); ++j) ret += char_array_3[j]; } diff --git a/lib/tests/unittests/Base64Test.cc b/lib/tests/unittests/Base64Test.cc index b26e37d0..b09deec2 100644 --- a/lib/tests/unittests/Base64Test.cc +++ b/lib/tests/unittests/Base64Test.cc @@ -5,12 +5,34 @@ DROGON_TEST(Base64) { std::string in{"drogon framework"}; - auto encoded = drogon::utils::base64Encode((const unsigned char *)in.data(), - (unsigned int)in.length()); + auto encoded = drogon::utils::base64Encode(in); auto decoded = drogon::utils::base64Decode(encoded); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); CHECK(decoded == in); + SUBSECTION(InvalidChars) + { + auto decoded = + drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw*=*="); + CHECK(decoded == in); + } + + SUBSECTION(InvalidCharsNoPadding) + { + auto decoded = + drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw**"); + CHECK(decoded == in); + } + + SUBSECTION(Unpadded) + { + std::string in{"drogon framework"}; + auto encoded = drogon::utils::base64EncodeUnpadded(in); + auto decoded = drogon::utils::base64Decode(encoded); + CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw"); + CHECK(decoded == in); + } + SUBSECTION(LongString) { std::string in; @@ -19,12 +41,9 @@ DROGON_TEST(Base64) { in.append(1, char(i)); } - auto out = drogon::utils::base64Encode((const unsigned char *)in.data(), - (unsigned int)in.length()); + auto out = drogon::utils::base64Encode(in); auto out2 = drogon::utils::base64Decode(out); - auto encoded = - drogon::utils::base64Encode((const unsigned char *)in.data(), - (unsigned int)in.length()); + auto encoded = drogon::utils::base64Encode(in); auto decoded = drogon::utils::base64Decode(encoded); CHECK(decoded == in); } @@ -32,15 +51,21 @@ DROGON_TEST(Base64) SUBSECTION(URLSafe) { std::string in{"drogon framework"}; - auto encoded = - drogon::utils::base64Encode((const unsigned char *)in.data(), - (unsigned int)in.length(), - true); + auto encoded = drogon::utils::base64Encode(in, true); auto decoded = drogon::utils::base64Decode(encoded); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); CHECK(decoded == in); } + SUBSECTION(UnpaddedURLSafe) + { + std::string in{"drogon framework"}; + auto encoded = drogon::utils::base64EncodeUnpadded(in, true); + auto decoded = drogon::utils::base64Decode(encoded); + CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw"); + CHECK(decoded == in); + } + SUBSECTION(LongURLSafe) { std::string in; @@ -49,10 +74,7 @@ DROGON_TEST(Base64) { in.append(1, char(i)); } - auto encoded = - drogon::utils::base64Encode((const unsigned char *)in.data(), - (unsigned int)in.length(), - true); + auto encoded = drogon::utils::base64Encode(in, true); auto decoded = drogon::utils::base64Decode(encoded); CHECK(decoded == in); }