|
3 | 3 | // that can be found in the LICENSE file at the root of the |
4 | 4 | // Mumble source tree or at <https://www.mumble.info/LICENSE>. |
5 | 5 |
|
| 6 | +// Inspired by https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp. |
| 7 | + |
6 | 8 | #ifndef MUMBLE_BASE64_HPP |
7 | 9 | #define MUMBLE_BASE64_HPP |
8 | 10 |
|
9 | | -#include "Macros.hpp" |
10 | | -#include "NonCopyable.hpp" |
11 | 11 | #include "Types.hpp" |
12 | 12 |
|
13 | | -#include <memory> |
| 13 | +#include <array> |
| 14 | +#include <cmath> |
| 15 | +#include <limits> |
| 16 | +#include <stdexcept> |
| 17 | + |
| 18 | +#include <gsl/span> |
14 | 19 |
|
15 | 20 | namespace mumble { |
16 | | -class MUMBLE_EXPORT Base64 : NonCopyable { |
17 | | -public: |
18 | | - class P; |
| 21 | +namespace base64 { |
| 22 | + using StrView = gsl::span< char >; |
| 23 | + using StrViewConst = gsl::span< const char >; |
| 24 | + |
| 25 | + static constexpr char PAD_CHAR = '='; |
| 26 | + |
| 27 | + static constexpr std::byte operator+(const std::byte lhs, const std::byte rhs) { |
| 28 | + const auto ret = std::to_integer< uint8_t >(lhs) + std::to_integer< uint8_t >(rhs); |
| 29 | + return std::byte(ret); |
| 30 | + } |
| 31 | + |
| 32 | + // The standard doesn't overload this operator with both operands as std::byte... |
| 33 | + static constexpr std::byte operator<<(const std::byte lhs, const std::byte rhs) { |
| 34 | + return lhs << std::to_integer< uint8_t >(rhs); |
| 35 | + } |
| 36 | + |
| 37 | + static constexpr std::byte charToByte(const char ch) { |
| 38 | + if (ch >= 'A' && ch <= 'Z') |
| 39 | + return std::byte(ch - 'A'); |
| 40 | + if (ch >= 'a' && ch <= 'z') |
| 41 | + return std::byte(ch - 'a' + ('Z' - 'A') + 1); |
| 42 | + if (ch >= '0' && ch <= '9') |
| 43 | + return std::byte(ch - '0' + ('Z' - 'A') + ('z' - 'a') + 2); |
| 44 | + if (ch == '+' || ch == '-') |
| 45 | + return std::byte(62); |
| 46 | + if (ch == '/' || ch == '_') |
| 47 | + return std::byte(63); |
| 48 | + |
| 49 | + throw std::range_error(std::string() + ch + " is not a valid character!"); |
| 50 | + } |
| 51 | + |
| 52 | + static constexpr char byteToChar(const std::byte byte) { |
| 53 | + const auto num = std::to_integer< char >(byte); |
| 54 | + |
| 55 | + if (num <= 25) |
| 56 | + return num + 'A'; |
| 57 | + if (num >= 26 && num <= 51) |
| 58 | + return num + 'a' - ('Z' - 'A') - 1; |
| 59 | + if (num >= 52 && num <= 61) |
| 60 | + return num + '0' - ('Z' - 'A') - ('z' - 'a') - 2; |
| 61 | + if (num == 62) |
| 62 | + return '+'; |
| 63 | + if (num == 63) |
| 64 | + return '/'; |
| 65 | + |
| 66 | + throw std::range_error(std::string() + num + " is not a valid byte!"); |
| 67 | + } |
| 68 | + |
| 69 | + static inline bool isValid(const char ch) { |
| 70 | + try { |
| 71 | + charToByte(ch); |
| 72 | + } catch (const std::range_error &) { |
| 73 | + return false; |
| 74 | + } |
| 75 | + |
| 76 | + return true; |
| 77 | + } |
| 78 | + |
| 79 | + static inline bool isValid(const StrViewConst in) { |
| 80 | + for (const auto ch : in) { |
| 81 | + if (!isValid(ch)) { |
| 82 | + return false; |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + return true; |
| 87 | + } |
| 88 | + |
| 89 | + static constexpr size_t decodedSize(const StrViewConst in) { |
| 90 | + size_t size = (in.size() / 4) * 3; |
| 91 | + // Base64 can have up to 2 pad characters, in order to make the string a multiple of 4. |
| 92 | + // In order to calculate the exact output size we have to ignore padding. |
| 93 | + if (auto iter = in.rbegin(); *iter == PAD_CHAR) { |
| 94 | + size -= *++iter == PAD_CHAR ? 2U : 1U; |
| 95 | + } |
| 96 | + |
| 97 | + return size; |
| 98 | + } |
| 99 | + |
| 100 | + static constexpr size_t encodedSize(const BufViewConst in) { |
| 101 | + // ceil((float) a / 3) == (a + 2) / 3, assuming a is an integer. |
| 102 | + return ((in.size() + 2) / 3) * 4; |
| 103 | + } |
| 104 | + |
| 105 | + static constexpr size_t decode(BufView out, StrViewConst in) { |
| 106 | + if (in.empty()) { |
| 107 | + return 0; |
| 108 | + } |
| 109 | + |
| 110 | + if (in.size() % 4) { |
| 111 | + throw std::invalid_argument("Input is not a multiple of 4!"); |
| 112 | + } |
| 113 | + |
| 114 | + const size_t size = decodedSize(in); |
| 115 | + if (out.size() < size) { |
| 116 | + throw std::invalid_argument("Insufficient output buffer size!"); |
| 117 | + } |
| 118 | + |
| 119 | + for (; !in.empty(); out = out.subspan(3), in = in.subspan(4)) { |
| 120 | + out[0] = (charToByte(in[0]) << std::byte(2)) + ((charToByte(in[1]) & std::byte(0x30)) >> 4); |
| 121 | + |
| 122 | + if (in[2] == '=') { |
| 123 | + break; |
| 124 | + } |
| 125 | + |
| 126 | + out[1] = |
| 127 | + ((charToByte(in[1]) & std::byte(0x0f)) << std::byte(4)) + ((charToByte(in[2]) & std::byte(0x3c)) >> 2); |
| 128 | + |
| 129 | + if (in[3] == '=') { |
| 130 | + break; |
| 131 | + } |
| 132 | + |
| 133 | + out[2] = ((charToByte(in[2]) & std::byte(0x03)) << std::byte(6)) + charToByte(in[3]); |
| 134 | + } |
| 135 | + |
| 136 | + return size; |
| 137 | + } |
| 138 | + |
| 139 | + static constexpr size_t encode(StrView out, BufViewConst in) { |
| 140 | + if (in.empty()) { |
| 141 | + return 0; |
| 142 | + } |
| 143 | + |
| 144 | + const size_t size = encodedSize(in); |
| 145 | + if (out.size() < size) { |
| 146 | + throw std::invalid_argument("Insufficient output buffer size!"); |
| 147 | + } |
| 148 | + |
| 149 | + for (; !in.empty(); out = out.subspan(4)) { |
| 150 | + out[0] = byteToChar((in[0] & std::byte(0xfc)) >> 2); |
19 | 151 |
|
20 | | - Base64(); |
21 | | - virtual ~Base64(); |
| 152 | + if (in.size() >= 2) { |
| 153 | + out[1] = byteToChar(((in[0] & std::byte(0x03)) << 4) + ((in[1] & std::byte(0xf0)) >> 4)); |
22 | 154 |
|
23 | | - virtual explicit operator bool(); |
| 155 | + if (in.size() >= 3) { |
| 156 | + out[2] = byteToChar(((in[1] & std::byte(0x0f)) << 2) + ((in[2] & std::byte(0xc0)) >> 6)); |
| 157 | + out[3] = byteToChar(in[2] & std::byte(0x3f)); |
| 158 | + } else { |
| 159 | + out[2] = byteToChar((in[1] & std::byte(0x0f)) << 2); |
| 160 | + out[3] = PAD_CHAR; |
| 161 | + } |
| 162 | + } else { |
| 163 | + out[1] = byteToChar((in[0] & std::byte(0x03)) << 4); |
| 164 | + out[2] = PAD_CHAR; |
| 165 | + out[3] = PAD_CHAR; |
| 166 | + } |
24 | 167 |
|
25 | | - virtual size_t decode(const BufView out, const BufViewConst in); |
26 | | - static size_t encode(const BufView out, const BufViewConst in); |
| 168 | + // All data blocks are guaranteed to be 3 bytes except the last one. |
| 169 | + in = in.subspan(std::min(size_t{ 3 }, in.size())); |
| 170 | + } |
27 | 171 |
|
28 | | -private: |
29 | | - std::unique_ptr< P > m_p; |
30 | | -}; |
| 172 | + return size; |
| 173 | + } |
| 174 | +}; // namespace base64 |
31 | 175 | } // namespace mumble |
32 | 176 |
|
33 | 177 | #endif |
0 commit comments