From aaebdbd069d74476363791526b870d70d91eab69 Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Wed, 24 Jun 2026 12:15:22 +0200 Subject: [PATCH 1/7] Prototype a Base64Encoder --- configure.ac | 1 + src/Makefile.am | 17 +++ src/base64/Base64Encoder.cc | 183 +++++++++++++++++++++++++ src/base64/Base64Encoder.h | 115 ++++++++++++++++ src/base64/Makefile.am | 16 +++ src/tests/testBase64Encoder.cc | 244 +++++++++++++++++++++++++++++++++ 6 files changed, 576 insertions(+) create mode 100644 src/base64/Base64Encoder.cc create mode 100644 src/base64/Base64Encoder.h create mode 100644 src/base64/Makefile.am create mode 100644 src/tests/testBase64Encoder.cc diff --git a/configure.ac b/configure.ac index 8fa781e4dbe..150c5301f9e 100644 --- a/configure.ac +++ b/configure.ac @@ -2573,6 +2573,7 @@ AC_CONFIG_FILES([ src/parser/Makefile src/proxyp/Makefile src/repl/Makefile + src/base64/Makefile src/sbuf/Makefile src/security/Makefile src/security/cert_generators/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 4480764023d..de1d907e57a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,7 @@ SUBDIRS = \ time \ debug \ base \ + base64 \ anyp \ helper \ dns \ @@ -982,6 +983,22 @@ tests_testSBufList_LDADD = \ $(XTRA_LIBS) tests_testSBufList_LDFLAGS = $(LIBADD_DL) +check_PROGRAMS += tests/testBase64Encoder +tests_testBase64Encoder_SOURCES = \ + tests/testBase64Encoder.cc +nodist_tests_testBase64Encoder_SOURCES = \ + tests/stub_debug.cc \ + tests/stub_libmem.cc +tests_testBase64Encoder_LDADD = \ + sbuf/libsbuf.la \ + base/libbase.la \ + base64/libbase64.la \ + $(LIBCPPUNIT_LIBS) \ + $(COMPAT_LIB) \ + $(XTRA_LIBS) \ + $(LIBNETTLE_LIBS) +tests_testBase64Encoder_LDFLAGS = $(LIBADD_DL) + check_PROGRAMS += tests/testString tests_testString_SOURCES = \ tests/testString.cc diff --git a/src/base64/Base64Encoder.cc b/src/base64/Base64Encoder.cc new file mode 100644 index 00000000000..77ef9d70c14 --- /dev/null +++ b/src/base64/Base64Encoder.cc @@ -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(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(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(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(static_cast(n), space); + + encoder_.checkSizeLimit(toCopy); + + std::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; +} \ No newline at end of file diff --git a/src/base64/Base64Encoder.h b/src/base64/Base64Encoder.h new file mode 100644 index 00000000000..b2047c4bf19 --- /dev/null +++ b/src/base64/Base64Encoder.h @@ -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 +#include + +/** 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 +{ +public: + /// Special value indicating no size limit + static constexpr size_t noLimit = std::numeric_limits::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); + + /// 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 +inline +SBuf ToBase64(Args&&... args) +{ + Base64Encoder encoder; + (encoder << ... << args); + return encoder.buf(); +} + +#endif /* SQUID_SRC_BASE64_BASE64ENCODER_H */ diff --git a/src/base64/Makefile.am b/src/base64/Makefile.am new file mode 100644 index 00000000000..cffab65525b --- /dev/null +++ b/src/base64/Makefile.am @@ -0,0 +1,16 @@ +## 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 = $(LIBNETTLE_LIBS) diff --git a/src/tests/testBase64Encoder.cc b/src/tests/testBase64Encoder.cc new file mode 100644 index 00000000000..2848aaaa7c3 --- /dev/null +++ b/src/tests/testBase64Encoder.cc @@ -0,0 +1,244 @@ +/* + * 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" +#include "compat/cppunit.h" +#include "event.h" +#include "MemObject.h" +#include "unitTestMain.h" + +#include "sbuf/Stream.h" + +/* + * test the Base64Encoder functionalities + */ + +class TestBase64Encoder : public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE(TestBase64Encoder); + CPPUNIT_TEST(testBase64EncoderDefault); + CPPUNIT_TEST(testBase64EncoderWithMaxSize); + CPPUNIT_TEST(testBase64EncoderWithInput); + CPPUNIT_TEST(testBase64EncoderStreaming); + CPPUNIT_TEST(testBase64EncoderBufAndClear); + CPPUNIT_TEST(testBase64EncoderPrint); + CPPUNIT_TEST(testBase64EncoderToBase64); + CPPUNIT_TEST(testBase64EncoderLargeInput); + CPPUNIT_TEST(testBase64EncoderMaxSize); + CPPUNIT_TEST(testBase64EncoderMaxSizeExceeded); + CPPUNIT_TEST(testBase64EncoderMaxSizeSBuf); + CPPUNIT_TEST(testBase64EncoderMaxSizeBoundary); + CPPUNIT_TEST(testBase64EncoderMaxSizeClear); + CPPUNIT_TEST_SUITE_END(); + +protected: + void testBase64EncoderDefault(); + void testBase64EncoderWithMaxSize(); + void testBase64EncoderWithInput(); + void testBase64EncoderStreaming(); + void testBase64EncoderBufAndClear(); + void testBase64EncoderPrint(); + void testBase64EncoderToBase64(); + void testBase64EncoderLargeInput(); + void testBase64EncoderMaxSize(); + void testBase64EncoderMaxSizeExceeded(); + void testBase64EncoderMaxSizeSBuf(); + void testBase64EncoderMaxSizeBoundary(); + void testBase64EncoderMaxSizeClear(); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( TestBase64Encoder ); + +/* let this test link sanely */ +void +eventAdd(const char *, EVH *, void *, double, int, bool) +{} +int64_t +MemObject::endOffset() const +{ return 0; } +/* end of stubs */ + +void +TestBase64Encoder::testBase64EncoderDefault() +{ + Base64Encoder encoder; + encoder << "Hello"; + auto result = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("SGVsbG8="), result); +} + +void +TestBase64Encoder::testBase64EncoderWithMaxSize() +{ + // Test encoder with max size limit that is not exceeded + Base64Encoder encoder(100); + encoder << "Test"; + auto result = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("VGVzdA=="), result); +} + +void +TestBase64Encoder::testBase64EncoderWithInput() +{ + SBuf input("Direct input"); + Base64Encoder encoder(input); + auto result = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("RGlyZWN0IGlucHV0"), result); +} + +void +TestBase64Encoder::testBase64EncoderStreaming() +{ + Base64Encoder encoder; + encoder << "Part1" << "Part2" << 123; + auto result = encoder.buf(); + // "Part1Part2123" base64 encoded + CPPUNIT_ASSERT_EQUAL(SBuf("UGFydDFQYXJ0MjEyMw=="), result); +} + +void +TestBase64Encoder::testBase64EncoderBufAndClear() +{ + Base64Encoder encoder; + encoder << "First"; + auto result1 = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("Rmlyc3Q="), result1); + + encoder.clearBuf(); + encoder << "Second"; + auto result2 = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("U2Vjb25k"), result2); + + CPPUNIT_ASSERT(result1 != result2); +} + +void +TestBase64Encoder::testBase64EncoderPrint() +{ + Base64Encoder encoder; + encoder << "Printable"; + SBufStream ssb; + ssb << encoder; + CPPUNIT_ASSERT_EQUAL(SBuf("UHJpbnRhYmxl"), ssb.buf()); +} + +void +TestBase64Encoder::testBase64EncoderToBase64() +{ + auto result = ToBase64("A", "B", "C"); + CPPUNIT_ASSERT_EQUAL(SBuf("QUJD"), result); +} + +void +TestBase64Encoder::testBase64EncoderLargeInput() +{ + // Create a string larger than the internal 4KB buffer (4096 bytes) + SBuf largeInput; + std::string as(5000, 'A'); + largeInput.append(as.c_str(), as.length()); + + Base64Encoder encoder; + encoder << largeInput; + auto result = encoder.buf(); + + // 5000 'A' chars = 5000 bytes = base64 encoded = 6668 chars (with padding) + CPPUNIT_ASSERT_EQUAL(static_cast(6668), result.length()); + + // Verify known prefix: 5000 'A's encoded starts with "QUFBQUFB..." (AAA->QUFB repeated) + CPPUNIT_ASSERT_EQUAL(static_cast('Q'), result[0]); + CPPUNIT_ASSERT_EQUAL(static_cast('U'), result[1]); + CPPUNIT_ASSERT_EQUAL(static_cast('F'), result[2]); + CPPUNIT_ASSERT_EQUAL(static_cast('B'), result[3]); + + // Verify padding at the end (5000 % 3 = 2, so 1 padding char '=') + CPPUNIT_ASSERT_EQUAL(static_cast('='), result[result.length() - 1]); + // Second to last is 'E' (from 'AA' -> 'QUE') + CPPUNIT_ASSERT_EQUAL(static_cast('E'), result[result.length() - 2]); +} + +void +TestBase64Encoder::testBase64EncoderMaxSize() +{ + // Test encoder with max size limit that is not exceeded + Base64Encoder encoder(50); + encoder << "Hello"; // "Hello" = 5 bytes -> base64 = 8 chars (with padding) + auto result = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("SGVsbG8="), result); +} + +void +TestBase64Encoder::testBase64EncoderMaxSizeExceeded() +{ + // Test that encoding over the limit throws TextException + Base64Encoder encoder(10); // Very small limit + encoder.exceptions(std::ios::badbit); // Enable exceptions on stream + CPPUNIT_ASSERT_THROW(encoder << "This is a long string that exceeds the limit", TextException); +} + +void +TestBase64Encoder::testBase64EncoderMaxSizeSBuf() +{ + // Test SBuf constructor with max size - input too large should throw + SBuf input("Direct input"); // 12 bytes -> base64 = 16 chars + bool threw = false; + // cannot use CPPUNIT_ASSERT_THROW here because the exception is thrown in the constructor + try { + Base64Encoder encoder(input, 10); // Limit too small - encoding happens in constructor + encoder.exceptions(std::ios::badbit); // Enable exceptions to catch stream errors + encoder.buf(); // This will throw if stream is in error state + } catch (const TextException &e) { + threw = true; + CPPUNIT_ASSERT(std::string(e.what()).find("size limit exceeded") != std::string::npos); + } catch (const std::ios::failure &e) { + // Stream throws ios::failure when exceptions enabled and badbit set + threw = true; + } + CPPUNIT_ASSERT(threw); +} + +void +TestBase64Encoder::testBase64EncoderMaxSizeBoundary() +{ + // Test exact boundary: "AB" = 2 bytes -> base64 = 4 chars + Base64Encoder encoder(4); + encoder << "AB"; + auto result = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("QUI="), result); + + // One more byte should exceed (limit 3 is too small for "AB" which needs 4) + Base64Encoder encoder2(3); + encoder2.exceptions(std::ios::badbit); + CPPUNIT_ASSERT_THROW(encoder2 << "AB", TextException); +} + +void +TestBase64Encoder::testBase64EncoderMaxSizeClear() +{ + // Test that clearBuf preserves the max size limit + Base64Encoder encoder(20); + encoder << "First"; + auto result1 = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("Rmlyc3Q="), result1); + + encoder.clearBuf(); + // Should still enforce limit + encoder.exceptions(std::ios::badbit); + CPPUNIT_ASSERT_THROW(encoder << "This is a very long string that exceeds limit", TextException); + // But encoding within limit should work after clear + encoder.clearBuf(); + encoder << "Short"; + auto result2 = encoder.buf(); + CPPUNIT_ASSERT_EQUAL(SBuf("U2hvcnQ="), result2); +} + +int +main(int argc, char *argv[]) +{ + return TestProgram().run(argc, argv); +} \ No newline at end of file From 89cb57eaaed475945c6673fcf80149bfc9dc7e8c Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Sat, 27 Jun 2026 10:33:05 +0200 Subject: [PATCH 2/7] fix memcpy --- src/base64/Base64Encoder.cc | 4 ++-- src/base64/Makefile.am | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/base64/Base64Encoder.cc b/src/base64/Base64Encoder.cc index 77ef9d70c14..0a656048877 100644 --- a/src/base64/Base64Encoder.cc +++ b/src/base64/Base64Encoder.cc @@ -168,7 +168,7 @@ Base64Encoder::Base64StreamBuf::xsputn(const char *s, std::streamsize n) encoder_.checkSizeLimit(toCopy); - std::memcpy(inputBuffer_ + inputBufferPos_, s, toCopy); + memcpy(inputBuffer_ + inputBufferPos_, s, toCopy); inputBufferPos_ += toCopy; s += toCopy; n -= toCopy; @@ -180,4 +180,4 @@ Base64Encoder::Base64StreamBuf::xsputn(const char *s, std::streamsize n) encoder_.encodePending(); // Sync after every append return written; -} \ No newline at end of file +} diff --git a/src/base64/Makefile.am b/src/base64/Makefile.am index cffab65525b..7a1662e3e4d 100644 --- a/src/base64/Makefile.am +++ b/src/base64/Makefile.am @@ -13,4 +13,8 @@ libbase64_la_SOURCES = \ Base64Encoder.cc \ Base64Encoder.h -libbase64_la_LIBADD = $(LIBNETTLE_LIBS) +libbase64_la_LIBADD = \ + $(top_builddir)/lib/libmiscencoding.la \ + $(COMPAT_LIB) \ + $(LIBNETTLE_LIBS) \ + $(XTRA_LIBS) From 61a4b134ebb9d3a9d2b118e6cca566a64caefbb8 Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Thu, 2 Jul 2026 08:14:21 +0100 Subject: [PATCH 3/7] Simpler API --- src/base64/Base64Encoder.cc | 180 ++------------------------- src/base64/Base64Encoder.h | 101 +-------------- src/tests/testBase64Encoder.cc | 219 +++------------------------------ 3 files changed, 30 insertions(+), 470 deletions(-) diff --git a/src/base64/Base64Encoder.cc b/src/base64/Base64Encoder.cc index 0a656048877..cbddbfe99a1 100644 --- a/src/base64/Base64Encoder.cc +++ b/src/base64/Base64Encoder.cc @@ -7,177 +7,15 @@ */ #include "squid.h" -#include "base/TextException.h" #include "base64/Base64Encoder.h" +#include "base64.h" -Base64Encoder::Base64Encoder(size_t maxEncodedSize) - : std::ostream(nullptr), - maxEncodedSize_(maxEncodedSize), - streamBuffer_(*this) +SBuf Base64Encode(const SBuf &input) { - 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(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(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(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(static_cast(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; -} + SBuf result; + const auto encodedLength = BASE64_ENCODE_RAW_LENGTH(input.length()); + char *buf = result.rawAppendStart(encodedLength); + base64_encode_raw(buf, input.length(), reinterpret_cast(input.rawContent())); + result.rawAppendFinish(buf, encodedLength); + return result; +} \ No newline at end of file diff --git a/src/base64/Base64Encoder.h b/src/base64/Base64Encoder.h index b2047c4bf19..6464add5a72 100644 --- a/src/base64/Base64Encoder.h +++ b/src/base64/Base64Encoder.h @@ -9,107 +9,8 @@ #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 -#include - -/** 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 -{ -public: - /// Special value indicating no size limit - static constexpr size_t noLimit = std::numeric_limits::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); - - /// 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 -inline -SBuf ToBase64(Args&&... args) -{ - Base64Encoder encoder; - (encoder << ... << args); - return encoder.buf(); -} +SBuf Base64Encode(const SBuf &input); #endif /* SQUID_SRC_BASE64_BASE64ENCODER_H */ diff --git a/src/tests/testBase64Encoder.cc b/src/tests/testBase64Encoder.cc index 2848aaaa7c3..920331fef00 100644 --- a/src/tests/testBase64Encoder.cc +++ b/src/tests/testBase64Encoder.cc @@ -7,236 +7,57 @@ */ #include "squid.h" -#include "base/TextException.h" #include "base64/Base64Encoder.h" #include "compat/cppunit.h" -#include "event.h" -#include "MemObject.h" #include "unitTestMain.h" -#include "sbuf/Stream.h" - -/* - * test the Base64Encoder functionalities - */ - -class TestBase64Encoder : public CPPUNIT_NS::TestFixture +class TestBase64Encode : public CPPUNIT_NS::TestFixture { - CPPUNIT_TEST_SUITE(TestBase64Encoder); - CPPUNIT_TEST(testBase64EncoderDefault); - CPPUNIT_TEST(testBase64EncoderWithMaxSize); - CPPUNIT_TEST(testBase64EncoderWithInput); - CPPUNIT_TEST(testBase64EncoderStreaming); - CPPUNIT_TEST(testBase64EncoderBufAndClear); - CPPUNIT_TEST(testBase64EncoderPrint); - CPPUNIT_TEST(testBase64EncoderToBase64); - CPPUNIT_TEST(testBase64EncoderLargeInput); - CPPUNIT_TEST(testBase64EncoderMaxSize); - CPPUNIT_TEST(testBase64EncoderMaxSizeExceeded); - CPPUNIT_TEST(testBase64EncoderMaxSizeSBuf); - CPPUNIT_TEST(testBase64EncoderMaxSizeBoundary); - CPPUNIT_TEST(testBase64EncoderMaxSizeClear); + CPPUNIT_TEST_SUITE(TestBase64Encode); + CPPUNIT_TEST(testBase64EncodeFunction); + CPPUNIT_TEST(testBase64EncodeFunctionEmpty); + CPPUNIT_TEST(testBase64EncodeFunctionLargeInput); CPPUNIT_TEST_SUITE_END(); protected: - void testBase64EncoderDefault(); - void testBase64EncoderWithMaxSize(); - void testBase64EncoderWithInput(); - void testBase64EncoderStreaming(); - void testBase64EncoderBufAndClear(); - void testBase64EncoderPrint(); - void testBase64EncoderToBase64(); - void testBase64EncoderLargeInput(); - void testBase64EncoderMaxSize(); - void testBase64EncoderMaxSizeExceeded(); - void testBase64EncoderMaxSizeSBuf(); - void testBase64EncoderMaxSizeBoundary(); - void testBase64EncoderMaxSizeClear(); + void testBase64EncodeFunction(); + void testBase64EncodeFunctionEmpty(); + void testBase64EncodeFunctionLargeInput(); }; -CPPUNIT_TEST_SUITE_REGISTRATION( TestBase64Encoder ); +CPPUNIT_TEST_SUITE_REGISTRATION( TestBase64Encode ); -/* let this test link sanely */ void -eventAdd(const char *, EVH *, void *, double, int, bool) -{} -int64_t -MemObject::endOffset() const -{ return 0; } -/* end of stubs */ - -void -TestBase64Encoder::testBase64EncoderDefault() +TestBase64Encode::testBase64EncodeFunction() { - Base64Encoder encoder; - encoder << "Hello"; - auto result = encoder.buf(); + SBuf input("Hello"); + SBuf result = Base64Encode(input); CPPUNIT_ASSERT_EQUAL(SBuf("SGVsbG8="), result); } void -TestBase64Encoder::testBase64EncoderWithMaxSize() -{ - // Test encoder with max size limit that is not exceeded - Base64Encoder encoder(100); - encoder << "Test"; - auto result = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("VGVzdA=="), result); -} - -void -TestBase64Encoder::testBase64EncoderWithInput() -{ - SBuf input("Direct input"); - Base64Encoder encoder(input); - auto result = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("RGlyZWN0IGlucHV0"), result); -} - -void -TestBase64Encoder::testBase64EncoderStreaming() -{ - Base64Encoder encoder; - encoder << "Part1" << "Part2" << 123; - auto result = encoder.buf(); - // "Part1Part2123" base64 encoded - CPPUNIT_ASSERT_EQUAL(SBuf("UGFydDFQYXJ0MjEyMw=="), result); -} - -void -TestBase64Encoder::testBase64EncoderBufAndClear() -{ - Base64Encoder encoder; - encoder << "First"; - auto result1 = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("Rmlyc3Q="), result1); - - encoder.clearBuf(); - encoder << "Second"; - auto result2 = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("U2Vjb25k"), result2); - - CPPUNIT_ASSERT(result1 != result2); -} - -void -TestBase64Encoder::testBase64EncoderPrint() -{ - Base64Encoder encoder; - encoder << "Printable"; - SBufStream ssb; - ssb << encoder; - CPPUNIT_ASSERT_EQUAL(SBuf("UHJpbnRhYmxl"), ssb.buf()); -} - -void -TestBase64Encoder::testBase64EncoderToBase64() +TestBase64Encode::testBase64EncodeFunctionEmpty() { - auto result = ToBase64("A", "B", "C"); - CPPUNIT_ASSERT_EQUAL(SBuf("QUJD"), result); + SBuf input(""); + SBuf result = Base64Encode(input); + CPPUNIT_ASSERT_EQUAL(SBuf(""), result); } void -TestBase64Encoder::testBase64EncoderLargeInput() +TestBase64Encode::testBase64EncodeFunctionLargeInput() { - // Create a string larger than the internal 4KB buffer (4096 bytes) - SBuf largeInput; - std::string as(5000, 'A'); - largeInput.append(as.c_str(), as.length()); - - Base64Encoder encoder; - encoder << largeInput; - auto result = encoder.buf(); + std::string largeInput(5000, 'A'); + SBuf input(largeInput.c_str(), largeInput.size()); + SBuf result = Base64Encode(input); - // 5000 'A' chars = 5000 bytes = base64 encoded = 6668 chars (with padding) CPPUNIT_ASSERT_EQUAL(static_cast(6668), result.length()); - - // Verify known prefix: 5000 'A's encoded starts with "QUFBQUFB..." (AAA->QUFB repeated) CPPUNIT_ASSERT_EQUAL(static_cast('Q'), result[0]); CPPUNIT_ASSERT_EQUAL(static_cast('U'), result[1]); CPPUNIT_ASSERT_EQUAL(static_cast('F'), result[2]); CPPUNIT_ASSERT_EQUAL(static_cast('B'), result[3]); - - // Verify padding at the end (5000 % 3 = 2, so 1 padding char '=') CPPUNIT_ASSERT_EQUAL(static_cast('='), result[result.length() - 1]); - // Second to last is 'E' (from 'AA' -> 'QUE') CPPUNIT_ASSERT_EQUAL(static_cast('E'), result[result.length() - 2]); } -void -TestBase64Encoder::testBase64EncoderMaxSize() -{ - // Test encoder with max size limit that is not exceeded - Base64Encoder encoder(50); - encoder << "Hello"; // "Hello" = 5 bytes -> base64 = 8 chars (with padding) - auto result = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("SGVsbG8="), result); -} - -void -TestBase64Encoder::testBase64EncoderMaxSizeExceeded() -{ - // Test that encoding over the limit throws TextException - Base64Encoder encoder(10); // Very small limit - encoder.exceptions(std::ios::badbit); // Enable exceptions on stream - CPPUNIT_ASSERT_THROW(encoder << "This is a long string that exceeds the limit", TextException); -} - -void -TestBase64Encoder::testBase64EncoderMaxSizeSBuf() -{ - // Test SBuf constructor with max size - input too large should throw - SBuf input("Direct input"); // 12 bytes -> base64 = 16 chars - bool threw = false; - // cannot use CPPUNIT_ASSERT_THROW here because the exception is thrown in the constructor - try { - Base64Encoder encoder(input, 10); // Limit too small - encoding happens in constructor - encoder.exceptions(std::ios::badbit); // Enable exceptions to catch stream errors - encoder.buf(); // This will throw if stream is in error state - } catch (const TextException &e) { - threw = true; - CPPUNIT_ASSERT(std::string(e.what()).find("size limit exceeded") != std::string::npos); - } catch (const std::ios::failure &e) { - // Stream throws ios::failure when exceptions enabled and badbit set - threw = true; - } - CPPUNIT_ASSERT(threw); -} - -void -TestBase64Encoder::testBase64EncoderMaxSizeBoundary() -{ - // Test exact boundary: "AB" = 2 bytes -> base64 = 4 chars - Base64Encoder encoder(4); - encoder << "AB"; - auto result = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("QUI="), result); - - // One more byte should exceed (limit 3 is too small for "AB" which needs 4) - Base64Encoder encoder2(3); - encoder2.exceptions(std::ios::badbit); - CPPUNIT_ASSERT_THROW(encoder2 << "AB", TextException); -} - -void -TestBase64Encoder::testBase64EncoderMaxSizeClear() -{ - // Test that clearBuf preserves the max size limit - Base64Encoder encoder(20); - encoder << "First"; - auto result1 = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("Rmlyc3Q="), result1); - - encoder.clearBuf(); - // Should still enforce limit - encoder.exceptions(std::ios::badbit); - CPPUNIT_ASSERT_THROW(encoder << "This is a very long string that exceeds limit", TextException); - // But encoding within limit should work after clear - encoder.clearBuf(); - encoder << "Short"; - auto result2 = encoder.buf(); - CPPUNIT_ASSERT_EQUAL(SBuf("U2hvcnQ="), result2); -} - int main(int argc, char *argv[]) { From 22ac40d1ab6da1c26ff9cd7a6d64b61a3f37e08d Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Sun, 19 Jul 2026 23:08:30 +0100 Subject: [PATCH 4/7] Add a char* variant of Base64Encoder --- src/Makefile.am | 2 ++ src/base64/Base64Encoder.cc | 11 ++++++--- src/base64/Base64Encoder.h | 1 + src/http.cc | 48 ++++++++++++++++--------------------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index de1d907e57a..b9956de1d44 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -511,6 +511,7 @@ squid_LDADD = \ http/libhttp.la \ dns/libdns.la \ base/libbase.la \ + base64/libbase64.la \ libsquid.la \ fs/libfs.la \ DiskIO/libdiskio.la \ @@ -2278,6 +2279,7 @@ tests_testCacheManager_LDADD = \ eui/libeui.la \ icmp/libicmp.la \ log/liblog.la \ + base64/libbase64.la \ format/libformat.la \ $(REPL_OBJS) \ $(ADAPTATION_LIBS) \ diff --git a/src/base64/Base64Encoder.cc b/src/base64/Base64Encoder.cc index cbddbfe99a1..88a8cc7396e 100644 --- a/src/base64/Base64Encoder.cc +++ b/src/base64/Base64Encoder.cc @@ -10,12 +10,17 @@ #include "base64/Base64Encoder.h" #include "base64.h" -SBuf Base64Encode(const SBuf &input) +SBuf Base64Encode(const char *input, size_t length) { SBuf result; - const auto encodedLength = BASE64_ENCODE_RAW_LENGTH(input.length()); + const auto encodedLength = BASE64_ENCODE_RAW_LENGTH(length); char *buf = result.rawAppendStart(encodedLength); - base64_encode_raw(buf, input.length(), reinterpret_cast(input.rawContent())); + base64_encode_raw(buf, length, reinterpret_cast(input)); result.rawAppendFinish(buf, encodedLength); return result; +} + +SBuf Base64Encode(const SBuf &input) +{ + return Base64Encode(input.rawContent(), input.length()); } \ No newline at end of file diff --git a/src/base64/Base64Encoder.h b/src/base64/Base64Encoder.h index 6464add5a72..04130f005a6 100644 --- a/src/base64/Base64Encoder.h +++ b/src/base64/Base64Encoder.h @@ -11,6 +11,7 @@ #include "sbuf/SBuf.h" +SBuf Base64Encode(const char *input, size_t length); SBuf Base64Encode(const SBuf &input); #endif /* SQUID_SRC_BASE64_BASE64ENCODER_H */ diff --git a/src/http.cc b/src/http.cc index 82c90586bbf..c80451839dd 100644 --- a/src/http.cc +++ b/src/http.cc @@ -19,7 +19,7 @@ #include "base/DelayedAsyncCalls.h" #include "base/Raw.h" #include "base/TextException.h" -#include "base64.h" +#include "base64/Base64Encoder.h" #include "CachePeer.h" #include "client_side.h" #include "comm/Connection.h" @@ -1834,11 +1834,6 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe } } - char loginbuf[base64_encode_len(MAX_LOGIN_SZ)]; - size_t blen; - struct base64_encode_ctx ctx; - base64_encode_init(&ctx); - /* Special mode to pass the username to the upstream cache */ if (*request->peer_login == '*') { const char *username = "-"; @@ -1850,10 +1845,10 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe username = request->auth_user_request->username(); #endif - blen = base64_encode_update(&ctx, loginbuf, strlen(username), reinterpret_cast(username)); - blen += base64_encode_update(&ctx, loginbuf+blen, strlen(request->peer_login +1), reinterpret_cast(request->peer_login +1)); - blen += base64_encode_final(&ctx, loginbuf+blen); - httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf); + SBuf toEncode(username); + toEncode.append(request->peer_login + 1); + SBuf encoded = Base64Encode(toEncode); + httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)encoded.length(), encoded.rawContent()); return; } @@ -1862,11 +1857,12 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe (strcmp(request->peer_login, "PASS") == 0 || strcmp(request->peer_login, "PROXYPASS") == 0)) { - blen = base64_encode_update(&ctx, loginbuf, request->extacl_user.size(), reinterpret_cast(request->extacl_user.rawBuf())); - blen += base64_encode_update(&ctx, loginbuf+blen, 1, reinterpret_cast(":")); - blen += base64_encode_update(&ctx, loginbuf+blen, request->extacl_passwd.size(), reinterpret_cast(request->extacl_passwd.rawBuf())); - blen += base64_encode_final(&ctx, loginbuf+blen); - httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf); + SBuf toEncode; + toEncode.append(request->extacl_user.rawBuf(), request->extacl_user.size()); + toEncode.append(':'); + toEncode.append(request->extacl_passwd.rawBuf(), request->extacl_passwd.size()); + SBuf encoded = Base64Encode(toEncode); + httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)encoded.length(), encoded.rawContent()); return; } // if no external user credentials are available to fake authentication with PASS acts like PASSTHRU @@ -1894,11 +1890,12 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe } #endif /* HAVE_KRB5 && HAVE_GSSAPI */ - blen = base64_encode_update(&ctx, loginbuf, strlen(request->peer_login), reinterpret_cast(request->peer_login)); - blen += base64_encode_final(&ctx, loginbuf+blen); - httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)blen, loginbuf); - return; -} + { + SBuf encoded = Base64Encode(SBuf(request->peer_login)); + httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)encoded.length(), encoded.rawContent()); + return; + } + } /* * build request headers and append them to a given MemBuf @@ -2018,14 +2015,9 @@ HttpStateData::httpBuildRequestHeader(HttpRequest * request, /* append Authorization if known in URL, not in header and going direct */ if (!hdr_out->has(Http::HdrType::AUTHORIZATION)) { if (flags.toOrigin && !request->url.userInfo().isEmpty()) { - static char result[base64_encode_len(MAX_URL*2)]; // should be big enough for a single URI segment - struct base64_encode_ctx ctx; - base64_encode_init(&ctx); - size_t blen = base64_encode_update(&ctx, result, request->url.userInfo().length(), reinterpret_cast(request->url.userInfo().rawContent())); - blen += base64_encode_final(&ctx, result+blen); - result[blen] = '\0'; - if (blen) - httpHeaderPutStrf(hdr_out, Http::HdrType::AUTHORIZATION, "Basic %.*s", (int)blen, result); + auto encoded = Base64Encode(request->url.userInfo()); + if (!encoded.isEmpty()) + httpHeaderPutStrf(hdr_out, Http::HdrType::AUTHORIZATION, "Basic %.*s", (int)encoded.length(), encoded.rawContent()); } } From dd9ee7650b5287ee4324453ffab4d289be4417e6 Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Mon, 20 Jul 2026 21:12:56 +0100 Subject: [PATCH 5/7] make base64_encode_single static --- include/base64.h | 6 ------ lib/base64.cc | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/include/base64.h b/include/base64.h index 9def0dd6a9a..b7d8592e9d0 100644 --- a/include/base64.h +++ b/include/base64.h @@ -76,12 +76,6 @@ base64_encode_init(struct base64_encode_ctx *ctx); void base64url_encode_init(struct base64_encode_ctx *ctx); -/* Encodes a single byte. Returns amount of output (always 1 or 2). */ -size_t -base64_encode_single(struct base64_encode_ctx *ctx, - char *dst, - uint8_t src); - /* Returns the number of output characters. DST should point to an * area of size at least BASE64_ENCODE_LENGTH(length). */ size_t diff --git a/lib/base64.cc b/lib/base64.cc index eeeca44cae3..8754bdbfb5e 100644 --- a/lib/base64.cc +++ b/lib/base64.cc @@ -236,7 +236,7 @@ base64_encode_init(struct base64_encode_ctx *ctx) } /* Encodes a single byte. */ -size_t +static size_t base64_encode_single(struct base64_encode_ctx *ctx, char *dst, uint8_t src) From 855f4ae4bdcdd76e8bbb18c0b612055b30a1a8fd Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Mon, 20 Jul 2026 21:22:09 +0100 Subject: [PATCH 6/7] Refactor Format::Format::assemble to use Base64Encode --- src/format/Format.cc | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/format/Format.cc b/src/format/Format.cc index 445a20712f3..62913c933c8 100644 --- a/src/format/Format.cc +++ b/src/format/Format.cc @@ -8,7 +8,7 @@ #include "squid.h" #include "AccessLogEntry.h" -#include "base64.h" +#include "base64/Base64Encoder.h" #include "client_side.h" #include "comm/Connection.h" #include "error/Detail.h" @@ -556,16 +556,8 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS case LFT_CLIENT_HANDSHAKE: if (al->request && al->request->clientConnectionManager.valid()) { const auto &handshake = al->request->clientConnectionManager->preservedClientData; - if (const auto rawLength = handshake.length()) { - // add 1 byte to optimize the c_str() conversion below - char *buf = sb.rawAppendStart(base64_encode_len(rawLength) + 1); - - struct base64_encode_ctx ctx; - base64_encode_init(&ctx); - auto encLength = base64_encode_update(&ctx, buf, rawLength, reinterpret_cast(handshake.rawContent())); - encLength += base64_encode_final(&ctx, buf + encLength); - - sb.rawAppendFinish(buf, encLength); + if (!handshake.isEmpty()) { + sb = Base64Encode(handshake); out = sb.c_str(); } } From d9a6d20d9784ef1a932b7f8b386d4a38fe68c45d Mon Sep 17 00:00:00 2001 From: Francesco Chemolli Date: Mon, 20 Jul 2026 22:13:56 +0100 Subject: [PATCH 7/7] Source format enforcement --- src/base64/Base64Encoder.cc | 4 ++-- src/http.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/base64/Base64Encoder.cc b/src/base64/Base64Encoder.cc index 88a8cc7396e..d6da6859dec 100644 --- a/src/base64/Base64Encoder.cc +++ b/src/base64/Base64Encoder.cc @@ -7,8 +7,8 @@ */ #include "squid.h" -#include "base64/Base64Encoder.h" #include "base64.h" +#include "base64/Base64Encoder.h" SBuf Base64Encode(const char *input, size_t length) { @@ -23,4 +23,4 @@ SBuf Base64Encode(const char *input, size_t length) SBuf Base64Encode(const SBuf &input) { return Base64Encode(input.rawContent(), input.length()); -} \ No newline at end of file +} diff --git a/src/http.cc b/src/http.cc index c80451839dd..dad45e4db9d 100644 --- a/src/http.cc +++ b/src/http.cc @@ -1895,7 +1895,7 @@ httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHe httpHeaderPutStrf(hdr_out, header, "Basic %.*s", (int)encoded.length(), encoded.rawContent()); return; } - } +} /* * build request headers and append them to a given MemBuf