-
Notifications
You must be signed in to change notification settings - Fork 648
[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
Draft
kinkie
wants to merge
2
commits into
squid-cache:master
Choose a base branch
from
kinkie:base64encoder
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
kinkie marked this conversation as resolved.
|
||
| { | ||
| 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); | ||
|
kinkie marked this conversation as resolved.
|
||
|
|
||
| /// 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 */ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.