-
Notifications
You must be signed in to change notification settings - Fork 640
[DISCUSS] Prototype a Base64Encoder #2452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| /* | ||
| * Copyright (C) 1996-2026 The Squid Software Foundation and contributors | ||
| * | ||
| * Squid software is distributed under GPLv2+ license and includes | ||
| * contributions from numerous individuals and organizations. | ||
| * Please see the COPYING and CONTRIBUTORS files for details. | ||
| */ | ||
|
|
||
| #include "squid.h" | ||
| #include "base/TextException.h" | ||
| #include "base64/Base64Encoder.h" | ||
|
|
||
| Base64Encoder::Base64Encoder(size_t maxEncodedSize) | ||
| : std::ostream(nullptr), | ||
| maxEncodedSize_(maxEncodedSize), | ||
| streamBuffer_(*this) | ||
| { | ||
| base64_encode_init(&ctx_); | ||
| rdbuf(&streamBuffer_); | ||
| clear(); | ||
| } | ||
|
|
||
| Base64Encoder::Base64Encoder(const SBuf &input, size_t maxEncodedSize) | ||
| : Base64Encoder(maxEncodedSize) | ||
| { | ||
| // Encode the input immediately - will throw if too large | ||
| *this << input; | ||
| } | ||
|
|
||
| Base64Encoder::~Base64Encoder() | ||
| { | ||
| // Ensure encoding is finalized; log but don't propagate exceptions to avoid terminate during unwinding | ||
| try { | ||
| streamBuffer_.pubsync(); | ||
| } catch (const std::exception &e) { | ||
| debugs(0, DBG_CRITICAL, "Base64Encoder dtor error: " << e.what()); | ||
| } catch (...) { | ||
| debugs(0, DBG_CRITICAL, "Base64Encoder dtor unknown error"); | ||
| } | ||
| } | ||
|
|
||
| SBuf | ||
| Base64Encoder::buf() | ||
| { | ||
| flush(); | ||
| return sink_; | ||
| } | ||
|
|
||
| Base64Encoder& | ||
| Base64Encoder::clearBuf() | ||
| { | ||
| flush(); | ||
| sink_.clear(); | ||
| base64_encode_init(&ctx_); | ||
| finalized_ = false; | ||
| clear(); // Clear stream error state (badbit, failbit, etc.) | ||
| return *this; | ||
| } | ||
|
|
||
| std::ostream& | ||
| operator<<(std::ostream& os, Base64Encoder& encoder) | ||
| { | ||
| encoder.flush(); | ||
| return encoder.sink_.print(os); | ||
| } | ||
|
|
||
| // --- Base64Encoder encoding implementation --- | ||
|
|
||
| void | ||
| Base64Encoder::checkSizeLimit(size_t newInputBytes) | ||
| { | ||
| // Since we sync after every append, sink_.length() is always up to date | ||
| // The additional encoded size for newInputBytes raw bytes is BASE64_ENCODE_RAW_LENGTH | ||
| const size_t additionalEncoded = BASE64_ENCODE_RAW_LENGTH(newInputBytes); | ||
| if (sink_.length() + additionalEncoded > maxEncodedSize_) | ||
| throw TextException("Base64Encoder output size limit exceeded", Here()); | ||
| } | ||
|
|
||
| void | ||
| Base64Encoder::encodePending() | ||
| { | ||
| if (streamBuffer_.inputBufferPos_ == 0) | ||
| return; | ||
|
|
||
| checkSizeLimit(0); // No additional new input, just check pending | ||
|
|
||
| const size_t maxEncoded = BASE64_ENCODE_LENGTH(streamBuffer_.inputBufferPos_) + BASE64_ENCODE_FINAL_LENGTH; | ||
| sink_.reserveSpace(maxEncoded); | ||
| char *dst = sink_.rawAppendStart(maxEncoded); | ||
| size_t encoded = base64_encode_update(&ctx_, dst, streamBuffer_.inputBufferPos_, | ||
| reinterpret_cast<const uint8_t*>(streamBuffer_.inputBuffer_)); | ||
| sink_.rawAppendFinish(dst, encoded); | ||
| streamBuffer_.inputBufferPos_ = 0; | ||
| streamBuffer_.setp(streamBuffer_.inputBuffer_, streamBuffer_.inputBuffer_ + 4096); | ||
| } | ||
|
|
||
| void | ||
| Base64Encoder::finalize() | ||
| { | ||
| if (finalized_) | ||
| return; | ||
|
|
||
| encodePending(); | ||
|
|
||
| const size_t maxFinal = BASE64_ENCODE_FINAL_LENGTH; | ||
| sink_.reserveSpace(maxFinal); | ||
| char *dst = sink_.rawAppendStart(maxFinal); | ||
| size_t encoded = base64_encode_final(&ctx_, dst); | ||
| sink_.rawAppendFinish(dst, encoded); | ||
|
|
||
| finalized_ = true; | ||
| } | ||
|
|
||
| // --- Base64StreamBuf implementation --- | ||
|
|
||
| Base64Encoder::Base64StreamBuf::Base64StreamBuf(Base64Encoder &encoder) | ||
| : encoder_(encoder) | ||
| { | ||
| inputBuffer_ = static_cast<char*>(memAllocate(MEM_4K_BUF)); | ||
| setp(inputBuffer_, inputBuffer_ + 4096); | ||
| } | ||
|
|
||
| Base64Encoder::Base64StreamBuf::~Base64StreamBuf() | ||
| { | ||
| memFree(inputBuffer_, MEM_4K_BUF); | ||
| inputBuffer_ = nullptr; | ||
|
|
||
| if (!encoder_.finalized_) { | ||
| try { | ||
| encoder_.finalize(); | ||
| } catch (const std::exception &e) { | ||
| debugs(0, DBG_CRITICAL, "Base64StreamBuf dtor error: " << e.what()); | ||
| } catch (...) { | ||
| debugs(0, DBG_CRITICAL, "Base64StreamBuf dtor unknown error"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| int | ||
| Base64Encoder::Base64StreamBuf::overflow(int_type ch) | ||
| { | ||
| if (ch != traits_type::eof()) { | ||
| encoder_.checkSizeLimit(1); | ||
| inputBuffer_[inputBufferPos_++] = static_cast<char>(ch); | ||
| if (inputBufferPos_ >= 4096) | ||
| encoder_.encodePending(); | ||
| } | ||
| encoder_.encodePending(); // Sync after every append | ||
| return ch; | ||
| } | ||
|
|
||
| int | ||
| Base64Encoder::Base64StreamBuf::sync() | ||
| { | ||
| encoder_.encodePending(); | ||
| encoder_.finalize(); | ||
| return 0; | ||
| } | ||
|
|
||
| std::streamsize | ||
| Base64Encoder::Base64StreamBuf::xsputn(const char *s, std::streamsize n) | ||
| { | ||
| std::streamsize written = 0; | ||
|
|
||
| while (n > 0) { | ||
| const size_t space = 4096 - inputBufferPos_; | ||
| const size_t toCopy = std::min<size_t>(static_cast<size_t>(n), space); | ||
|
|
||
| encoder_.checkSizeLimit(toCopy); | ||
|
|
||
| memcpy(inputBuffer_ + inputBufferPos_, s, toCopy); | ||
| inputBufferPos_ += toCopy; | ||
| s += toCopy; | ||
| n -= toCopy; | ||
| written += toCopy; | ||
|
|
||
| if (inputBufferPos_ >= 4096) | ||
| encoder_.encodePending(); | ||
| } | ||
|
|
||
| encoder_.encodePending(); // Sync after every append | ||
| return written; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| /* | ||
| * Copyright (C) 1996-2026 The Squid Software Foundation and contributors | ||
| * | ||
| * Squid software is distributed under GPLv2+ license and includes | ||
| * contributions from numerous individuals and organizations. | ||
| * Please see the COPYING and CONTRIBUTORS files for details. | ||
| */ | ||
|
|
||
| #ifndef SQUID_SRC_BASE64_BASE64ENCODER_H | ||
| #define SQUID_SRC_BASE64_BASE64ENCODER_H | ||
|
|
||
| #include "base/PackableStream.h" | ||
| #include "base64.h" | ||
| #include "mem/forward.h" | ||
| #include "sbuf/SBuf.h" | ||
|
|
||
| #include <ostream> | ||
| #include <limits> | ||
|
|
||
| /** Stream interface to write to a Base64-encoded SBuf. | ||
| * | ||
| * Data is appended using standard operator << semantics. The data is | ||
| * base64-encoded on the fly as it is written. The encoded result can be | ||
| * retrieved using the buf() method. | ||
| * | ||
| * This class inherits from std::ostream to provide a familiar streaming | ||
| * interface, similar to SBufStream. | ||
| */ | ||
| class Base64Encoder : public std::ostream | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The proposed inheritance is an API mistake for several reasons, including:
|
||
| { | ||
| public: | ||
| /// Special value indicating no size limit | ||
| static constexpr size_t noLimit = std::numeric_limits<size_t>::max(); | ||
|
|
||
| /// Create a Base64Encoder with optional maximum encoded output size limit | ||
| /// \param maxEncodedSize maximum encoded output size (default: noLimit) | ||
| explicit Base64Encoder(size_t maxEncodedSize = noLimit); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Burdening this SBuf-based class with explicitly maintaining output size restrictions is probably wrong. The underlying code should not care. Modern users should not care either. Legacy users, if any, would be better off with checking when converting from SBuf into their own storage. |
||
|
|
||
| /// Create a Base64Encoder and immediately encode the contents of a SBuf | ||
| /// \param input SBuf to encode immediately | ||
| /// \param maxEncodedSize maximum encoded output size (default: noLimit) | ||
| explicit Base64Encoder(const SBuf &input, size_t maxEncodedSize = noLimit); | ||
|
|
||
| /// Destructor finalizes the encoding | ||
| ~Base64Encoder() override; | ||
|
|
||
| /// Non-copyable (std::ostream is non-copyable) | ||
| Base64Encoder(const Base64Encoder&) = delete; | ||
| Base64Encoder& operator=(const Base64Encoder&) = delete; | ||
|
|
||
| /// Non-movable (std::ostream is non-movable) | ||
| Base64Encoder(Base64Encoder&&) = delete; | ||
| Base64Encoder& operator=(Base64Encoder&&) = delete; | ||
|
|
||
| /// Get the encoded result (finalizes encoding if not already done) | ||
| SBuf buf(); | ||
|
|
||
| /// Clear the stream's backing store and reset encoder state | ||
| Base64Encoder& clearBuf(); | ||
|
|
||
| /// Stream output operator for printing the encoded contents (finalizes encoding) | ||
| friend std::ostream& operator<<(std::ostream& os, Base64Encoder& encoder); | ||
|
|
||
| private: | ||
| /** Custom streambuf that buffers input data and delegates encoding to Base64Encoder. | ||
| * | ||
| * Only manages the input buffer. All encoding logic, size checking, | ||
| * and state management lives in Base64Encoder. | ||
| */ | ||
| class Base64StreamBuf : public std::streambuf | ||
| { | ||
| public: | ||
| Base64StreamBuf(Base64Encoder &encoder); | ||
| ~Base64StreamBuf() override; | ||
|
|
||
| protected: | ||
| int_type overflow(int_type ch = traits_type::eof()) override; | ||
| int sync() override; | ||
| std::streamsize xsputn(const char *s, std::streamsize n) override; | ||
|
|
||
| private: | ||
| Base64Encoder &encoder_; | ||
| char *inputBuffer_ = nullptr; | ||
| size_t inputBufferPos_ = 0; | ||
|
|
||
| // Base64Encoder needs access to these | ||
| friend class Base64Encoder; | ||
| }; | ||
|
|
||
| // Encoding state (moved from Base64StreamBuf) | ||
| const size_t maxEncodedSize_ = noLimit; | ||
| SBuf sink_; | ||
| base64_encode_ctx ctx_; | ||
| bool finalized_ = false; | ||
|
|
||
| // Encoding implementation (moved from Base64StreamBuf) | ||
| void checkSizeLimit(size_t newInputBytes); | ||
| void encodePending(); | ||
| void finalize(); | ||
|
|
||
| Base64StreamBuf streamBuffer_; | ||
| }; | ||
|
|
||
| /// Helper to encode multiple arguments and return the Base64-encoded result | ||
| /// Usage: SBuf result = ToBase64(arg1, arg2, ...); | ||
| template <typename... Args> | ||
| inline | ||
| SBuf ToBase64(Args&&... args) | ||
| { | ||
| Base64Encoder encoder; | ||
| (encoder << ... << args); | ||
| return encoder.buf(); | ||
| } | ||
|
|
||
| #endif /* SQUID_SRC_BASE64_BASE64ENCODER_H */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| ## Copyright (C) 1996-2026 The Squid Software Foundation and contributors | ||
| ## | ||
| ## Squid software is distributed under GPLv2+ license and includes | ||
| ## contributions from numerous individuals and organizations. | ||
| ## Please see the COPYING and CONTRIBUTORS files for details. | ||
| ## | ||
|
|
||
| include $(top_srcdir)/src/Common.am | ||
|
|
||
| noinst_LTLIBRARIES = libbase64.la | ||
|
|
||
| libbase64_la_SOURCES = \ | ||
| Base64Encoder.cc \ | ||
| Base64Encoder.h | ||
|
|
||
| libbase64_la_LIBADD = \ | ||
| $(top_builddir)/lib/libmiscencoding.la \ | ||
| $(COMPAT_LIB) \ | ||
| $(LIBNETTLE_LIBS) \ | ||
| $(XTRA_LIBS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sync() implementation essentially violates STL streambuf::sync() API. STL code that we do not control should be able to call this method multiple times, as needed, without being worried that the very first call effectively places (or should place) the stream into a "no more updates" state, a state that Base64Encoder enters when its finalize() method is called.