Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partial base64 in-place encoding support #2166

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions lib/inc/drogon/utils/Utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,36 @@ DROGON_EXPORT std::set<std::string> 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;
Expand Down Expand Up @@ -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;
Expand Down
50 changes: 32 additions & 18 deletions lib/src/Utilities.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down
25 changes: 21 additions & 4 deletions lib/tests/unittests/Base64Test.cc
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include <drogon/utils/Utilities.h>
#include <drogon/drogon_test.h>
#include <string>
#include <string_view>

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==");
Expand All @@ -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;
Expand All @@ -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==");
Expand All @@ -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");
Expand Down
Loading