From 09e2ed3b9aa6be43afa990f86b890616458bb0b3 Mon Sep 17 00:00:00 2001 From: killvxk <86879759@qq.com> Date: Tue, 23 Jul 2024 20:12:30 +0800 Subject: [PATCH 1/5] Update httplib.h fix close connection bugs --- httplib.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/httplib.h b/httplib.h index d170f3226e..1de538e9c4 100644 --- a/httplib.h +++ b/httplib.h @@ -3174,7 +3174,7 @@ process_server_socket_core(const std::atomic &svr_sock, socket_t sock, auto count = keep_alive_max_count; while (svr_sock != INVALID_SOCKET && count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { - auto close_connection = count == 1; + auto close_connection = true; auto connection_closed = false; ret = callback(close_connection, connection_closed); if (!ret || connection_closed) { break; } @@ -6182,7 +6182,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } // Prepare additional headers - if (close_connection || req.get_header_value("Connection") == "close") { + if (close_connection || stricmp(req.get_header_value("Connection").c_str(),"close")==0) { res.set_header("Connection", "close"); } else { std::stringstream ss; @@ -6821,7 +6821,7 @@ Server::process_request(Stream &strm, bool close_connection, return write_response(strm, close_connection, req, res); } - if (req.get_header_value("Connection") == "close") { + if (stricmp(req.get_header_value("Connection").c_str(),"close")==0) { connection_closed = true; } @@ -7236,7 +7236,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req, if (!ret) { return false; } - if (res.get_header_value("Connection") == "close" || + if (stricmp(req.get_header_value("Connection").c_str(),"close")==0 || (res.version == "HTTP/1.0" && res.reason != "Connection established")) { // TODO this requires a not-entirely-obvious chain of calls to be correct // for this to be safe. From 6e247b447fb47e08a547e75566f988a0d7c2220a Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 29 Nov 2024 17:17:05 -0500 Subject: [PATCH 2/5] Cleaner API (breaking change) --- README.md | 8 +- httplib.h | 517 ++++++++++++--------------------------------------- test/test.cc | 255 +++++++++++++------------ 3 files changed, 264 insertions(+), 516 deletions(-) diff --git a/README.md b/README.md index 6165702cb2..3a866f98cc 100644 --- a/README.md +++ b/README.md @@ -563,7 +563,7 @@ auto res = cli.Get("/hi", headers); ``` or ```c++ -auto res = cli.Get("/hi", {{"Hello", "World!"}}); +auto res = cli.Get("/hi", httplib::Headers{{"Hello", "World!"}}); ``` or ```c++ @@ -657,7 +657,7 @@ auto res = cli.Get("/large-data", std::string body; auto res = cli.Get( - "/stream", Headers(), + "/stream", [&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); return true; // return 'false' if you want to cancel the request. @@ -829,13 +829,13 @@ The default `Acdcept-Encoding` value contains all possible compression types. So ```c++ res = cli.Get("/resource/foo"); -res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", "gzip, deflate, br"}}); ``` If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. ```c++ -res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}}); +res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", ""}}); ``` ### Compress request body on client diff --git a/httplib.h b/httplib.h index 2a85da6b96..c687b38439 100644 --- a/httplib.h +++ b/httplib.h @@ -1204,34 +1204,30 @@ class ClientImpl { virtual bool is_valid() const; - Result Get(const std::string &path); - Result Get(const std::string &path, const Headers &headers); - Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, - Progress progress); - Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver); + Progress progress = nullptr); Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver); + Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver); + ContentReceiver content_receiver, Progress progress = nullptr); Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress); + ContentReceiver content_receiver, Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress); - + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + Progress progres = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + ContentReceiver content_receiver, Progress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress = nullptr); @@ -1243,20 +1239,14 @@ class ClientImpl { Result Post(const std::string &path, const Headers &headers); Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type); + Progress progress = nullptr); Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); @@ -1271,9 +1261,7 @@ class ClientImpl { const std::string &content_type); Result Post(const std::string &path, const Params ¶ms); Result Post(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); + const Params ¶ms, Progress progress = nullptr); Result Post(const std::string &path, const MultipartFormDataItems &items); Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); @@ -1286,20 +1274,14 @@ class ClientImpl { Result Put(const std::string &path); Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type); + Progress progress = nullptr); Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); Result Put(const std::string &path, @@ -1313,9 +1295,7 @@ class ClientImpl { const std::string &content_type); Result Put(const std::string &path, const Params ¶ms); Result Put(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); + const Params ¶ms, Progress progress = nullptr); Result Put(const std::string &path, const MultipartFormDataItems &items); Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); @@ -1327,24 +1307,15 @@ class ClientImpl { Result Patch(const std::string &path); Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); @@ -1360,26 +1331,17 @@ class ClientImpl { Result Delete(const std::string &path); Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type); Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); + Progress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type, Progress progress); + const std::string &content_type, Progress progress = nullptr); Result Delete(const std::string &path, const std::string &body, - const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); @@ -1479,8 +1441,8 @@ class ClientImpl { // shutdown_socket // close_socket // should ONLY be called when socket_mutex_ is locked. - // Also, shutdown_ssl and close_socket should also NOT be called concurrently - // with a DIFFERENT thread sending requests using that socket. + // Also, shutdown_ssl and close_socket should also NOT be called + // concurrently with a DIFFERENT thread sending requests using that socket. virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); void shutdown_socket(Socket &socket) const; void close_socket(Socket &socket); @@ -1638,34 +1600,30 @@ class Client { bool is_valid() const; - Result Get(const std::string &path); - Result Get(const std::string &path, const Headers &headers); - Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, - Progress progress); - Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver); + Progress progress = nullptr); Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver); + Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver); + ContentReceiver content_receiver, Progress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress); + Progress progress = nullptr); Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress); - + ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + Progress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + ContentReceiver content_receiver, Progress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress = nullptr); @@ -1677,20 +1635,14 @@ class Client { Result Post(const std::string &path, const Headers &headers); Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Post(const std::string &path, const std::string &body, - const std::string &content_type); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); @@ -1705,9 +1657,7 @@ class Client { const std::string &content_type); Result Post(const std::string &path, const Params ¶ms); Result Post(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); + const Params ¶ms, Progress progress = nullptr); Result Post(const std::string &path, const MultipartFormDataItems &items); Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); @@ -1720,20 +1670,14 @@ class Client { Result Put(const std::string &path); Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type); + Progress progress = nullptr); Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); Result Put(const std::string &path, @@ -1747,9 +1691,7 @@ class Client { const std::string &content_type); Result Put(const std::string &path, const Params ¶ms); Result Put(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); + const Params ¶ms, Progress progress = nullptr); Result Put(const std::string &path, const MultipartFormDataItems &items); Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); @@ -1761,24 +1703,15 @@ class Client { Result Patch(const std::string &path); Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type); @@ -1794,26 +1727,17 @@ class Client { Result Delete(const std::string &path); Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type); Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); + Progress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type, Progress progress); + const std::string &content_type, Progress progress = nullptr); Result Delete(const std::string &path, const std::string &body, - const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); + const std::string &content_type, Progress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress); + Progress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); @@ -2242,8 +2166,8 @@ Client::set_write_timeout(const std::chrono::duration &duration) { } /* - * Forward declarations and types that will be part of the .h file if split into - * .h + .cc. + * Forward declarations and types that will be part of the .h file if split + * into .h + .cc. */ std::string hosted_at(const std::string &hostname); @@ -2952,8 +2876,8 @@ inline bool mmap::open(const char *path) { LARGE_INTEGER size{}; if (!::GetFileSizeEx(hFile_, &size)) { return false; } - // If the following line doesn't compile due to QuadPart, update Windows SDK. - // See: + // If the following line doesn't compile due to QuadPart, update Windows + // SDK. See: // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 if (static_cast(size.QuadPart) > (std::numeric_limits::max)()) { @@ -3474,8 +3398,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa * * WSA_FLAG_NO_HANDLE_INHERIT: - * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with - * SP1, and later + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 + * with SP1, and later * */ if (sock == INVALID_SOCKET) { @@ -3769,12 +3693,12 @@ inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { return (l == 0) ? h - : str2tag_core( - s + 1, l - 1, - // Unsets the 6 high bits of h, therefore no overflow happens - (((std::numeric_limits::max)() >> 6) & - h * 33) ^ - static_cast(*s)); + : str2tag_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); } inline unsigned int str2tag(const std::string &s) { @@ -4288,8 +4212,9 @@ inline bool read_content_chunked(Stream &strm, T &x, // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked // transfer coding is complete when a chunk with a chunk-size of zero is - // received, possibly followed by a trailer section, and finally terminated by - // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // received, possibly followed by a trailer section, and finally terminated + // by an empty line". + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 // // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section // does't care for the existence of the final CRLF. In other words, it seems @@ -7443,8 +7368,9 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); - // Set this to false immediately - if it ever gets set to true by the end of - // the request, we know another thread instructed us to close the socket. + // Set this to false immediately - if it ever gets set to true by the end + // of the request, we know another thread instructed us to close the + // socket. socket_should_be_closed_when_request_is_done_ = false; auto is_alive = false; @@ -8045,8 +7971,8 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( const MultipartFormDataProviderItems &provider_items) const { size_t cur_item = 0; size_t cur_start = 0; - // cur_item and cur_start are copied to within the std::function and maintain - // state between successive calls + // cur_item and cur_start are copied to within the std::function and + // maintain state between successive calls return [&, cur_item, cur_start](size_t offset, DataSink &sink) mutable -> bool { if (!offset && !items.empty()) { @@ -8094,18 +8020,10 @@ ClientImpl::process_socket(const Socket &socket, inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const std::string &path) { - return Get(path, Headers(), Progress()); -} - inline Result ClientImpl::Get(const std::string &path, Progress progress) { return Get(path, Headers(), std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { - return Get(path, headers, Progress()); -} - inline Result ClientImpl::Get(const std::string &path, const Headers &headers, Progress progress) { Request req; @@ -8117,11 +8035,6 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, return send_(std::move(req)); } -inline Result ClientImpl::Get(const std::string &path, - ContentReceiver content_receiver) { - return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); -} - inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { @@ -8129,11 +8042,6 @@ inline Result ClientImpl::Get(const std::string &path, std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver) { - return Get(path, headers, nullptr, std::move(content_receiver), nullptr); -} - inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress) { @@ -8141,20 +8049,6 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return Get(path, Headers(), std::move(response_handler), - std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), - std::move(content_receiver), nullptr); -} - inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, @@ -8182,6 +8076,14 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, return send_(std::move(req)); } +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + Progress progress) { + if (params.empty()) { return Get(path, std::move(progress)); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, std::move(progress)); +} + inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress) { if (params.empty()) { return Get(path, headers); } @@ -8190,6 +8092,13 @@ inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, return Get(path_with_query, headers, std::move(progress)); } +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, Headers{}, nullptr, std::move(content_receiver), + std::move(progress)); +} + inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, @@ -8198,6 +8107,14 @@ inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, std::move(progress)); } +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, Headers{}, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, @@ -8242,13 +8159,6 @@ inline Result ClientImpl::Post(const std::string &path, const char *body, return Post(path, Headers(), body, content_length, content_type, nullptr); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body, content_length, - nullptr, nullptr, content_type, nullptr); -} - inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, @@ -8257,25 +8167,12 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers, nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Post(const std::string &path, const std::string &body, - const std::string &content_type) { - return Post(path, Headers(), body, content_type); -} - inline Result ClientImpl::Post(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return Post(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - nullptr); -} - inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, @@ -8319,12 +8216,6 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers, nullptr); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const Params ¶ms) { - auto query = detail::params_to_query_str(params); - return Post(path, headers, query, "application/x-www-form-urlencoded"); -} - inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const Params ¶ms, Progress progress) { auto query = detail::params_to_query_str(params); @@ -8382,13 +8273,6 @@ inline Result ClientImpl::Put(const std::string &path, const char *body, return Put(path, Headers(), body, content_length, content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body, content_length, - nullptr, nullptr, content_type, nullptr); -} - inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, @@ -8397,25 +8281,12 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers, nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const std::string &body, - const std::string &content_type) { - return Put(path, Headers(), body, content_type); -} - inline Result ClientImpl::Put(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return Put(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - nullptr); -} - inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, @@ -8459,12 +8330,6 @@ inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { return Put(path, Headers(), params); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const Params ¶ms) { - auto query = detail::params_to_query_str(params); - return Put(path, headers, query, "application/x-www-form-urlencoded"); -} - inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms, Progress progress) { auto query = detail::params_to_query_str(params); @@ -8515,12 +8380,6 @@ inline Result ClientImpl::Patch(const std::string &path) { return Patch(path, std::string(), std::string()); } -inline Result ClientImpl::Patch(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return Patch(path, Headers(), body, content_length, content_type); -} - inline Result ClientImpl::Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, @@ -8528,12 +8387,6 @@ inline Result ClientImpl::Patch(const std::string &path, const char *body, return Patch(path, Headers(), body, content_length, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return Patch(path, headers, body, content_length, content_type, nullptr); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, @@ -8543,12 +8396,6 @@ inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, - const std::string &body, - const std::string &content_type) { - return Patch(path, Headers(), body, content_type); -} - inline Result ClientImpl::Patch(const std::string &path, const std::string &body, const std::string &content_type, @@ -8556,12 +8403,6 @@ inline Result ClientImpl::Patch(const std::string &path, return Patch(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return Patch(path, headers, body, content_type, nullptr); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, @@ -8610,12 +8451,6 @@ inline Result ClientImpl::Delete(const std::string &path, return Delete(path, headers, std::string(), std::string()); } -inline Result ClientImpl::Delete(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return Delete(path, Headers(), body, content_length, content_type); -} - inline Result ClientImpl::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, @@ -8623,13 +8458,6 @@ inline Result ClientImpl::Delete(const std::string &path, const char *body, return Delete(path, Headers(), body, content_length, content_type, progress); } -inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, const char *body, - size_t content_length, - const std::string &content_type) { - return Delete(path, headers, body, content_length, content_type, nullptr); -} - inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, @@ -8647,12 +8475,6 @@ inline Result ClientImpl::Delete(const std::string &path, return send_(std::move(req)); } -inline Result ClientImpl::Delete(const std::string &path, - const std::string &body, - const std::string &content_type) { - return Delete(path, Headers(), body.data(), body.size(), content_type); -} - inline Result ClientImpl::Delete(const std::string &path, const std::string &body, const std::string &content_type, @@ -8661,13 +8483,6 @@ inline Result ClientImpl::Delete(const std::string &path, progress); } -inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, - const std::string &body, - const std::string &content_type) { - return Delete(path, headers, body.data(), body.size(), content_type); -} - inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const std::string &body, @@ -8697,8 +8512,8 @@ inline void ClientImpl::stop() { // If there is anything ongoing right now, the ONLY thread-safe thing we can // do is to shutdown_socket, so that threads using this socket suddenly // discover they can't read/write any more and error out. Everything else - // (closing the socket, shutting ssl down) is unsafe because these actions are - // not thread-safe. + // (closing the socket, shutting ssl down) is unsafe because these actions + // are not thread-safe. if (socket_requests_in_flight_ > 0) { shutdown_socket(socket_); @@ -9309,7 +9124,8 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { if (ca_cert_store) { if (ctx_) { if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { - // Free memory allocated for old cert and use new store `ca_cert_store` + // Free memory allocated for old cert and use new store + // `ca_cert_store` SSL_CTX_set_cert_store(ctx_, ca_cert_store); } } else { @@ -9333,7 +9149,8 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { return is_valid() && ClientImpl::create_and_connect_socket(socket, error); } -// Assumes that socket_mutex_ is locked and that there are no requests in flight +// Assumes that socket_mutex_ is locked and that there are no requests in +// flight inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &success, Error &error) { success = true; @@ -9732,10 +9549,6 @@ inline bool Client::is_valid() const { return cli_ != nullptr && cli_->is_valid(); } -inline Result Client::Get(const std::string &path) { return cli_->Get(path); } -inline Result Client::Get(const std::string &path, const Headers &headers) { - return cli_->Get(path, headers); -} inline Result Client::Get(const std::string &path, Progress progress) { return cli_->Get(path, std::move(progress)); } @@ -9743,14 +9556,6 @@ inline Result Client::Get(const std::string &path, const Headers &headers, Progress progress) { return cli_->Get(path, headers, std::move(progress)); } -inline Result Client::Get(const std::string &path, - ContentReceiver content_receiver) { - return cli_->Get(path, std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver) { - return cli_->Get(path, headers, std::move(content_receiver)); -} inline Result Client::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); @@ -9760,18 +9565,6 @@ inline Result Client::Get(const std::string &path, const Headers &headers, return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const std::string &path, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return cli_->Get(path, std::move(response_handler), - std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return cli_->Get(path, headers, std::move(response_handler), - std::move(content_receiver)); -} inline Result Client::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { @@ -9784,16 +9577,31 @@ inline Result Client::Get(const std::string &path, const Headers &headers, return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); } +inline Result Client::Get(const std::string &path, const Params ¶ms, + Progress progress) { + return cli_->Get(path, params, Headers{}, std::move(progress)); +} inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress) { return cli_->Get(path, params, headers, std::move(progress)); } +inline Result Client::Get(const std::string &path, const Params ¶ms, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, std::move(content_receiver), + std::move(progress)); +} inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, params, headers, std::move(content_receiver), std::move(progress)); } +inline Result Client::Get(const std::string &path, const Params ¶ms, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, @@ -9816,30 +9624,16 @@ inline Result Client::Post(const std::string &path, const char *body, const std::string &content_type) { return cli_->Post(path, body, content_length, content_type); } -inline Result Client::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Post(path, headers, body, content_length, content_type); -} inline Result Client::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, Progress progress) { return cli_->Post(path, headers, body, content_length, content_type, progress); } -inline Result Client::Post(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Post(path, body, content_type); -} inline Result Client::Post(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return cli_->Post(path, body, content_type, progress); } -inline Result Client::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return cli_->Post(path, headers, body, content_type); -} inline Result Client::Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, Progress progress) { @@ -9871,10 +9665,6 @@ inline Result Client::Post(const std::string &path, const Headers &headers, inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); } -inline Result Client::Post(const std::string &path, const Headers &headers, - const Params ¶ms) { - return cli_->Post(path, headers, params); -} inline Result Client::Post(const std::string &path, const Headers &headers, const Params ¶ms, Progress progress) { return cli_->Post(path, headers, params, progress); @@ -9904,29 +9694,15 @@ inline Result Client::Put(const std::string &path, const char *body, const std::string &content_type) { return cli_->Put(path, body, content_length, content_type); } -inline Result Client::Put(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Put(path, headers, body, content_length, content_type); -} inline Result Client::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, Progress progress) { return cli_->Put(path, headers, body, content_length, content_type, progress); } -inline Result Client::Put(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Put(path, body, content_type); -} inline Result Client::Put(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return cli_->Put(path, body, content_type, progress); } -inline Result Client::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return cli_->Put(path, headers, body, content_type); -} inline Result Client::Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, Progress progress) { @@ -9955,13 +9731,6 @@ inline Result Client::Put(const std::string &path, const Headers &headers, const std::string &content_type) { return cli_->Put(path, headers, std::move(content_provider), content_type); } -inline Result Client::Put(const std::string &path, const Params ¶ms) { - return cli_->Put(path, params); -} -inline Result Client::Put(const std::string &path, const Headers &headers, - const Params ¶ms) { - return cli_->Put(path, headers, params); -} inline Result Client::Put(const std::string &path, const Headers &headers, const Params ¶ms, Progress progress) { return cli_->Put(path, headers, params, progress); @@ -9988,22 +9757,12 @@ Client::Put(const std::string &path, const Headers &headers, inline Result Client::Patch(const std::string &path) { return cli_->Patch(path); } -inline Result Client::Patch(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return cli_->Patch(path, body, content_length, content_type); -} inline Result Client::Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, Progress progress) { return cli_->Patch(path, body, content_length, content_type, progress); } -inline Result Client::Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Patch(path, headers, body, content_length, content_type); -} inline Result Client::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, @@ -10011,20 +9770,11 @@ inline Result Client::Patch(const std::string &path, const Headers &headers, return cli_->Patch(path, headers, body, content_length, content_type, progress); } -inline Result Client::Patch(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Patch(path, body, content_type); -} inline Result Client::Patch(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return cli_->Patch(path, body, content_type, progress); } -inline Result Client::Patch(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return cli_->Patch(path, headers, body, content_type); -} inline Result Client::Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, @@ -10060,22 +9810,12 @@ inline Result Client::Delete(const std::string &path) { inline Result Client::Delete(const std::string &path, const Headers &headers) { return cli_->Delete(path, headers); } -inline Result Client::Delete(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return cli_->Delete(path, body, content_length, content_type); -} inline Result Client::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, Progress progress) { return cli_->Delete(path, body, content_length, content_type, progress); } -inline Result Client::Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Delete(path, headers, body, content_length, content_type); -} inline Result Client::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, @@ -10083,20 +9823,11 @@ inline Result Client::Delete(const std::string &path, const Headers &headers, return cli_->Delete(path, headers, body, content_length, content_type, progress); } -inline Result Client::Delete(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Delete(path, body, content_type); -} inline Result Client::Delete(const std::string &path, const std::string &body, const std::string &content_type, Progress progress) { return cli_->Delete(path, body, content_type, progress); } -inline Result Client::Delete(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return cli_->Delete(path, headers, body, content_type); -} inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, diff --git a/test/test.cc b/test/test.cc index d9f2036173..b2722fe7c2 100644 --- a/test/test.cc +++ b/test/test.cc @@ -513,37 +513,37 @@ TEST(GetHeaderValueTest, RegularValueInt) { TEST(GetHeaderValueTest, Range) { { - Headers headers = {make_range_header({{1, -1}})}; + auto headers = Headers{make_range_header({{1, -1}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=1-", val); } { - Headers headers = {make_range_header({{-1, 1}})}; + auto headers = Headers{make_range_header({{-1, 1}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=-1", val); } { - Headers headers = {make_range_header({{1, 10}})}; + auto headers = Headers{make_range_header({{1, 10}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=1-10", val); } { - Headers headers = {make_range_header({{1, 10}, {100, -1}})}; + auto headers = Headers{make_range_header({{1, 10}, {100, -1}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=1-10, 100-", val); } { - Headers headers = {make_range_header({{1, 10}, {100, 200}})}; + auto headers = Headers{make_range_header({{1, 10}, {100, 200}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=1-10, 100-200", val); } { - Headers headers = {make_range_header({{0, 0}, {-1, 1}})}; + auto headers = Headers{make_range_header({{0, 0}, {-1, 1}})}; auto val = detail::get_header_value(headers, "Range", 0, 0); EXPECT_STREQ("bytes=0-0, -1", val); } @@ -789,24 +789,47 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) { #endif cli.set_connection_timeout(2); - std::string body; - auto res = cli.Get( - "/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137", - [&](const Response &response) { - EXPECT_EQ(StatusCode::OK_200, response.status); - return true; - }, - [&](const char *data, size_t data_length) { - body.append(data, data_length); - return true; - }); - ASSERT_TRUE(res); + { + std::string body; + auto res = cli.Get( + "/httpgallery/chunked/chunkedimage.aspx", + [&](const Response &response) { + EXPECT_EQ(StatusCode::OK_200, response.status); + return true; + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + ASSERT_TRUE(res); - std::string out; - detail::read_file("./image.jpg", out); + std::string out; + detail::read_file("./image.jpg", out); - EXPECT_EQ(StatusCode::OK_200, res->status); - EXPECT_EQ(out, body); + EXPECT_EQ(StatusCode::OK_200, res->status); + EXPECT_EQ(out, body); + } + + { + std::string body; + auto res = cli.Get( + "/httpgallery/chunked/chunkedimage.aspx", Params{}, + [&](const Response &response) { + EXPECT_EQ(StatusCode::OK_200, response.status); + return true; + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + ASSERT_TRUE(res); + + std::string out; + detail::read_file("./image.jpg", out); + + EXPECT_EQ(StatusCode::OK_200, res->status); + EXPECT_EQ(out, body); + } } TEST(RangeTest, FromHTTPBin_Online) { @@ -835,7 +858,7 @@ TEST(RangeTest, FromHTTPBin_Online) { } { - Headers headers = {make_range_header({{1, -1}})}; + auto headers = Headers{make_range_header({{1, -1}})}; auto res = cli.Get(path, headers); ASSERT_TRUE(res); EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body); @@ -843,7 +866,7 @@ TEST(RangeTest, FromHTTPBin_Online) { } { - Headers headers = {make_range_header({{1, 10}})}; + auto headers = Headers{make_range_header({{1, 10}})}; auto res = cli.Get(path, headers); ASSERT_TRUE(res); EXPECT_EQ("bcdefghijk", res->body); @@ -851,7 +874,7 @@ TEST(RangeTest, FromHTTPBin_Online) { } { - Headers headers = {make_range_header({{0, 31}})}; + auto headers = Headers{make_range_header({{0, 31}})}; auto res = cli.Get(path, headers); ASSERT_TRUE(res); EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); @@ -859,7 +882,7 @@ TEST(RangeTest, FromHTTPBin_Online) { } { - Headers headers = {make_range_header({{0, -1}})}; + auto headers = Headers{make_range_header({{0, -1}})}; auto res = cli.Get(path, headers); ASSERT_TRUE(res); EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); @@ -867,7 +890,7 @@ TEST(RangeTest, FromHTTPBin_Online) { } { - Headers headers = {make_range_header({{0, 32}})}; + auto headers = Headers{make_range_header({{0, 32}})}; auto res = cli.Get(path, headers); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); @@ -1053,9 +1076,8 @@ TEST(CancelTest, NoCancelPost) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return true; }); + auto res = cli.Post("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return true; }); ASSERT_TRUE(res); EXPECT_EQ("Hello World!", res->body); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -1080,9 +1102,8 @@ TEST(CancelTest, WithCancelSmallPayloadPost) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Post("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1106,9 +1127,8 @@ TEST(CancelTest, WithCancelLargePayloadPost) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Post("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1132,9 +1152,8 @@ TEST(CancelTest, NoCancelPut) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return true; }); + auto res = cli.Put("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return true; }); ASSERT_TRUE(res); EXPECT_EQ("Hello World!", res->body); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -1159,9 +1178,8 @@ TEST(CancelTest, WithCancelSmallPayloadPut) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Put("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1185,9 +1203,8 @@ TEST(CancelTest, WithCancelLargePayloadPut) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Put("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1211,9 +1228,8 @@ TEST(CancelTest, NoCancelPatch) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return true; }); + auto res = cli.Patch("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return true; }); ASSERT_TRUE(res); EXPECT_EQ("Hello World!", res->body); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -1238,9 +1254,8 @@ TEST(CancelTest, WithCancelSmallPayloadPatch) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Patch("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1264,9 +1279,8 @@ TEST(CancelTest, WithCancelLargePayloadPatch) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Patch("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1290,9 +1304,8 @@ TEST(CancelTest, NoCancelDelete) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return true; }); + auto res = cli.Delete("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return true; }); ASSERT_TRUE(res); EXPECT_EQ("Hello World!", res->body); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -1317,9 +1330,8 @@ TEST(CancelTest, WithCancelSmallPayloadDelete) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Delete("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1343,9 +1355,8 @@ TEST(CancelTest, WithCancelLargePayloadDelete) { Client cli(HOST, PORT); cli.set_connection_timeout(std::chrono::seconds(5)); - auto res = - cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), - "application/json", [](uint64_t, uint64_t) { return false; }); + auto res = cli.Delete("/", JSON_DATA, "application/json", + [](uint64_t, uint64_t) { return false; }); ASSERT_TRUE(!res); EXPECT_EQ(Error::Canceled, res.error()); } @@ -1374,8 +1385,8 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) { } { - auto res = - cli.Get(path, {make_basic_authentication_header("hello", "world")}); + auto res = cli.Get( + path, Headers{make_basic_authentication_header("hello", "world")}); ASSERT_TRUE(res); EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body); @@ -1592,7 +1603,7 @@ TEST(HttpsToHttpRedirectTest2, Redirect_Online) { params.emplace("url", "http://www.google.com/"); params.emplace("status_code", "302"); - auto res = cli.Get("/httpbin/redirect-to", params, Headers{}); + auto res = cli.Get("/httpbin/redirect-to", params); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); } @@ -1604,7 +1615,7 @@ TEST(HttpsToHttpRedirectTest3, Redirect_Online) { Params params; params.emplace("url", "http://www.google.com/"); - auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{}); + auto res = cli.Get("/httpbin/redirect-to?status_code=302", params); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); } @@ -2004,7 +2015,7 @@ TEST(ErrorHandlerTest, ContentLength) { { Client cli(HOST, PORT); - auto res = cli.Get("/hi", {{"Accept-Encoding", ""}}); + auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ("text/html", res->get_header_value("Content-Type")); @@ -2087,7 +2098,7 @@ TEST(ExceptionTest, WithExceptionHandler) { Client cli(HOST, PORT); for (size_t j = 0; j < 100; j++) { - auto res = cli.Get("/hi", {{"Accept-Encoding", ""}}); + auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::InternalServerError_500, res->status); EXPECT_EQ("text/html", res->get_header_value("Content-Type")); @@ -2098,7 +2109,7 @@ TEST(ExceptionTest, WithExceptionHandler) { cli.set_keep_alive(true); for (size_t j = 0; j < 100; j++) { - auto res = cli.Get("/hi", {{"Accept-Encoding", ""}}); + auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::InternalServerError_500, res->status); EXPECT_EQ("text/html", res->get_header_value("Content-Type")); @@ -3037,7 +3048,7 @@ TEST_F(ServerTest, GetFileContent) { } TEST_F(ServerTest, GetFileContentWithRange) { - auto res = cli_.Get("/file_content", {{make_range_header({{1, 3}})}}); + auto res = cli_.Get("/file_content", Headers{{make_range_header({{1, 3}})}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("text/html", res->get_header_value("Content-Type")); @@ -3359,7 +3370,7 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) { } TEST_F(ServerTest, StaticFileRange) { - auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}}); + auto res = cli_.Get("/dir/test.abcde", Headers{make_range_header({{2, 3}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); @@ -3370,8 +3381,8 @@ TEST_F(ServerTest, StaticFileRange) { } TEST_F(ServerTest, StaticFileRanges) { - auto res = - cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}}); + auto res = cli_.Get("/dir/test.abcde", + Headers{make_range_header({{1, 2}, {4, -1}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_TRUE( @@ -3383,7 +3394,7 @@ TEST_F(ServerTest, StaticFileRanges) { } TEST_F(ServerTest, StaticFileRangeHead) { - auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}}); + auto res = cli_.Head("/dir/test.abcde", Headers{make_range_header({{2, 3}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); @@ -3393,7 +3404,7 @@ TEST_F(ServerTest, StaticFileRangeHead) { } TEST_F(ServerTest, StaticFileRangeBigFile) { - auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}}); + auto res = cli_.Get("/dir/1MB.txt", Headers{make_range_header({{-1, 5}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); @@ -3405,7 +3416,7 @@ TEST_F(ServerTest, StaticFileRangeBigFile) { } TEST_F(ServerTest, StaticFileRangeBigFile2) { - auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}}); + auto res = cli_.Get("/dir/1MB.txt", Headers{make_range_header({{1, 4097}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); @@ -3734,7 +3745,7 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding) { } TEST_F(ServerTest, GetStreamed2) { - auto res = cli_.Get("/streamed", {{make_range_header({{2, 3}})}}); + auto res = cli_.Get("/streamed", Headers{make_range_header({{2, 3}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("2", res->get_header_value("Content-Length")); @@ -3752,7 +3763,8 @@ TEST_F(ServerTest, GetStreamed) { } TEST_F(ServerTest, GetStreamedWithRange1) { - auto res = cli_.Get("/streamed-with-range", {{make_range_header({{3, 5}})}}); + auto res = + cli_.Get("/streamed-with-range", Headers{make_range_header({{3, 5}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("3", res->get_header_value("Content-Length")); @@ -3762,7 +3774,8 @@ TEST_F(ServerTest, GetStreamedWithRange1) { } TEST_F(ServerTest, GetStreamedWithRange2) { - auto res = cli_.Get("/streamed-with-range", {{make_range_header({{1, -1}})}}); + auto res = + cli_.Get("/streamed-with-range", Headers{make_range_header({{1, -1}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("6", res->get_header_value("Content-Length")); @@ -3772,7 +3785,7 @@ TEST_F(ServerTest, GetStreamedWithRange2) { } TEST_F(ServerTest, GetStreamedWithRangeSuffix1) { - auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-3"}}); + auto res = cli_.Get("/streamed-with-range", Headers{{"Range", "bytes=-3"}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("3", res->get_header_value("Content-Length")); @@ -3782,7 +3795,8 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) { } TEST_F(ServerTest, GetStreamedWithRangeSuffix2) { - auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}}); + auto res = + cli_.Get("/streamed-with-range?error", Headers{{"Range", "bytes=-9999"}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); @@ -3791,8 +3805,9 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) { } TEST_F(ServerTest, GetStreamedWithRangeError) { - auto res = cli_.Get("/streamed-with-range", - {{"Range", "bytes=92233720368547758079223372036854775806-" + auto res = + cli_.Get("/streamed-with-range", + Headers{{"Range", "bytes=92233720368547758079223372036854775806-" "92233720368547758079223372036854775807"}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); @@ -3804,9 +3819,9 @@ TEST_F(ServerTest, GetStreamedWithRangeError) { TEST_F(ServerTest, GetRangeWithMaxLongLength) { auto res = cli_.Get( "/with-range", - {{"Range", - "bytes=0-" + std::to_string(std::numeric_limits::max())}, - {"Accept-Encoding", ""}}); + Headers{{"Range", + "bytes=0-" + std::to_string(std::numeric_limits::max())}, + {"Accept-Encoding", ""}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("7", res->get_header_value("Content-Length")); @@ -3816,7 +3831,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) { } TEST_F(ServerTest, GetRangeWithZeroToInfinite) { - auto res = cli_.Get("/with-range", { + auto res = cli_.Get("/with-range", Headers{ {"Range", "bytes=0-"}, {"Accept-Encoding", ""}, }); @@ -3829,8 +3844,8 @@ TEST_F(ServerTest, GetRangeWithZeroToInfinite) { } TEST_F(ServerTest, GetStreamedWithRangeMultipart) { - auto res = - cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}}); + auto res = cli_.Get("/streamed-with-range", + Headers{make_range_header({{1, 2}, {4, 5}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("267", res->get_header_value("Content-Length")); @@ -3844,8 +3859,8 @@ TEST_F(ServerTest, GetStreamedWithTooManyRanges) { ranges.emplace_back(0, -1); } - auto res = - cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}}); + auto res = cli_.Get("/streamed-with-range?error", + Headers{make_range_header(ranges)}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); @@ -3855,7 +3870,7 @@ TEST_F(ServerTest, GetStreamedWithTooManyRanges) { TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) { auto res = cli_.Get("/streamed-with-range?error", - {{make_range_header({{0, -1}, {0, -1}})}}); + Headers{make_range_header({{0, -1}, {0, -1}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); @@ -3864,8 +3879,9 @@ TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) { } TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) { - auto res = cli_.Get("/streamed-with-range?error", - {{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}}); + auto res = + cli_.Get("/streamed-with-range?error", + Headers{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ("0", res->get_header_value("Content-Length")); @@ -3915,7 +3931,7 @@ TEST_F(ServerTest, ClientStop) { } TEST_F(ServerTest, GetWithRange1) { - auto res = cli_.Get("/with-range", { + auto res = cli_.Get("/with-range", Headers{ make_range_header({{3, 5}}), {"Accept-Encoding", ""}, }); @@ -3928,7 +3944,7 @@ TEST_F(ServerTest, GetWithRange1) { } TEST_F(ServerTest, GetWithRange2) { - auto res = cli_.Get("/with-range", { + auto res = cli_.Get("/with-range", Headers{ make_range_header({{1, -1}}), {"Accept-Encoding", ""}, }); @@ -3941,7 +3957,7 @@ TEST_F(ServerTest, GetWithRange2) { } TEST_F(ServerTest, GetWithRange3) { - auto res = cli_.Get("/with-range", { + auto res = cli_.Get("/with-range", Headers{ make_range_header({{0, 0}}), {"Accept-Encoding", ""}, }); @@ -3954,7 +3970,7 @@ TEST_F(ServerTest, GetWithRange3) { } TEST_F(ServerTest, GetWithRange4) { - auto res = cli_.Get("/with-range", { + auto res = cli_.Get("/with-range", Headers{ make_range_header({{-1, 2}}), {"Accept-Encoding", ""}, }); @@ -3967,13 +3983,15 @@ TEST_F(ServerTest, GetWithRange4) { } TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) { - auto res = cli_.Get("/with-range", {{make_range_header({{10000, 20000}})}}); + auto res = + cli_.Get("/with-range", Headers{make_range_header({{10000, 20000}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); } TEST_F(ServerTest, GetWithRangeMultipart) { - auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}}); + auto res = + cli_.Get("/with-range", Headers{make_range_header({{1, 2}, {4, 5}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ("267", res->get_header_value("Content-Length")); @@ -3982,15 +4000,15 @@ TEST_F(ServerTest, GetWithRangeMultipart) { } TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) { - auto res = - cli_.Get("/with-range", {{make_range_header({{-1, 2}, {10000, 30000}})}}); + auto res = cli_.Get("/with-range", + Headers{make_range_header({{-1, 2}, {10000, 30000}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); } TEST_F(ServerTest, GetWithRangeCustomizedResponse) { auto res = cli_.Get("/with-range-customized-response", - {{make_range_header({{1, 2}})}}); + Headers{make_range_header({{1, 2}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::BadRequest_400, res->status); EXPECT_EQ(true, res->has_header("Content-Length")); @@ -4000,7 +4018,7 @@ TEST_F(ServerTest, GetWithRangeCustomizedResponse) { TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) { auto res = cli_.Get("/with-range-customized-response", - {{make_range_header({{1, 2}, {4, 5}})}}); + Headers{make_range_header({{1, 2}, {4, 5}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::BadRequest_400, res->status); EXPECT_EQ(true, res->has_header("Content-Length")); @@ -4009,7 +4027,7 @@ TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) { } TEST_F(ServerTest, Issue1772) { - auto res = cli_.Get("/issue1772", {{make_range_header({{1000, -1}})}}); + auto res = cli_.Get("/issue1772", Headers{make_range_header({{1000, -1}})}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::Unauthorized_401, res->status); } @@ -5694,7 +5712,7 @@ TEST(GetWithParametersTest, GetWithParameters) { params.emplace("hello", "world"); params.emplace("hello2", "world2"); params.emplace("hello3", "world3"); - auto res = cli.Get("/", params, Headers{}); + auto res = cli.Get("/", params); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -5751,11 +5769,10 @@ TEST(GetWithParametersTest, GetWithParameters2) { params.emplace("hello", "world"); std::string body; - auto res = cli.Get("/", params, Headers{}, - [&](const char *data, size_t data_length) { - body.append(data, data_length); - return true; - }); + auto res = cli.Get("/", params, [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -5777,7 +5794,7 @@ TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) { Client cli(host); #endif - cli.set_default_headers({make_range_header({{1, 10}})}); + cli.set_default_headers(Headers{make_range_header({{1, 10}})}); cli.set_connection_timeout(5); { @@ -6597,7 +6614,7 @@ TEST(SendAPI, WithParamsInRequest) { ASSERT_TRUE(res); } { - auto res = cli.Get("/", {{"test", "test_value"}}, Headers{}); + auto res = cli.Get("/", Params{{"test", "test_value"}}); ASSERT_TRUE(res); } } @@ -6726,8 +6743,8 @@ TEST(YahooRedirectTest3, NewResultInterface_Online) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT TEST(DecodeWithChunkedEncoding, BrotliEncoding_Online) { Client cli("https://cdnjs.cloudflare.com/"); - auto res = - cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "br"}}); + auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", + Headers{{"Accept-Encoding", "br"}}); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); @@ -6756,7 +6773,7 @@ TEST(HttpsToHttpRedirectTest2, SimpleInterface_Online) { params.emplace("url", "http://www.google.com/"); params.emplace("status_code", "302"); - auto res = cli.Get("/httpbin/redirect-to", params, Headers{}); + auto res = cli.Get("/httpbin/redirect-to", params); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); } @@ -6768,7 +6785,7 @@ TEST(HttpsToHttpRedirectTest3, SimpleInterface_Online) { Params params; params.emplace("url", "http://www.google.com/"); - auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{}); + auto res = cli.Get("/httpbin/redirect-to?status_code=302", params); ASSERT_TRUE(res); EXPECT_EQ(StatusCode::OK_200, res->status); } @@ -7923,7 +7940,7 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) { svr.wait_until_ready(); Client cli(HOST, PORT); - cli.Get("/test", {{"Test", "_\n\r_\n\r_"}}); + cli.Get("/test", Headers{{"Test", "_\n\r_\n\r_"}}); } #ifndef _WIN32 From 2bf8354c01b6237fa609d5c63837ec3ceb0cb345 Mon Sep 17 00:00:00 2001 From: killvxk <86879759@qq.com> Date: Mon, 14 Jul 2025 17:45:20 +0800 Subject: [PATCH 3/5] Update httplib.h --- httplib.h | 4377 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 2966 insertions(+), 1411 deletions(-) diff --git a/httplib.h b/httplib.h index 0ef05e6c94..16aced15d1 100644 --- a/httplib.h +++ b/httplib.h @@ -1,14 +1,36 @@ // // httplib.h // -// Copyright (c) 2024 Yuji Hirose. All rights reserved. +// Copyright (c) 2025 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.18.3" +#define CPPHTTPLIB_VERSION "0.23.0" + +/* + * Platform compatibility check + */ + +#if defined(_WIN32) && !defined(_WIN64) +#error \ + "cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler." +#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8 +#warning \ + "cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler." +#elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8 +#warning \ + "cpp-httplib doesn't support platforms where size_t is less than 64 bits." +#endif + +#ifdef _WIN32 +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0602 +#error \ + "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later." +#endif +#endif /* * Configuration @@ -66,13 +88,17 @@ #define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 #endif +#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND +#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 +#endif + #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND #define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND -#ifdef _WIN32 -#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#ifdef _WIN64 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000 #else #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 #endif @@ -86,6 +112,10 @@ #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 #endif +#ifndef CPPHTTPLIB_HEADER_MAX_COUNT +#define CPPHTTPLIB_HEADER_MAX_COUNT 100 +#endif + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif @@ -118,6 +148,10 @@ #define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) #endif +#ifndef CPPHTTPLIB_SEND_BUFSIZ +#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u) +#endif + #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ #define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) #endif @@ -141,11 +175,15 @@ #define CPPHTTPLIB_LISTEN_BACKLOG 5 #endif +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + /* * Headers */ -#ifdef _WIN32 +#ifdef _WIN64 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif //_CRT_SECURE_NO_WARNINGS @@ -161,11 +199,7 @@ #pragma comment(lib, "ws2_32.lib") -#ifdef _WIN64 using ssize_t = __int64; -#else -using ssize_t = long; -#endif #endif // _MSC_VER #ifndef S_ISREG @@ -184,16 +218,23 @@ using ssize_t = long; #include #include +#if defined(__has_include) +#if __has_include() +// afunix.h uses types declared in winsock2.h, so has to be included after it. +#include +#define CPPHTTPLIB_HAVE_AFUNIX_H 1 +#endif +#endif + #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif +using nfds_t = unsigned long; using socket_t = SOCKET; -#ifdef CPPHTTPLIB_USE_POLL -#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) -#endif +using socklen_t = int; -#else // not _WIN32 +#else // not _WIN64 #include #if !defined(_AIX) && !defined(__MVS__) @@ -211,14 +252,11 @@ using socket_t = SOCKET; #ifdef __linux__ #include #endif +#include #include -#ifdef CPPHTTPLIB_USE_POLL #include -#endif -#include #include #include -#include #include #include #include @@ -227,7 +265,11 @@ using socket_t = int; #ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) #endif -#endif //_WIN32 +#endif //_WIN64 + +#if defined(__APPLE__) +#include +#endif #include #include @@ -240,7 +282,6 @@ using socket_t = int; #include #include #include -#include #include #include #include @@ -259,8 +300,17 @@ using socket_t = int; #include #include +#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \ + defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_OSX +#include +#include +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or + // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -#ifdef _WIN32 +#ifdef _WIN64 #include // these are defined in wincrypt.h and it breaks compilation if BoringSSL is @@ -273,20 +323,20 @@ using socket_t = int; #ifdef _MSC_VER #pragma comment(lib, "crypt32.lib") #endif -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#include +#endif // _WIN64 + +#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) #if TARGET_OS_OSX -#include #include -#endif // TARGET_OS_OSX -#endif // _WIN32 +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO #include #include #include #include -#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#if defined(_WIN64) && defined(OPENSSL_USE_APPLINK) #include #endif @@ -302,7 +352,7 @@ using socket_t = int; #error Sorry, OpenSSL versions prior to 3.0.0 are not supported #endif -#endif +#endif // CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_ZLIB_SUPPORT #include @@ -313,6 +363,10 @@ using socket_t = int; #include #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +#include +#endif + /* * Declaration */ @@ -396,6 +450,10 @@ struct hash { } }; +template +using unordered_set = std::unordered_set; + } // namespace case_ignore // This is based on @@ -428,6 +486,15 @@ struct scope_exit { } // namespace detail +enum SSLVerifierResponse { + // no decision has been made, use the built-in certificate verifier + NoDecisionMade, + // connection certificate is verified and accepted + CertificateAccepted, + // connection certificate was processed but is rejected + CertificateRejected +}; + enum StatusCode { // Information responses Continue_100 = 100, @@ -510,19 +577,53 @@ using Headers = using Params = std::multimap; using Match = std::smatch; -using Progress = std::function; +using DownloadProgress = std::function; +using UploadProgress = std::function; struct Response; using ResponseHandler = std::function; +struct FormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; + Headers headers; +}; + +struct FormField { + std::string name; + std::string content; + Headers headers; +}; +using FormFields = std::multimap; + +using FormFiles = std::multimap; + struct MultipartFormData { + FormFields fields; // Text fields from multipart + FormFiles files; // Files from multipart + + // Text field access + std::string get_field(const std::string &key, size_t id = 0) const; + std::vector get_fields(const std::string &key) const; + bool has_field(const std::string &key) const; + size_t get_field_count(const std::string &key) const; + + // File access + FormData get_file(const std::string &key, size_t id = 0) const; + std::vector get_files(const std::string &key) const; + bool has_file(const std::string &key) const; + size_t get_file_count(const std::string &key) const; +}; + +struct UploadFormData { std::string name; std::string content; std::string filename; std::string content_type; }; -using MultipartFormDataItems = std::vector; -using MultipartFormDataMap = std::multimap; +using UploadFormDataItems = std::vector; class DataSink { public: @@ -565,37 +666,34 @@ using ContentProviderWithoutLength = using ContentProviderResourceReleaser = std::function; -struct MultipartFormDataProvider { +struct FormDataProvider { std::string name; ContentProviderWithoutLength provider; std::string filename; std::string content_type; }; -using MultipartFormDataProviderItems = std::vector; +using FormDataProviderItems = std::vector; -using ContentReceiverWithProgress = - std::function; +using ContentReceiverWithProgress = std::function; using ContentReceiver = std::function; -using MultipartContentHeader = - std::function; +using FormDataHeader = std::function; class ContentReader { public: using Reader = std::function; - using MultipartReader = std::function; + using FormDataReader = + std::function; - ContentReader(Reader reader, MultipartReader multipart_reader) + ContentReader(Reader reader, FormDataReader multipart_reader) : reader_(std::move(reader)), - multipart_reader_(std::move(multipart_reader)) {} + formdata_reader_(std::move(multipart_reader)) {} - bool operator()(MultipartContentHeader header, - ContentReceiver receiver) const { - return multipart_reader_(std::move(header), std::move(receiver)); + bool operator()(FormDataHeader header, ContentReceiver receiver) const { + return formdata_reader_(std::move(header), std::move(receiver)); } bool operator()(ContentReceiver receiver) const { @@ -603,7 +701,7 @@ class ContentReader { } Reader reader_; - MultipartReader multipart_reader_; + FormDataReader formdata_reader_; }; using Range = std::pair; @@ -612,8 +710,10 @@ using Ranges = std::vector; struct Request { std::string method; std::string path; + std::string matched_route; Params params; Headers headers; + Headers trailers; std::string body; std::string remote_addr; @@ -624,15 +724,18 @@ struct Request { // for server std::string version; std::string target; - MultipartFormDataMap files; + MultipartFormData form; Ranges ranges; Match matches; std::unordered_map path_params; + std::function is_connection_closed = []() { return true; }; // for client + std::vector accept_content_types; ResponseHandler response_handler; ContentReceiverWithProgress content_receiver; - Progress progress; + DownloadProgress download_progress; + UploadProgress upload_progress; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT const SSL *ssl = nullptr; #endif @@ -640,27 +743,29 @@ struct Request { bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, - size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string &key) const; void set_header(const std::string &key, const std::string &val); + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + bool has_param(const std::string &key) const; std::string get_param_value(const std::string &key, size_t id = 0) const; size_t get_param_value_count(const std::string &key) const; bool is_multipart_form_data() const; - bool has_file(const std::string &key) const; - MultipartFormData get_file_value(const std::string &key) const; - std::vector get_file_values(const std::string &key) const; - // private members... size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; size_t content_length_ = 0; ContentProvider content_provider_; bool is_chunked_content_provider_ = false; size_t authorization_count_ = 0; + std::chrono::time_point start_time_ = + (std::chrono::steady_clock::time_point::min)(); }; struct Response { @@ -668,17 +773,22 @@ struct Response { int status = -1; std::string reason; Headers headers; + Headers trailers; std::string body; std::string location; // Redirect location bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, - size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string &key) const; void set_header(const std::string &key, const std::string &val); + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + void set_redirect(const std::string &url, int status = StatusCode::Found_302); void set_content(const char *s, size_t n, const std::string &content_type); void set_content(const std::string &s, const std::string &content_type); @@ -726,7 +836,8 @@ class Stream { virtual ~Stream() = default; virtual bool is_readable() const = 0; - virtual bool is_writable() const = 0; + virtual bool wait_readable() const = 0; + virtual bool wait_writable() const = 0; virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0; @@ -734,6 +845,8 @@ class Stream { virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; virtual socket_t socket() const = 0; + virtual time_t duration() const = 0; + ssize_t write(const char *ptr); ssize_t write(const std::string &s); }; @@ -837,6 +950,16 @@ using Logger = std::function; using SocketOptions = std::function; +namespace detail { + +bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void *optval, socklen_t optlen); +bool set_socket_opt(socket_t sock, int level, int optname, int opt); +bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, + time_t usec); + +} // namespace detail + void default_socket_options(socket_t sock); const char *status_message(int status); @@ -847,17 +970,23 @@ namespace detail { class MatcherBase { public: + MatcherBase(std::string pattern) : pattern_(pattern) {} virtual ~MatcherBase() = default; + const std::string &pattern() const { return pattern_; } + // Match request path and populate its matches and virtual bool match(Request &request) const = 0; + +private: + std::string pattern_; }; /** * Captures parameters in request path and stores them in Request::path_params * * Capture name is a substring of a pattern from : to /. - * The rest of the pattern is matched agains the request path directly + * The rest of the pattern is matched against the request path directly * Parameters are captured starting from the next character after * the end of the last matched static pattern fragment until the next /. * @@ -902,7 +1031,8 @@ class PathParamsMatcher final : public MatcherBase { */ class RegexMatcher final : public MatcherBase { public: - RegexMatcher(const std::string &pattern) : regex_(pattern) {} + RegexMatcher(const std::string &pattern) + : MatcherBase(pattern), regex_(pattern) {} bool match(Request &request) const override; @@ -969,11 +1099,15 @@ class Server { } Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); Server &set_post_routing_handler(Handler handler); + Server &set_pre_request_handler(HandlerWithResponse handler); + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); Server &set_logger(Logger logger); + Server &set_pre_compression_logger(Logger logger); Server &set_address_family(int family); Server &set_tcp_nodelay(bool on); @@ -1052,8 +1186,7 @@ class Server { 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( @@ -1074,20 +1207,20 @@ class Server { Response &res, const std::string &boundary, const std::string &content_type); bool read_content(Stream &strm, Request &req, Response &res); - bool - read_content_with_content_receiver(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver); + bool read_content_with_content_receiver(Stream &strm, Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) const; virtual bool process_and_close_socket(socket_t sock); std::atomic is_running_{false}; - std::atomic is_decommisioned{false}; + std::atomic is_decommissioned{false}; struct MountPointEntry { std::string mount_point; @@ -1114,9 +1247,11 @@ class Server { ExceptionHandler exception_handler_; HandlerWithResponse pre_routing_handler_; Handler post_routing_handler_; + HandlerWithResponse pre_request_handler_; Expect100ContinueHandler expect_100_continue_handler_; Logger logger_; + Logger pre_compression_logger_; int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; @@ -1161,6 +1296,17 @@ class Result { Headers &&request_headers = Headers{}) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)) {} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, + int ssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {} + Result(std::unique_ptr &&res, Error err, Headers &&request_headers, + int ssl_error, unsigned long ssl_openssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error), + ssl_openssl_error_(ssl_openssl_error) {} +#endif // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } @@ -1175,19 +1321,30 @@ class Result { // Error Error error() const { return err_; } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // SSL Error + int ssl_error() const { return ssl_error_; } + // OpenSSL Error + unsigned long ssl_openssl_error() const { return ssl_openssl_error_; } +#endif + // Request Headers bool has_request_header(const std::string &key) const; std::string get_request_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_request_header_value_u64(const std::string &key, - uint64_t def = 0, size_t id = 0) const; + size_t get_request_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_request_header_value_count(const std::string &key) const; private: std::unique_ptr res_; Error err_ = Error::Unknown; Headers request_headers_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int ssl_error_ = 0; + unsigned long ssl_openssl_error_ = 0; +#endif }; class ClientImpl { @@ -1204,147 +1361,86 @@ class ClientImpl { virtual bool is_valid() const; - Result Get(const std::string &path, Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - Progress progress = nullptr); - Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - Progress progres = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); - Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Post(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress = nullptr); - Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Put(const std::string &path, size_t content_length, - ContentProvider content_provider, const std::string &content_type); - Result Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress = nullptr); - Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - - Result Delete(const std::string &path); - Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); + // clang-format on bool send(Request &req, Response &res, Error &error); Result send(const Request &req); @@ -1382,6 +1478,10 @@ class ClientImpl { template void set_write_timeout(const std::chrono::duration &duration); + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1392,7 +1492,7 @@ class ClientImpl { void set_keep_alive(bool on); void set_follow_location(bool on); - void set_url_encode(bool on); + void set_path_encode(bool on); void set_compress(bool on); @@ -1419,7 +1519,8 @@ class ClientImpl { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); - void set_server_certificate_verifier(std::function verifier); + void set_server_certificate_verifier( + std::function verifier); #endif void set_logger(Logger logger); @@ -1441,8 +1542,8 @@ class ClientImpl { // shutdown_socket // close_socket // should ONLY be called when socket_mutex_ is locked. - // Also, shutdown_ssl and close_socket should also NOT be called - // concurrently with a DIFFERENT thread sending requests using that socket. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); void shutdown_socket(Socket &socket) const; void close_socket(Socket &socket); @@ -1490,6 +1591,7 @@ class ClientImpl { time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; std::string basic_auth_username_; std::string basic_auth_password_; @@ -1502,7 +1604,7 @@ class ClientImpl { bool keep_alive_ = false; bool follow_location_ = false; - bool url_encode_ = true; + bool path_encode_ = true; int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; @@ -1535,24 +1637,31 @@ class ClientImpl { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool server_certificate_verification_ = true; bool server_hostname_verification_ = true; - std::function server_certificate_verifier_; + std::function server_certificate_verifier_; #endif Logger logger_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; + unsigned long last_openssl_error_ = 0; +#endif + private: bool send_(Request &req, Response &res, Error &error); Result send_(Request &&req); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - bool is_ssl_peer_could_be_closed(SSL *ssl) const; -#endif socket_t create_client_socket(Error &error) const; bool read_response_line(Stream &strm, const Request &req, Response &res) const; bool write_request(Stream &strm, Request &req, bool close_connection, Error &error); bool redirect(Request &req, Response &res, Error &error); + bool create_redirect_client(const std::string &scheme, + const std::string &host, int port, Request &req, + Response &res, const std::string &path, + const std::string &location, Error &error); + template void setup_redirect_client(ClientType &client); bool handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); std::unique_ptr send_with_content_provider( @@ -1565,15 +1674,17 @@ class ClientImpl { const Headers &headers, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type, Progress progress); + const std::string &content_type, UploadProgress progress); ContentProviderWithoutLength get_multipart_content_provider( - const std::string &boundary, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) const; + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const; std::string adjust_host_string(const std::string &host) const; - virtual bool process_socket(const Socket &socket, - std::function callback); + virtual bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback); virtual bool is_ssl() const; }; @@ -1600,147 +1711,86 @@ class Client { bool is_valid() const; - Result Get(const std::string &path, Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - Progress progress = nullptr); - Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); - Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Post(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress = nullptr); - Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Put(const std::string &path, size_t content_length, - ContentProvider content_provider, const std::string &content_type); - Result Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress = nullptr); - Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); - Result Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - - Result Delete(const std::string &path); - Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type, - Progress progress = nullptr); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress = nullptr); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress = nullptr); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress = nullptr); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); + // clang-format on bool send(Request &req, Response &res, Error &error); Result send(const Request &req); @@ -1777,6 +1827,10 @@ class Client { template void set_write_timeout(const std::chrono::duration &duration); + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration &duration); + void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1787,6 +1841,7 @@ class Client { void set_keep_alive(bool on); void set_follow_location(bool on); + void set_path_encode(bool on); void set_url_encode(bool on); void set_compress(bool on); @@ -1807,7 +1862,8 @@ class Client { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); - void set_server_certificate_verifier(std::function verifier); + void set_server_certificate_verifier( + std::function verifier); #endif void set_logger(Logger logger); @@ -1861,6 +1917,9 @@ class SSLServer : public Server { SSL_CTX *ctx_; std::mutex ctx_mutex_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; +#endif }; class SSLClient final : public ClientImpl { @@ -1894,12 +1953,16 @@ class SSLClient final : public ClientImpl { void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); - bool process_socket(const Socket &socket, - std::function callback) override; + bool + process_socket(const Socket &socket, + std::chrono::time_point start_time, + std::function callback) override; bool is_ssl() const override; - bool connect_with_proxy(Socket &sock, Response &res, bool &success, - Error &error); + bool connect_with_proxy( + Socket &sock, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error); bool initialize_ssl(Socket &socket, Error &error); bool load_certs(); @@ -1936,46 +1999,91 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) { callback(static_cast(sec), static_cast(usec)); } -inline uint64_t get_header_value_u64(const Headers &headers, - const std::string &key, uint64_t def, - size_t id) { +template inline constexpr size_t str_len(const char (&)[N]) { + return N - 1; +} + +inline bool is_numeric(const std::string &str) { + return !str.empty() && + std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); +} + +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id, bool &is_invalid_value) { + is_invalid_value = false; auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); if (it != rng.second) { - return std::strtoull(it->second.data(), nullptr, 10); + if (is_numeric(it->second)) { + return std::strtoull(it->second.data(), nullptr, 10); + } else { + is_invalid_value = true; + } } return def; } +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id) { + bool dummy = false; + return get_header_value_u64(headers, key, def, id, dummy); +} + } // namespace detail -inline uint64_t Request::get_header_value_u64(const std::string &key, - uint64_t def, size_t id) const { +inline size_t Request::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { return detail::get_header_value_u64(headers, key, def, id); } -inline uint64_t Response::get_header_value_u64(const std::string &key, - uint64_t def, size_t id) const { +inline size_t Response::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { return detail::get_header_value_u64(headers, key, def, id); } -inline void default_socket_options(socket_t sock) { - int opt = 1; -#ifdef _WIN32 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&opt), sizeof(opt)); - setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - reinterpret_cast(&opt), sizeof(opt)); +namespace detail { + +inline bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void *optval, socklen_t optlen) { + return setsockopt(sock, level, optname, +#ifdef _WIN64 + reinterpret_cast(optval), #else -#ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, - reinterpret_cast(&opt), sizeof(opt)); + optval, +#endif + optlen) == 0; +} + +inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) { + return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval)); +} + +inline bool set_socket_opt_time(socket_t sock, int level, int optname, + time_t sec, time_t usec) { +#ifdef _WIN64 + auto timeout = static_cast(sec * 1000 + usec / 1000); #else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&opt), sizeof(opt)); + timeval timeout; + timeout.tv_sec = static_cast(sec); + timeout.tv_usec = static_cast(usec); #endif + return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout)); +} + +} // namespace detail + +inline void default_socket_options(socket_t sock) { + detail::set_socket_opt(sock, SOL_SOCKET, +#ifdef SO_REUSEPORT + SO_REUSEPORT, +#else + SO_REUSEADDR, #endif + 1); } inline const char *status_message(int status) { @@ -2056,9 +2164,9 @@ inline const char *status_message(int status) { inline std::string get_bearer_token_auth(const Request &req) { if (req.has_header("Authorization")) { - static std::string BearerHeaderPrefix = "Bearer "; + constexpr auto bearer_header_prefix_len = detail::str_len("Bearer "); return req.get_header_value("Authorization") - .substr(BearerHeaderPrefix.length()); + .substr(bearer_header_prefix_len); } return ""; } @@ -2119,9 +2227,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) { return os; } -inline uint64_t Result::get_request_header_value_u64(const std::string &key, - uint64_t def, - size_t id) const { +inline size_t Result::get_request_header_value_u64(const std::string &key, + size_t def, + size_t id) const { return detail::get_header_value_u64(request_headers_, key, def, id); } @@ -2147,6 +2255,14 @@ inline void ClientImpl::set_write_timeout( duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); } +template +inline void ClientImpl::set_max_timeout( + const std::chrono::duration &duration) { + auto msec = + std::chrono::duration_cast(duration).count(); + set_max_timeout(msec); +} + template inline void Client::set_connection_timeout( const std::chrono::duration &duration) { @@ -2165,15 +2281,31 @@ Client::set_write_timeout(const std::chrono::duration &duration) { cli_->set_write_timeout(duration); } +template +inline void +Client::set_max_timeout(const std::chrono::duration &duration) { + cli_->set_max_timeout(duration); +} + /* - * Forward declarations and types that will be part of the .h file if split - * into .h + .cc. + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. */ std::string hosted_at(const std::string &hostname); void hosted_at(const std::string &hostname, std::vector &addrs); +std::string encode_uri_component(const std::string &value); + +std::string encode_uri(const std::string &value); + +std::string decode_uri_component(const std::string &value); + +std::string decode_uri(const std::string &value); + +std::string encode_query_param(const std::string &value); + std::string append_query_params(const std::string &path, const Params ¶ms); std::pair make_range_header(const Ranges &ranges); @@ -2185,7 +2317,7 @@ make_basic_authentication_header(const std::string &username, namespace detail { -#if defined(_WIN32) +#if defined(_WIN64) inline std::wstring u8string_to_wstring(const char *s) { std::wstring ws; auto len = static_cast(strlen(s)); @@ -2207,7 +2339,7 @@ struct FileStat { bool is_dir() const; private: -#if defined(_WIN32) +#if defined(_WIN64) struct _stat st_; #else struct stat st_; @@ -2215,11 +2347,7 @@ struct FileStat { int ret_ = -1; }; -std::string encode_query_param(const std::string &value); - -std::string decode_url(https://github.com/const%20std::string%20&s,%20bool%20convert_plus_to_space); - -void read_file(const std::string &path, std::string &out); +std::string decode_path(const std::string &s, bool convert_plus_to_space); std::string trim_copy(const std::string &s); @@ -2239,10 +2367,12 @@ void split(const char *b, const char *e, char d, void split(const char *b, const char *e, char d, size_t m, std::function fn); -bool process_client_socket(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, - std::function callback); +bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback); socket_t create_client_socket(const std::string &host, const std::string &ip, int port, int address_family, bool tcp_nodelay, @@ -2268,13 +2398,16 @@ bool parse_multipart_boundary(const std::string &content_type, bool parse_range_header(const std::string &s, Ranges &ranges); +bool parse_accept_header(const std::string &s, + std::vector &content_types); + int close_socket(socket_t sock); ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); -enum class EncodingType { None = 0, Gzip, Brotli }; +enum class EncodingType { None = 0, Gzip, Brotli, Zstd }; EncodingType encoding_type(const Request &req, const Response &res); @@ -2284,12 +2417,14 @@ class BufferStream final : public Stream { ~BufferStream() override = default; bool is_readable() const override; - bool is_writable() const override; + bool wait_readable() const override; + bool wait_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; void get_local_ip_and_port(std::string &ip, int &port) const override; socket_t socket() const override; + time_t duration() const override; const std::string &get_buffer() const; @@ -2385,6 +2520,34 @@ class brotli_decompressor final : public decompressor { }; #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +class zstd_compressor : public compressor { +public: + zstd_compressor(); + ~zstd_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + ZSTD_CCtx *ctx_ = nullptr; +}; + +class zstd_decompressor : public decompressor { +public: + zstd_decompressor(); + ~zstd_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + ZSTD_DCtx *ctx_ = nullptr; +}; +#endif + // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` // to store data. The call can set memory on stack for performance. class stream_line_reader { @@ -2403,7 +2566,7 @@ class stream_line_reader { char *fixed_buffer_; const size_t fixed_buffer_size_; size_t fixed_buffer_used_size_ = 0; - std::string glowable_buffer_; + std::string growable_buffer_; }; class mmap { @@ -2419,7 +2582,7 @@ class mmap { const char *data() const; private: -#if defined(_WIN32) +#if defined(_WIN64) HANDLE hFile_ = NULL; HANDLE hMapping_ = NULL; #else @@ -2430,6 +2593,60 @@ class mmap { bool is_open_empty_file = false; }; +// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 +namespace fields { + +inline bool is_token_char(char c) { + return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || + c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; +} + +inline bool is_token(const std::string &s) { + if (s.empty()) { return false; } + for (auto c : s) { + if (!is_token_char(c)) { return false; } + } + return true; +} + +inline bool is_field_name(const std::string &s) { return is_token(s); } + +inline bool is_vchar(char c) { return c >= 33 && c <= 126; } + +inline bool is_obs_text(char c) { return 128 <= static_cast(c); } + +inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); } + +inline bool is_field_content(const std::string &s) { + if (s.empty()) { return true; } + + if (s.size() == 1) { + return is_field_vchar(s[0]); + } else if (s.size() == 2) { + return is_field_vchar(s[0]) && is_field_vchar(s[1]); + } else { + size_t i = 0; + + if (!is_field_vchar(s[i])) { return false; } + i++; + + while (i < s.size() - 1) { + auto c = s[i++]; + if (c == ' ' || c == '\t' || is_field_vchar(c)) { + } else { + return false; + } + } + + return is_field_vchar(s[i]); + } +} + +inline bool is_field_value(const std::string &s) { return is_field_content(s); } + +} // namespace fields + } // namespace detail // ---------------------------------------------------------------------------- @@ -2586,7 +2803,7 @@ inline bool is_valid_path(const std::string &path) { } inline FileStat::FileStat(const std::string &path) { -#if defined(_WIN32) +#if defined(_WIN64) auto wpath = u8string_to_wstring(path.c_str()); ret_ = _wstat(wpath.c_str(), &st_); #else @@ -2600,28 +2817,7 @@ inline bool FileStat::is_dir() const { return ret_ >= 0 && S_ISDIR(st_.st_mode); } -inline std::string encode_query_param(const std::string &value) { - std::ostringstream escaped; - escaped.fill('0'); - escaped << std::hex; - - for (auto c : value) { - if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || - c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || - c == ')') { - escaped << c; - } else { - escaped << std::uppercase; - escaped << '%' << std::setw(2) - << static_cast(static_cast(c)); - escaped << std::nouppercase; - } - } - - return escaped.str(); -} - -inline std::string encode_url(https://github.com/const%20std::string%20&s) { +inline std::string encode_path(const std::string &s) { std::string result; result.reserve(s.size()); @@ -2653,8 +2849,8 @@ inline std::string encode_url(https://github.com/const%20std::string%20&s) { return result; } -inline std::string decode_url(const std::string &s, - bool convert_plus_to_space) { +inline std::string decode_path(const std::string &s, + bool convert_plus_to_space) { std::string result; for (size_t i = 0; i < s.size(); i++) { @@ -2690,18 +2886,9 @@ inline std::string decode_url(const std::string &s, return result; } -inline void read_file(const std::string &path, std::string &out) { - std::ifstream fs(path, std::ios_base::binary); - fs.seekg(0, std::ios_base::end); - auto size = fs.tellg(); - fs.seekg(0); - out.resize(static_cast(size)); - fs.read(&out[0], static_cast(size)); -} - inline std::string file_extension(const std::string &path) { std::smatch m; - static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$"); if (std::regex_search(path, m, re)) { return m[1].str(); } return std::string(); } @@ -2785,18 +2972,18 @@ inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, fixed_buffer_size_(fixed_buffer_size) {} inline const char *stream_line_reader::ptr() const { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { return fixed_buffer_; } else { - return glowable_buffer_.data(); + return growable_buffer_.data(); } } inline size_t stream_line_reader::size() const { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { return fixed_buffer_used_size_; } else { - return glowable_buffer_.size(); + return growable_buffer_.size(); } } @@ -2807,13 +2994,18 @@ inline bool stream_line_reader::end_with_crlf() const { inline bool stream_line_reader::getline() { fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); + growable_buffer_.clear(); #ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR char prev_byte = 0; #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); @@ -2845,11 +3037,11 @@ inline void stream_line_reader::append(char c) { fixed_buffer_[fixed_buffer_used_size_++] = c; fixed_buffer_[fixed_buffer_used_size_] = '\0'; } else { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } - glowable_buffer_ += c; + growable_buffer_ += c; } } @@ -2860,24 +3052,19 @@ inline mmap::~mmap() { close(); } inline bool mmap::open(const char *path) { close(); -#if defined(_WIN32) +#if defined(_WIN64) auto wpath = u8string_to_wstring(path); if (wpath.empty()) { return false; } -#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, NULL); -#else - hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); -#endif if (hFile_ == INVALID_HANDLE_VALUE) { return false; } LARGE_INTEGER size{}; if (!::GetFileSizeEx(hFile_, &size)) { return false; } - // If the following line doesn't compile due to QuadPart, update Windows - // SDK. See: + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 if (static_cast(size.QuadPart) > (std::numeric_limits::max)()) { @@ -2886,12 +3073,8 @@ inline bool mmap::open(const char *path) { } size_ = static_cast(size.QuadPart); -#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 hMapping_ = ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); -#else - hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); -#endif // Special treatment for an empty file... if (hMapping_ == NULL && size_ == 0) { @@ -2905,11 +3088,7 @@ inline bool mmap::open(const char *path) { return false; } -#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); -#else - addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); -#endif if (addr_ == nullptr) { close(); @@ -2950,7 +3129,7 @@ inline const char *mmap::data() const { } inline void mmap::close() { -#if defined(_WIN32) +#if defined(_WIN64) if (addr_) { ::UnmapViewOfFile(addr_); addr_ = nullptr; @@ -2981,7 +3160,7 @@ inline void mmap::close() { size_ = 0; } inline int close_socket(socket_t sock) { -#ifdef _WIN32 +#ifdef _WIN64 return closesocket(sock); #else return close(sock); @@ -3004,7 +3183,7 @@ template inline ssize_t handle_EINTR(T fn) { inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { return handle_EINTR([&]() { return recv(sock, -#ifdef _WIN32 +#ifdef _WIN64 static_cast(ptr), static_cast(size), #else ptr, size, @@ -3017,7 +3196,7 @@ inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags) { return handle_EINTR([&]() { return send(sock, -#ifdef _WIN32 +#ifdef _WIN64 static_cast(ptr), static_cast(size), #else ptr, size, @@ -3026,76 +3205,73 @@ inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, }); } -inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL - struct pollfd pfd_read; - pfd_read.fd = sock; - pfd_read.events = POLLIN; - - auto timeout = static_cast(sec * 1000 + usec / 1000); - - return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) { +#ifdef _WIN64 + return ::WSAPoll(fds, nfds, timeout); #else -#ifndef _WIN32 - if (sock >= FD_SETSIZE) { return -1; } + return ::poll(fds, nfds, timeout); #endif +} - fd_set fds; +template +inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return -1; } + + fd_set fds, *rfds, *wfds; FD_ZERO(&fds); FD_SET(sock, &fds); + rfds = (Read ? &fds : nullptr); + wfds = (Read ? nullptr : &fds); timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); return handle_EINTR([&]() { - return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + return select(static_cast(sock + 1), rfds, wfds, nullptr, &tv); }); +#else + struct pollfd pfd; + pfd.fd = sock; + pfd.events = (Read ? POLLIN : POLLOUT); + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); }); #endif } -inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL - struct pollfd pfd_read; - pfd_read.fd = sock; - pfd_read.events = POLLOUT; +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} - auto timeout = static_cast(sec * 1000 + usec / 1000); +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { + return select_impl(sock, sec, usec); +} - return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); -#else -#ifndef _WIN32 - if (sock >= FD_SETSIZE) { return -1; } -#endif +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return Error::Connection; } - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); + fd_set fdsr, fdsw; + FD_ZERO(&fdsr); + FD_ZERO(&fdsw); + FD_SET(sock, &fdsr); + FD_SET(sock, &fdsw); timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); - return handle_EINTR([&]() { - return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, nullptr, &tv); }); -#endif -} - -inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, - time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL - struct pollfd pfd_read; - pfd_read.fd = sock; - pfd_read.events = POLLIN | POLLOUT; - - auto timeout = static_cast(sec * 1000 + usec / 1000); - auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); - - if (poll_res == 0) { return Error::ConnectionTimeout; } + if (ret == 0) { return Error::ConnectionTimeout; } - if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { auto error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, @@ -3106,28 +3282,18 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, return Error::Connection; #else -#ifndef _WIN32 - if (sock >= FD_SETSIZE) { return Error::Connection; } -#endif - - fd_set fdsr; - FD_ZERO(&fdsr); - FD_SET(sock, &fdsr); - - auto fdsw = fdsr; - auto fdse = fdsr; + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; - timeval tv; - tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); + auto timeout = static_cast(sec * 1000 + usec / 1000); - auto ret = handle_EINTR([&]() { - return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); - }); + auto poll_res = + handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); }); - if (ret == 0) { return Error::ConnectionTimeout; } + if (poll_res == 0) { return Error::ConnectionTimeout; } - if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { auto error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, @@ -3135,6 +3301,7 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, auto successful = res >= 0 && !error; return successful ? Error::Success : Error::Connection; } + return Error::Connection; #endif } @@ -3153,16 +3320,21 @@ inline bool is_socket_alive(socket_t sock) { class SocketStream final : public Stream { public: SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec); + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); ~SocketStream() override; bool is_readable() const override; - bool is_writable() const override; + bool wait_readable() const override; + bool wait_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; void get_local_ip_and_port(std::string &ip, int &port) const override; socket_t socket() const override; + time_t duration() const override; private: socket_t sock_; @@ -3170,6 +3342,8 @@ class SocketStream final : public Stream { time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; std::vector read_buff_; size_t read_buff_off_ = 0; @@ -3181,18 +3355,23 @@ class SocketStream final : public Stream { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLSocketStream final : public Stream { public: - SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, - time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec); + SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); ~SSLSocketStream() override; bool is_readable() const override; - bool is_writable() const override; + bool wait_readable() const override; + bool wait_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; void get_local_ip_and_port(std::string &ip, int &port) const override; socket_t socket() const override; + time_t duration() const override; private: socket_t sock_; @@ -3201,6 +3380,8 @@ class SSLSocketStream final : public Stream { time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time_; }; #endif @@ -3246,7 +3427,7 @@ process_server_socket_core(const std::atomic &svr_sock, socket_t sock, auto ret = false; auto count = keep_alive_max_count; while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { - auto close_connection = true; + auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); if (!ret || connection_closed) { break; } @@ -3271,18 +3452,20 @@ process_server_socket(const std::atomic &svr_sock, socket_t sock, }); } -inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec, - std::function callback) { +inline bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, + std::function callback) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, - write_timeout_sec, write_timeout_usec); + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); return callback(strm); } inline int shutdown_socket(socket_t sock) { -#ifdef _WIN32 +#ifdef _WIN64 return shutdown(sock, SD_BOTH); #else return shutdown(sock, SHUT_RDWR); @@ -3308,11 +3491,328 @@ unescape_abstract_namespace_unix_domain(const std::string &s) { return s; } +inline int getaddrinfo_with_timeout(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res, time_t timeout_sec) { +#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + if (timeout_sec <= 0) { + // No timeout specified, use standard getaddrinfo + return getaddrinfo(node, service, hints, res); + } + +#ifdef _WIN64 + // Windows-specific implementation using GetAddrInfoEx with overlapped I/O + OVERLAPPED overlapped = {0}; + HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!event) { return EAI_FAIL; } + + overlapped.hEvent = event; + + PADDRINFOEXW result_addrinfo = nullptr; + HANDLE cancel_handle = nullptr; + + ADDRINFOEXW hints_ex = {0}; + if (hints) { + hints_ex.ai_flags = hints->ai_flags; + hints_ex.ai_family = hints->ai_family; + hints_ex.ai_socktype = hints->ai_socktype; + hints_ex.ai_protocol = hints->ai_protocol; + } + + auto wnode = u8string_to_wstring(node); + auto wservice = u8string_to_wstring(service); + + auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr, + hints ? &hints_ex : nullptr, &result_addrinfo, + nullptr, &overlapped, nullptr, &cancel_handle); + + if (ret == WSA_IO_PENDING) { + auto wait_result = + ::WaitForSingleObject(event, static_cast(timeout_sec * 1000)); + if (wait_result == WAIT_TIMEOUT) { + if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); } + ::CloseHandle(event); + return EAI_AGAIN; + } + + DWORD bytes_returned; + if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped, + &bytes_returned, FALSE)) { + ::CloseHandle(event); + return ::WSAGetLastError(); + } + } + + ::CloseHandle(event); + + if (ret == NO_ERROR || ret == WSA_IO_PENDING) { + *res = reinterpret_cast(result_addrinfo); + return 0; + } + + return ret; +#elif defined(TARGET_OS_OSX) + // macOS implementation using CFHost API for asynchronous DNS resolution + CFStringRef hostname_ref = CFStringCreateWithCString( + kCFAllocatorDefault, node, kCFStringEncodingUTF8); + if (!hostname_ref) { return EAI_MEMORY; } + + CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref); + CFRelease(hostname_ref); + if (!host_ref) { return EAI_MEMORY; } + + // Set up context for callback + struct CFHostContext { + bool completed = false; + bool success = false; + CFArrayRef addresses = nullptr; + std::mutex mutex; + std::condition_variable cv; + } context; + + CFHostClientContext client_context; + memset(&client_context, 0, sizeof(client_context)); + client_context.info = &context; + + // Set callback + auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/, + const CFStreamError *error, void *info) { + auto ctx = static_cast(info); + std::lock_guard lock(ctx->mutex); + + if (error && error->error != 0) { + ctx->success = false; + } else { + Boolean hasBeenResolved; + ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved); + if (ctx->addresses && hasBeenResolved) { + CFRetain(ctx->addresses); + ctx->success = true; + } else { + ctx->success = false; + } + } + ctx->completed = true; + ctx->cv.notify_one(); + }; + + if (!CFHostSetClient(host_ref, callback, &client_context)) { + CFRelease(host_ref); + return EAI_SYSTEM; + } + + // Schedule on run loop + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + + // Start resolution + CFStreamError stream_error; + if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) { + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFRelease(host_ref); + return EAI_FAIL; + } + + // Wait for completion with timeout + auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec); + bool timed_out = false; + + { + std::unique_lock lock(context.mutex); + + while (!context.completed) { + auto now = std::chrono::steady_clock::now(); + if (now >= timeout_time) { + timed_out = true; + break; + } + + // Run the runloop for a short time + lock.unlock(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + lock.lock(); + } + } + + // Clean up + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFHostSetClient(host_ref, nullptr, nullptr); + + if (timed_out || !context.completed) { + CFHostCancelInfoResolution(host_ref, kCFHostAddresses); + CFRelease(host_ref); + return EAI_AGAIN; + } + + if (!context.success || !context.addresses) { + CFRelease(host_ref); + return EAI_NODATA; + } + + // Convert CFArray to addrinfo + CFIndex count = CFArrayGetCount(context.addresses); + if (count == 0) { + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_NODATA; + } + + struct addrinfo *result_addrinfo = nullptr; + struct addrinfo **current = &result_addrinfo; + + for (CFIndex i = 0; i < count; i++) { + CFDataRef addr_data = + static_cast(CFArrayGetValueAtIndex(context.addresses, i)); + if (!addr_data) continue; + + const struct sockaddr *sockaddr_ptr = + reinterpret_cast(CFDataGetBytePtr(addr_data)); + socklen_t sockaddr_len = static_cast(CFDataGetLength(addr_data)); + + // Allocate addrinfo structure + *current = static_cast(malloc(sizeof(struct addrinfo))); + if (!*current) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + + memset(*current, 0, sizeof(struct addrinfo)); + + // Set up addrinfo fields + (*current)->ai_family = sockaddr_ptr->sa_family; + (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP; + (*current)->ai_addrlen = sockaddr_len; + + // Copy sockaddr + (*current)->ai_addr = static_cast(malloc(sockaddr_len)); + if (!(*current)->ai_addr) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len); + + // Set port if service is specified + if (service && strlen(service) > 0) { + int port = atoi(service); + if (port > 0) { + if (sockaddr_ptr->sa_family == AF_INET) { + reinterpret_cast((*current)->ai_addr) + ->sin_port = htons(static_cast(port)); + } else if (sockaddr_ptr->sa_family == AF_INET6) { + reinterpret_cast((*current)->ai_addr) + ->sin6_port = htons(static_cast(port)); + } + } + } + + current = &((*current)->ai_next); + } + + CFRelease(context.addresses); + CFRelease(host_ref); + + *res = result_addrinfo; + return 0; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) + // Linux implementation using getaddrinfo_a for asynchronous DNS resolution + struct gaicb request; + struct gaicb *requests[1] = {&request}; + struct sigevent sevp; + struct timespec timeout; + + // Initialize the request structure + memset(&request, 0, sizeof(request)); + request.ar_name = node; + request.ar_service = service; + request.ar_request = hints; + + // Set up timeout + timeout.tv_sec = timeout_sec; + timeout.tv_nsec = 0; + + // Initialize sigevent structure (not used, but required) + memset(&sevp, 0, sizeof(sevp)); + sevp.sigev_notify = SIGEV_NONE; + + // Start asynchronous resolution + int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp); + if (start_result != 0) { return start_result; } + + // Wait for completion with timeout + int wait_result = + gai_suspend((const struct gaicb *const *)requests, 1, &timeout); + + if (wait_result == 0) { + // Completed successfully, get the result + int gai_result = gai_error(&request); + if (gai_result == 0) { + *res = request.ar_result; + return 0; + } else { + // Clean up on error + if (request.ar_result) { freeaddrinfo(request.ar_result); } + return gai_result; + } + } else if (wait_result == EAI_AGAIN) { + // Timeout occurred, cancel the request + gai_cancel(&request); + return EAI_AGAIN; + } else { + // Other error occurred + gai_cancel(&request); + return wait_result; + } +#else + // Fallback implementation using thread-based timeout for other Unix systems + std::mutex result_mutex; + std::condition_variable result_cv; + auto completed = false; + auto result = EAI_SYSTEM; + struct addrinfo *result_addrinfo = nullptr; + + std::thread resolve_thread([&]() { + auto thread_result = getaddrinfo(node, service, hints, &result_addrinfo); + + std::lock_guard lock(result_mutex); + result = thread_result; + completed = true; + result_cv.notify_one(); + }); + + // Wait for completion or timeout + std::unique_lock lock(result_mutex); + auto finished = result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), + [&] { return completed; }); + + if (finished) { + // Operation completed within timeout + resolve_thread.join(); + *res = result_addrinfo; + return result; + } else { + // Timeout occurred + resolve_thread.detach(); // Let the thread finish in background + return EAI_AGAIN; // Return timeout error + } +#endif +#else + (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo + return getaddrinfo(node, service, hints, res); +#endif +} + template socket_t create_socket(const std::string &host, const std::string &ip, int port, int address_family, int socket_flags, bool tcp_nodelay, bool ipv6_v6only, SocketOptions socket_options, - BindOrConnect bind_or_connect) { + BindOrConnect bind_or_connect, time_t timeout_sec = 0) { // Get address info const char *node = nullptr; struct addrinfo hints; @@ -3333,7 +3833,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, hints.ai_flags = socket_flags; } -#ifndef _WIN32 +#if !defined(_WIN64) || defined(CPPHTTPLIB_HAVE_AFUNIX_H) if (hints.ai_family == AF_UNIX) { const auto addrlen = host.length(); if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } @@ -3357,11 +3857,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 _WIN64 fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif #endif if (socket_options) { socket_options(sock); } +#ifdef _WIN64 + // 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); @@ -3374,7 +3882,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, auto service = std::to_string(port); - if (getaddrinfo(node, service.c_str(), &hints, &result)) { + if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result, + timeout_sec)) { #if defined __linux__ && !defined __ANDROID__ res_init(); #endif @@ -3384,7 +3893,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket -#ifdef _WIN32 +#ifdef _WIN64 auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); @@ -3398,8 +3907,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa * * WSA_FLAG_NO_HANDLE_INHERIT: - * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 - * with SP1, and later + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later * */ if (sock == INVALID_SOCKET) { @@ -3417,33 +3926,17 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, #endif if (sock == INVALID_SOCKET) { continue; } -#if !defined _WIN32 && !defined SOCK_CLOEXEC +#if !defined _WIN64 && !defined SOCK_CLOEXEC if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { close_socket(sock); continue; } #endif - if (tcp_nodelay) { - auto opt = 1; -#ifdef _WIN32 - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&opt), sizeof(opt)); -#else - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&opt), sizeof(opt)); -#endif - } + if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); } if (rp->ai_family == AF_INET6) { - auto opt = ipv6_v6only ? 1 : 0; -#ifdef _WIN32 - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, - reinterpret_cast(&opt), sizeof(opt)); -#else - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, - reinterpret_cast(&opt), sizeof(opt)); -#endif + set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0); } if (socket_options) { socket_options(sock); } @@ -3461,7 +3954,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, } inline void set_nonblocking(socket_t sock, bool nonblocking) { -#ifdef _WIN32 +#ifdef _WIN64 auto flags = nonblocking ? 1UL : 0UL; ioctlsocket(sock, FIONBIO, &flags); #else @@ -3472,7 +3965,7 @@ inline void set_nonblocking(socket_t sock, bool nonblocking) { } inline bool is_connection_error() { -#ifdef _WIN32 +#ifdef _WIN64 return WSAGetLastError() != WSAEWOULDBLOCK; #else return errno != EINPROGRESS; @@ -3488,7 +3981,10 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) { hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) { + return false; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); auto ret = false; @@ -3503,7 +3999,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) { return ret; } -#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#if !defined _WIN64 && !defined ANDROID && !defined _AIX && !defined __MVS__ #define USE_IF2IP #endif @@ -3586,40 +4082,15 @@ inline socket_t create_client_socket( } set_nonblocking(sock2, false); - - { -#ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec * 1000 + - read_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec); - tv.tv_usec = static_cast(read_timeout_usec); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } - { - -#ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec * 1000 + - write_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec); - tv.tv_usec = static_cast(write_timeout_usec); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } + set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec, + read_timeout_usec); + set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec, + write_timeout_usec); error = Error::Success; return true; - }); + }, + connection_timeout_sec); // Pass DNS timeout if (sock != INVALID_SOCKET) { error = Error::Success; @@ -3667,7 +4138,7 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { -#ifndef _WIN32 +#ifndef _WIN64 if (addr.ss_family == AF_UNIX) { #if defined(__linux__) struct ucred ucred; @@ -3675,7 +4146,7 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { port = ucred.pid; } -#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) pid_t pid; socklen_t len = sizeof(pid); if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { @@ -3693,12 +4164,12 @@ inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { return (l == 0) ? h - : str2tag_core(s + 1, l - 1, - // Unsets the 6 high bits of h, therefore no - // overflow happens - (((std::numeric_limits::max)() >> 6) & - h * 33) ^ - static_cast(*s)); + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); } inline unsigned int str2tag(const std::string &s) { @@ -3818,6 +4289,12 @@ inline EncodingType encoding_type(const Request &req, const Response &res) { if (ret) { return EncodingType::Gzip; } #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + // TODO: 'Accept-Encoding' has zstd, not zstd;q=0 + ret = s.find("zstd") != std::string::npos; + if (ret) { return EncodingType::Zstd; } +#endif + return EncodingType::None; } @@ -4026,6 +4503,61 @@ inline bool brotli_decompressor::decompress(const char *data, } #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +inline zstd_compressor::zstd_compressor() { + ctx_ = ZSTD_createCCtx(); + ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast); +} + +inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); } + +inline bool zstd_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue; + ZSTD_inBuffer input = {data, data_length, 0}; + + bool finished; + do { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + + finished = last ? (remaining == 0) : (input.pos == input.size); + + } while (!finished); + + return true; +} + +inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); } + +inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); } + +inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; } + +inline bool zstd_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + std::array buff{}; + ZSTD_inBuffer input = {data, data_length, 0}; + + while (input.pos < input.size) { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + } + + return true; +} +#endif + inline bool has_header(const Headers &headers, const std::string &key) { return headers.find(key) != headers.end(); } @@ -4052,6 +4584,9 @@ inline bool parse_header(const char *beg, const char *end, T fn) { p++; } + auto name = std::string(beg, p); + if (!detail::fields::is_field_name(name)) { return false; } + if (p == end) { return false; } auto key_end = p; @@ -4067,22 +4602,17 @@ inline bool parse_header(const char *beg, const char *end, T fn) { if (!key_len) { return false; } auto key = std::string(beg, key_end); - auto val = case_ignore::equal(key, "Location") - ? std::string(p, end) - : decode_url(/service/std::string(p, end), false); - - // NOTE: From RFC 9110: - // Field values containing CR, LF, or NUL characters are - // invalid and dangerous, due to the varying ways that - // implementations might parse and interpret those - // characters; a recipient of CR, LF, or NUL within a field - // value MUST either reject the message or replace each of - // those characters with SP before further processing or - // forwarding of that message. - static const std::string CR_LF_NUL("\r\n\0", 3); - if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; } - - fn(key, val); + auto val = std::string(p, end); + + if (!detail::fields::is_field_value(val)) { return false; } + + if (case_ignore::equal(key, "Location") || + case_ignore::equal(key, "Referer")) { + fn(key, val); + } else { + fn(key, decode_path(val, false)); + } + return true; } @@ -4094,6 +4624,8 @@ inline bool read_headers(Stream &strm, Headers &headers) { char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); + size_t header_count = 0; + for (;;) { if (!line_reader.getline()) { return false; } @@ -4114,33 +4646,38 @@ inline bool read_headers(Stream &strm, Headers &headers) { if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + // Check header count limit + if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } + // Exclude line terminator auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; if (!parse_header(line_reader.ptr(), end, - [&](const std::string &key, std::string &val) { + [&](const std::string &key, const std::string &val) { headers.emplace(key, val); })) { return false; } + + header_count++; } return true; } -inline bool read_content_with_length(Stream &strm, uint64_t len, - Progress progress, +inline bool read_content_with_length(Stream &strm, size_t len, + DownloadProgress progress, ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; while (r < len) { auto read_len = static_cast(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } if (!out(buf, static_cast(n), r, len)) { return false; } - r += static_cast(n); + r += static_cast(n); if (progress) { if (!progress(r, len)) { return false; } @@ -4150,83 +4687,154 @@ inline bool read_content_with_length(Stream &strm, uint64_t len, return true; } -inline void skip_content_with_length(Stream &strm, uint64_t len) { +inline void skip_content_with_length(Stream &strm, size_t len) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; while (r < len) { auto read_len = static_cast(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return; } - r += static_cast(n); + r += static_cast(n); } } -inline bool read_content_without_length(Stream &strm, - ContentReceiverWithProgress out) { +enum class ReadContentResult { + Success, // Successfully read the content + PayloadTooLarge, // The content exceeds the specified payload limit + Error // An error occurred while reading the content +}; + +inline ReadContentResult +read_content_without_length(Stream &strm, size_t payload_max_length, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); - if (n <= 0) { return true; } + if (n == 0) { return ReadContentResult::Success; } + if (n < 0) { return ReadContentResult::Error; } + + // Check if adding this data would exceed the payload limit + if (r > payload_max_length || + payload_max_length - r < static_cast(n)) { + return ReadContentResult::PayloadTooLarge; + } - if (!out(buf, static_cast(n), r, 0)) { return false; } - r += static_cast(n); + if (!out(buf, static_cast(n), r, 0)) { + return ReadContentResult::Error; + } + r += static_cast(n); } - return true; + return ReadContentResult::Success; } template -inline bool read_content_chunked(Stream &strm, T &x, - ContentReceiverWithProgress out) { +inline ReadContentResult read_content_chunked(Stream &strm, T &x, + size_t payload_max_length, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } unsigned long chunk_len; + size_t total_len = 0; while (true) { char *end_ptr; chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); - if (end_ptr == line_reader.ptr()) { return false; } - if (chunk_len == ULONG_MAX) { return false; } + if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; } + if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; } if (chunk_len == 0) { break; } + // Check if adding this chunk would exceed the payload limit + if (total_len > payload_max_length || + payload_max_length - total_len < chunk_len) { + return ReadContentResult::PayloadTooLarge; + } + + total_len += chunk_len; + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { - return false; + return ReadContentResult::Error; } - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } - if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + if (strcmp(line_reader.ptr(), "\r\n") != 0) { + return ReadContentResult::Error; + } - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } } assert(chunk_len == 0); - // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked // transfer coding is complete when a chunk with a chunk-size of zero is - // received, possibly followed by a trailer section, and finally terminated - // by an empty line". - // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 // // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section // does't care for the existence of the final CRLF. In other words, it seems // to be ok whether the final CRLF exists or not in the chunked data. // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 // - // According to the reference code in RFC 9112, cpp-htpplib now allows - // chuncked transfer coding data without the final CRLF. - if (!line_reader.getline()) { return true; } + // According to the reference code in RFC 9112, cpp-httplib now allows + // chunked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return ReadContentResult::Success; } + + // RFC 7230 Section 4.1.2 - Headers prohibited in trailers + thread_local case_ignore::unordered_set prohibited_trailers = { + // Message framing + "transfer-encoding", "content-length", + + // Routing + "host", + + // Authentication + "authorization", "www-authenticate", "proxy-authenticate", + "proxy-authorization", "cookie", "set-cookie", + + // Request modifiers + "cache-control", "expect", "max-forwards", "pragma", "range", "te", + + // Response control + "age", "expires", "date", "location", "retry-after", "vary", "warning", + + // Payload processing + "content-encoding", "content-type", "content-range", "trailer"}; + + // Parse declared trailer headers once for performance + case_ignore::unordered_set declared_trailers; + if (has_header(x.headers, "Trailer")) { + auto trailer_header = get_header_value(x.headers, "Trailer", "", 0); + auto len = std::strlen(trailer_header); + + split(trailer_header, trailer_header + len, ',', + [&](const char *b, const char *e) { + std::string key(b, e); + if (prohibited_trailers.find(key) == prohibited_trailers.end()) { + declared_trailers.insert(key); + } + }); + } + size_t trailer_header_count = 0; while (strcmp(line_reader.ptr(), "\r\n") != 0) { - if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return ReadContentResult::Error; + } + + // Check trailer header count limit + if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { + return ReadContentResult::Error; + } // Exclude line terminator constexpr auto line_terminator_len = 2; @@ -4234,13 +4842,16 @@ inline bool read_content_chunked(Stream &strm, T &x, parse_header(line_reader.ptr(), end, [&](const std::string &key, const std::string &val) { - x.headers.emplace(key, val); + if (declared_trailers.find(key) != declared_trailers.end()) { + x.trailers.emplace(key, val); + trailer_header_count++; + } }); - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } } - return true; + return ReadContentResult::Success; } inline bool is_chunked_transfer_encoding(const Headers &headers) { @@ -4269,13 +4880,20 @@ bool prepare_content_receiver(T &x, int &status, #else status = StatusCode::UnsupportedMediaType_415; return false; +#endif + } else if (encoding == "zstd") { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; #endif } if (decompressor) { if (decompressor->is_valid()) { ContentReceiverWithProgress out = [&](const char *buf, size_t n, - uint64_t off, uint64_t len) { + size_t off, size_t len) { return decompressor->decompress(buf, n, [&](const char *buf2, size_t n2) { return receiver(buf2, n2, off, len); @@ -4289,8 +4907,8 @@ bool prepare_content_receiver(T &x, int &status, } } - ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, - uint64_t len) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off, + size_t len) { return receiver(buf, n, off, len); }; return callback(std::move(out)); @@ -4298,8 +4916,8 @@ bool prepare_content_receiver(T &x, int &status, template bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, - Progress progress, ContentReceiverWithProgress receiver, - bool decompress) { + DownloadProgress progress, + ContentReceiverWithProgress receiver, bool decompress) { return prepare_content_receiver( x, status, std::move(receiver), decompress, [&](const ContentReceiverWithProgress &out) { @@ -4307,12 +4925,35 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, x, out); + auto result = read_content_chunked(strm, x, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } } else if (!has_header(x.headers, "Content-Length")) { - ret = read_content_without_length(strm, out); + auto result = + read_content_without_length(strm, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } } else { - auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); - if (len > payload_max_length) { + auto is_invalid_value = false; + auto len = get_header_value_u64(x.headers, "Content-Length", + (std::numeric_limits::max)(), + 0, is_invalid_value); + + if (is_invalid_value) { + ret = false; + } else if (len > payload_max_length) { exceed_payload_max_length = true; skip_content_with_length(strm, len); ret = false; @@ -4377,17 +5018,29 @@ inline bool write_data(Stream &strm, const char *d, size_t l) { } template -inline bool write_content(Stream &strm, const ContentProvider &content_provider, - size_t offset, size_t length, T is_shutting_down, - Error &error) { +inline bool write_content_with_progress(Stream &strm, + const ContentProvider &content_provider, + size_t offset, size_t length, + T is_shutting_down, + const UploadProgress &upload_progress, + Error &error) { size_t end_offset = offset + length; + size_t start_offset = offset; auto ok = true; DataSink data_sink; data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { - if (strm.is_writable() && write_data(strm, d, l)) { + if (write_data(strm, d, l)) { offset += l; + + if (upload_progress && length > 0) { + size_t current_written = offset - start_offset; + if (!upload_progress(current_written, length)) { + ok = false; + return false; + } + } } else { ok = false; } @@ -4395,10 +5048,10 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider, return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; while (offset < end_offset && !is_shutting_down()) { - if (!strm.is_writable()) { + if (!strm.wait_writable()) { error = Error::Write; return false; } else if (!content_provider(offset, end_offset - offset, data_sink)) { @@ -4414,6 +5067,14 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider, return true; } +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + return write_content_with_progress(strm, content_provider, offset, length, + is_shutting_down, nullptr, error); +} + template inline bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, @@ -4436,17 +5097,17 @@ write_content_without_length(Stream &strm, data_sink.write = [&](const char *d, size_t l) -> bool { if (ok) { offset += l; - if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + if (!write_data(strm, d, l)) { ok = false; } } return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; data_sink.done = [&](void) { data_available = false; }; while (data_available && !is_shutting_down()) { - if (!strm.is_writable()) { + if (!strm.wait_writable()) { return false; } else if (!content_provider(offset, 0, data_sink)) { return false; @@ -4481,10 +5142,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (!strm.is_writable() || - !write_data(strm, chunk.data(), chunk.size())) { - ok = false; - } + if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; } } } else { ok = false; @@ -4493,7 +5151,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, return ok; }; - data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); }; auto done_with_trailer = [&](const Headers *trailer) { if (!ok) { return; } @@ -4513,17 +5171,14 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, if (!payload.empty()) { // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (!strm.is_writable() || - !write_data(strm, chunk.data(), chunk.size())) { + if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; return; } } - static const std::string done_marker("0\r\n"); - if (!write_data(strm, done_marker.data(), done_marker.size())) { - ok = false; - } + constexpr const char done_marker[] = "0\r\n"; + if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; } // Trailer if (trailer) { @@ -4535,8 +5190,8 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, } } - static const std::string crlf("\r\n"); - if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + constexpr const char crlf[] = "\r\n"; + if (!write_data(strm, crlf, str_len(crlf))) { ok = false; } }; data_sink.done = [&](void) { done_with_trailer(nullptr); }; @@ -4546,7 +5201,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, }; while (data_available && !is_shutting_down()) { - if (!strm.is_writable()) { + if (!strm.wait_writable()) { error = Error::Write; return false; } else if (!content_provider(offset, 0, data_sink)) { @@ -4605,7 +5260,7 @@ inline std::string params_to_query_str(const Params ¶ms) { if (it != params.begin()) { query += "&"; } query += it->first; query += "="; - query += encode_query_param(it->second); + query += httplib::encode_uri_component(it->second); } return query; } @@ -4628,7 +5283,7 @@ inline void parse_query_text(const char *data, std::size_t size, }); if (!key.empty()) { - params.emplace(decode_url(https://github.com/key,%20true), decode_url(https://github.com/val,%20true)); + params.emplace(decode_path(key, true), decode_path(val, true)); } }); } @@ -4723,9 +5378,126 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try { } catch (...) { return false; } #endif -class MultipartFormDataParser { +inline bool parse_accept_header(const std::string &s, + std::vector &content_types) { + content_types.clear(); + + // Empty string is considered valid (no preference) + if (s.empty()) { return true; } + + // Check for invalid patterns: leading/trailing commas or consecutive commas + if (s.front() == ',' || s.back() == ',' || + s.find(",,") != std::string::npos) { + return false; + } + + struct AcceptEntry { + std::string media_type; + double quality; + int order; // Original order in header + }; + + std::vector entries; + int order = 0; + bool has_invalid_entry = false; + + // Split by comma and parse each entry + split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) { + std::string entry(b, e); + entry = trim_copy(entry); + + if (entry.empty()) { + has_invalid_entry = true; + return; + } + + AcceptEntry accept_entry; + accept_entry.quality = 1.0; // Default quality + accept_entry.order = order++; + + // Find q= parameter + auto q_pos = entry.find(";q="); + if (q_pos == std::string::npos) { q_pos = entry.find("; q="); } + + if (q_pos != std::string::npos) { + // Extract media type (before q parameter) + accept_entry.media_type = trim_copy(entry.substr(0, q_pos)); + + // Extract quality value + auto q_start = entry.find('=', q_pos) + 1; + auto q_end = entry.find(';', q_start); + if (q_end == std::string::npos) { q_end = entry.length(); } + + std::string quality_str = + trim_copy(entry.substr(q_start, q_end - q_start)); + if (quality_str.empty()) { + has_invalid_entry = true; + return; + } + + try { + accept_entry.quality = std::stod(quality_str); + // Check if quality is in valid range [0.0, 1.0] + if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) { + has_invalid_entry = true; + return; + } + } catch (...) { + has_invalid_entry = true; + return; + } + } else { + // No quality parameter, use entire entry as media type + accept_entry.media_type = entry; + } + + // Remove additional parameters from media type + auto param_pos = accept_entry.media_type.find(';'); + if (param_pos != std::string::npos) { + accept_entry.media_type = + trim_copy(accept_entry.media_type.substr(0, param_pos)); + } + + // Basic validation of media type format + if (accept_entry.media_type.empty()) { + has_invalid_entry = true; + return; + } + + // Check for basic media type format (should contain '/' or be '*') + if (accept_entry.media_type != "*" && + accept_entry.media_type.find('/') == std::string::npos) { + has_invalid_entry = true; + return; + } + + entries.push_back(accept_entry); + }); + + // Return false if any invalid entry was found + if (has_invalid_entry) { return false; } + + // Sort by quality (descending), then by original order (ascending) + std::sort(entries.begin(), entries.end(), + [](const AcceptEntry &a, const AcceptEntry &b) { + if (a.quality != b.quality) { + return a.quality > b.quality; // Higher quality first + } + return a.order < b.order; // Earlier order first for same quality + }); + + // Extract sorted media types + content_types.reserve(entries.size()); + for (const auto &entry : entries) { + content_types.push_back(entry.media_type); + } + + return true; +} + +class FormDataParser { public: - MultipartFormDataParser() = default; + FormDataParser() = default; void set_boundary(std::string &&boundary) { boundary_ = boundary; @@ -4735,18 +5507,17 @@ class MultipartFormDataParser { bool is_valid() const { return is_valid_; } - bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, - const MultipartContentHeader &header_callback) { + bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, + const ContentReceiver &content_callback) { buf_append(buf, n); while (buf_size() > 0) { switch (state_) { case 0: { // Initial boundary - buf_erase(buf_find(dash_boundary_crlf_)); - if (dash_boundary_crlf_.size() > buf_size()) { return true; } - if (!buf_start_with(dash_boundary_crlf_)) { return false; } - buf_erase(dash_boundary_crlf_.size()); + auto pos = buf_find(dash_boundary_crlf_); + if (pos == buf_size()) { return true; } + buf_erase(pos + dash_boundary_crlf_.size()); state_ = 1; break; } @@ -4778,13 +5549,23 @@ class MultipartFormDataParser { return false; } - static const std::string header_content_type = "Content-Type:"; + // Parse and emplace space trimmed headers into a map + if (!parse_header( + header.data(), header.data() + header.size(), + [&](const std::string &key, const std::string &val) { + file_.headers.emplace(key, val); + })) { + is_valid_ = false; + return false; + } + + constexpr const char header_content_type[] = "Content-Type:"; if (start_with_case_ignore(header, header_content_type)) { file_.content_type = - trim_copy(header.substr(header_content_type.size())); + trim_copy(header.substr(str_len(header_content_type))); } else { - static const std::regex re_content_disposition( + thread_local const std::regex re_content_disposition( R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", std::regex_constants::icase); @@ -4806,13 +5587,13 @@ class MultipartFormDataParser { it = params.find("filename*"); if (it != params.end()) { - // Only allow UTF-8 enconnding... - static const std::regex re_rfc5987_encoding( + // Only allow UTF-8 encoding... + thread_local const std::regex re_rfc5987_encoding( R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); std::smatch m2; if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { - file_.filename = decode_url(https://github.com/m2[1],%20false); // override... + file_.filename = decode_path(m2[1], false); // override... } else { is_valid_ = false; return false; @@ -4877,12 +5658,13 @@ class MultipartFormDataParser { file_.name.clear(); file_.filename.clear(); file_.content_type.clear(); + file_.headers.clear(); } - bool start_with_case_ignore(const std::string &a, - const std::string &b) const { - if (a.size() < b.size()) { return false; } - for (size_t i = 0; i < b.size(); i++) { + bool start_with_case_ignore(const std::string &a, const char *b) const { + const auto b_len = strlen(b); + if (a.size() < b_len) { return false; } + for (size_t i = 0; i < b_len; i++) { if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { return false; } @@ -4898,7 +5680,7 @@ class MultipartFormDataParser { size_t state_ = 0; bool is_valid_ = false; - MultipartFormData file_; + FormData file_; // Buffer bool start_with(const std::string &a, size_t spos, size_t epos, @@ -4969,19 +5751,18 @@ class MultipartFormDataParser { }; inline std::string random_string(size_t length) { - static const char data[] = + constexpr const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - // std::random_device might actually be deterministic on some - // platforms, but due to lack of support in the c++ standard library, - // doing better requires either some ugly hacks or breaking portability. - static std::random_device seed_gen; - - // Request 128 bits of entropy for initialization - static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), - seed_gen()}; - - static std::mt19937 engine(seed_sequence); + thread_local auto engine([]() { + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + return std::mt19937(seed_sequence); + }()); std::string result; for (size_t i = 0; i < length; i++) { @@ -5037,7 +5818,7 @@ serialize_multipart_formdata_get_content_type(const std::string &boundary) { } inline std::string -serialize_multipart_formdata(const MultipartFormDataItems &items, +serialize_multipart_formdata(const UploadFormDataItems &items, const std::string &boundary, bool finish = true) { std::string body; @@ -5051,13 +5832,68 @@ serialize_multipart_formdata(const MultipartFormDataItems &items, return body; } +inline void coalesce_ranges(Ranges &ranges, size_t content_length) { + if (ranges.size() <= 1) return; + + // Sort ranges by start position + std::sort(ranges.begin(), ranges.end(), + [](const Range &a, const Range &b) { return a.first < b.first; }); + + Ranges coalesced; + coalesced.reserve(ranges.size()); + + for (auto &r : ranges) { + auto first_pos = r.first; + auto last_pos = r.second; + + // Handle special cases like in range_error + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = static_cast(content_length); + } + + if (first_pos == -1) { + first_pos = static_cast(content_length) - last_pos; + last_pos = static_cast(content_length) - 1; + } + + if (last_pos == -1 || last_pos >= static_cast(content_length)) { + last_pos = static_cast(content_length) - 1; + } + + // Skip invalid ranges + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos < static_cast(content_length))) { + continue; + } + + // Coalesce with previous range if overlapping or adjacent (but not + // identical) + if (!coalesced.empty()) { + auto &prev = coalesced.back(); + // Check if current range overlaps or is adjacent to previous range + // but don't coalesce identical ranges (allow duplicates) + if (first_pos <= prev.second + 1 && + !(first_pos == prev.first && last_pos == prev.second)) { + // Extend the previous range + prev.second = (std::max)(prev.second, last_pos); + continue; + } + } + + // Add new range + coalesced.emplace_back(first_pos, last_pos); + } + + ranges = std::move(coalesced); +} + inline bool range_error(Request &req, Response &res) { if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { - ssize_t contant_len = static_cast( + ssize_t content_len = static_cast( res.content_length_ ? res.content_length_ : res.body.size()); - ssize_t prev_first_pos = -1; - ssize_t prev_last_pos = -1; + std::vector> processed_ranges; size_t overwrapping_count = 0; // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 @@ -5073,12 +5909,12 @@ inline bool range_error(Request &req, Response &res) { if (first_pos == -1 && last_pos == -1) { first_pos = 0; - last_pos = contant_len; + last_pos = content_len; } if (first_pos == -1) { - first_pos = contant_len - last_pos; - last_pos = contant_len - 1; + first_pos = content_len - last_pos; + last_pos = content_len - 1; } // NOTE: RFC-9110 '14.1.2. Byte Ranges': @@ -5090,28 +5926,31 @@ inline bool range_error(Request &req, Response &res) { // with a value that is one less than the current length of the selected // representation). // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 - if (last_pos == -1 || last_pos >= contant_len) { - last_pos = contant_len - 1; + if (last_pos == -1 || last_pos >= content_len) { + last_pos = content_len - 1; } // Range must be within content length if (!(0 <= first_pos && first_pos <= last_pos && - last_pos <= contant_len - 1)) { + last_pos <= content_len - 1)) { return true; } - // Ranges must be in ascending order - if (first_pos <= prev_first_pos) { return true; } - // Request must not have more than two overlapping ranges - if (first_pos <= prev_last_pos) { - overwrapping_count++; - if (overwrapping_count > 2) { return true; } + for (const auto &processed_range : processed_ranges) { + if (!(last_pos < processed_range.first || + first_pos > processed_range.second)) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + break; // Only count once per range + } } - prev_first_pos = (std::max)(prev_first_pos, first_pos); - prev_last_pos = (std::max)(prev_last_pos, last_pos); + processed_ranges.emplace_back(first_pos, last_pos); } + + // After validation, coalesce overlapping ranges as per RFC 9110 + coalesce_ranges(req.ranges, static_cast(content_len)); } return false; @@ -5230,10 +6069,14 @@ write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, inline bool expect_content(const Request &req) { if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || - req.method == "PRI" || req.method == "DELETE") { + req.method == "DELETE") { + return true; + } + if (req.has_header("Content-Length") && + req.get_header_value_u64("Content-Length") > 0) { return true; } - // TODO: check if Content-Length is set + if (is_chunked_transfer_encoding(req.headers)) { return true; } return false; } @@ -5278,10 +6121,77 @@ inline std::string SHA_256(const std::string &s) { inline std::string SHA_512(const std::string &s) { return message_digest(s, EVP_sha512()); } -#endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -#ifdef _WIN32 +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} + +inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) { + detail::set_nonblocking(sock, true); + auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + char buf[1]; + return !SSL_peek(ssl, buf, 1) && + SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; +} + +#ifdef _WIN64 // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store inline bool load_system_certs_on_windows(X509_STORE *store) { @@ -5308,8 +6218,8 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { return result; } -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#if TARGET_OS_OSX +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) template using CFObjectPtr = std::unique_ptr::type, void (*)(CFTypeRef)>; @@ -5397,11 +6307,10 @@ inline bool load_system_certs_on_macos(X509_STORE *store) { return result; } -#endif // TARGET_OS_OSX -#endif // _WIN32 +#endif // _WIN64 #endif // CPPHTTPLIB_OPENSSL_SUPPORT -#ifdef _WIN32 +#ifdef _WIN64 class WSInit { public: WSInit() { @@ -5419,74 +6328,13 @@ class WSInit { static WSInit wsinit_; #endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline std::pair make_digest_authentication_header( - const Request &req, const std::map &auth, - size_t cnonce_count, const std::string &cnonce, const std::string &username, - const std::string &password, bool is_proxy = false) { - std::string nc; - { - std::stringstream ss; - ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; - nc = ss.str(); - } - - std::string qop; - if (auth.find("qop") != auth.end()) { - qop = auth.at("qop"); - if (qop.find("auth-int") != std::string::npos) { - qop = "auth-int"; - } else if (qop.find("auth") != std::string::npos) { - qop = "auth"; - } else { - qop.clear(); - } - } - - std::string algo = "MD5"; - if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } - - std::string response; - { - auto H = algo == "SHA-256" ? detail::SHA_256 - : algo == "SHA-512" ? detail::SHA_512 - : detail::MD5; - - auto A1 = username + ":" + auth.at("realm") + ":" + password; - - auto A2 = req.method + ":" + req.path; - if (qop == "auth-int") { A2 += ":" + H(req.body); } - - if (qop.empty()) { - response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); - } else { - response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + - ":" + qop + ":" + H(A2)); - } - } - - auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; - - auto field = "Digest username=\"" + username + "\", realm=\"" + - auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + - "\", uri=\"" + req.path + "\", algorithm=" + algo + - (qop.empty() ? ", response=\"" - : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + - cnonce + "\", response=\"") + - response + "\"" + - (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); - - auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); -} -#endif - inline bool parse_www_authenticate(const Response &res, std::map &auth, bool is_proxy) { auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; if (res.has_header(auth_key)) { - static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + thread_local auto re = + std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); auto s = res.get_header_value(auth_key); auto pos = s.find(' '); if (pos != std::string::npos) { @@ -5547,7 +6395,8 @@ inline void hosted_at(const std::string &hostname, hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { + if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints, + &result, 0)) { #if defined __linux__ && !defined __ANDROID__ res_init(); #endif @@ -5567,31 +6416,119 @@ inline void hosted_at(const std::string &hostname, } } -inline std::string append_query_params(const std::string &path, - const Params ¶ms) { - std::string path_with_query = path; - const static std::regex re("[^?]+\\?.*"); - auto delm = std::regex_match(path, re) ? '&' : '?'; - path_with_query += delm + detail::params_to_query_str(params); - return path_with_query; -} +inline std::string encode_uri_component(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; -// Header utilities -inline std::pair -make_range_header(const Ranges &ranges) { - std::string field = "bytes="; - auto i = 0; - for (const auto &r : ranges) { - if (i != 0) { field += ", "; } - if (r.first != -1) { field += std::to_string(r.first); } - field += '-'; - if (r.second != -1) { field += std::to_string(r.second); } - i++; + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } } - return std::make_pair("Range", std::move(field)); + + return escaped.str(); } -inline std::pair +inline std::string encode_uri(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || + c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string decode_uri_component(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +inline std::string decode_uri(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +[[deprecated("Use encode_uri_component instead")]] +inline std::string encode_query_param(const std::string &value) { + return encode_uri_component(value); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + thread_local const std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair +make_range_header(const Ranges &ranges) { + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair make_basic_authentication_header(const std::string &username, const std::string &password, bool is_proxy) { auto field = "Basic " + detail::base64_encode(username + ":" + password); @@ -5624,11 +6561,30 @@ inline size_t Request::get_header_value_count(const std::string &key) const { inline void Request::set_header(const std::string &key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { headers.emplace(key, val); } } +inline bool Request::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Request::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + inline bool Request::has_param(const std::string &key) const { return params.find(key) != params.end(); } @@ -5652,19 +6608,47 @@ inline bool Request::is_multipart_form_data() const { return !content_type.rfind("multipart/form-data", 0); } -inline bool Request::has_file(const std::string &key) const { - return files.find(key) != files.end(); +// Multipart FormData implementation +inline std::string MultipartFormData::get_field(const std::string &key, + size_t id) const { + auto rng = fields.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.content; } + return std::string(); } -inline MultipartFormData Request::get_file_value(const std::string &key) const { - auto it = files.find(key); - if (it != files.end()) { return it->second; } - return MultipartFormData(); +inline std::vector +MultipartFormData::get_fields(const std::string &key) const { + std::vector values; + auto rng = fields.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second.content); + } + return values; +} + +inline bool MultipartFormData::has_field(const std::string &key) const { + return fields.find(key) != fields.end(); +} + +inline size_t MultipartFormData::get_field_count(const std::string &key) const { + auto r = fields.equal_range(key); + return static_cast(std::distance(r.first, r.second)); } -inline std::vector -Request::get_file_values(const std::string &key) const { - std::vector values; +inline FormData MultipartFormData::get_file(const std::string &key, + size_t id) const { + auto rng = files.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return FormData(); +} + +inline std::vector +MultipartFormData::get_files(const std::string &key) const { + std::vector values; auto rng = files.equal_range(key); for (auto it = rng.first; it != rng.second; it++) { values.push_back(it->second); @@ -5672,6 +6656,15 @@ Request::get_file_values(const std::string &key) const { return values; } +inline bool MultipartFormData::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline size_t MultipartFormData::get_file_count(const std::string &key) const { + auto r = files.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + // Response implementation inline bool Response::has_header(const std::string &key) const { return headers.find(key) != headers.end(); @@ -5690,13 +6683,31 @@ inline size_t Response::get_header_value_count(const std::string &key) const { inline void Response::set_header(const std::string &key, const std::string &val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { headers.emplace(key, val); } } +inline bool Response::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Response::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Response::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} inline void Response::set_redirect(const std::string &url, int stat) { - if (!detail::has_crlf(url)) { + if (detail::fields::is_field_value(url)) { set_header("Location", url); if (300 <= stat && stat < 400) { this->status = stat; @@ -5797,29 +6808,60 @@ inline ssize_t Stream::write(const std::string &s) { namespace detail { +inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, + time_t timeout_sec, time_t timeout_usec, + time_t &actual_timeout_sec, + time_t &actual_timeout_usec) { + auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000); + + 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; +} + // Socket stream implementation -inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec) +inline SocketStream::SocketStream( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time_(start_time), + read_buff_(read_buff_size_, 0) {} inline SocketStream::~SocketStream() = default; inline bool SocketStream::is_readable() const { - return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + return read_buff_off_ < read_buff_content_size_; } -inline bool SocketStream::is_writable() const { +inline bool SocketStream::wait_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; +} + +inline bool SocketStream::wait_writable() const { return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && is_socket_alive(sock_); } inline ssize_t SocketStream::read(char *ptr, size_t size) { -#ifdef _WIN32 +#ifdef _WIN64 size = (std::min)(size, static_cast((std::numeric_limits::max)())); #else @@ -5840,7 +6882,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) { } } - if (!is_readable()) { return -1; } + if (!wait_readable()) { return -1; } read_buff_off_ = 0; read_buff_content_size_ = 0; @@ -5865,9 +6907,9 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) { } inline ssize_t SocketStream::write(const char *ptr, size_t size) { - if (!is_writable()) { return -1; } + if (!wait_writable()) { return -1; } -#if defined(_WIN32) && !defined(_WIN64) +#if defined(_WIN64) && !defined(_WIN64) size = (std::min)(size, static_cast((std::numeric_limits::max)())); #endif @@ -5887,10 +6929,18 @@ inline void SocketStream::get_local_ip_and_port(std::string &ip, 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_) + .count(); +} + // Buffer stream implementation inline bool BufferStream::is_readable() const { return true; } -inline bool BufferStream::is_writable() const { return true; } +inline bool BufferStream::wait_readable() const { return true; } + +inline bool BufferStream::wait_writable() const { return true; } inline ssize_t BufferStream::read(char *ptr, size_t size) { #if defined(_MSC_VER) && _MSC_VER < 1910 @@ -5915,10 +6965,13 @@ inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, inline socket_t BufferStream::socket() const { return 0; } +inline time_t BufferStream::duration() const { return 0; } + inline const std::string &BufferStream::get_buffer() const { return buffer; } -inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { - static constexpr char marker[] = "/:"; +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) + : MatcherBase(pattern) { + constexpr const char marker[] = "/:"; // One past the last ending position of a path param substring std::size_t last_param_end = 0; @@ -5939,7 +6992,7 @@ inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { static_fragments_.push_back( pattern.substr(last_param_end, marker_pos - last_param_end + 1)); - const auto param_name_start = marker_pos + 2; + const auto param_name_start = marker_pos + str_len(marker); auto sep_pos = pattern.find(separator, param_name_start); if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } @@ -6019,7 +7072,7 @@ inline bool RegexMatcher::match(Request &request) const { inline Server::Server() : new_task_queue( [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { -#ifndef _WIN32 +#ifndef _WIN64 signal(SIGPIPE, SIG_IGN); #endif } @@ -6168,11 +7221,21 @@ inline Server &Server::set_post_routing_handler(Handler handler) { return *this; } +inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) { + pre_request_handler_ = std::move(handler); + return *this; +} + inline Server &Server::set_logger(Logger logger) { logger_ = std::move(logger); return *this; } +inline Server &Server::set_pre_compression_logger(Logger logger) { + pre_compression_logger_ = std::move(logger); + return *this; +} + inline Server & Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); @@ -6246,12 +7309,12 @@ inline Server &Server::set_payload_max_length(size_t length) { inline bool Server::bind_to_port(const std::string &host, int port, int socket_flags) { auto ret = bind_internal(host, port, socket_flags); - if (ret == -1) { is_decommisioned = true; } + if (ret == -1) { is_decommissioned = true; } return ret >= 0; } inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { auto ret = bind_internal(host, 0, socket_flags); - if (ret == -1) { is_decommisioned = true; } + if (ret == -1) { is_decommissioned = true; } return ret; } @@ -6265,7 +7328,7 @@ inline bool Server::listen(const std::string &host, int port, inline bool Server::is_running() const { return is_running_; } inline void Server::wait_until_ready() const { - while (!is_running_ && !is_decommisioned) { + while (!is_running_ && !is_decommissioned) { std::this_thread::sleep_for(std::chrono::milliseconds{1}); } } @@ -6277,10 +7340,10 @@ inline void Server::stop() { detail::shutdown_socket(sock); detail::close_socket(sock); } - is_decommisioned = false; + is_decommissioned = false; } -inline void Server::decommission() { is_decommisioned = true; } +inline void Server::decommission() { is_decommissioned = true; } inline bool Server::parse_request_line(const char *s, Request &req) const { auto len = strlen(s); @@ -6303,7 +7366,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) const { if (count != 3) { return false; } } - static const std::set methods{ + thread_local const std::set methods{ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; @@ -6323,7 +7386,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) const { detail::divide(req.target, '?', [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, std::size_t rhs_size) { - req.path = detail::decode_url( + req.path = detail::decode_path( std::string(lhs_data, lhs_size), false); detail::parse_query_text(rhs_data, rhs_size, req.params); }); @@ -6362,7 +7425,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } // Prepare additional headers - if (close_connection || stricmp(req.get_header_value("Connection").c_str(),"close")==0) { + if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); } else { std::string s = "timeout="; @@ -6457,6 +7520,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req, } else if (type == detail::EncodingType::Brotli) { #ifdef CPPHTTPLIB_BROTLI_SUPPORT compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique(); #endif } else { compressor = detail::make_unique(); @@ -6473,8 +7540,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req, } inline bool Server::read_content(Stream &strm, Request &req, Response &res) { - MultipartFormDataMap::iterator cur; - auto file_count = 0; + FormFields::iterator cur_field; + FormFiles::iterator cur_file; + auto is_text_field = false; + size_t count = 0; if (read_content_core( strm, req, res, // Regular @@ -6483,18 +7552,32 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) { req.body.append(buf, n); return true; }, - // Multipart - [&](const MultipartFormData &file) { - if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + // Multipart FormData + [&](const FormData &file) { + if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { return false; } - cur = req.files.emplace(file.name, file); + + if (file.filename.empty()) { + cur_field = req.form.fields.emplace( + file.name, FormField{file.name, file.content, file.headers}); + is_text_field = true; + } else { + cur_file = req.form.files.emplace(file.name, file); + is_text_field = false; + } return true; }, [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { return false; } - content.append(buf, n); + if (is_text_field) { + auto &content = cur_field->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } else { + auto &content = cur_file->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } return true; })) { const auto &content_type = req.get_header_value("Content-Type"); @@ -6512,19 +7595,16 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) { inline bool Server::read_content_with_content_receiver( Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) { + FormDataHeader multipart_header, ContentReceiver multipart_receiver) { return read_content_core(strm, req, res, std::move(receiver), std::move(multipart_header), std::move(multipart_receiver)); } -inline bool -Server::read_content_core(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) const { - detail::MultipartFormDataParser multipart_form_data_parser; +inline bool Server::read_content_core( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) const { + detail::FormDataParser multipart_form_data_parser; ContentReceiverWithProgress out; if (req.is_multipart_form_data()) { @@ -6536,24 +7616,13 @@ Server::read_content_core(Stream &strm, Request &req, Response &res, } multipart_form_data_parser.set_boundary(std::move(boundary)); - out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { - /* For debug - size_t pos = 0; - while (pos < n) { - auto read_size = (std::min)(1, n - pos); - auto ret = multipart_form_data_parser.parse( - buf + pos, read_size, multipart_receiver, multipart_header); - if (!ret) { return false; } - pos += read_size; - } - return true; - */ - return multipart_form_data_parser.parse(buf, n, multipart_receiver, - multipart_header); + out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { + return multipart_form_data_parser.parse(buf, n, multipart_header, + multipart_receiver); }; } else { - out = [receiver](const char *buf, size_t n, uint64_t /*off*/, - uint64_t /*len*/) { return receiver(buf, n); }; + out = [receiver](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { return receiver(buf, n); }; } if (req.method == "DELETE" && !req.has_header("Content-Length")) { @@ -6575,8 +7644,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)) { @@ -6609,7 +7677,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); } @@ -6639,7 +7707,7 @@ Server::create_server_socket(const std::string &host, int port, inline int Server::bind_internal(const std::string &host, int port, int socket_flags) { - if (is_decommisioned) { return -1; } + if (is_decommissioned) { return -1; } if (!is_valid()) { return -1; } @@ -6666,7 +7734,7 @@ inline int Server::bind_internal(const std::string &host, int port, } inline bool Server::listen_internal() { - if (is_decommisioned) { return false; } + if (is_decommissioned) { return false; } auto ret = true; is_running_ = true; @@ -6676,7 +7744,7 @@ inline bool Server::listen_internal() { std::unique_ptr task_queue(new_task_queue()); while (svr_sock_ != INVALID_SOCKET) { -#ifndef _WIN32 +#ifndef _WIN64 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { #endif auto val = detail::select_read(svr_sock_, idle_interval_sec_, @@ -6685,12 +7753,12 @@ inline bool Server::listen_internal() { task_queue->on_idle(); continue; } -#ifndef _WIN32 +#ifndef _WIN64 } #endif -#if defined _WIN32 - // sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT, +#if defined _WIN64 + // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT, // OVERLAPPED socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); #elif defined SOCK_CLOEXEC @@ -6717,35 +7785,10 @@ inline bool Server::listen_internal() { break; } - { -#ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec_ * 1000 + - read_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec_); - tv.tv_usec = static_cast(read_timeout_usec_); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } - { - -#ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec_ * 1000 + - write_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec_); - tv.tv_usec = static_cast(write_timeout_usec_); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, + read_timeout_sec_, read_timeout_usec_); + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO, + write_timeout_sec_, write_timeout_usec_); if (!task_queue->enqueue( [this, sock]() { process_and_close_socket(sock); })) { @@ -6757,7 +7800,7 @@ inline bool Server::listen_internal() { task_queue->shutdown(); } - is_decommisioned = !ret; + is_decommissioned = !ret; return ret; } @@ -6768,9 +7811,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; } @@ -6782,7 +7824,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { return read_content_with_content_receiver( strm, req, res, std::move(receiver), nullptr, nullptr); }, - [&](MultipartContentHeader header, ContentReceiver receiver) { + [&](FormDataHeader header, ContentReceiver receiver) { return read_content_with_content_receiver(strm, req, res, nullptr, std::move(header), std::move(receiver)); @@ -6845,7 +7887,11 @@ inline bool Server::dispatch_request(Request &req, Response &res, const auto &handler = x.second; if (matcher->match(req)) { - handler(req, res); + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res); + } return true; } } @@ -6897,6 +7943,8 @@ inline void Server::apply_ranges(const Request &req, Response &res, res.set_header("Content-Encoding", "gzip"); } else if (type == detail::EncodingType::Brotli) { res.set_header("Content-Encoding", "br"); + } else if (type == detail::EncodingType::Zstd) { + res.set_header("Content-Encoding", "zstd"); } } } @@ -6924,6 +7972,8 @@ inline void Server::apply_ranges(const Request &req, Response &res, } if (type != detail::EncodingType::None) { + if (pre_compression_logger_) { pre_compression_logger_(req, res); } + std::unique_ptr compressor; std::string content_encoding; @@ -6936,6 +7986,11 @@ inline void Server::apply_ranges(const Request &req, Response &res, #ifdef CPPHTTPLIB_BROTLI_SUPPORT compressor = detail::make_unique(); content_encoding = "br"; +#endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique(); + content_encoding = "zstd"; #endif } @@ -6965,7 +8020,11 @@ inline bool Server::dispatch_request_for_content_reader( const auto &handler = x.second; if (matcher->match(req)) { - handler(req, res, content_reader); + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res, content_reader); + } return true; } } @@ -6991,10 +8050,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, res.version = "HTTP/1.1"; res.headers = default_headers_; -#ifdef _WIN32 - // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). -#else -#ifndef CPPHTTPLIB_USE_POLL +#ifdef __APPLE__ // Socket file descriptor exceeded FD_SETSIZE... if (strm.socket() >= FD_SETSIZE) { Headers dummy; @@ -7003,15 +8059,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr, return write_response(strm, close_connection, req, res); } #endif -#endif - - // Check if the request URI doesn't exceed the limit - if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { - Headers dummy; - detail::read_headers(strm, dummy); - res.status = StatusCode::UriTooLong_414; - return write_response(strm, close_connection, req, res); - } // Request line and headers if (!parse_request_line(line_reader.ptr(), req) || @@ -7020,7 +8067,15 @@ Server::process_request(Stream &strm, const std::string &remote_addr, return write_response(strm, close_connection, req, res); } - if (stricmp(req.get_header_value("Connection").c_str(),"close")==0) { + // Check if the request URI doesn't exceed the limit + if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { connection_closed = true; } @@ -7039,6 +8094,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr, req.set_header("LOCAL_ADDR", req.local_addr); req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + if (req.has_header("Accept")) { + const auto &accept_header = req.get_header_value("Accept"); + if (!detail::parse_accept_header(accept_header, req.accept_content_types)) { + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + } + if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { @@ -7066,6 +8129,12 @@ Server::process_request(Stream &strm, const std::string &remote_addr, } } + // Setup `is_connection_closed` method + auto sock = strm.socket(); + req.is_connection_closed = [sock]() { + return !detail::is_socket_alive(sock); + }; + // Routing auto routed = false; #ifdef CPPHTTPLIB_NO_EXCEPTIONS @@ -7191,6 +8260,16 @@ inline ClientImpl::ClientImpl(const std::string &host, int port, client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} inline ClientImpl::~ClientImpl() { + // Wait until all the requests in flight are handled. + size_t retry_count = 10; + while (retry_count-- > 0) { + { + std::lock_guard guard(socket_mutex_); + if (socket_requests_in_flight_ == 0) { break; } + } + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } + std::lock_guard guard(socket_mutex_); shutdown_socket(socket_); close_socket(socket_); @@ -7206,6 +8285,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { read_timeout_usec_ = rhs.read_timeout_usec_; write_timeout_sec_ = rhs.write_timeout_sec_; write_timeout_usec_ = rhs.write_timeout_usec_; + max_timeout_msec_ = rhs.max_timeout_msec_; basic_auth_username_ = rhs.basic_auth_username_; basic_auth_password_ = rhs.basic_auth_password_; bearer_token_auth_token_ = rhs.bearer_token_auth_token_; @@ -7215,7 +8295,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { #endif keep_alive_ = rhs.keep_alive_; follow_location_ = rhs.follow_location_; - url_encode_ = rhs.url_encode_; + path_encode_ = rhs.path_encode_; address_family_ = rhs.address_family_; tcp_nodelay_ = rhs.tcp_nodelay_; ipv6_v6only_ = rhs.ipv6_v6only_; @@ -7315,9 +8395,9 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, if (!line_reader.getline()) { return false; } #ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR - const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); #else - const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); #endif std::cmatch m; @@ -7352,25 +8432,12 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) { return ret; } -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const { - detail::set_nonblocking(socket_.sock, true); - auto se = detail::scope_exit( - [&]() { detail::set_nonblocking(socket_.sock, false); }); - - char buf[1]; - return !SSL_peek(ssl, buf, 1) && - SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; -} -#endif - inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); - // Set this to false immediately - if it ever gets set to true by the end - // of the request, we know another thread instructed us to close the - // socket. + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. socket_should_be_closed_when_request_is_done_ = false; auto is_alive = false; @@ -7379,12 +8446,14 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (is_alive && is_ssl()) { - if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; } + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + is_alive = false; + } } #endif if (!is_alive) { - // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // Attempt to avoid sigpipe by shutting down non-gracefully if it seems // like the other side has already closed the connection Also, there // cannot be any requests in flight from other threads since we locked // request_mutex_, so safe to close everything immediately @@ -7404,7 +8473,8 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { auto &scli = static_cast(*this); if (!proxy_host_.empty() && proxy_port_ != -1) { auto success = false; - if (!scli.connect_with_proxy(socket_, res, success, error)) { + if (!scli.connect_with_proxy(socket_, req.start_time_, res, success, + error)) { return success; } } @@ -7450,7 +8520,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { } }); - ret = process_socket(socket_, [&](Stream &strm) { + ret = process_socket(socket_, req.start_time_, [&](Stream &strm) { return handle_request(strm, req, res, close_connection, error); }); @@ -7470,7 +8540,12 @@ inline Result ClientImpl::send_(Request &&req) { auto res = detail::make_unique(); auto error = Error::Success; auto ret = send(req, *res, error); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers), + last_ssl_error_, last_openssl_error_}; +#else return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +#endif } inline bool ClientImpl::handle_request(Stream &strm, Request &req, @@ -7497,7 +8572,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req, if (!ret) { return false; } - if (stricmp(req.get_header_value("Connection").c_str(),"close")==0 || + if (res.get_header_value("Connection") == "close" || (res.version == "HTTP/1.0" && res.reason != "Connection established")) { // TODO this requires a not-entirely-obvious chain of calls to be correct // for this to be safe. @@ -7559,7 +8634,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { auto location = res.get_header_value("location"); if (location.empty()) { return false; } - const static std::regex re( + thread_local const std::regex re( R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); std::smatch m; @@ -7585,26 +8660,152 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } - auto path = detail::decode_url(https://github.com/next_path,%20true) + next_query; + auto path = detail::decode_path(next_path, true) + next_query; + // Same host redirect - use current client if (next_scheme == scheme && next_host == host_ && next_port == port_) { return detail::redirect(*this, req, res, path, location, error); - } else { - if (next_scheme == "https") { + } + + // Cross-host/scheme redirect - create new client with robust setup + return create_redirect_client(next_scheme, next_host, next_port, req, res, + path, location, error); +} + +// New method for robust redirect client creation +inline bool ClientImpl::create_redirect_client( + const std::string &scheme, const std::string &host, int port, Request &req, + Response &res, const std::string &path, const std::string &location, + Error &error) { + // Determine if we need SSL + auto need_ssl = (scheme == "https"); + + // Clean up request headers that are host/client specific + // Remove headers that should not be carried over to new host + auto headers_to_remove = + std::vector{"Host", "Proxy-Authorization", "Authorization"}; + + for (const auto &header_name : headers_to_remove) { + auto it = req.headers.find(header_name); + while (it != req.headers.end()) { + it = req.headers.erase(it); + it = req.headers.find(header_name); + } + } + + // Create appropriate client type and handle redirect + if (need_ssl) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - SSLClient cli(next_host, next_port); - cli.copy_settings(*this); - if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } - return detail::redirect(cli, req, res, path, location, error); + // Create SSL client for HTTPS redirect + SSLClient redirect_client(host, port); + + // Setup basic client configuration first + setup_redirect_client(redirect_client); + + // SSL-specific configuration for proxy environments + if (!proxy_host_.empty() && proxy_port_ != -1) { + // Critical: Disable SSL verification for proxy environments + redirect_client.enable_server_certificate_verification(false); + redirect_client.enable_server_hostname_verification(false); + } else { + // For direct SSL connections, copy SSL verification settings + redirect_client.enable_server_certificate_verification( + server_certificate_verification_); + redirect_client.enable_server_hostname_verification( + server_hostname_verification_); + } + + // Handle CA certificate store and paths if available + if (ca_cert_store_) { redirect_client.set_ca_cert_store(ca_cert_store_); } + if (!ca_cert_file_path_.empty()) { + redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_); + } + + // Client certificates are set through constructor for SSLClient + // NOTE: SSLClient constructor already takes client_cert_path and + // client_key_path so we need to create it properly if client certs are + // needed + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); #else - return false; + // SSL not supported - set appropriate error + error = Error::SSLConnection; + return false; #endif - } else { - ClientImpl cli(next_host, next_port); - cli.copy_settings(*this); - return detail::redirect(cli, req, res, path, location, error); + } else { + // HTTP redirect + ClientImpl redirect_client(host, port); + + // Setup client with robust configuration + setup_redirect_client(redirect_client); + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); + } +} + +// New method for robust client setup (based on basic_manual_redirect.cpp logic) +template +inline void ClientImpl::setup_redirect_client(ClientType &client) { + // Copy basic settings first + client.set_connection_timeout(connection_timeout_sec_); + client.set_read_timeout(read_timeout_sec_, read_timeout_usec_); + client.set_write_timeout(write_timeout_sec_, write_timeout_usec_); + client.set_keep_alive(keep_alive_); + client.set_follow_location( + true); // Enable redirects to handle multi-step redirects + client.set_path_encode(path_encode_); + client.set_compress(compress_); + client.set_decompress(decompress_); + + // Copy authentication settings BEFORE proxy setup + if (!basic_auth_username_.empty()) { + client.set_basic_auth(basic_auth_username_, basic_auth_password_); + } + if (!bearer_token_auth_token_.empty()) { + client.set_bearer_token_auth(bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!digest_auth_username_.empty()) { + client.set_digest_auth(digest_auth_username_, digest_auth_password_); + } +#endif + + // Setup proxy configuration (CRITICAL ORDER - proxy must be set + // before proxy auth) + if (!proxy_host_.empty() && proxy_port_ != -1) { + // First set proxy host and port + client.set_proxy(proxy_host_, proxy_port_); + + // Then set proxy authentication (order matters!) + if (!proxy_basic_auth_username_.empty()) { + client.set_proxy_basic_auth(proxy_basic_auth_username_, + proxy_basic_auth_password_); } + if (!proxy_bearer_token_auth_token_.empty()) { + client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!proxy_digest_auth_username_.empty()) { + client.set_proxy_digest_auth(proxy_digest_auth_username_, + proxy_digest_auth_password_); + } +#endif } + + // Copy network and socket settings + client.set_address_family(address_family_); + client.set_tcp_nodelay(tcp_nodelay_); + client.set_ipv6_v6only(ipv6_v6only_); + if (socket_options_) { client.set_socket_options(socket_options_); } + if (!interface_.empty()) { client.set_interface(interface_); } + + // Copy logging and headers + if (logger_) { client.set_logger(logger_); } + + // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers + // Each new client should generate its own headers based on its target host } inline bool ClientImpl::write_content_with_provider(Stream &strm, @@ -7627,8 +8828,9 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm, return detail::write_content_chunked(strm, req.content_provider_, is_shutting_down, *compressor, error); } else { - return detail::write_content(strm, req.content_provider_, 0, - req.content_length_, is_shutting_down, error); + return detail::write_content_with_progress( + strm, req.content_provider_, 0, req.content_length_, is_shutting_down, + req.upload_progress, error); } } @@ -7642,7 +8844,11 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, } if (!req.has_header("Host")) { - if (is_ssl()) { + // For Unix socket connections, use "localhost" as Host header (similar to + // curl behavior) + if (address_family_ == AF_UNIX) { + req.set_header("Host", "localhost"); + } else if (is_ssl()) { if (port_ == 443) { req.set_header("Host", host_); } else { @@ -7668,6 +8874,10 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, #ifdef CPPHTTPLIB_ZLIB_SUPPORT if (!accept_encoding.empty()) { accept_encoding += ", "; } accept_encoding += "gzip, deflate"; +#endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "zstd"; #endif req.set_header("Accept-Encoding", accept_encoding); } @@ -7743,7 +8953,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, : append_query_params(req.path, req.params); const auto &path = - url_encode_ ? detail::encode_url(https://github.com/path_with_query) : path_with_query; + path_encode_ ? detail::encode_path(path_with_query) : path_with_query; detail::write_request_line(bstrm, req.method, path); @@ -7762,9 +8972,29 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, return write_content_with_provider(strm, req, error); } - if (!detail::write_data(strm, req.body.data(), req.body.size())) { - error = Error::Write; - return false; + if (req.upload_progress) { + auto body_size = req.body.size(); + size_t written = 0; + auto data = req.body.data(); + + while (written < body_size) { + size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written); + if (!detail::write_data(strm, data + written, to_write)) { + error = Error::Write; + return false; + } + written += to_write; + + if (!req.upload_progress(written, body_size)) { + error = Error::Canceled; + return false; + } + } + } else { + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } } return true; @@ -7853,12 +9083,15 @@ inline Result ClientImpl::send_with_content_provider( const std::string &method, const std::string &path, const Headers &headers, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, UploadProgress progress) { Request req; req.method = method; req.headers = headers; req.path = path; - req.progress = progress; + req.upload_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } auto error = Error::Success; @@ -7866,7 +9099,12 @@ inline Result ClientImpl::send_with_content_provider( req, body, content_length, std::move(content_provider), std::move(content_provider_without_length), content_type, error); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{std::move(res), error, std::move(req.headers), last_ssl_error_, + last_openssl_error_}; +#else return Result{std::move(res), error, std::move(req.headers)}; +#endif } inline std::string @@ -7885,7 +9123,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, if (is_ssl()) { auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; if (!is_proxy_enabled) { - if (is_ssl_peer_could_be_closed(socket_.ssl)) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { error = Error::SSLPeerCouldBeClosed_; return false; } @@ -7917,23 +9155,23 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, auto out = req.content_receiver ? static_cast( - [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + [&](const char *buf, size_t n, size_t off, size_t len) { if (redirect) { return true; } auto ret = req.content_receiver(buf, n, off, len); if (!ret) { error = Error::Canceled; } return ret; }) : static_cast( - [&](const char *buf, size_t n, uint64_t /*off*/, - uint64_t /*len*/) { + [&](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { assert(res.body.size() + n <= res.body.max_size()); res.body.append(buf, n); return true; }); - auto progress = [&](uint64_t current, uint64_t total) { - if (!req.progress || redirect) { return true; } - auto ret = req.progress(current, total); + auto progress = [&](size_t current, size_t total) { + if (!req.download_progress || redirect) { return true; } + auto ret = req.download_progress(current, total); if (!ret) { error = Error::Canceled; } return ret; }; @@ -7967,12 +9205,12 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, } inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( - const std::string &boundary, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) const { + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const { size_t cur_item = 0; size_t cur_start = 0; - // cur_item and cur_start are copied to within the std::function and - // maintain state between successive calls + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls return [&, cur_item, cur_start](size_t offset, DataSink &sink) mutable -> bool { if (!offset && !items.empty()) { @@ -8010,41 +9248,55 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( }; } -inline bool -ClientImpl::process_socket(const Socket &socket, - std::function callback) { +inline bool ClientImpl::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { return detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, std::move(callback)); + write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback)); } inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const std::string &path, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, + DownloadProgress progress) { return Get(path, Headers(), std::move(progress)); } +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + DownloadProgress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} + inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - Progress progress) { + DownloadProgress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; - req.progress = std::move(progress); + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } @@ -8052,7 +9304,7 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, Headers(), std::move(response_handler), std::move(content_receiver), std::move(progress)); } @@ -8060,7 +9312,7 @@ inline Result ClientImpl::Get(const std::string &path, inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { Request req; req.method = "GET"; req.path = path; @@ -8068,58 +9320,30 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, req.response_handler = std::move(response_handler); req.content_receiver = [content_receiver](const char *data, size_t data_length, - uint64_t /*offset*/, uint64_t /*total_length*/) { + size_t /*offset*/, size_t /*total_length*/) { return content_receiver(data, data_length); }; - req.progress = std::move(progress); + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, - Progress progress) { - if (params.empty()) { return Get(path, std::move(progress)); } - - std::string path_with_query = append_query_params(path, params); - return Get(path_with_query, std::move(progress)); -} - -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress) { - if (params.empty()) { return Get(path, headers); } - - std::string path_with_query = append_query_params(path, params); - return Get(path_with_query, headers, std::move(progress)); -} - -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, - ContentReceiver content_receiver, - Progress progress) { - return Get(path, params, Headers{}, nullptr, std::move(content_receiver), - std::move(progress)); -} - inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, params, headers, nullptr, std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, - ResponseHandler response_handler, - ContentReceiver content_receiver, - Progress progress) { - return Get(path, params, Headers{}, std::move(response_handler), - std::move(content_receiver), std::move(progress)); -} - inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { if (params.empty()) { return Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); @@ -8140,6 +9364,9 @@ inline Result ClientImpl::Head(const std::string &path, req.method = "HEAD"; req.headers = headers; req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8155,91 +9382,63 @@ inline Result ClientImpl::Post(const std::string &path, inline Result ClientImpl::Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return Post(path, Headers(), body, content_length, content_type, nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, const std::string &content_type, - Progress progress) { - return send_with_content_provider("POST", path, headers, body, content_length, - nullptr, nullptr, content_type, progress); + UploadProgress progress) { + return Post(path, Headers(), body, content_length, content_type, progress); } inline Result ClientImpl::Post(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return Post(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, - Progress progress) { - return send_with_content_provider("POST", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - progress); -} - inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { return Post(path, Headers(), params); } inline Result ClientImpl::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return Post(path, Headers(), content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result ClientImpl::Post(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Post(path, Headers(), std::move(content_provider), content_type); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, - content_length, std::move(content_provider), - nullptr, content_type, nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, - std::move(content_provider), content_type, - nullptr); + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), std::move(content_provider), content_type, + progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { + const Params ¶ms) { auto query = detail::params_to_query_str(params); - return Post(path, headers, query, "application/x-www-form-urlencoded", - progress); + return Post(path, headers, query, "application/x-www-form-urlencoded"); } inline Result ClientImpl::Post(const std::string &path, - const MultipartFormDataItems &items) { - return Post(path, Headers(), items); + const UploadFormDataItems &items, + UploadProgress progress) { + return Post(path, Headers(), items, progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { + const UploadFormDataItems &items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Post(path, headers, body, content_type); + return Post(path, headers, body, content_type, progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } @@ -8247,113 +9446,305 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Post(path, headers, body, content_type); + return Post(path, headers, body, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); } -inline Result -ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); return send_with_content_provider( "POST", path, headers, nullptr, 0, nullptr, get_multipart_content_provider(boundary, items, provider_items), - content_type, nullptr); + content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "POST"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } inline Result ClientImpl::Put(const std::string &path) { return Put(path, std::string(), std::string()); } +inline Result ClientImpl::Put(const std::string &path, const Headers &headers) { + return Put(path, headers, nullptr, 0, std::string()); +} + inline Result ClientImpl::Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return Put(path, Headers(), body, content_length, content_type); + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), body, content_length, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), std::move(content_provider), content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Put(path, Headers(), items, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type, progress); } inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return send_with_content_provider("PUT", path, headers, body, content_length, nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const std::string &body, - const std::string &content_type, - Progress progress) { - return Put(path, Headers(), body, content_type, progress); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PUT"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); +} + +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, - Progress progress) { - return send_with_content_provider("PUT", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - progress); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + UploadProgress progress) { + return Patch(path, headers, nullptr, 0, std::string(), progress); } -inline Result ClientImpl::Put(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return Put(path, Headers(), content_length, std::move(content_provider), - content_type); +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), body, content_length, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Put(path, Headers(), std::move(content_provider), content_type); +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, - content_length, std::move(content_provider), - nullptr, content_type, nullptr); +inline Result ClientImpl::Patch(const std::string &path, const Params ¶ms) { + return Patch(path, Headers(), params); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, - std::move(content_provider), content_type, - nullptr); +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { - return Put(path, Headers(), params); +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), std::move(content_provider), content_type, + progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const Params ¶ms) { auto query = detail::params_to_query_str(params); - return Put(path, headers, query, "application/x-www-form-urlencoded", - progress); + return Patch(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Put(const std::string &path, - const MultipartFormDataItems &items) { - return Put(path, Headers(), items); +inline Result ClientImpl::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Patch(path, Headers(), items, progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Put(path, headers, body, content_type); + return Patch(path, headers, body, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } @@ -8361,124 +9752,107 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Put(path, headers, body, content_type); -} - -inline Result -ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { - const auto &boundary = detail::make_multipart_data_boundary(); - const auto &content_type = - detail::serialize_multipart_formdata_get_content_type(boundary); - return send_with_content_provider( - "PUT", path, headers, nullptr, 0, nullptr, - get_multipart_content_provider(boundary, items, provider_items), - content_type, nullptr); -} -inline Result ClientImpl::Patch(const std::string &path) { - return Patch(path, std::string(), std::string()); -} - -inline Result ClientImpl::Patch(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type, - Progress progress) { - return Patch(path, Headers(), body, content_length, content_type, progress); + return Patch(path, headers, body, content_type, progress); } inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, body, content_length, nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, - const std::string &body, - const std::string &content_type, - Progress progress) { - return Patch(path, Headers(), body, content_type, progress); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, body.data(), body.size(), nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return Patch(path, Headers(), content_length, std::move(content_provider), - content_type); -} - -inline Result ClientImpl::Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Patch(path, Headers(), std::move(content_provider), content_type); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, nullptr, content_length, std::move(content_provider), - nullptr, content_type, nullptr); + nullptr, content_type, progress); } inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, - nullptr); + progress); } -inline Result ClientImpl::Delete(const std::string &path) { - return Delete(path, Headers(), std::string(), std::string()); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PATCH", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); } -inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers) { - return Delete(path, headers, std::string(), std::string()); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PATCH"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } -inline Result ClientImpl::Delete(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type, - Progress progress) { - return Delete(path, Headers(), body, content_length, content_type, progress); +inline Result ClientImpl::Delete(const std::string &path, + DownloadProgress progress) { + return Delete(path, Headers(), std::string(), std::string(), progress); } inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, const char *body, + const Headers &headers, + DownloadProgress progress) { + return Delete(path, headers, std::string(), std::string(), progress); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { - Request req; - req.method = "DELETE"; - req.headers = headers; - req.path = path; - req.progress = progress; - - if (!content_type.empty()) { req.set_header("Content-Type", content_type); } - req.body.assign(body, content_length); - - return send_(std::move(req)); + DownloadProgress progress) { + return Delete(path, Headers(), body, content_length, content_type, progress); } inline Result ClientImpl::Delete(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return Delete(path, Headers(), body.data(), body.size(), content_type, progress); } @@ -8487,11 +9861,44 @@ inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return Delete(path, headers, body.data(), body.size(), content_type, progress); } +inline Result ClientImpl::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return Delete(path, Headers(), params, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const Params ¶ms, + DownloadProgress progress) { + auto query = detail::params_to_query_str(params); + return Delete(path, headers, query, "application/x-www-form-urlencoded", + progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type, + DownloadProgress progress) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + req.download_progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + inline Result ClientImpl::Options(const std::string &path) { return Options(path, Headers()); } @@ -8502,6 +9909,9 @@ inline Result ClientImpl::Options(const std::string &path, req.method = "OPTIONS"; req.headers = headers; req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8512,8 +9922,8 @@ inline void ClientImpl::stop() { // If there is anything ongoing right now, the ONLY thread-safe thing we can // do is to shutdown_socket, so that threads using this socket suddenly // discover they can't read/write any more and error out. Everything else - // (closing the socket, shutting ssl down) is unsafe because these actions - // are not thread-safe. + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. if (socket_requests_in_flight_ > 0) { shutdown_socket(socket_); @@ -8555,6 +9965,10 @@ inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { write_timeout_usec_ = usec; } +inline void ClientImpl::set_max_timeout(time_t msec) { + max_timeout_msec_ = msec; +} + inline void ClientImpl::set_basic_auth(const std::string &username, const std::string &password) { basic_auth_username_ = username; @@ -8577,7 +9991,7 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } -inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } +inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; } inline void ClientImpl::set_hostname_addr_map(std::map addr_map) { @@ -8680,7 +10094,7 @@ inline void ClientImpl::enable_server_hostname_verification(bool enabled) { } inline void ClientImpl::set_server_certificate_verifier( - std::function verifier) { + std::function verifier) { server_certificate_verifier_ = verifier; } #endif @@ -8733,22 +10147,13 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, // Note that it is not always possible to avoid SIGPIPE, this is merely a // best-efforts. if (shutdown_gracefully) { -#ifdef _WIN32 (void)(sock); - SSL_shutdown(ssl); -#else - timeval tv; - tv.tv_sec = 1; - tv.tv_usec = 0; - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); - - auto ret = SSL_shutdown(ssl); - while (ret == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds{100}); - ret = SSL_shutdown(ssl); + // SSL_shutdown() returns 0 on first call (indicating close_notify alert + // sent) and 1 on subsequent call (indicating close_notify alert received) + if (SSL_shutdown(ssl) == 0) { + // Expected to return 1, but even if it doesn't, we free ssl + SSL_shutdown(ssl); } -#endif } std::lock_guard guard(ctx_mutex); @@ -8758,8 +10163,8 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock, template bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, U ssl_connect_or_accept, - time_t timeout_sec, - time_t timeout_usec) { + time_t timeout_sec, time_t timeout_usec, + int *ssl_error) { auto res = 0; while ((res = ssl_connect_or_accept(ssl)) != 1) { auto err = SSL_get_error(ssl, res); @@ -8772,6 +10177,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, break; default: break; } + if (ssl_error) { *ssl_error = err; } return false; } return true; @@ -8793,56 +10199,64 @@ inline bool process_server_socket_ssl( } template -inline bool -process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, T callback) { +inline bool process_client_socket_ssl( + SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, T callback) { SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, - write_timeout_sec, write_timeout_usec); + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); return callback(strm); } -class SSLInit { -public: - SSLInit() { - OPENSSL_init_ssl( - OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); - } -}; - // SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, - time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec) +inline SSLSocketStream::SSLSocketStream( + socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec) { + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time_(start_time) { SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } inline SSLSocketStream::~SSLSocketStream() = default; inline bool SSLSocketStream::is_readable() const { - return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + return SSL_pending(ssl_) > 0; +} + +inline bool SSLSocketStream::wait_readable() const { + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; } -inline bool SSLSocketStream::is_writable() const { +inline bool SSLSocketStream::wait_writable() const { return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && - is_socket_alive(sock_); + is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_); } inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { if (SSL_pending(ssl_) > 0) { return SSL_read(ssl_, ptr, static_cast(size)); - } else if (is_readable()) { + } else if (wait_readable()) { auto ret = SSL_read(ssl_, ptr, static_cast(size)); if (ret < 0) { auto err = SSL_get_error(ssl_, ret); auto n = 1000; -#ifdef _WIN32 +#ifdef _WIN64 while (--n >= 0 && (err == SSL_ERROR_WANT_READ || (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) { @@ -8851,23 +10265,25 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { #endif if (SSL_pending(ssl_) > 0) { return SSL_read(ssl_, ptr, static_cast(size)); - } else if (is_readable()) { + } else if (wait_readable()) { std::this_thread::sleep_for(std::chrono::microseconds{10}); ret = SSL_read(ssl_, ptr, static_cast(size)); if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); } else { - return -1; + break; } } + assert(ret < 0); } return ret; + } else { + return -1; } - return -1; } inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { - if (is_writable()) { + if (wait_writable()) { auto handle_size = static_cast( std::min(size, (std::numeric_limits::max)())); @@ -8875,22 +10291,23 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { if (ret < 0) { auto err = SSL_get_error(ssl_, ret); auto n = 1000; -#ifdef _WIN32 +#ifdef _WIN64 while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) { #else while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { #endif - if (is_writable()) { + if (wait_writable()) { std::this_thread::sleep_for(std::chrono::microseconds{10}); ret = SSL_write(ssl_, ptr, static_cast(handle_size)); if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); } else { - return -1; + break; } } + assert(ret < 0); } return ret; } @@ -8909,7 +10326,11 @@ inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, inline socket_t SSLSocketStream::socket() const { return sock_; } -static SSLInit sslinit_; +inline time_t SSLSocketStream::duration() const { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_) + .count(); +} } // namespace detail @@ -8937,6 +10358,7 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1 || SSL_CTX_check_private_key(ctx_) != 1) { + last_ssl_error_ = static_cast(ERR_get_error()); SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { @@ -9010,7 +10432,8 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { sock, ctx_, ctx_mutex_, [&](SSL *ssl2) { return detail::ssl_connect_or_accept_nonblocking( - sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_, + &last_ssl_error_); }, [](SSL * /*ssl2*/) { return true; }); @@ -9078,6 +10501,7 @@ inline SSLClient::SSLClient(const std::string &host, int port, SSL_FILETYPE_PEM) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), SSL_FILETYPE_PEM) != 1) { + last_openssl_error_ = ERR_get_error(); SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -9104,6 +10528,7 @@ inline SSLClient::SSLClient(const std::string &host, int port, if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + last_openssl_error_ = ERR_get_error(); SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -9124,8 +10549,7 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { if (ca_cert_store) { if (ctx_) { if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { - // Free memory allocated for old cert and use new store - // `ca_cert_store` + // Free memory allocated for old cert and use new store `ca_cert_store` SSL_CTX_set_cert_store(ctx_, ca_cert_store); } } else { @@ -9149,18 +10573,23 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { return is_valid() && ClientImpl::create_and_connect_socket(socket, error); } -// Assumes that socket_mutex_ is locked and that there are no requests in -// flight -inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, - bool &success, Error &error) { +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy( + Socket &socket, + std::chrono::time_point start_time, + Response &res, bool &success, Error &error) { success = true; Response proxy_res; if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { Request req2; req2.method = "CONNECT"; req2.path = host_and_port_; + if (max_timeout_msec_ > 0) { + req2.start_time_ = std::chrono::steady_clock::now(); + } return process_request(strm, req2, proxy_res, false, error); })) { // Thread-safe to close everything because we are assuming there are no @@ -9177,10 +10606,23 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, !proxy_digest_auth_password_.empty()) { std::map auth; if (detail::parse_www_authenticate(proxy_res, auth, true)) { + // Close the current socket and create a new one for the authenticated + // request + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + + // Create a new socket for the authenticated CONNECT request + if (!create_and_connect_socket(socket, error)) { + success = false; + return false; + } + proxy_res = Response(); if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream &strm) { Request req3; req3.method = "CONNECT"; req3.path = host_and_port_; @@ -9188,6 +10630,9 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); + if (max_timeout_msec_ > 0) { + req3.start_time_ = std::chrono::steady_clock::now(); + } return process_request(strm, req3, proxy_res, false, error); })) { // Thread-safe to close everything because we are assuming there are @@ -9227,23 +10672,24 @@ inline bool SSLClient::load_certs() { if (!ca_cert_file_path_.empty()) { if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { + last_openssl_error_ = ERR_get_error(); ret = false; } } else if (!ca_cert_dir_path_.empty()) { if (!SSL_CTX_load_verify_locations(ctx_, nullptr, ca_cert_dir_path_.c_str())) { + last_openssl_error_ = ERR_get_error(); ret = false; } } else { auto loaded = false; -#ifdef _WIN32 +#ifdef _WIN64 loaded = detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#if TARGET_OS_OSX +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); -#endif // TARGET_OS_OSX -#endif // _WIN32 +#endif // _WIN64 if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } }); @@ -9265,21 +10711,29 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { if (!detail::ssl_connect_or_accept_nonblocking( socket.sock, ssl2, SSL_connect, connection_timeout_sec_, - connection_timeout_usec_)) { + connection_timeout_usec_, &last_ssl_error_)) { error = Error::SSLConnection; return false; } if (server_certificate_verification_) { + auto verification_status = SSLVerifierResponse::NoDecisionMade; + if (server_certificate_verifier_) { - if (!server_certificate_verifier_(ssl2)) { - error = Error::SSLServerVerification; - return false; - } - } else { + verification_status = server_certificate_verifier_(ssl2); + } + + if (verification_status == SSLVerifierResponse::CertificateRejected) { + last_openssl_error_ = ERR_get_error(); + error = Error::SSLServerVerification; + return false; + } + + if (verification_status == SSLVerifierResponse::NoDecisionMade) { verify_result_ = SSL_get_verify_result(ssl2); if (verify_result_ != X509_V_OK) { + last_openssl_error_ = static_cast(verify_result_); error = Error::SSLServerVerification; return false; } @@ -9288,12 +10742,14 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { auto se = detail::scope_exit([&] { X509_free(server_cert); }); if (server_cert == nullptr) { + last_openssl_error_ = ERR_get_error(); error = Error::SSLServerVerification; return false; } if (server_hostname_verification_) { if (!verify_host(server_cert)) { + last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH; error = Error::SSLServerHostnameVerification; return false; } @@ -9343,13 +10799,15 @@ inline void SSLClient::shutdown_ssl_impl(Socket &socket, assert(socket.ssl == nullptr); } -inline bool -SSLClient::process_socket(const Socket &socket, - std::function callback) { +inline bool SSLClient::process_socket( + const Socket &socket, + std::chrono::time_point start_time, + std::function callback) { assert(socket.ssl); return detail::process_client_socket_ssl( socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, std::move(callback)); + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time, + std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -9386,8 +10844,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto type = GEN_DNS; - struct in6_addr addr6{}; - struct in_addr addr{}; + struct in6_addr addr6 = {}; + struct in_addr addr = {}; size_t addr_len = 0; #ifndef __MINGW32__ @@ -9549,63 +11007,54 @@ inline bool Client::is_valid() const { return cli_ != nullptr && cli_->is_valid(); } -inline Result Client::Get(const std::string &path, Progress progress) { +inline Result Client::Get(const std::string &path, DownloadProgress progress) { return cli_->Get(path, std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, - Progress progress) { + DownloadProgress progress) { return cli_->Get(path, headers, std::move(progress)); } inline Result Client::Get(const std::string &path, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Params ¶ms, - Progress progress) { - return cli_->Get(path, params, Headers{}, std::move(progress)); -} -inline Result Client::Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress) { + const Headers &headers, DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(progress)); } -inline Result Client::Get(const std::string &path, const Params ¶ms, - ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, params, std::move(content_receiver), - std::move(progress)); -} inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const std::string &path, const Params ¶ms, - ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, params, std::move(response_handler), - std::move(content_receiver), std::move(progress)); -} inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); } @@ -9621,219 +11070,324 @@ inline Result Client::Post(const std::string &path, const Headers &headers) { } inline Result Client::Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Post(path, body, content_length, content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, body, content_length, content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, body, content_length, content_type, progress); } inline Result Client::Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, body, content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, body, content_type, progress); } inline Result Client::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Post(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Post(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, std::move(content_provider), content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Post(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, std::move(content_provider), content_type, + progress); } inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); } inline Result Client::Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - return cli_->Post(path, headers, params, progress); + const Params ¶ms) { + return cli_->Post(path, headers, params); } inline Result Client::Post(const std::string &path, - const MultipartFormDataItems &items) { - return cli_->Post(path, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, items, progress); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, headers, items, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { - return cli_->Post(path, headers, items); + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Post(path, headers, items, boundary, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { - return cli_->Post(path, headers, items, boundary); + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Post(path, headers, items, provider_items, progress); } -inline Result -Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { - return cli_->Post(path, headers, items, provider_items); +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Post(path, headers, body, content_type, content_receiver, + progress); } + inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const Headers &headers) { + return cli_->Put(path, headers); +} inline Result Client::Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Put(path, body, content_length, content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, body, content_length, content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, body, content_length, content_type, progress); } inline Result Client::Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, body, content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, body, content_type, progress); } inline Result Client::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Put(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Put(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, std::move(content_provider), content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Put(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, std::move(content_provider), content_type, + progress); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); } inline Result Client::Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - return cli_->Put(path, headers, params, progress); + const Params ¶ms) { + return cli_->Put(path, headers, params); } inline Result Client::Put(const std::string &path, - const MultipartFormDataItems &items) { - return cli_->Put(path, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, items, progress); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, headers, items, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { - return cli_->Put(path, headers, items); + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Put(path, headers, items, boundary, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { - return cli_->Put(path, headers, items, boundary); + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Put(path, headers, items, provider_items, progress); } -inline Result -Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { - return cli_->Put(path, headers, items, provider_items); +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Put(path, headers, body, content_type, content_receiver, + progress); } + inline Result Client::Patch(const std::string &path) { return cli_->Patch(path); } +inline Result Client::Patch(const std::string &path, const Headers &headers) { + return cli_->Patch(path, headers); +} inline Result Client::Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, body, content_length, content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, headers, body, content_length, content_type, progress); } inline Result Client::Patch(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, body, content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, headers, body, content_type, progress); } inline Result Client::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Patch(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Patch(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Patch(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, std::move(content_provider), content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Patch(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Patch(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, std::move(content_provider), content_type, + progress); +} +inline Result Client::Patch(const std::string &path, const Params ¶ms) { + return cli_->Patch(path, params); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Patch(path, headers, params); +} +inline Result Client::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Patch(path, headers, items, boundary, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, provider_items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Patch(path, headers, body, content_type, content_receiver, + progress); } -inline Result Client::Delete(const std::string &path) { - return cli_->Delete(path); + +inline Result Client::Delete(const std::string &path, + DownloadProgress progress) { + return cli_->Delete(path, progress); } -inline Result Client::Delete(const std::string &path, const Headers &headers) { - return cli_->Delete(path, headers); +inline Result Client::Delete(const std::string &path, const Headers &headers, + DownloadProgress progress) { + return cli_->Delete(path, headers, progress); } inline Result Client::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, body, content_length, content_type, progress); } inline Result Client::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, headers, body, content_length, content_type, progress); } inline Result Client::Delete(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, body, content_type, progress); } inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, headers, body, content_type, progress); } +inline Result Client::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return cli_->Delete(path, params, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const Params ¶ms, DownloadProgress progress) { + return cli_->Delete(path, headers, params, progress); +} + inline Result Client::Options(const std::string &path) { return cli_->Options(path); } @@ -9912,7 +11466,12 @@ inline void Client::set_follow_location(bool on) { cli_->set_follow_location(on); } -inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } +inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); } + +[[deprecated("Use set_path_encode instead")]] +inline void Client::set_url_encode(bool on) { + cli_->set_path_encode(on); +} inline void Client::set_compress(bool on) { cli_->set_compress(on); } @@ -9949,7 +11508,7 @@ inline void Client::enable_server_hostname_verification(bool enabled) { } inline void Client::set_server_certificate_verifier( - std::function verifier) { + std::function verifier) { cli_->set_server_certificate_verifier(verifier); } #endif @@ -9993,8 +11552,4 @@ inline SSL_CTX *Client::ssl_context() const { } // namespace httplib -#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) -#undef poll -#endif - #endif // CPPHTTPLIB_HTTPLIB_H From ec22eb42148ce41b82d142d174f0e3f7c5ed4432 Mon Sep 17 00:00:00 2001 From: killvxk <86879759@qq.com> Date: Mon, 14 Jul 2025 17:56:18 +0800 Subject: [PATCH 4/5] Update httplib.h --- httplib.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/httplib.h b/httplib.h index 16aced15d1..064cfff29b 100644 --- a/httplib.h +++ b/httplib.h @@ -3427,7 +3427,7 @@ process_server_socket_core(const std::atomic &svr_sock, socket_t sock, auto ret = false; auto count = keep_alive_max_count; while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { - auto close_connection = count == 1; + auto close_connection = true; auto connection_closed = false; ret = callback(close_connection, connection_closed); if (!ret || connection_closed) { break; } @@ -7425,7 +7425,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } // Prepare additional headers - if (close_connection || req.get_header_value("Connection") == "close") { + if (close_connection || req.get_header_value("Connection") == "close" || req.get_header_value("Connection") == "Close") { res.set_header("Connection", "close"); } else { std::string s = "timeout="; @@ -8075,7 +8075,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, return write_response(strm, close_connection, req, res); } - if (req.get_header_value("Connection") == "close") { + if (req.get_header_value("Connection") == "close"||req.get_header_value("Connection") == "Close") { connection_closed = true; } @@ -8572,7 +8572,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req, if (!ret) { return false; } - if (res.get_header_value("Connection") == "close" || + if (res.get_header_value("Connection") == "close" ||req.get_header_value("Connection") == "Close"|| (res.version == "HTTP/1.0" && res.reason != "Connection established")) { // TODO this requires a not-entirely-obvious chain of calls to be correct // for this to be safe. From 09e4cc6e41b09d6ce0c539e5a0f02eab07b89e1e Mon Sep 17 00:00:00 2001 From: killvxk <86879759@qq.com> Date: Mon, 14 Jul 2025 17:57:54 +0800 Subject: [PATCH 5/5] Update README.md --- README.md | 332 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 292 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 3a866f98cc..8553114ff3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ cpp-httplib A C++11 single-file header-only cross platform HTTP/HTTPS library. -It's extremely easy to setup. Just include the **httplib.h** file in your code! +It's extremely easy to set up. Just include the **httplib.h** file in your code! > [!IMPORTANT] > This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. @@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080); #include "path/to/httplib.h" // HTTP -httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co/"); +httplib::Client cli("http://yhirose.github.io/"); // HTTPS -httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co/"); +httplib::Client cli("https://yhirose.github.io/"); auto res = cli.Get("/hi"); res->status; @@ -85,6 +85,49 @@ cli.enable_server_hostname_verification(false); > [!NOTE] > When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. +### SSL Error Handling + +When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields: + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +httplib::Client cli("https://example.com/"); + +auto res = cli.Get("/"); +if (!res) { + // Check the error type + auto err = res.error(); + + switch (err) { + case httplib::Error::SSLConnection: + std::cout << "SSL connection failed, SSL error: " + << res->ssl_error() << std::endl; + break; + + case httplib::Error::SSLLoadingCerts: + std::cout << "SSL cert loading failed, OpenSSL error: " + << std::hex << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerVerification: + std::cout << "SSL verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerHostnameVerification: + std::cout << "SSL hostname verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + default: + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } + } +} +``` + Server ------ @@ -125,6 +168,21 @@ int main(void) res.set_content(req.body, "text/plain"); }); + // If the handler takes time to finish, you can also poll the connection state + svr.Get("/task", [&](const Request& req, Response& res) { + const char * result = nullptr; + process.run(); // for example, starting an external process + while (result == nullptr) { + sleep(1); + if (req.is_connection_closed()) { + process.kill(); // kill the process + return; + } + result = process.stdout(); // != nullptr if the process finishes + } + res.set_content(result, "text/plain"); + }); + svr.Get("/stop", [&](const Request& req, Response& res) { svr.stop(); }); @@ -172,7 +230,7 @@ svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); ``` -The followings are built-in mappings: +The following are built-in mappings: | Extension | MIME Type | Extension | MIME Type | | :--------- | :-------------------------- | :--------- | :-------------------------- | @@ -186,7 +244,7 @@ The followings are built-in mappings: | bmp | image/bmp | 7z | application/x-7z-compressed | | gif | image/gif | atom | application/atom+xml | | png | image/png | pdf | application/pdf | -| svg | image/svg+xml | mjs, js | application/javascript | +| svg | image/svg+xml | mjs, js | text/javascript | | webp | image/webp | json | application/json | | ico | image/x-icon | rss | application/rss+xml | | tif | image/tiff | tar | application/x-tar | @@ -217,6 +275,18 @@ svr.set_logger([](const auto& req, const auto& res) { }); ``` +You can also set a pre-compression logger to capture request/response data before compression is applied. This is useful for debugging and monitoring purposes when you need to see the original, uncompressed response content: + +```cpp +svr.set_pre_compression_logger([](const auto& req, const auto& res) { + // Log before compression - res.body contains uncompressed content + // Content-Encoding header is not yet set + your_pre_compression_logger(req, res); +}); +``` + +The pre-compression logger is only called when compression would be applied. For responses without compression, only the regular logger is called. + ### Error handler ```cpp @@ -270,16 +340,96 @@ svr.set_post_routing_handler([](const auto& req, auto& res) { }); ``` -### 'multipart/form-data' POST data +### Pre request handler ```cpp -svr.Post("/multipart", [&](const auto& req, auto& res) { - auto size = req.files.size(); - auto ret = req.has_file("name1"); - const auto& file = req.get_file_value("name1"); - // file.filename; - // file.content_type; - // file.content; +svr.set_pre_request_handler([](const auto& req, auto& res) { + if (req.matched_route == "/user/:user") { + auto user = req.path_params.at("user"); + if (user != "john") { + res.status = StatusCode::Forbidden_403; + res.set_content("error", "text/html"); + return Server::HandlerResponse::Handled; + } + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Form data handling + +#### URL-encoded form data ('application/x-www-form-urlencoded') + +```cpp +svr.Post("/form", [&](const auto& req, auto& res) { + // URL query parameters and form-encoded data are accessible via req.params + std::string username = req.get_param_value("username"); + std::string password = req.get_param_value("password"); + + // Handle multiple values with same name + auto interests = req.get_param_values("interests"); + + // Check existence + if (req.has_param("newsletter")) { + // Handle newsletter subscription + } +}); +``` + +#### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const Request& req, Response& res) { + // Access text fields (from form inputs without files) + std::string username = req.form.get_field("username"); + std::string bio = req.form.get_field("bio"); + + // Access uploaded files + if (req.form.has_file("avatar")) { + const auto& file = req.form.get_file("avatar"); + std::cout << "Uploaded file: " << file.filename + << " (" << file.content_type << ") - " + << file.content.size() << " bytes" << std::endl; + + // Access additional headers if needed + for (const auto& header : file.headers) { + std::cout << "Header: " << header.first << " = " << header.second << std::endl; + } + + // Save to disk + std::ofstream ofs(file.filename, std::ios::binary); + ofs << file.content; + } + + // Handle multiple values with same name + auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes + for (const auto& tag : tags) { + std::cout << "Tag: " << tag << std::endl; + } + + auto documents = req.form.get_files("documents"); // multiple file upload + for (const auto& doc : documents) { + std::cout << "Document: " << doc.filename + << " (" << doc.content.size() << " bytes)" << std::endl; + } + + // Check existence before accessing + if (req.form.has_field("newsletter")) { + std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl; + } + + // Get counts for validation + if (req.form.get_field_count("tags") > 5) { + res.status = StatusCode::BadRequest_400; + res.set_content("Too many tags", "text/plain"); + return; + } + + // Summary + std::cout << "Received " << req.form.fields.size() << " text fields and " + << req.form.files.size() << " files" << std::endl; + + res.set_content("Upload successful", "text/plain"); }); ``` @@ -290,16 +440,29 @@ svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // NOTE: `content_reader` is blocking until every form data field is read - MultipartFormDataItems files; + // This approach allows streaming processing of large files + std::vector items; content_reader( - [&](const MultipartFormData &file) { - files.push_back(file); + [&](const FormData &item) { + items.push_back(item); return true; }, [&](const char *data, size_t data_length) { - files.back().content.append(data, data_length); + items.back().content.append(data, data_length); return true; }); + + // Process the received items + for (const auto& item : items) { + if (item.filename.empty()) { + // Text field + std::cout << "Field: " << item.name << " = " << item.content << std::endl; + } else { + // File + std::cout << "File: " << item.name << " (" << item.filename << ") - " + << item.content.size() << " bytes" << std::endl; + } + } } else { std::string body; content_reader([&](const char *data, size_t data_length) { @@ -391,11 +554,11 @@ svr.Get("/chunked", [&](const Request& req, Response& res) { ```cpp svr.Get("/content", [&](const Request &req, Response &res) { - res.set_file_content("./path/to/conent.html"); + res.set_file_content("./path/to/content.html"); }); svr.Get("/content", [&](const Request &req, Response &res) { - res.set_file_content("./path/to/conent", "text/html"); + res.set_file_content("./path/to/content", "text/html"); }); ``` @@ -420,7 +583,7 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) { ### Keep-Alive connection ```cpp -svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_max_count(2); // Default is 100 svr.set_keep_alive_timeout(10); // Default is 5 ``` @@ -447,7 +610,7 @@ Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/e ### Default thread pool support -`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. +`ThreadPool` is used as the **default** task queue, with a default thread count of 8 or `std::thread::hardware_concurrency() - 1`, whichever is greater. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. If you want to set the thread count at runtime, there is no convenient way... But here is how. @@ -547,9 +710,11 @@ enum Error { SSLConnection, SSLLoadingCerts, SSLServerVerification, + SSLServerHostnameVerification, UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, + ProxyConnection, }; ``` @@ -563,7 +728,7 @@ auto res = cli.Get("/hi", headers); ``` or ```c++ -auto res = cli.Get("/hi", httplib::Headers{{"Hello", "World!"}}); +auto res = cli.Get("/hi", {{"Hello", "World!"}}); ``` or ```c++ @@ -603,7 +768,7 @@ auto res = cli.Post("/post", params); ### POST with Multipart Form Data ```c++ -httplib::MultipartFormDataItems items = { +httplib::UploadFormDataItems items = { { "text1", "text default", "", "" }, { "text2", "aωb", "", "" }, { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, @@ -639,6 +804,9 @@ res = cli.Options("/resource/foo"); cli.set_connection_timeout(0, 300000); // 300 milliseconds cli.set_read_timeout(5, 0); // 5 seconds cli.set_write_timeout(5, 0); // 5 seconds + +// This method works the same as curl's `--max-time` option +cli.set_max_timeout(5000); // 5 seconds ``` ### Receive content with a content receiver @@ -657,7 +825,7 @@ auto res = cli.Get("/large-data", std::string body; auto res = cli.Get( - "/stream", + "/stream", Headers(), [&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); return true; // return 'false' if you want to cancel the request. @@ -703,7 +871,7 @@ auto res = cli.Post( httplib::Client cli(url, port); // prints: 0 / 000 bytes => 50% complete -auto res = cli.Get("/", [](uint64_t len, uint64_t total) { +auto res = cli.Get("/", [](size_t len, size_t total) { printf("%lld / %lld bytes => %d%% complete\n", len, total, (int)(len*100/total)); @@ -802,6 +970,45 @@ res->status; // 200 cli.set_interface("eth0"); // Interface name, IP address or host name ``` +### Automatic Path Encoding + +The client automatically encodes special characters in URL paths by default: + +```cpp +httplib::Client cli("https://example.com/"); + +// Automatic path encoding (default behavior) +cli.set_path_encode(true); +auto res = cli.Get("/path with spaces/file.txt"); // Automatically encodes spaces + +// Disable automatic path encoding +cli.set_path_encode(false); +auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths +``` + +- `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths + - `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters + - `false`: Sends paths as-is without encoding (useful for pre-encoded URLs) + +### Performance Note for Local Connections + +> [!WARNING] +> On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost". +> +> See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264 + +```cpp +// May be slower on Windows due to DNS resolution delays +httplib::Client cli("localhost", 8080); +httplib::Server svr; +svr.listen("localhost", 8080); + +// Faster alternative for local connections +httplib::Client cli("127.0.0.1", 8080); +httplib::Server svr; +svr.listen("127.0.0.1", 8080); +``` + Compression ----------- @@ -812,6 +1019,7 @@ The server can apply compression to the following MIME type contents: * application/javascript * application/json * application/xml + * application/protobuf * application/xhtml+xml ### Zlib Support @@ -823,19 +1031,24 @@ The server can apply compression to the following MIME type contents: Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail. +### Zstd Support + +Zstd compression is available with `CPPHTTPLIB_ZSTD_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/facebook/zstd for more detail. + ### Default `Accept-Encoding` value -The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same. +The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. ```c++ res = cli.Get("/resource/foo"); -res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", "gzip, deflate, br"}}); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "br, gzip, deflate, zstd"}}); ``` If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. ```c++ -res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", ""}}); +res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}}); ``` ### Compress request body on client @@ -854,11 +1067,6 @@ res->body; // Compressed data ``` -Use `poll` instead of `select` ------------------------------- - -`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. - Unix Domain Socket Support -------------------------- @@ -866,7 +1074,7 @@ Unix Domain Socket support is available on Linux and macOS. ```c++ // Server -httplib::Server svr("./my-socket.sock"); +httplib::Server svr; svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); // Client @@ -874,7 +1082,36 @@ httplib::Client cli("./my-socket.sock"); cli.set_address_family(AF_UNIX); ``` -"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. +"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. + +This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior: + + +URI Encoding/Decoding Utilities +------------------------------- + +cpp-httplib provides utility functions for URI encoding and decoding: + +```cpp +#include + +std::string url = "https://example.com/search?q=hello%20world"; +std::string encoded = httplib::encode_uri(url); +std::string decoded = httplib::decode_uri(encoded); + +std::string param = "hello world"; +std::string encoded_component = httplib::encode_uri_component(param); +std::string decoded_component = httplib::decode_uri_component(encoded_component); +``` + +### Functions + +- `encode_uri(const std::string &value)` - Encodes a full URI, preserving reserved characters like `://`, `?`, `&`, `=` +- `decode_uri(const std::string &value)` - Decodes a URI-encoded string +- `encode_uri_component(const std::string &value)` - Encodes a URI component (query parameter, path segment), encoding all reserved characters +- `decode_uri_component(const std::string &value)` - Decodes a URI component + +Use `encode_uri()` for full URLs and `encode_uri_component()` for individual query parameters or path segments. Split httplib.h into .h and .cc @@ -916,9 +1153,6 @@ From Docker Hub ```bash > docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server -... - -> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Serving HTTP on 0.0.0.0 port 80 ... 192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." @@ -928,6 +1162,24 @@ Serving HTTP on 0.0.0.0 port 80 ... NOTE ---- +### Regular Expression Stack Overflow + +> [!CAUTION] +> When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method. +> +> ```cpp +> // This pattern can cause stack overflow with large input +> svr.Get(".*", handler); +> ``` +> +> Consider using simpler patterns or path parameters to avoid this issue: +> +> ```cpp +> // Safer alternatives +> svr.Get("/users/:id", handler); // Path parameters +> svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns +> ``` + ### g++ g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). @@ -951,12 +1203,12 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32 > cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. > [!NOTE] -> Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. +> Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. License ------- -MIT license (© 2024 Yuji Hirose) +MIT license (© 2025 Yuji Hirose) Special Thanks To -----------------