From dc774638960f575922f1371a84bc2d9babd15aa0 Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 5 Dec 2025 00:09:01 -0500 Subject: [PATCH] Refactor ETag handling: separate strong and weak ETag checks for If-Range requests --- httplib.h | 20 ++++++++++++++------ test/test.cc | 21 +++++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/httplib.h b/httplib.h index 3c2dff2..5f0537d 100644 --- a/httplib.h +++ b/httplib.h @@ -3032,10 +3032,14 @@ inline time_t parse_http_date(const std::string &date_str) { #endif } -// Check if the string is an ETag (starts with '"' or 'W/"') -inline bool is_etag(const std::string &s) { - return !s.empty() && - (s[0] == '"' || (s.size() > 2 && s[0] == 'W' && s[1] == '/')); +// Check if the string is a weak ETag (starts with 'W/"') +inline bool is_weak_etag(const std::string &s) { + return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"'; +} + +// Check if the string is a strong ETag (starts with '"' but not 'W/"') +inline bool is_strong_etag(const std::string &s) { + return !s.empty() && s[0] == '"'; } inline size_t to_utf8(int code, char *buff) { @@ -8399,9 +8403,13 @@ inline bool Server::handle_file_request(Request &req, Response &res) { auto if_range = req.get_header_value("If-Range"); auto valid = false; - if (detail::is_etag(if_range)) { - // ETag comparison (weak comparison for If-Range per RFC 9110) + if (detail::is_strong_etag(if_range)) { + // RFC 9110 Section 13.1.5: If-Range requires strong ETag + // comparison. valid = (!etag.empty() && if_range == etag); + } else if (detail::is_weak_etag(if_range)) { + // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5) + valid = false; } else { // HTTP-date comparison auto if_range_time = detail::parse_http_date(if_range); diff --git a/test/test.cc b/test/test.cc index a9dcf8b..d93a27b 100644 --- a/test/test.cc +++ b/test/test.cc @@ -12875,13 +12875,16 @@ TEST(ETagTest, IfRangeWithETag) { ASSERT_TRUE(res1->has_header("ETag")); std::string etag = res1->get_header_value("ETag"); - // Range request with matching If-Range (ETag): should get 206 + // RFC 9110 Section 13.1.5: If-Range requires strong ETag comparison. + // Since our server generates weak ETags (W/"..."), If-Range with our + // ETag should NOT result in partial content - it should return full content. Headers h2 = {{"Range", "bytes=0-4"}, {"If-Range", etag}}; auto res2 = cli.Get("/static/if_range_testfile.txt", h2); ASSERT_TRUE(res2); - EXPECT_EQ(206, res2->status); - EXPECT_EQ("01234", res2->body); - EXPECT_TRUE(res2->has_header("Content-Range")); + // Weak ETag in If-Range -> full content (200), not partial (206) + EXPECT_EQ(200, res2->status); + EXPECT_EQ(content, res2->body); + EXPECT_FALSE(res2->has_header("Content-Range")); // Range request with non-matching If-Range (ETag): should get 200 (full // content) @@ -12892,6 +12895,16 @@ TEST(ETagTest, IfRangeWithETag) { EXPECT_EQ(content, res3->body); EXPECT_FALSE(res3->has_header("Content-Range")); + // Range request with strong ETag (hypothetical - our server doesn't generate + // strong ETags, but if client sends a strong ETag that doesn't match, it + // should return full content) + Headers h4 = {{"Range", "bytes=0-4"}, {"If-Range", "\"strong-etag\""}}; + auto res4 = cli.Get("/static/if_range_testfile.txt", h4); + ASSERT_TRUE(res4); + EXPECT_EQ(200, res4->status); + EXPECT_EQ(content, res4->body); + EXPECT_FALSE(res4->has_header("Content-Range")); + svr.stop(); t.join(); std::remove(fname);