summaryrefslogtreecommitdiff
path: root/common/base64.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'common/base64.hpp')
-rw-r--r--common/base64.hpp392
1 files changed, 392 insertions, 0 deletions
diff --git a/common/base64.hpp b/common/base64.hpp
new file mode 100644
index 00000000..563247a6
--- /dev/null
+++ b/common/base64.hpp
@@ -0,0 +1,392 @@
+/*
+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>
+*/
+
+#ifndef PUBLIC_DOMAIN_BASE64_HPP_
+#define PUBLIC_DOMAIN_BASE64_HPP_
+
+#include <cstdint>
+#include <iterator>
+#include <stdexcept>
+#include <string>
+
+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.");
+ }
+};
+
+#endif // !PUBLIC_DOMAIN_BASE64_HPP_