From 5126a138a8b7b07bd8d83c59d8facfa43c6392dc Mon Sep 17 00:00:00 2001 From: harbinresearcher Date: Fri, 23 Oct 2026 14:10:57 +0800 Subject: [PATCH 1/2] Honor decompression setting when opening response streams --- httplib.h | 2 +- test/test.cc | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index f529734cfd..d7fd7bc6bb 100644 --- a/httplib.h +++ b/httplib.h @@ -16018,7 +16018,7 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) { auto content_encoding = detail::get_combined_header_value( handle.response->headers, "Content-Encoding"); - if (!content_encoding.empty()) { + if (decompress_ && !content_encoding.empty()) { // Same policy as prepare_content_receiver(): reject a coding we know about // but were not built with, pass an unrecognized one through as-is. handle.decompressor_ = detail::create_decompressor(content_encoding); diff --git a/test/test.cc b/test/test.cc index e53aa80923..c66dc0cba3 100644 --- a/test/test.cc +++ b/test/test.cc @@ -23645,6 +23645,47 @@ static void with_single_response(const std::string &response, Fn fn) { fn(cli); } +// Disabling content decoding must preserve the same bytes through every API, +// including builds without the decoder for the advertised Content-Encoding. +TEST(StreamDecompressionTest, DisabledPreservesEncodedBody) { + const std::string gzipped(GZIPPED_HELLO_WORLD, sizeof(GZIPPED_HELLO_WORLD)); + const auto chunked = detail::from_i_to_hex(gzipped.size()) + "\r\n" + + gzipped + "\r\n0\r\n\r\n"; + const std::string framed_bodies[] = { + "Content-Length: " + std::to_string(gzipped.size()) + "\r\n\r\n" + + gzipped, + "Transfer-Encoding: chunked\r\n\r\n" + chunked, "\r\n" + gzipped}; + + for (const auto &framed_body : framed_bodies) { + const auto response = "HTTP/1.1 200 OK\r\n" + "Content-Encoding: gzip\r\n" + "Connection: close\r\n" + + framed_body; + with_single_response(response, [&](Client &cli) { + cli.set_decompress(false); + auto res = cli.Get("/"); + ASSERT_TRUE(res) << to_string(res.error()); + EXPECT_EQ(gzipped, res->body); + }); + with_single_response(response, [&](Client &cli) { + cli.set_decompress(false); + auto handle = cli.open_stream("GET", "/"); + ASSERT_TRUE(handle.is_valid()) << to_string(handle.error); + EXPECT_EQ("gzip", handle.response->get_header_value("Content-Encoding")); + EXPECT_EQ(gzipped, read_all(handle)); + EXPECT_FALSE(handle.has_read_error()); + }); + with_single_response(response, [&](Client &cli) { + cli.set_decompress(false); + auto res = stream::Get(cli, "/", 7); + ASSERT_TRUE(res) << to_string(res.error()); + EXPECT_EQ("gzip", res.get_header_value("Content-Encoding")); + EXPECT_EQ(gzipped, res.read_all()); + EXPECT_FALSE(res.has_read_error()); + }); + } +} + TEST(StreamBodyEndTest, BodyEndingAtConnectionClose) { // Neither Content-Length nor chunked: the body ends at the close const char *close_delimited = "HTTP/1.1 200 OK\r\n" From ba6941b27503f2176ac8fb2b3ce2037088c057d3 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 25 Oct 2026 19:49:06 -0400 Subject: [PATCH 2/2] Make the stream decompression test build everywhere and cover only what the fix changes The test called detail::from_i_to_hex, which the split build and MSVC do not see, so every CI job failed to compile. The fix is one branch ahead of the framing, so a single Content-Length body through open_stream() and stream::Get() covers it; the chunked and close-delimited bodies and the buffered control repeated what other tests already check. --- test/test.cc | 64 ++++++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/test/test.cc b/test/test.cc index c66dc0cba3..aa889b966e 100644 --- a/test/test.cc +++ b/test/test.cc @@ -23645,45 +23645,35 @@ static void with_single_response(const std::string &response, Fn fn) { fn(cli); } -// Disabling content decoding must preserve the same bytes through every API, -// including builds without the decoder for the advertised Content-Encoding. +// set_decompress(false) hands out the encoded bytes through the streaming APIs +// too, also in a build without the decoder for the advertised coding. TEST(StreamDecompressionTest, DisabledPreservesEncodedBody) { const std::string gzipped(GZIPPED_HELLO_WORLD, sizeof(GZIPPED_HELLO_WORLD)); - const auto chunked = detail::from_i_to_hex(gzipped.size()) + "\r\n" + - gzipped + "\r\n0\r\n\r\n"; - const std::string framed_bodies[] = { - "Content-Length: " + std::to_string(gzipped.size()) + "\r\n\r\n" + - gzipped, - "Transfer-Encoding: chunked\r\n\r\n" + chunked, "\r\n" + gzipped}; - - for (const auto &framed_body : framed_bodies) { - const auto response = "HTTP/1.1 200 OK\r\n" - "Content-Encoding: gzip\r\n" - "Connection: close\r\n" + - framed_body; - with_single_response(response, [&](Client &cli) { - cli.set_decompress(false); - auto res = cli.Get("/"); - ASSERT_TRUE(res) << to_string(res.error()); - EXPECT_EQ(gzipped, res->body); - }); - with_single_response(response, [&](Client &cli) { - cli.set_decompress(false); - auto handle = cli.open_stream("GET", "/"); - ASSERT_TRUE(handle.is_valid()) << to_string(handle.error); - EXPECT_EQ("gzip", handle.response->get_header_value("Content-Encoding")); - EXPECT_EQ(gzipped, read_all(handle)); - EXPECT_FALSE(handle.has_read_error()); - }); - with_single_response(response, [&](Client &cli) { - cli.set_decompress(false); - auto res = stream::Get(cli, "/", 7); - ASSERT_TRUE(res) << to_string(res.error()); - EXPECT_EQ("gzip", res.get_header_value("Content-Encoding")); - EXPECT_EQ(gzipped, res.read_all()); - EXPECT_FALSE(res.has_read_error()); - }); - } + const auto response = "HTTP/1.1 200 OK\r\n" + "Content-Encoding: gzip\r\n" + "Content-Length: " + + std::to_string(gzipped.size()) + + "\r\n" + "Connection: close\r\n\r\n" + + gzipped; + + with_single_response(response, [&](Client &cli) { + cli.set_decompress(false); + auto handle = cli.open_stream("GET", "/"); + ASSERT_TRUE(handle.is_valid()) << to_string(handle.error); + EXPECT_EQ("gzip", handle.response->get_header_value("Content-Encoding")); + EXPECT_EQ(gzipped, read_all(handle)); + EXPECT_FALSE(handle.has_read_error()); + }); + + with_single_response(response, [&](Client &cli) { + cli.set_decompress(false); + auto res = stream::Get(cli, "/", 7); + ASSERT_TRUE(res) << to_string(res.error()); + EXPECT_EQ("gzip", res.get_header_value("Content-Encoding")); + EXPECT_EQ(gzipped, res.read_all()); + EXPECT_FALSE(res.has_read_error()); + }); } TEST(StreamBodyEndTest, BodyEndingAtConnectionClose) {