From a7b886b9cb982a415906490f8fdd0e7292dac878 Mon Sep 17 00:00:00 2001 From: metsw24-max Date: Thu, 2 Jul 2026 05:42:00 +0530 Subject: [PATCH] Use an unsigned accumulator in base64_encode (#2477) * use an unsigned accumulator in base64_encode * Forward-declare detail::base64_encode for split builds --- httplib.h | 4 +++- test/test.cc | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index 58c63d04..8221eeb6 100644 --- a/httplib.h +++ b/httplib.h @@ -4665,7 +4665,9 @@ inline std::string base64_encode(const std::string &in) { std::string out; out.reserve(in.size()); - auto val = 0; + // Unsigned: once four bytes are folded in the top bit is set, so the next + // `val << 8` would left-shift a negative int (undefined behaviour). + uint32_t val = 0; auto valb = -6; for (auto c : in) { diff --git a/test/test.cc b/test/test.cc index 3444e85c..addfc710 100644 --- a/test/test.cc +++ b/test/test.cc @@ -420,6 +420,32 @@ TEST(SanitizeFilenameTest, VariousPatterns) { EXPECT_EQ("", httplib::sanitize_filename(" ")); } +// Forward declaration: in split builds split.py strips `inline` and moves the +// definition into httplib.cc, so detail::base64_encode is not visible from the +// public httplib.h. Re-declaring it here lets the tests link against the symbol +// in both header-only and split builds. +namespace httplib { +namespace detail { +std::string base64_encode(const std::string &in); +} // namespace detail +} // namespace httplib + +TEST(Base64EncodeTest, KnownAnswers) { + // RFC 4648 test vectors. Inputs of four bytes or more exercise the round + // where the accumulator's top bit is already set before the next shift. + EXPECT_EQ("", detail::base64_encode("")); + EXPECT_EQ("Zg==", detail::base64_encode("f")); + EXPECT_EQ("Zm8=", detail::base64_encode("fo")); + EXPECT_EQ("Zm9v", detail::base64_encode("foo")); + EXPECT_EQ("Zm9vYg==", detail::base64_encode("foob")); + EXPECT_EQ("Zm9vYmE=", detail::base64_encode("fooba")); + EXPECT_EQ("Zm9vYmFy", detail::base64_encode("foobar")); + + // High bytes keep the top bit set across several rounds. + EXPECT_EQ("AAECA//+wIB/", detail::base64_encode(std::string( + "\x00\x01\x02\x03\xff\xfe\xc0\x80\x7f", 9))); +} + TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) { string unescapedCharacters = "-_.!~*'()";