Browse Source

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.
yhirose 2 days ago
parent
commit
2e3cb0f985
2 changed files with 19 additions and 29 deletions
  1. 8 29
      httplib.h
  2. 11 0
      test/test.cc

+ 8 - 29
httplib.h

@@ -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 - 0
test/test.cc

@@ -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