From 5c2f5077f5885db6f100a76ee9b17b72381526ef Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 8 Aug 2023 10:55:37 -0500 Subject: [PATCH 1/2] GH-1461 Do not add or expect `=` at end of base64 encoded strings. --- libraries/libfc/src/variant.cpp | 7 ++++--- libraries/libfc/test/variant/test_variant.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/libfc/src/variant.cpp b/libraries/libfc/src/variant.cpp index da1e648da0..56705ad18e 100644 --- a/libraries/libfc/src/variant.cpp +++ b/libraries/libfc/src/variant.cpp @@ -490,7 +490,7 @@ std::string variant::as_string()const return *reinterpret_cast(this) ? "true" : "false"; case blob_type: if( get_blob().data.size() ) - return base64_encode( get_blob().data.data(), get_blob().data.size() ) + "="; + return base64_encode( get_blob().data.data(), get_blob().data.size() ); return std::string(); case null_type: return std::string(); @@ -533,10 +533,11 @@ blob variant::as_blob()const { const std::string& str = get_string(); if( str.size() == 0 ) return blob(); - if( str.back() == '=' ) - { + try { std::string b64 = base64_decode( get_string() ); return blob( { std::vector( b64.begin(), b64.end() ) } ); + } catch(const std::exception&) { + // unable to decode, just return the raw chars } return blob( { std::vector( str.begin(), str.end() ) } ); } diff --git a/libraries/libfc/test/variant/test_variant.cpp b/libraries/libfc/test/variant/test_variant.cpp index 827b420ed0..cfef4e50be 100644 --- a/libraries/libfc/test/variant/test_variant.cpp +++ b/libraries/libfc/test/variant/test_variant.cpp @@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE(variant_format_string_limited) const string target_result = format_prefix + a_short_list + " " + "{" + "\"b\":\"" + b_short_list + "\",\"c\":\"" + c_short_list + "\"}" + " " + "[\"" + d_short_list + "\",\"" + e_short_list + "\"]" + " " + - base64_encode( a_blob.data.data(), a_blob.data.size() ) + "=" + " " + + base64_encode( a_blob.data.data(), a_blob.data.size() ) + " " + g_short_list; BOOST_CHECK_EQUAL( result, target_result); From 0842972b1edad772e03d12973d7e7b16e6845d47 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 8 Aug 2023 10:56:39 -0500 Subject: [PATCH 2/2] GH-1461 Update base64 from upstream --- libraries/libfc/include/fc/crypto/base64.hpp | 9 +- libraries/libfc/src/crypto/base64.cpp | 392 ++++++++++++++----- libraries/libfc/test/test_base64.cpp | 134 ++++++- 3 files changed, 435 insertions(+), 100 deletions(-) diff --git a/libraries/libfc/include/fc/crypto/base64.hpp b/libraries/libfc/include/fc/crypto/base64.hpp index 9559214df1..d57e2d11e5 100644 --- a/libraries/libfc/include/fc/crypto/base64.hpp +++ b/libraries/libfc/include/fc/crypto/base64.hpp @@ -1,14 +1,15 @@ #pragma once #include +#include namespace fc { std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); inline std::string base64_encode(char const* bytes_to_encode, unsigned int in_len) { return base64_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64_encode( const std::string& enc ); -std::string base64_decode( const std::string& encoded_string); +std::string base64_encode( const std::string_view& enc ); +std::string base64_decode( const std::string_view& encoded_string); std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len); inline std::string base64url_encode(char const* bytes_to_encode, unsigned int in_len) { return base64url_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64url_encode( const std::string& enc ); -std::string base64url_decode( const std::string& encoded_string); +std::string base64url_encode( const std::string_view& enc ); +std::string base64url_decode( const std::string_view& encoded_string); } // namespace fc diff --git a/libraries/libfc/src/crypto/base64.cpp b/libraries/libfc/src/crypto/base64.cpp index 5ff4e68290..d5369dcbad 100644 --- a/libraries/libfc/src/crypto/base64.cpp +++ b/libraries/libfc/src/crypto/base64.cpp @@ -1,10 +1,14 @@ -#include -#include -#include -/* +/* base64.cpp and base64.h - Copyright (C) 2004-2008 René Nyffenegger + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + https://github.com/ReneNyffenegger/cpp-base64 + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages @@ -28,134 +32,332 @@ */ +#include +#include + +#include +#include +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + namespace fc { -static constexpr char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; -static constexpr char base64url_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; +// base64.hpp +// Added template return type + +std::string base64_encode (std::string const& s, bool url = false); +std::string base64_encode_pem (std::string const& s); +std::string base64_encode_mime(std::string const& s); + +std::string base64_decode(std::string const& s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const*, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode (std::string_view s, bool url = false); +std::string base64_encode_pem (std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +// base64.cpp + +// Includes performance improvement from unmerged PR: https://github.com/ReneNyffenegger/cpp-base64/pull/27 + + // + // Depending on the url parameter in base64_chars, one of + // two sets of base64 characters needs to be chosen. + // They differ in their last two characters. + // +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_"}; + +static const unsigned char from_base64_chars[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 62, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +static unsigned int pos_of_char(const unsigned char chr) { + // + // Return the position of chr within base64_encode() + // + + auto c = from_base64_chars[chr]; + if (c != 64) return c; + + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + // throw std::runtime_error("Input is not valid base64-encoded data."); + + // + // FC_ASSERT instead of throw runtime_error + // + FC_ASSERT(false, "encountered non-base64 character"); +} + +static std::string insert_linebreaks(std::string str, size_t distance) { + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return std::string{}; + } + + size_t pos = distance; -static_assert(sizeof(base64_chars) == sizeof(base64url_chars), "base64 and base64url must have the same amount of chars"); + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) { + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) { + return encode_with_line_breaks(s); +} -static inline void throw_on_nonbase64(unsigned char c, const char* const b64_chars) { - FC_ASSERT(isalnum(c) || (c == b64_chars[sizeof(base64_chars)-3]) || (c == b64_chars[sizeof(base64_chars)-2]), "encountered non-base64 character"); +template +static std::string encode(String s, bool url) { + return base64_encode(reinterpret_cast(s.data()), s.length(), url); } -std::string base64_encode_impl(unsigned char const* bytes_to_encode, unsigned int in_len, const char* const b64_chars) { +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { + + size_t len_encoded = (in_len +2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; + std::string ret; + ret.reserve(len_encoded); - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + unsigned int pos = 0; - for(i = 0; (i <4) ; i++) - ret += b64_chars[char_array_4[i]]; - i = 0; + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos+1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos+2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; } - } - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + return ret; +} + +template +static std::string decode(String const& encoded_string, bool remove_linebreaks) { + // + // decode(…) is templated so that it can be used with String = const std::string& + // or std::string_view (requires at least C++17) + // + + if (encoded_string.empty()) return std::string{}; + + if (remove_linebreaks) { - for (j = 0; (j < i + 1); j++) - ret += b64_chars[char_array_4[j]]; + std::string copy(encoded_string); - while((i++ < 3)) - ret += '='; + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); - } + return base64_decode(copy, false); + } - return ret; + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + // + // The approximate length (bytes) of the decoded string might be one or + // two bytes smaller, depending on the amount of trailing equal signs + // in the encoded string. This approximation is needed to reserve + // enough space in the string to be returned. + // + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + // + // Iterate over encoded input string in chunks. The size of all + // chunks except the last one is 4 bytes. + // + // The last chunk might be padded with equal signs or dots + // in order to make it 4 bytes in size as well, but this + // is not required as per RFC 2045. + // + // All chunks except the last one produce three output bytes. + // + // The last chunk produces at least one and up to three bytes. + // + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); + + if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos+2) != '=' && + encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); + ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); + + if ( ( pos + 3 < length_of_string ) && + encoded_string.at(pos+3) != '=' && + encoded_string.at(pos+3) != '.' + ) + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); + } + } + + pos += 4; + } + return ret; } -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode_impl(bytes_to_encode, in_len, base64_chars); +std::string base64_decode(std::string const& s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); } -std::string base64_encode( const std::string& enc ) { - char const* s = enc.c_str(); - return base64_encode( (unsigned char const*)s, enc.size() ); +std::string base64_encode(std::string const& s, bool url) { + return encode(s, url); } -std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode_impl(bytes_to_encode, in_len, base64url_chars); +std::string base64_encode_pem (std::string const& s) { + return encode_pem(s); } -std::string base64url_encode( const std::string& enc ) { - char const* s = enc.c_str(); - return base64url_encode( (unsigned char const*)s, enc.size() ); +std::string base64_encode_mime(std::string const& s) { + return encode_mime(s); } -std::string base64_decode_impl(std::string const& encoded_string, const char* const b64_chars) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// - while (in_len-- && encoded_string[in_] != '=') { - throw_on_nonbase64(encoded_string[in_], b64_chars); - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = strchr(b64_chars, char_array_4[i]) - b64_chars; +std::string base64_encode(std::string_view s, bool url) { + return encode(s, url); +} - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((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]; +std::string base64_encode_pem(std::string_view s) { + return encode_pem(s); +} - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } +std::string base64_encode_mime(std::string_view s) { + return encode_mime(s); +} - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; +std::string base64_decode(std::string_view s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} - for (j = 0; j <4; j++) - char_array_4[j] = strchr(b64_chars, char_array_4[j]) - b64_chars; +#endif // __cplusplus >= 201703L - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((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]; +// end base64.cpp - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } +// fc interface - return ret; +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode(bytes_to_encode, in_len, false); } - -std::string base64_decode(std::string const& encoded_string) { - return base64_decode_impl(encoded_string, base64_chars); +std::string base64_encode(const std::string_view& enc) { + return base64_encode(enc, false); +} +std::string base64_decode(const std::string_view& encoded_string) { + return base64_decode(encoded_string, false); } -std::string base64url_decode(std::string const& encoded_string) { - return base64_decode_impl(encoded_string, base64url_chars); +std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode(bytes_to_encode, in_len, true); +} +std::string base64url_encode(const std::string_view& enc) { + return base64_encode(enc, true); +} +std::string base64url_decode(const std::string_view& encoded_string) { + return base64_decode(encoded_string, true); } } // namespace fc - diff --git a/libraries/libfc/test/test_base64.cpp b/libraries/libfc/test/test_base64.cpp index ff6ac6a0ec..38fb4e8cf8 100644 --- a/libraries/libfc/test/test_base64.cpp +++ b/libraries/libfc/test/test_base64.cpp @@ -40,7 +40,9 @@ BOOST_AUTO_TEST_CASE(base64dec_extraequals) try { auto input = "YWJjMTIzJCYoKSc/tPUB+n5h========="s; auto expected_output = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; - BOOST_CHECK_EQUAL(expected_output, base64_decode(input)); + BOOST_CHECK_EXCEPTION(base64_decode(input), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("encountered non-base64 character") != std::string::npos; + }); } FC_LOG_AND_RETHROW(); BOOST_AUTO_TEST_CASE(base64dec_bad_stuff) try { @@ -51,4 +53,134 @@ BOOST_AUTO_TEST_CASE(base64dec_bad_stuff) try { }); } FC_LOG_AND_RETHROW(); +// tests from https://github.com/ReneNyffenegger/cpp-base64/blob/master/test.cpp +BOOST_AUTO_TEST_CASE(base64_cpp_base64_tests) try { + // + // Note: this file must be encoded in UTF-8 + // for the following test, otherwise, the test item + // fails. + // + const std::string orig = + "René Nyffenegger\n" + "http://www.renenyffenegger.ch\n" + "passion for data\n"; + + std::string encoded = base64_encode(reinterpret_cast(orig.c_str()), orig.length()); + std::string decoded = base64_decode(encoded); + + BOOST_CHECK_EQUAL(encoded, "UmVuw6kgTnlmZmVuZWdnZXIKaHR0cDovL3d3dy5yZW5lbnlmZmVuZWdnZXIuY2gKcGFzc2lvbiBmb3IgZGF0YQo="); + BOOST_CHECK_EQUAL(decoded, orig); + + // Test all possibilites of fill bytes (none, one =, two ==) + // References calculated with: https://www.base64encode.org/ + + std::string rest0_original = "abc"; + std::string rest0_reference = "YWJj"; + + std::string rest0_encoded = base64_encode(reinterpret_cast(rest0_original.c_str()), + rest0_original.length()); + std::string rest0_decoded = base64_decode(rest0_encoded); + + BOOST_CHECK_EQUAL(rest0_decoded, rest0_original); + BOOST_CHECK_EQUAL(rest0_reference, rest0_encoded); + + std::string rest1_original = "abcd"; + std::string rest1_reference = "YWJjZA=="; + + std::string rest1_encoded = base64_encode(reinterpret_cast(rest1_original.c_str()), + rest1_original.length()); + std::string rest1_decoded = base64_decode(rest1_encoded); + + BOOST_CHECK_EQUAL(rest1_decoded, rest1_original); + BOOST_CHECK_EQUAL(rest1_reference, rest1_encoded); + + std::string rest2_original = "abcde"; + std::string rest2_reference = "YWJjZGU="; + + std::string rest2_encoded = base64_encode(reinterpret_cast(rest2_original.c_str()), + rest2_original.length()); + std::string rest2_decoded = base64_decode(rest2_encoded); + + BOOST_CHECK_EQUAL(rest2_decoded, rest2_original); + BOOST_CHECK_EQUAL(rest2_reference, rest2_encoded); + + // -------------------------------------------------------------- + // + // Data that is 17 bytes long requires one padding byte when + // base-64 encoded. Such an encoded string could not correctly + // be decoded when encoded with «url semantics». This bug + // was discovered by https://github.com/kosniaz. The following + // test checks if this bug was fixed: + // + std::string a17_orig = "aaaaaaaaaaaaaaaaa"; + std::string a17_encoded = base64_encode(a17_orig); + std::string a17_encoded_url = base64url_encode(a17_orig); + + BOOST_CHECK_EQUAL(a17_encoded, "YWFhYWFhYWFhYWFhYWFhYWE="); + BOOST_CHECK_EQUAL(a17_encoded_url, "YWFhYWFhYWFhYWFhYWFhYWE."); + BOOST_CHECK_EQUAL(base64_decode(a17_encoded_url), a17_orig); + BOOST_CHECK_EQUAL(base64_decode(a17_encoded), a17_orig); + + // -------------------------------------------------------------- + + // characters 63 and 64 / URL encoding + + std::string s_6364 = "\x03" "\xef" "\xff" "\xf9"; + + std::string s_6364_encoded = base64_encode(s_6364); + std::string s_6364_encoded_url = base64url_encode(s_6364); + + BOOST_CHECK_EQUAL(s_6364_encoded, "A+//+Q=="); + BOOST_CHECK_EQUAL(s_6364_encoded_url, "A-__-Q.."); + BOOST_CHECK_EQUAL(base64_decode(s_6364_encoded), s_6364); + BOOST_CHECK_EQUAL(base64_decode(s_6364_encoded_url), s_6364); + + // ---------------------------------------------- + + std::string unpadded_input = "YWJjZGVmZw"; // Note the 'missing' "==" + std::string unpadded_decoded = base64_decode(unpadded_input); + BOOST_CHECK_EQUAL(unpadded_decoded, "abcdefg"); + + unpadded_input = "YWJjZGU"; // Note the 'missing' "=" + unpadded_decoded = base64_decode(unpadded_input); + BOOST_CHECK_EQUAL(unpadded_decoded, "abcde"); + + unpadded_input = ""; + unpadded_decoded = base64_decode(unpadded_input); + BOOST_CHECK_EQUAL(unpadded_decoded, ""); + + unpadded_input = "YQ"; + unpadded_decoded = base64_decode(unpadded_input); + BOOST_CHECK_EQUAL(unpadded_decoded, "a"); + + unpadded_input = "YWI"; + unpadded_decoded = base64_decode(unpadded_input); + BOOST_CHECK_EQUAL(unpadded_decoded, "ab"); + + // -------------------------------------------------------------- + // + // 2022-11-01 + // Replace + // encoded_string[…] with encoded_sring.at(…) + // in + // decode() + // + std::string not_null_terminated = std::string(1, 'a'); + BOOST_CHECK_THROW(base64_decode(not_null_terminated), std::out_of_range); + + // -------------------------------------------------------------- + // + // Test the string_view interface (which required C++17) + // + std::string_view sv_orig = "foobarbaz"; + std::string sv_encoded = base64_encode(sv_orig); + + BOOST_CHECK_EQUAL(sv_encoded, "Zm9vYmFyYmF6"); + + std::string sv_decoded = base64_decode(sv_encoded); + + BOOST_CHECK_EQUAL(sv_decoded, sv_orig); + +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file