Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -7386,8 +7386,17 @@ inline ReadContentResult read_content_chunked(Stream &strm, T &x,
}

inline bool is_chunked_transfer_encoding(const Headers &headers) {
return case_ignore::equal(
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
// RFC 9112 6.1: a message is framed with the chunked coding when "chunked"
// is the final transfer coding. The field value may list several codings
// (e.g. "gzip, chunked"), so match the last coding token case-insensitively
// rather than comparing the whole field value against "chunked".
auto value = get_header_value(headers, "Transfer-Encoding", "", 0);

std::string last_coding;
split(value, value + std::strlen(value), ',',
[&](const char *b, const char *e) { last_coding.assign(b, e); });

return case_ignore::equal(last_coding, "chunked");
}

template <typename T, typename U>
Expand Down Expand Up @@ -13220,9 +13229,8 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
handle.body_reader_.content_length = content_length;
}

auto transfer_encoding =
handle.response->get_header_value("Transfer-Encoding");
handle.body_reader_.chunked = (transfer_encoding == "chunked");
handle.body_reader_.chunked =
detail::is_chunked_transfer_encoding(handle.response->headers);

auto content_encoding = handle.response->get_header_value("Content-Encoding");
if (!content_encoding.empty()) {
Expand Down
32 changes: 32 additions & 0 deletions test/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,38 @@ TEST(SanitizeFilenameTest, VariousPatterns) {
EXPECT_EQ("", httplib::sanitize_filename(" "));
}

// Forward declaration (see base64_encode note below) so the split build can
// see the symbol from the test.
namespace httplib {
namespace detail {
bool is_chunked_transfer_encoding(const Headers &headers);
} // namespace detail
} // namespace httplib

TEST(ChunkedTransferEncodingTest, DetectsChunkedAsFinalCoding) {
auto with_te = [](const char *te) {
Headers h;
if (te) { h.emplace("Transfer-Encoding", te); }
return h;
};

// Sole coding, matched case-insensitively.
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("chunked")));
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("Chunked")));
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("CHUNKED")));

// RFC 9112 6.1: chunked as the final coding of a list still frames the body.
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip, chunked")));
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip,chunked")));
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip, Chunked ")));

// Not chunk-framed: chunked absent, or not the final coding.
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("gzip")));
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("chunked, gzip")));
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("")));
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te(nullptr)));
}

// 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
Expand Down
Loading