/* | |
This is free and unencumbered software released into the public domain. | |
Anyone is free to copy, modify, publish, use, compile, sell, or | |
distribute this software, either in source code form or as a compiled | |
binary, for any purpose, commercial or non-commercial, and by any | |
means. | |
In jurisdictions that recognize copyright laws, the author or authors | |
of this software dedicate any and all copyright interest in the | |
software to the public domain. We make this dedication for the benefit | |
of the public at large and to the detriment of our heirs and | |
successors. We intend this dedication to be an overt act of | |
relinquishment in perpetuity of all present and future rights to this | |
software under copyright law. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
OTHER DEALINGS IN THE SOFTWARE. | |
For more information, please refer to <http://unlicense.org> | |
*/ | |
class base64_error : public std::runtime_error | |
{ | |
public: | |
using std::runtime_error::runtime_error; | |
}; | |
class base64 | |
{ | |
public: | |
enum class alphabet | |
{ | |
/** the alphabet is detected automatically */ | |
auto_, | |
/** the standard base64 alphabet is used */ | |
standard, | |
/** like `standard` except that the characters `+` and `/` are replaced by `-` and `_` respectively*/ | |
url_filename_safe | |
}; | |
enum class decoding_behavior | |
{ | |
/** if the input is not padded, the remaining bits are ignored */ | |
moderate, | |
/** if a padding character is encounter decoding is finished */ | |
loose | |
}; | |
/** | |
Encodes all the elements from `in_begin` to `in_end` to `out`. | |
@warning The source and destination cannot overlap. The destination must be able to hold at least | |
`required_encode_size(std::distance(in_begin, in_end))`, otherwise the behavior depends on the output iterator. | |
@tparam Input_iterator the source; the returned elements are cast to `std::uint8_t` and should not be greater than | |
8 bits | |
@tparam Output_iterator the destination; the elements written to it are from the type `char` | |
@param in_begin the beginning of the source | |
@param in_end the ending of the source | |
@param out the destination iterator | |
@param alphabet which alphabet should be used | |
@returns the iterator to the next element past the last element copied | |
@throws see `Input_iterator` and `Output_iterator` | |
*/ | |
template<typename Input_iterator, typename Output_iterator> | |
static Output_iterator encode(Input_iterator in_begin, Input_iterator in_end, Output_iterator out, | |
alphabet alphabet = alphabet::standard) | |
{ | |
constexpr auto pad = '='; | |
const char* alpha = alphabet == alphabet::url_filename_safe | |
? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" | |
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
while (in_begin != in_end) { | |
std::uint8_t i0 = 0, i1 = 0, i2 = 0; | |
// first character | |
i0 = static_cast<std::uint8_t>(*in_begin); | |
++in_begin; | |
*out = alpha[i0 >> 2 & 0x3f]; | |
++out; | |
// part of first character and second | |
if (in_begin != in_end) { | |
i1 = static_cast<std::uint8_t>(*in_begin); | |
++in_begin; | |
*out = alpha[((i0 & 0x3) << 4) | (i1 >> 4 & 0x0f)]; | |
++out; | |
} else { | |
*out = alpha[(i0 & 0x3) << 4]; | |
++out; | |
// last padding | |
*out = pad; | |
++out; | |
// last padding | |
*out = pad; | |
++out; | |
break; | |
} | |
// part of second character and third | |
if (in_begin != in_end) { | |
i2 = static_cast<std::uint8_t>(*in_begin); | |
++in_begin; | |
*out = alpha[((i1 & 0xf) << 2) | (i2 >> 6 & 0x03)]; | |
++out; | |
} else { | |
*out = alpha[(i1 & 0xf) << 2]; | |
++out; | |
// last padding | |
*out = pad; | |
++out; | |
break; | |
} | |
// rest of third | |
*out = alpha[i2 & 0x3f]; | |
++out; | |
} | |
return out; | |
} | |
/** | |
Encodes a string. | |
@param str the string that should be encoded | |
@param alphabet which alphabet should be used | |
@returns the encoded base64 string | |
@throws see base64::encode() | |
*/ | |
static std::string encode(const std::string& str, alphabet alphabet = alphabet::standard) | |
{ | |
std::string result; | |
result.reserve(required_encode_size(str.length()) + 1); | |
encode(str.begin(), str.end(), std::back_inserter(result), alphabet); | |
return result; | |
} | |
/** | |
Encodes a char array. | |
@param buffer the char array | |
@param size the size of the array | |
@param alphabet which alphabet should be used | |
@returns the encoded string | |
*/ | |
static std::string encode(const char* buffer, std::size_t size, alphabet alphabet = alphabet::standard) | |
{ | |
std::string result; | |
result.reserve(required_encode_size(size) + 1); | |
encode(buffer, buffer + size, std::back_inserter(result), alphabet); | |
return result; | |
} | |
/** | |
Decodes all the elements from `in_begin` to `in_end` to `out`. `in_begin` may point to the same location as `out`, | |
in other words: inplace decoding is possible. | |
@warning The destination must be able to hold at least `required_decode_size(std::distance(in_begin, in_end))`, | |
otherwise the behavior depends on the output iterator. | |
@tparam Input_iterator the source; the returned elements are cast to `char` | |
@tparam Output_iterator the destination; the elements written to it are from the type `std::uint8_t` | |
@param in_begin the beginning of the source | |
@param in_end the ending of the source | |
@param out the destination iterator | |
@param alphabet which alphabet should be used | |
@param behavior the behavior when an error was detected | |
@returns the iterator to the next element past the last element copied | |
@throws base64_error depending on the set behavior | |
@throws see `Input_iterator` and `Output_iterator` | |
*/ | |
template<typename Input_iterator, typename Output_iterator> | |
static Output_iterator decode(Input_iterator in_begin, Input_iterator in_end, Output_iterator out, | |
alphabet alphabet = alphabet::auto_, | |
decoding_behavior behavior = decoding_behavior::moderate) | |
{ | |
//constexpr auto pad = '='; | |
std::uint8_t last = 0; | |
auto bits = 0; | |
while (in_begin != in_end) { | |
auto c = *in_begin; | |
++in_begin; | |
if (c == '=') { | |
break; | |
} | |
auto part = _base64_value(alphabet, c); | |
// enough bits for one byte | |
if (bits + 6 >= 8) { | |
*out = (last << (8 - bits)) | (part >> (bits - 2)); | |
++out; | |
bits -= 2; | |
} else { | |
bits += 6; | |
} | |
last = part; | |
} | |
// check padding | |
if (behavior != decoding_behavior::loose) { | |
while (in_begin != in_end) { | |
auto c = *in_begin; | |
++in_begin; | |
if (c != '=') { | |
throw base64_error("invalid base64 character."); | |
} | |
} | |
} | |
return out; | |
} | |
/** | |
Decodes a string. | |
@param str the base64 encoded string | |
@param alphabet which alphabet should be used | |
@param behavior the behavior when an error was detected | |
@returns the decoded string | |
@throws see base64::decode() | |
*/ | |
static std::string decode(const std::string& str, alphabet alphabet = alphabet::auto_, | |
decoding_behavior behavior = decoding_behavior::moderate) | |
{ | |
std::string result; | |
result.reserve(max_decode_size(str.length())); | |
decode(str.begin(), str.end(), std::back_inserter(result), alphabet, behavior); | |
return result; | |
} | |
/** | |
Decodes a string. | |
@param buffer the base64 encoded buffer | |
@param size the size of the buffer | |
@param alphabet which alphabet should be used | |
@param behavior the behavior when an error was detected | |
@returns the decoded string | |
@throws see base64::decode() | |
*/ | |
static std::string decode(const char* buffer, std::size_t size, alphabet alphabet = alphabet::auto_, | |
decoding_behavior behavior = decoding_behavior::moderate) | |
{ | |
std::string result; | |
result.reserve(max_decode_size(size)); | |
decode(buffer, buffer + size, std::back_inserter(result), alphabet, behavior); | |
return result; | |
} | |
/** | |
Decodes a string inplace. | |
@param[in,out] str the base64 encoded string | |
@param alphabet which alphabet should be used | |
@param behavior the behavior when an error was detected | |
@throws base64::decode_inplace() | |
*/ | |
static void decode_inplace(std::string& str, alphabet alphabet = alphabet::auto_, | |
decoding_behavior behavior = decoding_behavior::moderate) | |
{ | |
str.resize(decode(str.begin(), str.end(), str.begin(), alphabet, behavior) - str.begin()); | |
} | |
/** | |
Decodes a char array inplace. | |
@param[in,out] str the string array | |
@param size the length of the array | |
@param alphabet which alphabet should be used | |
@param behavior the behavior when an error was detected | |
@returns the pointer to the next element past the last element decoded | |
@throws base64::decode_inplace() | |
*/ | |
static char* decode_inplace(char* str, std::size_t size, alphabet alphabet = alphabet::auto_, | |
decoding_behavior behavior = decoding_behavior::moderate) | |
{ | |
return decode(str, str + size, str, alphabet, behavior); | |
} | |
/** | |
Returns the required decoding size for a given size. The value is calculated with the following formula: | |
$$ | |
\lceil \frac{size}{4} \rceil \cdot 3 | |
$$ | |
@param size the size of the encoded input | |
@returns the size of the resulting decoded buffer; this the absolute maximum | |
*/ | |
static std::size_t max_decode_size(std::size_t size) noexcept | |
{ | |
return (size / 4 + (size % 4 ? 1 : 0)) * 3; | |
} | |
/** | |
Returns the required encoding size for a given size. The value is calculated with the following formula: | |
$$ | |
\lceil \frac{size}{3} \rceil \cdot 4 | |
$$ | |
@param size the size of the decoded input | |
@returns the size of the resulting encoded buffer | |
*/ | |
static std::size_t required_encode_size(std::size_t size) noexcept | |
{ | |
return (size / 3 + (size % 3 ? 1 : 0)) * 4; | |
} | |
private: | |
static std::uint8_t _base64_value(alphabet& alphabet, char c) | |
{ | |
if (c >= 'A' && c <= 'Z') { | |
return c - 'A'; | |
} else if (c >= 'a' && c <= 'z') { | |
return c - 'a' + 26; | |
} else if (c >= '0' && c <= '9') { | |
return c - '0' + 52; | |
} | |
// comes down to alphabet | |
if (alphabet == alphabet::standard) { | |
if (c == '+') { | |
return 62; | |
} else if (c == '/') { | |
return 63; | |
} | |
} else if (alphabet == alphabet::url_filename_safe) { | |
if (c == '-') { | |
return 62; | |
} else if (c == '_') { | |
return 63; | |
} | |
} // auto detect | |
else { | |
if (c == '+') { | |
alphabet = alphabet::standard; | |
return 62; | |
} else if (c == '/') { | |
alphabet = alphabet::standard; | |
return 63; | |
} else if (c == '-') { | |
alphabet = alphabet::url_filename_safe; | |
return 62; | |
} else if (c == '_') { | |
alphabet = alphabet::url_filename_safe; | |
return 63; | |
} | |
} | |
throw base64_error("invalid base64 character."); | |
} | |
}; | |