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
#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;
struct MultipartState {
std::vector<std::string> owned;
std::vector<MultipartSegment> segs;
+ std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
};
auto state = std::make_shared<MultipartState>();
state->owned = std::move(owned);
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;
};
}
*/
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) {
return false;
}
+ req.body_consumed_ = true;
+
if (req.is_multipart_form_data()) {
if (!multipart_form_data_parser.is_valid()) {
res.status = StatusCode::BadRequest_400;
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); })) {
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);
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);
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);
}
}
#endif
+ auto ret = false;
if (routed) {
if (res.status == -1) {
res.status = req.ranges.empty() ? StatusCode::OK_200
}
// 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<detail::mmap>(path.c_str());
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; }