diff --git a/CMakeLists.txt b/CMakeLists.txt index 0353b0c..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: @@ -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() 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() diff --git a/httplib.h b/httplib.h index 33d7f97..ae4507d 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 @@ -145,6 +145,10 @@ #define CPPHTTPLIB_LISTEN_BACKLOG 5 #endif +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + /* * Headers */ @@ -188,6 +192,9 @@ using ssize_t = long; #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 @@ -1080,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( @@ -2059,7 +2065,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, @@ -3064,6 +3072,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); @@ -3365,7 +3378,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 +3416,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 @@ -3538,7 +3551,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 +3574,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 + // remove the option. + 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 +3595,6 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, } return sock; } -#endif auto service = std::to_string(port); @@ -6046,6 +6065,8 @@ 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; } @@ -6060,7 +6081,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 +6179,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(); } @@ -6858,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)) { @@ -6892,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); } @@ -7026,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; } @@ -7318,8 +7337,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 @@ -9200,7 +9220,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 +9326,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(); } diff --git a/test/test.cc b/test/test.cc index acbd11a..a57b7c9 100644 --- a/test/test.cc +++ b/test/test.cc @@ -43,6 +43,10 @@ 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 @@ -70,7 +74,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 +170,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)); @@ -2867,6 +2871,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 +3664,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"; @@ -8623,7 +8639,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);