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/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) diff --git a/src/Makefile.am b/src/Makefile.am index 4480764023d..b9956de1d44 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,7 @@ SUBDIRS = \ time \ debug \ base \ + base64 \ anyp \ helper \ dns \ @@ -510,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 \ @@ -982,6 +984,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 @@ -2261,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 new file mode 100644 index 00000000000..d6da6859dec --- /dev/null +++ b/src/base64/Base64Encoder.cc @@ -0,0 +1,26 @@ +/* + * 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 "base64.h" +#include "base64/Base64Encoder.h" + +SBuf Base64Encode(const char *input, size_t length) +{ + SBuf result; + const auto encodedLength = BASE64_ENCODE_RAW_LENGTH(length); + char *buf = result.rawAppendStart(encodedLength); + 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()); +} diff --git a/src/base64/Base64Encoder.h b/src/base64/Base64Encoder.h new file mode 100644 index 00000000000..04130f005a6 --- /dev/null +++ b/src/base64/Base64Encoder.h @@ -0,0 +1,17 @@ +/* + * 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 "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/base64/Makefile.am b/src/base64/Makefile.am new file mode 100644 index 00000000000..7a1662e3e4d --- /dev/null +++ b/src/base64/Makefile.am @@ -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) 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(); } } diff --git a/src/http.cc b/src/http.cc index 82c90586bbf..dad45e4db9d 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,10 +1890,11 @@ 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; + } } /* @@ -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()); } } diff --git a/src/tests/testBase64Encoder.cc b/src/tests/testBase64Encoder.cc new file mode 100644 index 00000000000..920331fef00 --- /dev/null +++ b/src/tests/testBase64Encoder.cc @@ -0,0 +1,65 @@ +/* + * 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 "base64/Base64Encoder.h" +#include "compat/cppunit.h" +#include "unitTestMain.h" + +class TestBase64Encode : public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE(TestBase64Encode); + CPPUNIT_TEST(testBase64EncodeFunction); + CPPUNIT_TEST(testBase64EncodeFunctionEmpty); + CPPUNIT_TEST(testBase64EncodeFunctionLargeInput); + CPPUNIT_TEST_SUITE_END(); + +protected: + void testBase64EncodeFunction(); + void testBase64EncodeFunctionEmpty(); + void testBase64EncodeFunctionLargeInput(); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( TestBase64Encode ); + +void +TestBase64Encode::testBase64EncodeFunction() +{ + SBuf input("Hello"); + SBuf result = Base64Encode(input); + CPPUNIT_ASSERT_EQUAL(SBuf("SGVsbG8="), result); +} + +void +TestBase64Encode::testBase64EncodeFunctionEmpty() +{ + SBuf input(""); + SBuf result = Base64Encode(input); + CPPUNIT_ASSERT_EQUAL(SBuf(""), result); +} + +void +TestBase64Encode::testBase64EncodeFunctionLargeInput() +{ + std::string largeInput(5000, 'A'); + SBuf input(largeInput.c_str(), largeInput.size()); + SBuf result = Base64Encode(input); + + CPPUNIT_ASSERT_EQUAL(static_cast(6668), result.length()); + 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]); + CPPUNIT_ASSERT_EQUAL(static_cast('='), result[result.length() - 1]); + CPPUNIT_ASSERT_EQUAL(static_cast('E'), result[result.length() - 2]); +} + +int +main(int argc, char *argv[]) +{ + return TestProgram().run(argc, argv); +} \ No newline at end of file