Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ SUBDIRS = \
time \
debug \
base \
base64 \
anyp \
helper \
dns \
Expand Down Expand Up @@ -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
Expand Down
183 changes: 183 additions & 0 deletions src/base64/Base64Encoder.cc
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;
}
115 changes: 115 additions & 0 deletions src/base64/Base64Encoder.h
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
{
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);

/// 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 */
20 changes: 20 additions & 0 deletions src/base64/Makefile.am
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)
Loading
Loading