diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2025-10-19 00:08:11 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2025-10-19 00:19:30 +0200 |
| commit | df56d2eb26b34b0af590f3aedfda7896f4b103dd (patch) | |
| tree | f58c38e7ed25c2cec3f0fc16d7c8a75806c0d569 /src | |
| parent | 800bdbb18617dfac36067b8841781a92b19d57c1 (diff) | |
base64: Add new module
Encodes and decodes base64. No linebreaks, always padding.
Diffstat (limited to 'src')
| -rw-r--r-- | src/base64.cc | 142 | ||||
| -rw-r--r-- | src/base64.hh | 25 |
2 files changed, 167 insertions, 0 deletions
diff --git a/src/base64.cc b/src/base64.cc new file mode 100644 index 0000000..ce7b41d --- /dev/null +++ b/src/base64.cc @@ -0,0 +1,142 @@ +#include "base64.hh" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <vector> + +namespace base64 { + +namespace { + +std::string_view kAlphabet( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"); + +[[nodiscard]] +inline char encode(uint8_t c) { + return kAlphabet[c]; +} + +[[nodiscard]] +inline uint8_t decode(char c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } + if (c >= 'a' && c <= 'z') { + return 26 + (c - 'a'); + } + if (c >= '0' && c <= '9') { + return 52 + (c - '0'); + } + if (c == '+') + return 62; + if (c == '/') + return 63; + return 255; +} + +} // namespace + +std::string encode(std::span<uint8_t const> data) { + std::string ret; + encode(data, ret); + return ret; +} + +void encode(std::span<uint8_t const> in, std::string& out) { + out.reserve((in.size() * 4 + 3) / 3); + size_t const leftover = in.size() % 3; + size_t const end = in.size() - leftover; + size_t i = 0; + for (; i < end; i += 3) { + out.push_back(encode(in[i] >> 2)); + out.push_back(encode(((in[i] & 0x03) << 4) | (in[i + 1] >> 4))); + out.push_back(encode(((in[i + 1] & 0x0f) << 2) | (in[i + 2] >> 6))); + out.push_back(encode(in[i + 2] & 0x3f)); + } + switch (leftover) { // NOLINT(bugprone-switch-missing-default-case) + case 0: + return; + case 1: + out.push_back(encode(in[i] >> 2)); + out.push_back(encode((in[i] & 0x03) << 4)); + out.push_back('='); + break; + case 2: + out.push_back(encode(in[i] >> 2)); + out.push_back(encode(((in[i] & 0x03) << 4) | (in[i + 1] >> 4))); + out.push_back(encode((in[i + 1] & 0x0f) << 2)); + break; + } + out.push_back('='); +} + +std::optional<std::vector<uint8_t>> decode(std::string_view value) { + std::vector<uint8_t> ret; + if (decode(value, ret)) + return ret; + return std::nullopt; +} + +bool decode(std::string_view in, std::vector<uint8_t>& out) { + if (in.size() % 4) + return false; + + if (in.empty()) + return true; + + size_t pad; + if (in.back() == '=') { + if (in[in.size() - 2] == '=') { + pad = 2; + } else { + pad = 1; + } + } else { + pad = 0; + } + + size_t const end = in.size() - (pad ? 4 : 0); + size_t i = 0; + for (; i < end; i += 4) { + auto v1 = decode(in[i]); + auto v2 = decode(in[i + 1]); + auto v3 = decode(in[i + 2]); + auto v4 = decode(in[i + 3]); + if (v1 == 255 || v2 == 255 || v3 == 255 || v4 == 255) + return false; + out.push_back((v1 << 2) | (v2 >> 4)); + out.push_back(((v2 & 0xf) << 4) | (v3 >> 2)); + out.push_back(((v3 & 0x3) << 6) | v4); + } + switch (pad) { // NOLINT(bugprone-switch-missing-default-case) + case 0: + break; + case 1: { + auto v1 = decode(in[i]); + auto v2 = decode(in[i + 1]); + auto v3 = decode(in[i + 2]); + if (v1 == 255 || v2 == 255 || v3 == 255) + return false; + out.push_back((v1 << 2) | (v2 >> 4)); + out.push_back(((v2 & 0xf) << 4) | (v3 >> 2)); + break; + } + case 2: { + auto v1 = decode(in[i]); + auto v2 = decode(in[i + 1]); + if (v1 == 255 || v2 == 255) + return false; + out.push_back((v1 << 2) | (v2 >> 4)); + break; + } + } + return true; +} + +} // namespace base64 diff --git a/src/base64.hh b/src/base64.hh new file mode 100644 index 0000000..491755c --- /dev/null +++ b/src/base64.hh @@ -0,0 +1,25 @@ +#ifndef BASE64_HH +#define BASE64_HH + +#include <cstdint> +#include <optional> +#include <span> +#include <string> +#include <vector> + +namespace base64 { + +[[nodiscard]] +std::string encode(std::span<uint8_t const> data); + +void encode(std::span<uint8_t const> in, std::string& out); + +[[nodiscard]] +std::optional<std::vector<uint8_t>> decode(std::string_view value); + +[[nodiscard]] +bool decode(std::string_view in, std::vector<uint8_t>& out); + +} // namespace base64 + +#endif // BASE64_HH |
