From b0f0dd3e516a783b46bb15b20e6a6579367e2ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Gallou=C3=ABt?= Date: Sat, 28 Mar 2026 08:59:44 +0100 Subject: [PATCH] vendor : update cpp-httplib to 0.40.0 (#21100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adrien Gallouët --- scripts/sync_vendor.py | 2 +- tools/server/server-http.cpp | 10 +-- vendor/cpp-httplib/httplib.cpp | 145 +++++++++++++++++++++++---------- vendor/cpp-httplib/httplib.h | 14 +++- 4 files changed, 119 insertions(+), 52 deletions(-) diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 6524cae83a..3f1e74f7cb 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.39.0" +HTTPLIB_VERSION = "refs/tags/v0.40.0" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/tools/server/server-http.cpp b/tools/server/server-http.cpp index 1dabaeee28..2262577e1d 100644 --- a/tools/server/server-http.cpp +++ b/tools/server/server-http.cpp @@ -113,16 +113,10 @@ bool server_http_context::init(const common_params & params) { srv->set_read_timeout (params.timeout_read); srv->set_write_timeout(params.timeout_write); srv->set_socket_options([reuse_port = params.reuse_port](socket_t sock) { - int opt = 1; -#ifdef _WIN32 - const char * optval = (const char *)&opt; -#else - const void * optval = &opt; -#endif - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, sizeof(opt)); + httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1); if (reuse_port) { #ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, optval, sizeof(opt)); + httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEPORT, 1); #else LOG_WRN("%s: SO_REUSEPORT is not supported\n", __func__); #endif diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index caa87abff6..8ff1da57bb 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -467,10 +467,6 @@ bool set_socket_opt_impl(socket_t sock, int level, int optname, optlen) == 0; } -bool set_socket_opt(socket_t sock, int level, int optname, int optval) { - return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval)); -} - bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec) { #ifdef _WIN32 @@ -2218,7 +2214,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, #ifdef _WIN32 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so // remove the option. - detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); + set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); #endif bool dummy; @@ -4373,6 +4369,7 @@ make_multipart_content_provider(const UploadFormDataItems &items, struct MultipartState { std::vector owned; std::vector segs; + std::vector buf = std::vector(CPPHTTPLIB_SEND_BUFSIZ); }; auto state = std::make_shared(); state->owned = std::move(owned); @@ -4381,19 +4378,49 @@ make_multipart_content_provider(const UploadFormDataItems &items, state->segs = std::move(segs); return [state](size_t offset, size_t length, DataSink &sink) -> bool { + // Buffer multiple small segments into fewer, larger writes to avoid + // excessive TCP packets when there are many form data items (#2410) + auto &buf = state->buf; + auto buf_size = buf.size(); + size_t buf_len = 0; + size_t remaining = length; + + // Find the first segment containing 'offset' size_t pos = 0; - for (const auto &seg : state->segs) { - // Loop invariant: pos <= offset (proven by advancing pos only when - // offset - pos >= seg.size, i.e., the segment doesn't contain offset) - if (seg.size > 0 && offset - pos < seg.size) { - size_t seg_offset = offset - pos; - size_t available = seg.size - seg_offset; - size_t to_write = (std::min)(available, length); - return sink.write(seg.data + seg_offset, to_write); - } + size_t seg_idx = 0; + for (; seg_idx < state->segs.size(); seg_idx++) { + const auto &seg = state->segs[seg_idx]; + if (seg.size > 0 && offset - pos < seg.size) { break; } pos += seg.size; } - return true; // past end (shouldn't be reached when content_length is exact) + + size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0; + + for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) { + const auto &seg = state->segs[seg_idx]; + size_t available = seg.size - seg_offset; + size_t to_copy = (std::min)(available, remaining); + const char *src = seg.data + seg_offset; + seg_offset = 0; // only the first segment has a non-zero offset + + while (to_copy > 0) { + size_t space = buf_size - buf_len; + size_t chunk = (std::min)(to_copy, space); + std::memcpy(buf.data() + buf_len, src, chunk); + buf_len += chunk; + src += chunk; + to_copy -= chunk; + remaining -= chunk; + + if (buf_len == buf_size) { + if (!sink.write(buf.data(), buf_len)) { return false; } + buf_len = 0; + } + } + } + + if (buf_len > 0) { return sink.write(buf.data(), buf_len); } + return true; }; } @@ -5264,13 +5291,18 @@ bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx, */ void default_socket_options(socket_t sock) { - detail::set_socket_opt(sock, SOL_SOCKET, + set_socket_opt(sock, SOL_SOCKET, #ifdef SO_REUSEPORT - SO_REUSEPORT, + SO_REUSEPORT, #else - SO_REUSEADDR, + SO_REUSEADDR, #endif - 1); + 1); +} + +bool set_socket_opt(socket_t sock, int level, int optname, int optval) { + return detail::set_socket_opt_impl(sock, level, optname, &optval, + sizeof(optval)); } std::string get_bearer_token_auth(const Request &req) { @@ -7418,6 +7450,8 @@ bool Server::read_content_core( return false; } + req.body_consumed_ = true; + if (req.is_multipart_form_data()) { if (!multipart_form_data_parser.is_valid()) { res.status = StatusCode::BadRequest_400; @@ -7688,9 +7722,7 @@ bool Server::listen_internal() { detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec_, write_timeout_usec_); - if (tcp_nodelay_) { - detail::set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); - } + if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); } if (!task_queue->enqueue( [this, sock]() { process_and_close_socket(sock); })) { @@ -8036,8 +8068,19 @@ Server::process_request(Stream &strm, const std::string &remote_addr, return write_response(strm, close_connection, req, res); } + // RFC 9112 §6.3: Reject requests with both a non-zero Content-Length and + // any Transfer-Encoding to prevent request smuggling. Content-Length: 0 is + // tolerated for compatibility with existing clients. + if (req.get_header_value_u64("Content-Length") > 0 && + req.has_header("Transfer-Encoding")) { + connection_closed = true; + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + // Check if the request URI doesn't exceed the limit if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + connection_closed = true; res.status = StatusCode::UriTooLong_414; output_error_log(Error::ExceedUriMaxLength, &req); return write_response(strm, close_connection, req, res); @@ -8066,6 +8109,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, 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)) { + connection_closed = true; res.status = StatusCode::BadRequest_400; output_error_log(Error::HTTPParsing, &req); return write_response(strm, close_connection, req, res); @@ -8075,6 +8119,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, 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)) { + connection_closed = true; res.status = StatusCode::RangeNotSatisfiable_416; output_error_log(Error::InvalidRangeHeader, &req); return write_response(strm, close_connection, req, res); @@ -8202,6 +8247,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, } } #endif + auto ret = false; if (routed) { if (res.status == -1) { res.status = req.ranges.empty() ? StatusCode::OK_200 @@ -8209,6 +8255,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, } // Serve file content by using a content provider + auto file_open_error = false; if (!res.file_content_path_.empty()) { const auto &path = res.file_content_path_; auto mm = std::make_shared(path.c_str()); @@ -8218,37 +8265,53 @@ Server::process_request(Stream &strm, const std::string &remote_addr, res.content_provider_ = nullptr; res.status = StatusCode::NotFound_404; output_error_log(Error::OpenFile, &req); - return write_response(strm, close_connection, req, res); - } + file_open_error = true; + } else { + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } - auto content_type = res.file_content_content_type_; - if (content_type.empty()) { - content_type = detail::find_content_type( - path, file_extension_and_mimetype_map_, default_file_mimetype_); + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); } - - res.set_content_provider( - mm->size(), content_type, - [mm](size_t offset, size_t length, DataSink &sink) -> bool { - sink.write(mm->data() + offset, length); - return true; - }); } - if (detail::range_error(req, res)) { + if (file_open_error) { + ret = write_response(strm, close_connection, req, res); + } else if (detail::range_error(req, res)) { res.body.clear(); res.content_length_ = 0; res.content_provider_ = nullptr; res.status = StatusCode::RangeNotSatisfiable_416; - return write_response(strm, close_connection, req, res); + ret = write_response(strm, close_connection, req, res); + } else { + ret = write_response_with_content(strm, close_connection, req, res); } - - return write_response_with_content(strm, close_connection, req, res); } else { if (res.status == -1) { res.status = StatusCode::NotFound_404; } - - return write_response(strm, close_connection, req, res); + ret = write_response(strm, close_connection, req, res); } + + // Drain any unconsumed request body to prevent request smuggling on + // keep-alive connections. + if (!req.body_consumed_ && detail::expect_content(req)) { + int drain_status = 200; // required by read_content signature + if (!detail::read_content( + strm, req, payload_max_length_, drain_status, nullptr, + [](const char *, size_t, size_t, size_t) { return true; }, false)) { + // Body exceeds payload limit or read error — close the connection + // to prevent leftover bytes from being misinterpreted. + connection_closed = true; + } + } + + return ret; } bool Server::is_valid() const { return true; } diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index ce1681fcbe..2967ddf5e5 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.39.0" -#define CPPHTTPLIB_VERSION_NUM "0x002700" +#define CPPHTTPLIB_VERSION "0.40.0" +#define CPPHTTPLIB_VERSION_NUM "0x002800" #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 @@ -1266,6 +1266,7 @@ struct Request { bool is_multipart_form_data() const; // private members... + bool body_consumed_ = false; size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; size_t content_length_ = 0; ContentProvider content_provider_; @@ -1475,6 +1476,8 @@ using SocketOptions = std::function; void default_socket_options(socket_t sock); +bool set_socket_opt(socket_t sock, int level, int optname, int optval); + const char *status_message(int status); std::string to_string(Error error); @@ -1564,6 +1567,13 @@ ssize_t write_headers(Stream &strm, const Headers &headers); bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec); +size_t get_multipart_content_length(const UploadFormDataItems &items, + const std::string &boundary); + +ContentProvider +make_multipart_content_provider(const UploadFormDataItems &items, + const std::string &boundary); + } // namespace detail class Server {