diff --git a/httplib.h b/httplib.h index a3d44c9a2c..1fa0943324 100644 --- a/httplib.h +++ b/httplib.h @@ -2957,7 +2957,18 @@ inline size_t get_header_value_u64(const Headers &headers, std::advance(it, static_cast(id)); if (it != rng.second) { if (is_numeric(it->second)) { - return static_cast(std::strtoull(it->second.data(), nullptr, 10)); + errno = 0; + auto val = std::strtoull(it->second.data(), nullptr, 10); + auto result = static_cast(val); + // strtoull saturates to ULLONG_MAX on overflow, and the size_t cast + // truncates a value that doesn't fit (a Content-Length above 2^32 wraps + // to a small length on 32-bit builds). Either way the framing length + // would be wrong, so flag it rather than return a bogus size. + if (errno == ERANGE || static_cast(result) != val) { + is_invalid_value = true; + return (std::numeric_limits::max)(); + } + return result; } else { is_invalid_value = true; } diff --git a/test/test.cc b/test/test.cc index 1410438f63..a0cf231029 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1258,6 +1258,26 @@ TEST(GetHeaderValueTest, RegularInvalidValueInt) { EXPECT_TRUE(is_invalid_value); } +TEST(GetHeaderValueTest, OutOfRangeValueInt) { + // An all-digit value that overflows size_t must be reported as invalid, not + // silently saturated/truncated: strtoull would otherwise return ULLONG_MAX + // (or, on 32-bit builds, the cast would wrap a large length to a small one), + // leaving the framing length wrong while is_invalid_value stayed false. + Headers headers = {{"Content-Length", "99999999999999999999999999"}}; + auto is_invalid_value = false; + detail::get_header_value_u64(headers, "Content-Length", 0, 0, + is_invalid_value); + EXPECT_TRUE(is_invalid_value); + + // A well-formed length is unaffected. + Headers ok = {{"Content-Length", "1234"}}; + is_invalid_value = false; + auto val = detail::get_header_value_u64(ok, "Content-Length", 0, 0, + is_invalid_value); + EXPECT_EQ(1234ull, val); + EXPECT_FALSE(is_invalid_value); +} + TEST(GetHeaderValueTest, Range) { { Headers headers = {make_range_header({{1, -1}})}; @@ -15873,13 +15893,14 @@ TEST(OpenStreamMalformedContentLength, OutOfRange) { auto port = port_future.get(); ASSERT_GT(port, 0); - // Before the fix, std::stoull would throw std::out_of_range here and - // crash the process. After the fix, strtoull silently clamps to - // ULLONG_MAX so the stream opens without crashing. The important thing - // is that the process does NOT terminate. + // Historically std::stoull would throw std::out_of_range here and crash + // the process, then strtoull silently clamped to ULLONG_MAX and the + // stream opened with a bogus framing length. Now the out-of-range value + // is flagged invalid, so the stream fails to open, matching the + // not-a-number case above. The process still must NOT terminate. Client cli("127.0.0.1", port); auto handle = cli.open_stream("GET", "/"); - EXPECT_TRUE(handle.is_valid()); + EXPECT_FALSE(handle.is_valid()); server_thread.join(); }