From 5d717e6d917bd1074af1892d7f856b45ad291b3c Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 8 Feb 2026 09:40:26 -1000 Subject: [PATCH] Resolve #2347 --- httplib.h | 6 +++-- test/Makefile | 2 +- test/test.cc | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index a2b655b..55dd9d6 100644 --- a/httplib.h +++ b/httplib.h @@ -9943,8 +9943,10 @@ inline bool Server::check_if_not_modified(const Request &req, Response &res, // simplified implementation requires exact matches. auto ret = detail::split_find(val.data(), val.data() + val.size(), ',', [&](const char *b, const char *e) { - return std::equal(b, e, "*") || - std::equal(b, e, etag.begin()); + auto seg_len = static_cast(e - b); + return (seg_len == 1 && *b == '*') || + (seg_len == etag.size() && + std::equal(b, e, etag.begin())); }); if (ret) { diff --git a/test/Makefile b/test/Makefile index b75eac9..9269b3d 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,5 +1,5 @@ CXX = clang++ -CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address +CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO -fsanitize=address # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS ifneq ($(OS), Windows_NT) UNAME_S := $(shell uname -s) diff --git a/test/test.cc b/test/test.cc index 4636627..4a219c1 100644 --- a/test/test.cc +++ b/test/test.cc @@ -13730,6 +13730,76 @@ TEST(ETagTest, StaticFileETagIfNoneMatchStarNotFound) { t.join(); } +TEST(ETagTest, IfNoneMatchBoundaryCheck) { + using namespace httplib; + + // Create a test file + const char *fname = "etag_boundary_testfile.txt"; + const char *content = "boundary-test"; + { + std::ofstream ofs(fname); + ofs << content; + ASSERT_TRUE(ofs.good()); + } + + Server svr; + svr.set_mount_point("/static", "."); + auto t = std::thread([&]() { svr.listen("localhost", PORT); }); + svr.wait_until_ready(); + + Client cli(HOST, PORT); + + // Get the actual ETag + auto res1 = cli.Get("/static/etag_boundary_testfile.txt"); + ASSERT_TRUE(res1); + ASSERT_EQ(200, res1->status); + ASSERT_TRUE(res1->has_header("ETag")); + std::string etag = res1->get_header_value("ETag"); + + // Test 1: Very long ETag value (longer than actual ETag) + // Should NOT match and return 200 (not trigger out-of-bounds read) + Headers h1 = {{"If-None-Match", "W/" + "\"very-long-etag-value-that-is-much-longer-" + "than-the-actual-etag-value\""}}; + auto res2 = cli.Get("/static/etag_boundary_testfile.txt", h1); + ASSERT_TRUE(res2); + EXPECT_EQ(200, res2->status); // Should not match + + // Test 2: Long string followed by wildcard + // Should match on "*" and return 304 (without out-of-bounds read on the long + // string) + Headers h2 = {{"If-None-Match", "W/\"another-very-long-value\", *"}}; + auto res3 = cli.Get("/static/etag_boundary_testfile.txt", h2); + ASSERT_TRUE(res3); + EXPECT_EQ(304, res3->status); // Should match on "*" + + // Test 3: Wildcard followed by long string + // Should match on "*" immediately and return 304 + Headers h3 = {{"If-None-Match", "*, W/\"long-value-after-wildcard\""}}; + auto res4 = cli.Get("/static/etag_boundary_testfile.txt", h3); + ASSERT_TRUE(res4); + EXPECT_EQ(304, res4->status); // Should match on "*" + + // Test 4: Multiple long non-matching values + // Should NOT match and return 200 (test that all comparisons are safe) + Headers h4 = {{"If-None-Match", "W/\"first-long-non-matching-value\", " + "W/\"second-long-non-matching-value\", " + "W/\"third-long-non-matching-value\""}}; + auto res5 = cli.Get("/static/etag_boundary_testfile.txt", h4); + ASSERT_TRUE(res5); + EXPECT_EQ(200, res5->status); // Should not match + + // Test 5: Single character that is not "*" (edge case) + Headers h5 = {{"If-None-Match", "X"}}; + auto res6 = cli.Get("/static/etag_boundary_testfile.txt", h5); + ASSERT_TRUE(res6); + EXPECT_EQ(200, res6->status); // Should not match + + svr.stop(); + t.join(); + std::remove(fname); +} + TEST(ETagTest, LastModifiedAndIfModifiedSince) { using namespace httplib;