From dfdc4369b82c2bf8d97b20f963e77d6852201139 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Sun, 1 Oct 2023 22:57:35 +0200 Subject: [PATCH] base64: add base64_reader and base64_writer Signed-off-by: Simon Rozman --- include/stdex/base64.hpp | 284 ++++++++++++++++++++++++++++++++------- 1 file changed, 233 insertions(+), 51 deletions(-) diff --git a/include/stdex/base64.hpp b/include/stdex/base64.hpp index c3bbedf7a..8e5bf1eea 100644 --- a/include/stdex/base64.hpp +++ b/include/stdex/base64.hpp @@ -6,6 +6,7 @@ #pragma once #include "compat.hpp" +#include "stream.hpp" #include #include #include @@ -52,14 +53,13 @@ namespace stdex /// /// Constructs blank encoding session /// - base64_enc() noexcept : num(0) + base64_enc() noexcept : m_num(0) { - buf[0] = 0; - buf[1] = 0; - buf[2] = 0; + m_buf[0] = 0; + m_buf[1] = 0; + m_buf[2] = 0; } - /// /// Encodes one block of information, and _appends_ it to the output /// @@ -78,34 +78,32 @@ namespace stdex // Convert data character by character. for (size_t i = 0;; i++) { - if (num >= 3) { + if (m_num >= 3) { encode(out); - num = 0; + m_num = 0; } if (i >= size) break; - buf[num++] = reinterpret_cast(data)[i]; + m_buf[m_num++] = reinterpret_cast(data)[i]; } // If this is the last block, flush the buffer. - if (is_last && num) { - encode(out, num); - num = 0; + if (is_last && m_num) { + encode(out, m_num); + m_num = 0; } } - /// /// Resets encoding session /// void clear() noexcept { - num = 0; + m_num = 0; } - /// /// Returns maximum encoded size /// @@ -115,10 +113,9 @@ namespace stdex /// size_t enc_size(_In_ size_t size) const noexcept { - return ((num + size + 2)/3)*4; + return ((m_num + size + 2)/3)*4; } - protected: /// /// Encodes one complete internal buffer of data @@ -126,13 +123,12 @@ namespace stdex template void encode(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &out) { - out += base64_enc_lookup[ buf[0] >> 2 ]; - out += base64_enc_lookup[((buf[0] << 4) | (buf[1] >> 4)) & 0x3f]; - out += base64_enc_lookup[((buf[1] << 2) | (buf[2] >> 6)) & 0x3f]; - out += base64_enc_lookup[ buf[2] & 0x3f]; + out += base64_enc_lookup[ m_buf[0] >> 2 ]; + out += base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f]; + out += base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f]; + out += base64_enc_lookup[ m_buf[2] & 0x3f]; } - /// /// Encodes partial internal buffer of data /// @@ -140,18 +136,18 @@ namespace stdex void encode(_Inout_ std::basic_string<_Elem, _Traits, _Ax> &out, _In_ size_t size) { if (size > 0) { - out += base64_enc_lookup[buf[0] >> 2]; + out += base64_enc_lookup[m_buf[0] >> 2]; if (size > 1) { - out += base64_enc_lookup[((buf[0] << 4) | (buf[1] >> 4)) & 0x3f]; + out += base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f]; if (size > 2) { - out += base64_enc_lookup[((buf[1] << 2) | (buf[2] >> 6)) & 0x3f]; - out += base64_enc_lookup[buf[2] & 0x3f]; + out += base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f]; + out += base64_enc_lookup[m_buf[2] & 0x3f]; } else { - out += base64_enc_lookup[(buf[1] << 2) & 0x3f]; + out += base64_enc_lookup[(m_buf[1] << 2) & 0x3f]; out += '='; } } else { - out += base64_enc_lookup[(buf[0] << 4) & 0x3f]; + out += base64_enc_lookup[(m_buf[0] << 4) & 0x3f]; out += '='; out += '='; } @@ -163,10 +159,109 @@ namespace stdex } } + protected: + uint8_t m_buf[3]; ///< Internal buffer + size_t m_num; ///< Number of bytes used in `m_buf` + }; + + /// + /// Converts to Base64 when writing to a stream + /// + class base64_writer : public stdex::stream::converter, protected base64_enc + { + public: + base64_writer(_Inout_ stdex::stream::basic& source, _In_ size_t max_blocks = 19) : + stdex::stream::converter(source), + m_max_blocks(max_blocks), + m_num_blocks(0) + {} + + virtual ~base64_writer() + { + // Flush the buffer. + if (m_num) { + if (++m_num_blocks > m_max_blocks) { + *m_source << '\n'; + m_num_blocks = 1; + } + encode(m_num); + } + } + + virtual _Success_(return != 0) size_t write( + _In_reads_bytes_opt_(length) const void* data, _In_ size_t length) + { + assert(data || !length); + for (size_t i = 0;; i++) { + if (m_num >= 3) { + if (++m_num_blocks > m_max_blocks) { + *m_source << '\n'; + m_num_blocks = 1; + } + encode(); + if (!m_source->ok()) _Unlikely_ { + m_state = m_source->state(); + return length - i; + } + m_num = 0; + } + if (i >= length) { + m_state = stdex::stream::state_t::ok; + return length; + } + m_buf[m_num++] = reinterpret_cast(data)[i]; + } + } protected: - uint8_t buf[3]; ///< Internal buffer - size_t num; ///< Number of bytes used in `buf` + /// + /// Encodes one complete internal buffer of data + /// + void encode() + { + char out[4]; + out[0] = base64_enc_lookup[ m_buf[0] >> 2 ]; + out[1] = base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f]; + out[2] = base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f]; + out[3] = base64_enc_lookup[ m_buf[2] & 0x3f]; + m_source->write_array(out, sizeof(*out), _countof(out)); + } + + /// + /// Encodes partial internal buffer of data + /// + void encode(_In_ size_t size) + { + char out[4]; + if (size > 0) { + out[0] = base64_enc_lookup[m_buf[0] >> 2]; + if (size > 1) { + out[1] = base64_enc_lookup[((m_buf[0] << 4) | (m_buf[1] >> 4)) & 0x3f]; + if (size > 2) { + out[2] = base64_enc_lookup[((m_buf[1] << 2) | (m_buf[2] >> 6)) & 0x3f]; + out[3] = base64_enc_lookup[m_buf[2] & 0x3f]; + } else { + out[2] = base64_enc_lookup[(m_buf[1] << 2) & 0x3f]; + out[3] = '='; + } + } else { + out[1] = base64_enc_lookup[(m_buf[0] << 4) & 0x3f]; + out[2] = '='; + out[3] = '='; + } + } else { + out[0] = '='; + out[1] = '='; + out[2] = '='; + out[3] = '='; + } + m_source->write_array(out, sizeof(*out), _countof(out)); + } + + protected: + size_t + m_max_blocks, ///> Maximum number of Base64 blocks (4 chars) to write without a line break (SIZE_MAX no line breaks) + m_num_blocks; ///> Number of Base64 blocks (4 chars), written after last line break }; /// @@ -178,15 +273,14 @@ namespace stdex /// /// Constructs blank decoding session /// - base64_dec() noexcept : num(0) + base64_dec() noexcept : m_num(0) { - buf[0] = 0; - buf[1] = 0; - buf[2] = 0; - buf[3] = 0; + m_buf[0] = 0; + m_buf[1] = 0; + m_buf[2] = 0; + m_buf[3] = 0; } - /// /// Decodes one block of information, and _appends_ it to the output /// @@ -208,10 +302,9 @@ namespace stdex out.reserve(out.size() + dec_size(size)); for (size_t i = 0;; i++) { - if (num >= 4) { + if (m_num >= 4) { // Buffer full; decode it. size_t nibbles = decode(out); - num = 0; if (nibbles < 3) { is_last = true; break; @@ -222,21 +315,19 @@ namespace stdex break; int x = data[i]; - if ((buf[num] = x < _countof(base64_dec_lookup) ? base64_dec_lookup[x] : 255) != 255) - num++; + if ((m_buf[m_num] = x < _countof(base64_dec_lookup) ? base64_dec_lookup[x] : 255) != 255) + m_num++; } } - /// /// Resets decoding session /// void clear() noexcept { - num = 0; + m_num = 0; } - /// /// Returns maximum decoded size /// @@ -246,10 +337,9 @@ namespace stdex /// size_t dec_size(_In_ size_t size) const noexcept { - return ((num + size + 3)/4)*3; + return ((m_num + size + 3)/4)*3; } - protected: /// /// Decodes one complete internal buffer of data @@ -257,11 +347,12 @@ namespace stdex template size_t decode(_Inout_ std::vector<_Ty, _Ax> &out) { - out.push_back((_Ty)(((buf[0] << 2) | (buf[1] >> 4)) & 0xff)); - if (buf[2] < 64) { - out.push_back((_Ty)(((buf[1] << 4) | (buf[2] >> 2)) & 0xff)); - if (buf[3] < 64) { - out.push_back((_Ty)(((buf[2] << 6) | buf[3]) & 0xff)); + m_num = 0; + out.push_back((_Ty)(((m_buf[0] << 2) | (m_buf[1] >> 4)) & 0xff)); + if (m_buf[2] < 64) { + out.push_back((_Ty)(((m_buf[1] << 4) | (m_buf[2] >> 2)) & 0xff)); + if (m_buf[3] < 64) { + out.push_back((_Ty)(((m_buf[2] << 6) | m_buf[3]) & 0xff)); return 3; } else return 2; @@ -269,9 +360,100 @@ namespace stdex return 1; } + protected: + uint8_t m_buf[4]; ///< Internal buffer + size_t m_num; ///< Number of bytes used in `m_buf` + }; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 26495) +#endif + + /// + /// Converts from Base64 when reading from a stream + /// + class base64_reader : public stdex::stream::converter, protected base64_dec + { + public: + base64_reader(_Inout_ stdex::stream::basic& source) : + stdex::stream::converter(source), + m_temp_off(0), + m_temp_len(0) + {} + +#pragma warning(suppress: 6101) // See [1] below + virtual _Success_(return != 0 || length == 0) size_t read( + _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length) + { + assert(data || !length); + for (size_t to_read = length;;) { + if (m_temp_len >= to_read) { + memcpy(data, m_temp + m_temp_off, to_read); + m_temp_off += to_read; + m_temp_len -= to_read; + m_state = stdex::stream::state_t::ok; + return length; + } + if (m_temp_len) { + memcpy(data, m_temp + m_temp_off, m_temp_len); + reinterpret_cast(data) += m_temp_len; + to_read -= m_temp_len; + m_temp_off = 0; + m_temp_len = 0; + } + // Read one Base64 block (4 chars) + while (m_num < 4) { + uint8_t x; + *m_source >> x; + if (!m_source->ok()) _Unlikely_ { + m_state = m_source->state(); + return length - to_read; // [1] Code analysis misses `length - to_read` bytes were written to data in previous loop iterations. + } + if ((m_buf[m_num] = base64_dec_lookup[x]) != 255) + m_num++; + } + decode(); + if (m_temp_len < 3 && to_read >= 3) { + // If Base64 indicates end of data, truncate read to hint the client, end of Base64 data has been reached. + memcpy(data, m_temp + m_temp_off, m_temp_len); + m_temp_off = 0; + m_temp_len = 0; + to_read -= m_temp_len; + m_state = stdex::stream::state_t::ok; + return length - to_read; // [1] Code analysis misses `length - to_read` bytes were written to data in previous loop iterations. + } + } + } protected: - uint8_t buf[4]; ///< Internal buffer - size_t num; ///< Number of bytes used in `buf` + /// + /// Decodes one complete internal buffer of data + /// + void decode() + { + m_num = 0; + m_temp_off = 0; + m_temp[0] = ((m_buf[0] << 2) | (m_buf[1] >> 4)) & 0xff; + if (m_buf[2] < 64) { + m_temp[1] = ((m_buf[1] << 4) | (m_buf[2] >> 2)) & 0xff; + if (m_buf[3] < 64) { + m_temp[2] = ((m_buf[2] << 6) | m_buf[3]) & 0xff; + m_temp_len = 3; + } else + m_temp_len = 2; + } else + m_temp_len = 1; + } + + protected: + char m_temp[3]; ///< Temporary buffer + size_t + m_temp_off, ///< Index of data start in `m_temp` + m_temp_len; ///< Number of bytes of data in `m_temp` }; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif }