From 65ce51aed7f15e40e8fb6d2c0a8efb10bcb40126 Mon Sep 17 00:00:00 2001 From: Jean-Francois Simoneau Date: Tue, 18 Mar 2025 19:17:47 -0400 Subject: [PATCH 01/16] Fix start_time shadow variable (#2114) --- httplib.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/httplib.h b/httplib.h index 33d7f97..836618f 100644 --- a/httplib.h +++ b/httplib.h @@ -3365,7 +3365,7 @@ private: time_t write_timeout_sec_; time_t write_timeout_usec_; time_t max_timeout_msec_; - const std::chrono::time_point start_time; + const std::chrono::time_point start_time_; std::vector read_buff_; size_t read_buff_off_ = 0; @@ -3403,7 +3403,7 @@ private: time_t write_timeout_sec_; time_t write_timeout_usec_; time_t max_timeout_msec_; - const std::chrono::time_point start_time; + const std::chrono::time_point start_time_; }; #endif @@ -6060,7 +6060,7 @@ inline SocketStream::SocketStream( read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec), - max_timeout_msec_(max_timeout_msec), start_time(start_time), + max_timeout_msec_(max_timeout_msec), start_time_(start_time), read_buff_(read_buff_size_, 0) {} inline SocketStream::~SocketStream() = default; @@ -6158,7 +6158,7 @@ inline socket_t SocketStream::socket() const { return sock_; } inline time_t SocketStream::duration() const { return std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time) + std::chrono::steady_clock::now() - start_time_) .count(); } @@ -9200,7 +9200,7 @@ inline SSLSocketStream::SSLSocketStream( read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec), - max_timeout_msec_(max_timeout_msec), start_time(start_time) { + max_timeout_msec_(max_timeout_msec), start_time_(start_time) { SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } @@ -9306,7 +9306,7 @@ inline socket_t SSLSocketStream::socket() const { return sock_; } inline time_t SSLSocketStream::duration() const { return std::chrono::duration_cast( - std::chrono::steady_clock::now() - start_time) + std::chrono::steady_clock::now() - start_time_) .count(); } From 72b35befb2421e69f0ba7de8859ad7036b4245a2 Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 25 Mar 2025 00:14:24 +0100 Subject: [PATCH 02/16] Add AF_UNIX support on windows (#2115) Signed-off-by: Piotr Stankiewicz --- httplib.h | 11 +++++++++-- test/test.cc | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index 836618f..1f06f77 100644 --- a/httplib.h +++ b/httplib.h @@ -187,6 +187,7 @@ using ssize_t = long; #include #include #include +#include #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 @@ -3538,7 +3539,6 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, hints.ai_flags = socket_flags; } -#ifndef _WIN32 if (hints.ai_family == AF_UNIX) { const auto addrlen = host.length(); if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } @@ -3562,11 +3562,19 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, sizeof(addr) - sizeof(addr.sun_path) + addrlen); #ifndef SOCK_CLOEXEC +#ifndef _WIN32 fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif #endif if (socket_options) { socket_options(sock); } +#ifdef _WIN32 + // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so avoid + // setting default_socket_options. + detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); +#endif + bool dummy; if (!bind_or_connect(sock, hints, dummy)) { close_socket(sock); @@ -3575,7 +3583,6 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, } return sock; } -#endif auto service = std::to_string(port); diff --git a/test/test.cc b/test/test.cc index acbd11a..7100ebc 100644 --- a/test/test.cc +++ b/test/test.cc @@ -70,7 +70,6 @@ static void read_file(const std::string &path, std::string &out) { fs.read(&out[0], static_cast(size)); } -#ifndef _WIN32 class UnixSocketTest : public ::testing::Test { protected: void TearDown() override { std::remove(pathname_.c_str()); } @@ -167,6 +166,7 @@ TEST_F(UnixSocketTest, abstract) { } #endif +#ifndef _WIN32 TEST(SocketStream, wait_writable_UNIX) { int fds[2]; ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); From 7dbf5471ce451364786f745da7154e2f487df716 Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 24 Mar 2025 19:16:48 -0400 Subject: [PATCH 03/16] Fix the style error and comment --- httplib.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index 1f06f77..5d2d0db 100644 --- a/httplib.h +++ b/httplib.h @@ -184,10 +184,10 @@ using ssize_t = long; #define NOMINMAX #endif // NOMINMAX +#include #include #include #include -#include #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 @@ -3570,8 +3570,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, if (socket_options) { socket_options(sock); } #ifdef _WIN32 - // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so avoid - // setting default_socket_options. + // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so + // remove the option. detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); #endif From dbc4af819a2cd462f0b844784be7b52c46584845 Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 25 Mar 2025 13:36:20 +0100 Subject: [PATCH 04/16] Fix compilation error on windows (#2118) afunix.h uses types declared in winsock2.h, and so has to be included after it. Including afunix.h first will result in a somewhat unhelpful compilation error: error C3646: 'sun_family': unknown override specifier Signed-off-by: Piotr Stankiewicz --- httplib.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index 5d2d0db..0f981dc 100644 --- a/httplib.h +++ b/httplib.h @@ -184,11 +184,13 @@ using ssize_t = long; #define NOMINMAX #endif // NOMINMAX -#include #include #include #include +// afunix.h uses types declared in winsock2.h, so has to be included after it. +#include + #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif From 0dbe8ba1446d714f94fcc3202db765754f6ca987 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sat, 29 Mar 2025 15:46:22 +0000 Subject: [PATCH 05/16] Support zstd also via pkg-config (#2121) * Support zstd also via pkg-config It doesn't always provide cmake config * Find zstd with pkg-config also in non-required case Code by @sum01, slightly modified --- CMakeLists.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0353b0c..d540151 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,10 +159,26 @@ elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE) endif() if(HTTPLIB_REQUIRE_ZSTD) - find_package(zstd REQUIRED) + find_package(zstd) + if(NOT zstd_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() set(HTTPLIB_IS_USING_ZSTD TRUE) elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE) find_package(zstd QUIET) + if(NOT zstd_FOUND) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd) + + if(TARGET PkgConfig::zstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() + endif() + endif() + # Both find_package and PkgConf set a XXX_FOUND var set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND}) endif() From b7e33b08f17ae096de3f01a45e7e0a9ebe3c157b Mon Sep 17 00:00:00 2001 From: KTGH Date: Mon, 31 Mar 2025 20:34:28 -0400 Subject: [PATCH 06/16] Add missing component comment (#2124) Fix #2123 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d540151..b0e0fda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ ------------------------------------------------------------------------------- - After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available. + After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli zstd) is available. This creates a httplib::httplib target (if found and if listed components are supported). It can be linked like so: From 3e3a8cc02f2bbd92e47b2f8a4fc8fcada112202b Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 5 Apr 2025 22:38:50 -0400 Subject: [PATCH 07/16] Made the max timeout threshold for SSL longer. --- test/test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.cc b/test/test.cc index 7100ebc..4fd9983 100644 --- a/test/test.cc +++ b/test/test.cc @@ -8623,7 +8623,7 @@ TEST(MaxTimeoutTest, ContentStream) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT TEST(MaxTimeoutTest, ContentStreamSSL) { time_t timeout = 2000; - time_t threshold = 500; // SSL_shutdown is slow on some operating systems. + time_t threshold = 1200; // SSL_shutdown is slow on some operating systems. SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); From 65d6316d65aecdc1ca02e46902782e31e57ab9f3 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 5 Apr 2025 22:40:08 -0400 Subject: [PATCH 08/16] Fix #2113 --- httplib.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/httplib.h b/httplib.h index 0f981dc..9af65a3 100644 --- a/httplib.h +++ b/httplib.h @@ -6055,6 +6055,10 @@ inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, auto actual_timeout_msec = (std::min)(max_timeout_msec - duration_msec, timeout_msec); + if (actual_timeout_msec < 0) { + actual_timeout_msec = 0; + } + actual_timeout_sec = actual_timeout_msec / 1000; actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; } From 9e4aed482e707ffbdcee197aa455de53c6f54379 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sun, 6 Apr 2025 09:02:25 -0400 Subject: [PATCH 09/16] Fix style error --- httplib.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index 9af65a3..0e1d522 100644 --- a/httplib.h +++ b/httplib.h @@ -6055,9 +6055,7 @@ inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, auto actual_timeout_msec = (std::min)(max_timeout_msec - duration_msec, timeout_msec); - if (actual_timeout_msec < 0) { - actual_timeout_msec = 0; - } + if (actual_timeout_msec < 0) { actual_timeout_msec = 0; } actual_timeout_sec = actual_timeout_msec / 1000; actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; From caf7c55785ed0d259486c6bb80843bbc3dcc9cba Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Tue, 8 Apr 2025 21:08:41 +0100 Subject: [PATCH 10/16] Fix regression of #2121 (#2126) --- cmake/httplibConfig.cmake.in | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cmake/httplibConfig.cmake.in b/cmake/httplibConfig.cmake.in index bf57364..19dbe69 100644 --- a/cmake/httplibConfig.cmake.in +++ b/cmake/httplibConfig.cmake.in @@ -39,7 +39,25 @@ if(@HTTPLIB_IS_USING_BROTLI@) endif() if(@HTTPLIB_IS_USING_ZSTD@) - find_dependency(zstd) + set(httplib_fd_zstd_quiet_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(httplib_fd_zstd_quiet_arg QUIET) + endif() + set(httplib_fd_zstd_required_arg) + if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + set(httplib_fd_zstd_required_arg REQUIRED) + endif() + find_package(zstd QUIET) + if(NOT zstd_FOUND) + find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd) + + if(TARGET PkgConfig::zstd) + add_library(zstd::libzstd ALIAS PkgConfig::zstd) + endif() + endif() + endif() set(httplib_zstd_FOUND ${zstd_FOUND}) endif() From 9589519d5823366d176773725751129dcb1b15fd Mon Sep 17 00:00:00 2001 From: yhirose Date: Thu, 17 Apr 2025 11:52:22 -0400 Subject: [PATCH 11/16] Fix #2130 --- httplib.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/httplib.h b/httplib.h index 0e1d522..cb182c4 100644 --- a/httplib.h +++ b/httplib.h @@ -7329,8 +7329,9 @@ Server::process_request(Stream &strm, const std::string &remote_addr, } // Setup `is_connection_closed` method - req.is_connection_closed = [&]() { - return !detail::is_socket_alive(strm.socket()); + auto sock = strm.socket(); + req.is_connection_closed = [sock]() { + return !detail::is_socket_alive(sock); }; // Routing From 7b752106ac42bd5b907793950d9125a0972c8e8e Mon Sep 17 00:00:00 2001 From: Ville Vesilehto Date: Sat, 3 May 2025 11:39:01 +0300 Subject: [PATCH 12/16] Merge commit from fork * fix(parser): Limit line length in getline Prevents potential infinite loop and memory exhaustion in stream_line_reader::getline by enforcing max line length. Signed-off-by: Ville Vesilehto * fix: increase default max line length to 32k LONG_QUERY_VALUE test is set at 25k. Signed-off-by: Ville Vesilehto * test(client): expect read error with too long query Adds a test case (`TooLongQueryValue`) to verify client behavior when the request URI is excessively long, exceeding `CPPHTTPLIB_MAX_LINE_LENGTH`. In this scenario, the server is expected to reset the connection. Signed-off-by: Ville Vesilehto --------- Signed-off-by: Ville Vesilehto --- httplib.h | 9 +++++++++ test/test.cc | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/httplib.h b/httplib.h index cb182c4..a2aa24f 100644 --- a/httplib.h +++ b/httplib.h @@ -145,6 +145,10 @@ #define CPPHTTPLIB_LISTEN_BACKLOG 5 #endif +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + /* * Headers */ @@ -3067,6 +3071,11 @@ inline bool stream_line_reader::getline() { #endif for (size_t i = 0;; i++) { + if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { + // Treat exceptionally long lines as an error to + // prevent infinite loops/memory exhaustion + return false; + } char byte; auto n = strm_.read(&byte, 1); diff --git a/test/test.cc b/test/test.cc index 4fd9983..7f5cc8a 100644 --- a/test/test.cc +++ b/test/test.cc @@ -43,6 +43,9 @@ const int PORT = 1234; const string LONG_QUERY_VALUE = string(25000, '@'); const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE; +const string TOO_LONG_QUERY_VALUE = string(35000, '@'); +const string TOO_LONG_QUERY_URL = "/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE; + const std::string JSON_DATA = "{\"hello\":\"world\"}"; const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB @@ -2867,6 +2870,11 @@ protected: EXPECT_EQ(LONG_QUERY_URL, req.target); EXPECT_EQ(LONG_QUERY_VALUE, req.get_param_value("key")); }) + .Get("/too-long-query-value", + [&](const Request &req, Response & /*res*/) { + EXPECT_EQ(TOO_LONG_QUERY_URL, req.target); + EXPECT_EQ(TOO_LONG_QUERY_VALUE, req.get_param_value("key")); + }) .Get("/array-param", [&](const Request &req, Response & /*res*/) { EXPECT_EQ(3u, req.get_param_value_count("array")); @@ -3655,6 +3663,13 @@ TEST_F(ServerTest, LongQueryValue) { EXPECT_EQ(StatusCode::UriTooLong_414, res->status); } +TEST_F(ServerTest, TooLongQueryValue) { + auto res = cli_.Get(TOO_LONG_QUERY_URL.c_str()); + + ASSERT_FALSE(res); + EXPECT_EQ(Error::Read, res.error()); +} + TEST_F(ServerTest, TooLongHeader) { Request req; req.method = "GET"; From a0de42ebc41d5a7b24175f5766b6a811b056bf09 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 3 May 2025 17:40:34 +0900 Subject: [PATCH 13/16] clang-format --- test/test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test.cc b/test/test.cc index 7f5cc8a..a57b7c9 100644 --- a/test/test.cc +++ b/test/test.cc @@ -44,7 +44,8 @@ const string LONG_QUERY_VALUE = string(25000, '@'); const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE; const string TOO_LONG_QUERY_VALUE = string(35000, '@'); -const string TOO_LONG_QUERY_URL = "/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE; +const string TOO_LONG_QUERY_URL = + "/too-long-query-value?key=" + TOO_LONG_QUERY_VALUE; const std::string JSON_DATA = "{\"hello\":\"world\"}"; From 3af7f2c16147f3fbc6e4d717032daf505dc1652c Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 3 May 2025 21:24:22 +0900 Subject: [PATCH 14/16] Release v0.20.1 --- httplib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index a2aa24f..0aa4e62 100644 --- a/httplib.h +++ b/httplib.h @@ -8,7 +8,7 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.20.0" +#define CPPHTTPLIB_VERSION "0.20.1" /* * Configuration From 61893a00a42d0f5ac00c9422d63bd82ccdc56531 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 3 May 2025 22:50:47 +0900 Subject: [PATCH 15/16] Fix #2135 --- httplib.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/httplib.h b/httplib.h index 0aa4e62..766819d 100644 --- a/httplib.h +++ b/httplib.h @@ -2066,7 +2066,9 @@ template inline constexpr size_t str_len(const char (&)[N]) { } inline bool is_numeric(const std::string &str) { - return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); + return !str.empty() && + std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); } inline uint64_t get_header_value_u64(const Headers &headers, From c216dc94d20e617de237860f3cbbc5b92e820e06 Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 9 May 2025 18:45:31 +0900 Subject: [PATCH 16/16] Code cleanup --- httplib.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/httplib.h b/httplib.h index 766819d..ae4507d 100644 --- a/httplib.h +++ b/httplib.h @@ -1087,8 +1087,7 @@ private: bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); - bool handle_file_request(const Request &req, Response &res, - bool head = false); + bool handle_file_request(const Request &req, Response &res); bool dispatch_request(Request &req, Response &res, const Handlers &handlers) const; bool dispatch_request_for_content_reader( @@ -6880,8 +6879,7 @@ Server::read_content_core(Stream &strm, Request &req, Response &res, return true; } -inline bool Server::handle_file_request(const Request &req, Response &res, - bool head) { +inline bool Server::handle_file_request(const Request &req, Response &res) { for (const auto &entry : base_dirs_) { // Prefix match if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { @@ -6914,7 +6912,7 @@ inline bool Server::handle_file_request(const Request &req, Response &res, return true; }); - if (!head && file_request_handler_) { + if (req.method != "HEAD" && file_request_handler_) { file_request_handler_(req, res); } @@ -7048,9 +7046,8 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { } // File handler - auto is_head_request = req.method == "HEAD"; - if ((req.method == "GET" || is_head_request) && - handle_file_request(req, res, is_head_request)) { + if ((req.method == "GET" || req.method == "HEAD") && + handle_file_request(req, res)) { return true; }