Parse chunked trailers when the stream API decompresses

StreamHandle::read() parsed the trailers only on the path without a
decompressor, so response->trailers stayed empty for a compressed
chunked response. Parse them in one place for both paths, and only
after the last chunk: after a failed read it waited another read
timeout for a trailer line, and parse_trailers_if_needed() called
before the end read body bytes as trailers.
This commit is contained in:
yhirose committed 2026-10-18 12:41:23 -04:00
1 parent 07edfb86b4
commit 2e3cb0f985
2 files changed
+19 -29

No files matched your search

+8 -29
View File
@@ -15941,24 +15941,11 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
if (!is_valid() || !response) { return -1; }
if (decompressor_) { return read_with_decompression(buf, len); }
auto n = detail::read_body_content(stream_, body_reader_, buf, len);
if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
trailers_parsed_ = true;
if (body_reader_.chunked_decoder) {
if (!body_reader_.chunked_decoder->parse_trailers_into(
response->trailers, response->headers)) {
return n;
}
} else {
detail::ChunkedDecoder dec(*stream_);
if (!dec.parse_trailers_into(response->trailers, response->headers)) {
return n;
}
}
}
auto n = decompressor_
? read_with_decompression(buf, len)
: detail::read_body_content(stream_, body_reader_, buf, len);
if (n == 0) { parse_trailers_if_needed(); }
return n;
}
@@ -16014,22 +16001,14 @@ inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
}
inline void ClientImpl::StreamHandle::parse_trailers_if_needed() {
if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
// The trailers follow the last chunk, so there are none before it
auto &decoder = body_reader_.chunked_decoder;
if (!response || trailers_parsed_ || !decoder || !decoder->finished) {
return;
}
trailers_parsed_ = true;
const auto bufsiz = 128;
char line_buf[bufsiz];
detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
if (!line_reader.getline()) { return; }
if (!detail::parse_trailers(line_reader, response->trailers,
response->headers)) {
return;
}
decoder->parse_trailers_into(response->trailers, response->headers);
}
namespace detail {
+11
View File
@@ -23632,6 +23632,17 @@ TEST_F(OpenStreamTest, Gzip) {
EXPECT_EQ("gzip", handle.response->get_header_value("Content-Encoding"));
EXPECT_EQ(100u * 1024u, read_all(handle).size());
}
TEST_F(OpenStreamTest, TrailersWithGzip) {
Client cli("127.0.0.1", port_);
auto handle =
cli.open_stream("GET", "/streamed-chunked-with-prohibited-trailer", {},
{{"Accept-Encoding", "gzip"}});
ASSERT_TRUE(handle.is_valid());
EXPECT_EQ("gzip", handle.response->get_header_value("Content-Encoding"));
EXPECT_EQ("123456789", read_all(handle));
EXPECT_EQ("yes", handle.response->get_trailer_value("X-Allowed"));
}
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT