Skip to content

Commit 0161429

Browse files
committed
match chunked as the final coding in is_chunked_transfer_encoding
1 parent 32abac3 commit 0161429

2 files changed

Lines changed: 61 additions & 5 deletions

File tree

httplib.h

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7386,8 +7386,24 @@ inline ReadContentResult read_content_chunked(Stream &strm, T &x,
73867386
}
73877387

73887388
inline bool is_chunked_transfer_encoding(const Headers &headers) {
7389-
return case_ignore::equal(
7390-
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
7389+
// RFC 9112 6.1: a message is framed with the chunked coding when "chunked"
7390+
// is the final transfer coding. The field value may list several codings
7391+
// (e.g. "gzip, chunked"), and multiple Transfer-Encoding lines are
7392+
// equivalent to a single comma-joined list in received order (RFC 9110
7393+
// 5.3), so match the last coding token of the last field line,
7394+
// case-insensitively.
7395+
auto rng = headers.equal_range("Transfer-Encoding");
7396+
const std::string *value = nullptr;
7397+
for (auto it = rng.first; it != rng.second; ++it) {
7398+
value = &it->second;
7399+
}
7400+
if (!value) { return false; }
7401+
7402+
std::string last_coding;
7403+
split(value->data(), value->data() + value->size(), ',',
7404+
[&](const char *b, const char *e) { last_coding.assign(b, e); });
7405+
7406+
return case_ignore::equal(last_coding, "chunked");
73917407
}
73927408

73937409
template <typename T, typename U>
@@ -13220,9 +13236,8 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
1322013236
handle.body_reader_.content_length = content_length;
1322113237
}
1322213238

13223-
auto transfer_encoding =
13224-
handle.response->get_header_value("Transfer-Encoding");
13225-
handle.body_reader_.chunked = (transfer_encoding == "chunked");
13239+
handle.body_reader_.chunked =
13240+
detail::is_chunked_transfer_encoding(handle.response->headers);
1322613241

1322713242
auto content_encoding = handle.response->get_header_value("Content-Encoding");
1322813243
if (!content_encoding.empty()) {

test/test.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,47 @@ TEST(SanitizeFilenameTest, VariousPatterns) {
420420
EXPECT_EQ("", httplib::sanitize_filename(" "));
421421
}
422422

423+
// Forward declaration (see base64_encode note below) so the split build can
424+
// see the symbol from the test.
425+
namespace httplib {
426+
namespace detail {
427+
bool is_chunked_transfer_encoding(const Headers &headers);
428+
} // namespace detail
429+
} // namespace httplib
430+
431+
TEST(ChunkedTransferEncodingTest, DetectsChunkedAsFinalCoding) {
432+
auto with_te = [](const char *te) {
433+
Headers h;
434+
if (te) { h.emplace("Transfer-Encoding", te); }
435+
return h;
436+
};
437+
438+
// Sole coding, matched case-insensitively.
439+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("chunked")));
440+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("Chunked")));
441+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("CHUNKED")));
442+
443+
// RFC 9112 6.1: chunked as the final coding of a list still frames the body.
444+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip, chunked")));
445+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip,chunked")));
446+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(with_te("gzip, Chunked ")));
447+
448+
// Multiple field lines combine in received order; the overall last token
449+
// wins.
450+
{
451+
Headers h;
452+
h.emplace("Transfer-Encoding", "gzip");
453+
h.emplace("Transfer-Encoding", "chunked");
454+
EXPECT_TRUE(detail::is_chunked_transfer_encoding(h));
455+
}
456+
457+
// Not chunk-framed: chunked absent, or not the final coding.
458+
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("gzip")));
459+
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("chunked, gzip")));
460+
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te("")));
461+
EXPECT_FALSE(detail::is_chunked_transfer_encoding(with_te(nullptr)));
462+
}
463+
423464
// Forward declaration: in split builds split.py strips `inline` and moves the
424465
// definition into httplib.cc, so detail::base64_encode is not visible from the
425466
// public httplib.h. Re-declaring it here lets the tests link against the symbol

0 commit comments

Comments
 (0)