From 5c9285776ee1803d8dc5f3ced5e3179454bd6bbd Mon Sep 17 00:00:00 2001 From: yhirose Date: Thu, 14 May 2026 23:19:36 +0900 Subject: [PATCH] Fix crash on empty X-Forwarded-For with trusted proxies configured --- httplib.h | 8 +++++++- test/test.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index 80d34d9..4a31b57 100644 --- a/httplib.h +++ b/httplib.h @@ -11888,6 +11888,11 @@ get_client_ip(const std::string &x_forwarded_for, ip_list.emplace_back(std::string(b + r.first, b + r.second)); }); + // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields + // no segments. Signal "no client IP derived" with an empty string so the + // caller can fall back to the connection-level remote address. + if (ip_list.empty()) { return std::string(); } + for (size_t i = 0; i < ip_list.size(); ++i) { auto ip = ip_list[i]; @@ -11978,7 +11983,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr, if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) { auto x_forwarded_for = req.get_header_value("X-Forwarded-For"); - req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies_); + auto derived = get_client_ip(x_forwarded_for, trusted_proxies_); + req.remote_addr = derived.empty() ? remote_addr : derived; } else { req.remote_addr = remote_addr; } diff --git a/test/test.cc b/test/test.cc index 9d6fb62..3f1a4cd 100644 --- a/test/test.cc +++ b/test/test.cc @@ -14251,6 +14251,53 @@ TEST(ForwardedHeadersTest, HandlesWhitespaceAroundIPs) { EXPECT_EQ(observed_remote_addr, "203.0.113.66"); } +// An X-Forwarded-For header whose value parses to zero IP segments must not +// crash the server (it used to call front() on an empty vector inside +// get_client_ip). The connection-level remote address must be retained instead. +static void run_malformed_xff_test(const std::string &xff_value) { + Server svr; + svr.set_trusted_proxies({"192.0.2.45"}); + + std::string observed_remote_addr; + svr.Get("/ip", [&](const Request &req, Response &res) { + observed_remote_addr = req.remote_addr; + res.set_content("ok", "text/plain"); + }); + + int port = 0; + thread t = thread([&]() { + port = svr.bind_to_any_port(HOST); + svr.listen_after_bind(); + }); + auto se = detail::scope_exit([&] { + svr.stop(); + t.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + Client cli(HOST, port); + auto res = cli.Get("/ip", {{"X-Forwarded-For", xff_value}}); + + ASSERT_TRUE(res); + EXPECT_EQ(StatusCode::OK_200, res->status); + EXPECT_TRUE(observed_remote_addr == "::1" || + observed_remote_addr == "127.0.0.1"); +} + +TEST(ForwardedHeadersTest, EmptyXForwardedFor_DoesNotCrash) { + run_malformed_xff_test(""); +} + +TEST(ForwardedHeadersTest, CommaOnlyXForwardedFor_DoesNotCrash) { + run_malformed_xff_test(","); +} + +TEST(ForwardedHeadersTest, MultipleCommasXForwardedFor_DoesNotCrash) { + run_malformed_xff_test(", , ,"); +} + #ifndef _WIN32 TEST(ServerRequestParsingTest, RequestWithoutContentLengthOrTransferEncoding) { Server svr;