|
| 1 | +/******************************************************************************* |
| 2 | +MIT License |
| 3 | +
|
| 4 | +Copyright (c) 2020 Natasha England-Elbro |
| 5 | +
|
| 6 | +Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | +of this software and associated documentation files (the "Software"), to deal |
| 8 | +in the Software without restriction, including without limitation the rights |
| 9 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | +copies of the Software, and to permit persons to whom the Software is |
| 11 | +furnished to do so, subject to the following conditions: |
| 12 | +
|
| 13 | +The above copyright notice and this permission notice shall be included in all |
| 14 | +copies or substantial portions of the Software. |
| 15 | +
|
| 16 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | +SOFTWARE. |
| 23 | +******************************************************************************/ |
| 24 | + |
| 25 | +/*/ |
| 26 | +---------------------------------------- |
| 27 | +Base64 - C++ file |
| 28 | +Created on 19/01/2020 by Natasha England-Elbro, |
| 29 | +----------------------------------------- |
| 30 | +/*/ |
| 31 | + |
| 32 | +#ifndef LOCKETTE_BASE64_INL_CC86FA91CA464542919F0B82B87F98EA |
| 33 | +#define LOCKETTE_BASE64_INL_CC86FA91CA464542919F0B82B87F98EA |
| 34 | +/** |
| 35 | + * @file Header only file containing functions and classes for base64 encoding and decoding |
| 36 | + */ |
| 37 | + |
| 38 | + |
| 39 | +#include <cstddef> |
| 40 | +#include <string> |
| 41 | +#include <stdexcept> |
| 42 | + |
| 43 | +namespace tgk::utils::b64 { |
| 44 | + |
| 45 | +/// Unsigned char byte |
| 46 | +using byte = unsigned char; |
| 47 | + |
| 48 | +/// Defines the char type to use |
| 49 | +/** |
| 50 | + * wchar_t - UNICODE support |
| 51 | + * char - ASCII only (also remove the L from the B64_LOOKUP_TBL if your doing this) |
| 52 | + */ |
| 53 | +using uchar = char; |
| 54 | + |
| 55 | +/// Base64 alphabet for conversions |
| 56 | +constexpr uchar B64_LOOKUP_TBL[] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}; |
| 57 | +/// Character used for padding '=' by default |
| 58 | +constexpr uchar B64_PAD = '='; |
| 59 | + |
| 60 | + |
| 61 | +/// Get the length of a message after being base64 encoded |
| 62 | +/** |
| 63 | + * @tparam T: Any container with a size() method returning a valid size of the container |
| 64 | + * @param msg: A container of type T containing the message |
| 65 | + * @return The size of the message after being base64 encoded |
| 66 | + */ |
| 67 | +template <typename T> |
| 68 | +size_t constexpr get_encoded_size(T msg) { |
| 69 | + return ((msg.size() / 3) + (msg.size() % 3 > 0) * 4); |
| 70 | +} |
| 71 | + |
| 72 | +/// Get the size of an encoded message when decoded |
| 73 | +/** |
| 74 | + * @tparam T: Any container with a size() method returning a valid size of the container |
| 75 | + * @tparam C: Any implementable number basically |
| 76 | + * @param msg: A container of type T containing the message |
| 77 | + * @param padding: The amount of padding the message has, defaults to 0 |
| 78 | + * @return The size required the hold the message once decoded |
| 79 | + */ |
| 80 | +template <typename T, typename C> |
| 81 | +size_t constexpr get_decoded_size(T msg, C padding = 0) { |
| 82 | + return ((msg.size() / 4) * 3) - padding; |
| 83 | +} |
| 84 | + |
| 85 | +/// Check if something is valid base64 |
| 86 | +/** |
| 87 | + * Returns true if msg is valid base64 (dividable by 4 exactly) |
| 88 | + * @tparam T: Any container with a size() method returning a valid size |
| 89 | + * @param msg: A container matching T which contains the elements to check |
| 90 | + * @return True or False depending on if msg is valid base64 (with padding) |
| 91 | + */ |
| 92 | +template <typename T> |
| 93 | +constexpr bool is_valid_b64(T msg) { |
| 94 | + return (msg.size() % 4) == 0; |
| 95 | +} |
| 96 | + |
| 97 | +/// Perform base64 encoding on an iterable container |
| 98 | +/** |
| 99 | + * |
| 100 | + * @tparam T: Specifes a container which must implement size() to get its size and have an iterator object member. (i.e |
| 101 | + * std::vector) |
| 102 | + * @tparam C: The type of char to use, defaults to uchar which is wchar_t, supporting UNICODE |
| 103 | + * @tparam STR: A stream with the << operator |
| 104 | + * @tparam LK: The type of the lookup table. Must allow array access (using [ ] operator) |
| 105 | + * @param in: The container of type T which contains the elements to encode |
| 106 | + * @param out: The stream to push the output into |
| 107 | + * @param pad_char: The character to pad the output with, defaults to B64_PAD which is '=' |
| 108 | + * @param lookup_tbl: The lookup table to use for conversion, default to B64_LOOKUP_TBL which is standard non-urlsafe |
| 109 | + * base64 encoding |
| 110 | + * @return The encoded string as basic_string<C> (which by default will be the same as wstring as C defaults to wchar_t) |
| 111 | + */ |
| 112 | + |
| 113 | +template <typename C = uchar, typename T, typename LK = C> |
| 114 | +void b64_encode(T in, std::basic_ostream<C> &out, size_t in_size = -1, C pad_char = B64_PAD, LK lookup_tbl[] = |
| 115 | +const_cast<LK *> |
| 116 | +(B64_LOOKUP_TBL)) { |
| 117 | + |
| 118 | + if (in_size < 0) in_size = in.size(); |
| 119 | + |
| 120 | + // Set the iterator (yes I called it c because I get to write c++) |
| 121 | + typename T::iterator c = in.begin(); |
| 122 | + |
| 123 | + long oct = 0; |
| 124 | + |
| 125 | + for (size_t sec = 0; sec < in_size / 3; ++sec) { |
| 126 | + // Split the character into octs |
| 127 | + oct = (*c++) << 16; |
| 128 | + oct += (*c++) << 8; |
| 129 | + oct += (*c++); |
| 130 | + |
| 131 | + // Perform the shifts and append the shifted bits |
| 132 | + out << (1, lookup_tbl[(oct & 0x00fc0000) >> 18]); |
| 133 | + out << (1, lookup_tbl[(oct & 0x0003f000) >> 12]); |
| 134 | + out << (1, lookup_tbl[(oct & 0x00000fc0) >> 6]); |
| 135 | + out << (1, lookup_tbl[(oct & 0x0000003f)]); |
| 136 | + } |
| 137 | + |
| 138 | + // Pad the output |
| 139 | + // Check if remainder of dividing by 3 |
| 140 | + auto remains = in.size() % 3; |
| 141 | + |
| 142 | + if (remains >= 1) { |
| 143 | + bool single_pad = remains == 2; |
| 144 | + oct = (*c++) << 16; |
| 145 | + if (single_pad) { |
| 146 | + // Single pad |
| 147 | + oct += (*c++) << 8; |
| 148 | + } |
| 149 | + |
| 150 | + // Shift |
| 151 | + out << (1, lookup_tbl[(oct & 0x00FC0000) >> 18]); |
| 152 | + out << (1, lookup_tbl[(oct & 0x0003F000) >> 12]); |
| 153 | + if (single_pad) { |
| 154 | + out << (1, lookup_tbl[(oct & 0x00000FC0) >> 6]); |
| 155 | + } |
| 156 | + // Add padding |
| 157 | + out << (single_pad ? 1 : 2, pad_char); |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +/// Decode a base64 encoded string |
| 162 | +/** |
| 163 | + * @tparam T: Any iterable container with a size() method returning a valid size |
| 164 | + * @tparam C: The char type to use, defaults to uchar (UNICODE) |
| 165 | + * @tparam STR: Any container with an append() method and initaliser |
| 166 | + * @param in: A container of type T containg the elements to decode |
| 167 | + * @param out: The stream to push to output too |
| 168 | + * @param pad_char: The char of type C used for padding on the encoded message. Defaults to '=' |
| 169 | + * @return The decoded string as an std::basic_string<C> |
| 170 | + * @note Currently this is hardcoded for non-urlsafe decoding. |
| 171 | + */ |
| 172 | +template <typename C = uchar, typename T> |
| 173 | +void b64_decode(T in, std::basic_ostream<C> &out, size_t in_size = -1, C pad_char = B64_PAD) { |
| 174 | + // Throw if [in] is invalid/not b64 encoded |
| 175 | + if (!is_valid_b64(in)) { |
| 176 | + throw std::invalid_argument("tgk::utils::b64::b64_decode received an invalid (not base64 " |
| 177 | + "encoded) message to decode!"); |
| 178 | + } |
| 179 | + if (in_size < 0) in_size = in.size(); |
| 180 | + |
| 181 | + auto padded = 0; |
| 182 | + if (in_size) { |
| 183 | + // Calculate padding |
| 184 | + if (*(in.end() - 1) == pad_char) padded++; |
| 185 | + if (*(in.end() - 2) == pad_char) padded++; |
| 186 | + } |
| 187 | + |
| 188 | + long oct = 0; |
| 189 | + |
| 190 | + for (auto c : in) { |
| 191 | + // Split each 4 char segment |
| 192 | + for (size_t sec = 0; sec < 4; ++sec) { |
| 193 | + oct <<= 4; // oct == 00010000 if oct = 1 |
| 194 | + |
| 195 | + // [A-Z] |
| 196 | + if (c >= 0x41 && c <= 0x5A) { |
| 197 | + oct |= c - 0x41; |
| 198 | + // [a-z] |
| 199 | + } else if (c >= 0x61 && c <= 0x7A) { |
| 200 | + oct |= c - 0x47; |
| 201 | + // [0-9] |
| 202 | + } else if (c >= 0x30 && c <= 0x39) { |
| 203 | + oct |= c + 0x04; |
| 204 | + // '+' |
| 205 | + } else if (c == 0x2b) { |
| 206 | + oct |= 0x3e; |
| 207 | + // '/' |
| 208 | + } else if (c == 0x2f) { oct |= 0x3f; } |
| 209 | + else if (c == pad_char) { |
| 210 | + // Deal with padding |
| 211 | + switch (padded) { |
| 212 | + case 1: |
| 213 | + // Single padded |
| 214 | + out << ((oct >> 16) & 0x000000ff); |
| 215 | + out << ((oct >> 8) & 0x000000ff); |
| 216 | + return; |
| 217 | + case 2: |
| 218 | + // Doubled passed |
| 219 | + out << ((oct >> 10) & 0x000000ff); |
| 220 | + return; |
| 221 | + default: |
| 222 | + throw std::invalid_argument("tgk::utils::b64::b64_decode received an invalid (not base64 " |
| 223 | + "encoded) message to decode!"); |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + out << ((oct >> 16) & 0x000000ff); |
| 228 | + out << ((oct >> 8) & 0x000000ff); |
| 229 | + out << (oct & 0x000000ff); |
| 230 | + } |
| 231 | +} |
| 232 | +} |
| 233 | + |
| 234 | +#endif //LOCKETTE_BASE64_INL_CC86FA91CA464542919F0B82B87F98EA |
0 commit comments