diff --git a/lib/inc/drogon/utils/Utilities.h b/lib/inc/drogon/utils/Utilities.h index dc0f71837c..035b66595b 100644 --- a/lib/inc/drogon/utils/Utilities.h +++ b/lib/inc/drogon/utils/Utilities.h @@ -128,26 +128,36 @@ DROGON_EXPORT std::set splitStringToSet( DROGON_EXPORT std::string getUuid(bool lowercase = true); /// Get the encoded length of base64. -constexpr size_t base64EncodedLength(size_t in_len, bool padded = true) +constexpr size_t base64EncodedLength(size_t inLen, bool padded = true) { - return padded ? ((in_len + 3 - 1) / 3) * 4 : (in_len * 8 + 6 - 1) / 6; + return padded ? ((inLen + 3 - 1) / 3) * 4 : (inLen * 8 + 6 - 1) / 6; } /// Encode the string to base64 format. +/// Supports in-place encoding up to 12 input bytes. +/// To encode more than 12 input bytes, use the `base64EncodeInPlace` overload. DROGON_EXPORT void base64Encode(const unsigned char *bytesToEncode, size_t inLen, unsigned char *outputBuffer, bool urlSafe = false, bool padded = true); +/// Encode the string to base64 format. +/// Uses minimal dynamic allocation for encoding. +/// TODO: To be implemented +/*DROGON_EXPORT void base64EncodeInPlace(const unsigned char *bytesToEncode, + size_t inLen, + unsigned char *outputBuffer, + bool urlSafe = false, + bool padded = true);*/ + /// Encode the string to base64 format. inline std::string base64Encode(const unsigned char *bytesToEncode, size_t inLen, bool urlSafe = false, bool padded = true) { - std::string ret; - ret.resize(base64EncodedLength(inLen, padded)); + std::string ret(base64EncodedLength(inLen, padded), uint8_t(0)); base64Encode( bytesToEncode, inLen, (unsigned char *)ret.data(), urlSafe, padded); return ret; @@ -204,8 +214,7 @@ DROGON_EXPORT size_t base64Decode(const char *encodedString, inline std::string base64Decode(std::string_view encodedString) { auto inLen = encodedString.size(); - std::string ret; - ret.resize(base64DecodedLength(inLen)); + std::string ret(base64DecodedLength(inLen), uint8_t(0)); ret.resize( base64Decode(encodedString.data(), inLen, (unsigned char *)ret.data())); return ret; diff --git a/lib/src/Utilities.cc b/lib/src/Utilities.cc index 6d9fc46dcd..6bbca8cce6 100644 --- a/lib/src/Utilities.cc +++ b/lib/src/Utilities.cc @@ -446,34 +446,48 @@ void base64Encode(const unsigned char *bytesToEncode, bool urlSafe, bool padded) { - int i = 0; + // If buffers differ, or we are doing in-place encoding with an input of + // less than or equal to 12 bytes + assert(bytesToEncode != outputBuffer || inLen <= 12); + + int i = 0, j = 1; unsigned char charArray3[3]; unsigned char charArray4[4]; const std::string_view charSet = urlSafe ? urlBase64Chars : base64Chars; size_t a = 0; - while (inLen--) + for (; inLen >= 3; inLen -= 3) { - charArray3[i++] = *(bytesToEncode++); - if (i == 3) - { - charArray4[0] = (charArray3[0] & 0xfc) >> 2; - charArray4[1] = - ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); - charArray4[2] = - ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); - charArray4[3] = charArray3[2] & 0x3f; - - for (i = 0; (i < 4); ++i, ++a) - outputBuffer[a] = charSet[charArray4[i]]; - i = 0; - } + for (; i < 3; ++i) + charArray3[i] = *(bytesToEncode++); + + charArray4[0] = (charArray3[0] & 0xfc) >> 2; + charArray4[1] = + ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4); + charArray4[2] = + ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); + charArray4[3] = charArray3[2] & 0x3f; + + for (i = 0; i < j; ++i) + charArray3[i] = *(bytesToEncode++); + + for (i = 0; (i < 4); ++i, ++a) + outputBuffer[a] = charSet[charArray4[i]]; + + i = j; + j = (j + 1) & + (4 - 1); // This is to avoid an if statement: if(j == 4)j = 0; } + for (; i < inLen; ++i) + charArray3[i] = *(bytesToEncode++); + i = inLen; // This is needed because in some cases `i` could be greater + // than `inLen` + if (i) { - for (int j = i; j < 3; ++j) + for (j = i; j < 3; ++j) charArray3[j] = '\0'; charArray4[0] = (charArray3[0] & 0xfc) >> 2; @@ -483,7 +497,7 @@ void base64Encode(const unsigned char *bytesToEncode, ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6); charArray4[3] = charArray3[2] & 0x3f; - for (int j = 0; (j <= i); ++j, ++a) + for (j = 0; j <= i; ++j, ++a) outputBuffer[a] = charSet[charArray4[j]]; if (padded) diff --git a/lib/tests/unittests/Base64Test.cc b/lib/tests/unittests/Base64Test.cc index b09deec24a..5bdfc56e2a 100644 --- a/lib/tests/unittests/Base64Test.cc +++ b/lib/tests/unittests/Base64Test.cc @@ -1,10 +1,11 @@ #include #include #include +#include DROGON_TEST(Base64) { - std::string in{"drogon framework"}; + constexpr std::string_view in = "drogon framework"; auto encoded = drogon::utils::base64Encode(in); auto decoded = drogon::utils::base64Decode(encoded); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); @@ -26,13 +27,31 @@ DROGON_TEST(Base64) 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(InPlace12) + { + constexpr std::string_view in = "base64encode"; + std::string encoded(drogon::utils::base64EncodedLength(in.size()), + '\0'); + std::memcpy(encoded.data(), in.data(), in.size()); + drogon::utils::base64Encode((const unsigned char *)encoded.data(), + in.size(), + (unsigned char *)encoded.data()); + auto decoded = drogon::utils::base64Decode(encoded); + CHECK(encoded == "YmFzZTY0ZW5jb2Rl"); + CHECK(decoded == in); + + // In-place decoding + encoded.resize(drogon::utils::base64Decode( + encoded.data(), encoded.size(), (unsigned char *)encoded.data())); + CHECK(encoded == in); + } + SUBSECTION(LongString) { std::string in; @@ -50,7 +69,6 @@ DROGON_TEST(Base64) SUBSECTION(URLSafe) { - std::string in{"drogon framework"}; auto encoded = drogon::utils::base64Encode(in, true); auto decoded = drogon::utils::base64Decode(encoded); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); @@ -59,7 +77,6 @@ DROGON_TEST(Base64) SUBSECTION(UnpaddedURLSafe) { - std::string in{"drogon framework"}; auto encoded = drogon::utils::base64EncodeUnpadded(in, true); auto decoded = drogon::utils::base64Decode(encoded); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");