@@ -467,10 +467,6 @@ bool set_socket_opt_impl(socket_t sock, int level, int optname,
467467 optlen) == 0;
468468}
469469
470- bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
471- return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval));
472- }
473-
474470bool set_socket_opt_time(socket_t sock, int level, int optname,
475471 time_t sec, time_t usec) {
476472#ifdef _WIN32
@@ -2218,7 +2214,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
22182214#ifdef _WIN32
22192215 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
22202216 // remove the option.
2221- detail:: set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
2217+ set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
22222218#endif
22232219
22242220 bool dummy;
@@ -4373,6 +4369,7 @@ make_multipart_content_provider(const UploadFormDataItems &items,
43734369 struct MultipartState {
43744370 std::vector<std::string> owned;
43754371 std::vector<MultipartSegment> segs;
4372+ std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
43764373 };
43774374 auto state = std::make_shared<MultipartState>();
43784375 state->owned = std::move(owned);
@@ -4381,19 +4378,49 @@ make_multipart_content_provider(const UploadFormDataItems &items,
43814378 state->segs = std::move(segs);
43824379
43834380 return [state](size_t offset, size_t length, DataSink &sink) -> bool {
4381+ // Buffer multiple small segments into fewer, larger writes to avoid
4382+ // excessive TCP packets when there are many form data items (#2410)
4383+ auto &buf = state->buf;
4384+ auto buf_size = buf.size();
4385+ size_t buf_len = 0;
4386+ size_t remaining = length;
4387+
4388+ // Find the first segment containing 'offset'
43844389 size_t pos = 0;
4385- for (const auto &seg : state->segs) {
4386- // Loop invariant: pos <= offset (proven by advancing pos only when
4387- // offset - pos >= seg.size, i.e., the segment doesn't contain offset)
4388- if (seg.size > 0 && offset - pos < seg.size) {
4389- size_t seg_offset = offset - pos;
4390- size_t available = seg.size - seg_offset;
4391- size_t to_write = (std::min)(available, length);
4392- return sink.write(seg.data + seg_offset, to_write);
4393- }
4390+ size_t seg_idx = 0;
4391+ for (; seg_idx < state->segs.size(); seg_idx++) {
4392+ const auto &seg = state->segs[seg_idx];
4393+ if (seg.size > 0 && offset - pos < seg.size) { break; }
43944394 pos += seg.size;
43954395 }
4396- return true; // past end (shouldn't be reached when content_length is exact)
4396+
4397+ size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0;
4398+
4399+ for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) {
4400+ const auto &seg = state->segs[seg_idx];
4401+ size_t available = seg.size - seg_offset;
4402+ size_t to_copy = (std::min)(available, remaining);
4403+ const char *src = seg.data + seg_offset;
4404+ seg_offset = 0; // only the first segment has a non-zero offset
4405+
4406+ while (to_copy > 0) {
4407+ size_t space = buf_size - buf_len;
4408+ size_t chunk = (std::min)(to_copy, space);
4409+ std::memcpy(buf.data() + buf_len, src, chunk);
4410+ buf_len += chunk;
4411+ src += chunk;
4412+ to_copy -= chunk;
4413+ remaining -= chunk;
4414+
4415+ if (buf_len == buf_size) {
4416+ if (!sink.write(buf.data(), buf_len)) { return false; }
4417+ buf_len = 0;
4418+ }
4419+ }
4420+ }
4421+
4422+ if (buf_len > 0) { return sink.write(buf.data(), buf_len); }
4423+ return true;
43974424 };
43984425}
43994426
@@ -5264,13 +5291,18 @@ bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx,
52645291 */
52655292
52665293void default_socket_options(socket_t sock) {
5267- detail:: set_socket_opt(sock, SOL_SOCKET,
5294+ set_socket_opt(sock, SOL_SOCKET,
52685295#ifdef SO_REUSEPORT
5269- SO_REUSEPORT,
5296+ SO_REUSEPORT,
52705297#else
5271- SO_REUSEADDR,
5298+ SO_REUSEADDR,
52725299#endif
5273- 1);
5300+ 1);
5301+ }
5302+
5303+ bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
5304+ return detail::set_socket_opt_impl(sock, level, optname, &optval,
5305+ sizeof(optval));
52745306}
52755307
52765308std::string get_bearer_token_auth(const Request &req) {
@@ -7418,6 +7450,8 @@ bool Server::read_content_core(
74187450 return false;
74197451 }
74207452
7453+ req.body_consumed_ = true;
7454+
74217455 if (req.is_multipart_form_data()) {
74227456 if (!multipart_form_data_parser.is_valid()) {
74237457 res.status = StatusCode::BadRequest_400;
@@ -7688,9 +7722,7 @@ bool Server::listen_internal() {
76887722 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
76897723 write_timeout_sec_, write_timeout_usec_);
76907724
7691- if (tcp_nodelay_) {
7692- detail::set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1);
7693- }
7725+ if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
76947726
76957727 if (!task_queue->enqueue(
76967728 [this, sock]() { process_and_close_socket(sock); })) {
@@ -8036,8 +8068,19 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
80368068 return write_response(strm, close_connection, req, res);
80378069 }
80388070
8071+ // RFC 9112 §6.3: Reject requests with both a non-zero Content-Length and
8072+ // any Transfer-Encoding to prevent request smuggling. Content-Length: 0 is
8073+ // tolerated for compatibility with existing clients.
8074+ if (req.get_header_value_u64("Content-Length") > 0 &&
8075+ req.has_header("Transfer-Encoding")) {
8076+ connection_closed = true;
8077+ res.status = StatusCode::BadRequest_400;
8078+ return write_response(strm, close_connection, req, res);
8079+ }
8080+
80398081 // Check if the request URI doesn't exceed the limit
80408082 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
8083+ connection_closed = true;
80418084 res.status = StatusCode::UriTooLong_414;
80428085 output_error_log(Error::ExceedUriMaxLength, &req);
80438086 return write_response(strm, close_connection, req, res);
@@ -8066,6 +8109,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
80668109 if (req.has_header("Accept")) {
80678110 const auto &accept_header = req.get_header_value("Accept");
80688111 if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
8112+ connection_closed = true;
80698113 res.status = StatusCode::BadRequest_400;
80708114 output_error_log(Error::HTTPParsing, &req);
80718115 return write_response(strm, close_connection, req, res);
@@ -8075,6 +8119,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
80758119 if (req.has_header("Range")) {
80768120 const auto &range_header_value = req.get_header_value("Range");
80778121 if (!detail::parse_range_header(range_header_value, req.ranges)) {
8122+ connection_closed = true;
80788123 res.status = StatusCode::RangeNotSatisfiable_416;
80798124 output_error_log(Error::InvalidRangeHeader, &req);
80808125 return write_response(strm, close_connection, req, res);
@@ -8202,13 +8247,15 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
82028247 }
82038248 }
82048249#endif
8250+ auto ret = false;
82058251 if (routed) {
82068252 if (res.status == -1) {
82078253 res.status = req.ranges.empty() ? StatusCode::OK_200
82088254 : StatusCode::PartialContent_206;
82098255 }
82108256
82118257 // Serve file content by using a content provider
8258+ auto file_open_error = false;
82128259 if (!res.file_content_path_.empty()) {
82138260 const auto &path = res.file_content_path_;
82148261 auto mm = std::make_shared<detail::mmap>(path.c_str());
@@ -8218,37 +8265,53 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
82188265 res.content_provider_ = nullptr;
82198266 res.status = StatusCode::NotFound_404;
82208267 output_error_log(Error::OpenFile, &req);
8221- return write_response(strm, close_connection, req, res);
8222- }
8268+ file_open_error = true;
8269+ } else {
8270+ auto content_type = res.file_content_content_type_;
8271+ if (content_type.empty()) {
8272+ content_type = detail::find_content_type(
8273+ path, file_extension_and_mimetype_map_, default_file_mimetype_);
8274+ }
82238275
8224- auto content_type = res.file_content_content_type_;
8225- if (content_type.empty()) {
8226- content_type = detail::find_content_type(
8227- path, file_extension_and_mimetype_map_, default_file_mimetype_);
8276+ res.set_content_provider(
8277+ mm->size(), content_type,
8278+ [mm](size_t offset, size_t length, DataSink &sink) -> bool {
8279+ sink.write(mm->data() + offset, length);
8280+ return true;
8281+ });
82288282 }
8229-
8230- res.set_content_provider(
8231- mm->size(), content_type,
8232- [mm](size_t offset, size_t length, DataSink &sink) -> bool {
8233- sink.write(mm->data() + offset, length);
8234- return true;
8235- });
82368283 }
82378284
8238- if (detail::range_error(req, res)) {
8285+ if (file_open_error) {
8286+ ret = write_response(strm, close_connection, req, res);
8287+ } else if (detail::range_error(req, res)) {
82398288 res.body.clear();
82408289 res.content_length_ = 0;
82418290 res.content_provider_ = nullptr;
82428291 res.status = StatusCode::RangeNotSatisfiable_416;
8243- return write_response(strm, close_connection, req, res);
8292+ ret = write_response(strm, close_connection, req, res);
8293+ } else {
8294+ ret = write_response_with_content(strm, close_connection, req, res);
82448295 }
8245-
8246- return write_response_with_content(strm, close_connection, req, res);
82478296 } else {
82488297 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
8249-
8250- return write_response(strm, close_connection, req, res);
8298+ ret = write_response(strm, close_connection, req, res);
8299+ }
8300+
8301+ // Drain any unconsumed request body to prevent request smuggling on
8302+ // keep-alive connections.
8303+ if (!req.body_consumed_ && detail::expect_content(req)) {
8304+ int drain_status = 200; // required by read_content signature
8305+ if (!detail::read_content(
8306+ strm, req, payload_max_length_, drain_status, nullptr,
8307+ [](const char *, size_t, size_t, size_t) { return true; }, false)) {
8308+ // Body exceeds payload limit or read error — close the connection
8309+ // to prevent leftover bytes from being misinterpreted.
8310+ connection_closed = true;
8311+ }
82518312 }
8313+
8314+ return ret;
82528315}
82538316
82548317bool Server::is_valid() const { return true; }
0 commit comments