From b3a8af80b9a4c44bd8c9d197f6bd9b3bd1a3edca Mon Sep 17 00:00:00 2001 From: yhirose Date: Thu, 12 Mar 2026 23:54:50 -0400 Subject: [PATCH] Add port validation and corresponding tests to prevent overflow and out-of-range values --- httplib.h | 16 +++++++++++++--- test/test.cc | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index df3686d1..b756bc63 100644 --- a/httplib.h +++ b/httplib.h @@ -689,6 +689,14 @@ inline from_chars_result from_chars(const char *first, const char *last, return {first + (endptr - s.c_str()), std::errc{}}; } +inline bool parse_port(const std::string &s, int &port) { + int val = 0; + auto r = from_chars(s.data(), s.data() + s.size(), val); + if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; } + port = val; + return true; +} + } // namespace detail enum SSLVerifierResponse { @@ -12722,7 +12730,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { auto next_port = port_; if (!port_str.empty()) { - next_port = std::stoi(port_str); + if (!detail::parse_port(port_str, next_port)) { return false; } } else if (!next_scheme.empty()) { next_port = next_scheme == "https" ? 443 : 80; } @@ -14493,7 +14501,8 @@ inline Client::Client(const std::string &scheme_host_port, if (host.empty()) { host = m[3].str(); } auto port_str = m[4].str(); - auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + auto port = is_ssl ? 443 : 80; + if (!port_str.empty() && !detail::parse_port(port_str, port)) { return; } if (is_ssl) { #ifdef CPPHTTPLIB_SSL_ENABLED @@ -19906,7 +19915,8 @@ inline WebSocketClient::WebSocketClient( if (host_.empty()) { host_ = m[3].str(); } auto port_str = m[4].str(); - port_ = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + port_ = is_ssl ? 443 : 80; + if (!port_str.empty() && !detail::parse_port(port_str, port_)) { return; } path_ = m[5].str(); diff --git a/test/test.cc b/test/test.cc index d9c371a5..b001058a 100644 --- a/test/test.cc +++ b/test/test.cc @@ -2330,6 +2330,31 @@ TEST(RedirectToDifferentPort, Redirect) { EXPECT_EQ("Hello World!", res->body); } +TEST(RedirectToDifferentPort, OverflowPortNumber) { + Server svr; + svr.Get("/redir", [&](const Request & /*req*/, Response &res) { + // Port number that overflows int — should not crash + res.set_redirect("http://localhost:99999999999999999999/target"); + }); + + auto port = svr.bind_to_any_port(HOST); + auto thread = std::thread([&]() { svr.listen_after_bind(); }); + auto se = detail::scope_exit([&] { + svr.stop(); + thread.join(); + ASSERT_FALSE(svr.is_running()); + }); + + svr.wait_until_ready(); + + Client cli(HOST, port); + cli.set_follow_location(true); + + auto res = cli.Get("/redir"); + // Should fail gracefully, not crash (no valid response due to bad port) + EXPECT_FALSE(res); +} + TEST(RedirectFromPageWithContent, Redirect) { Server svr; @@ -9441,6 +9466,18 @@ TEST(HostAndPortPropertiesTest, NoSSLWithSimpleAPI) { ASSERT_EQ(1234, cli.port()); } +TEST(HostAndPortPropertiesTest, OverflowPortNumber) { + // Port number that overflows int — should not crash, client becomes invalid + httplib::Client cli("http://www.google.com:99999999999999999999"); + ASSERT_FALSE(cli.is_valid()); +} + +TEST(HostAndPortPropertiesTest, PortOutOfRange) { + // Port 99999 exceeds valid range (1-65535) — should not crash + httplib::Client cli("http://www.google.com:99999"); + ASSERT_FALSE(cli.is_valid()); +} + #ifdef CPPHTTPLIB_SSL_ENABLED TEST(HostAndPortPropertiesTest, SSL) { httplib::SSLClient cli("www.google.com"); @@ -16485,6 +16522,14 @@ TEST(WebSocketTest, InvalidURL) { // Missing host ws::WebSocketClient ws4("ws://:8080/path"); EXPECT_FALSE(ws4.is_valid()); + + // Port number overflow — should not crash + ws::WebSocketClient ws5("ws://localhost:99999999999999999999/path"); + EXPECT_FALSE(ws5.is_valid()); + + // Port out of range + ws::WebSocketClient ws6("ws://localhost:99999/path"); + EXPECT_FALSE(ws6.is_valid()); } TEST(WebSocketTest, UnsupportedScheme) {