Skip to content

Commit b0f0dd3

Browse files
authored
vendor : update cpp-httplib to 0.40.0 (ggml-org#21100)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
1 parent 0eb4764 commit b0f0dd3

4 files changed

Lines changed: 119 additions & 52 deletions

File tree

scripts/sync_vendor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import subprocess
77

8-
HTTPLIB_VERSION = "refs/tags/v0.39.0"
8+
HTTPLIB_VERSION = "refs/tags/v0.40.0"
99

1010
vendor = {
1111
"https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp",

tools/server/server-http.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,10 @@ bool server_http_context::init(const common_params & params) {
113113
srv->set_read_timeout (params.timeout_read);
114114
srv->set_write_timeout(params.timeout_write);
115115
srv->set_socket_options([reuse_port = params.reuse_port](socket_t sock) {
116-
int opt = 1;
117-
#ifdef _WIN32
118-
const char * optval = (const char *)&opt;
119-
#else
120-
const void * optval = &opt;
121-
#endif
122-
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, optval, sizeof(opt));
116+
httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
123117
if (reuse_port) {
124118
#ifdef SO_REUSEPORT
125-
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, optval, sizeof(opt));
119+
httplib::set_socket_opt(sock, SOL_SOCKET, SO_REUSEPORT, 1);
126120
#else
127121
LOG_WRN("%s: SO_REUSEPORT is not supported\n", __func__);
128122
#endif

vendor/cpp-httplib/httplib.cpp

Lines changed: 104 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
474470
bool 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

52665293
void 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

52765308
std::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

82548317
bool Server::is_valid() const { return true; }

vendor/cpp-httplib/httplib.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
#ifndef CPPHTTPLIB_HTTPLIB_H
99
#define CPPHTTPLIB_HTTPLIB_H
1010

11-
#define CPPHTTPLIB_VERSION "0.39.0"
12-
#define CPPHTTPLIB_VERSION_NUM "0x002700"
11+
#define CPPHTTPLIB_VERSION "0.40.0"
12+
#define CPPHTTPLIB_VERSION_NUM "0x002800"
1313

1414
#ifdef _WIN32
1515
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
@@ -1266,6 +1266,7 @@ struct Request {
12661266
bool is_multipart_form_data() const;
12671267

12681268
// private members...
1269+
bool body_consumed_ = false;
12691270
size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
12701271
size_t content_length_ = 0;
12711272
ContentProvider content_provider_;
@@ -1475,6 +1476,8 @@ using SocketOptions = std::function<void(socket_t sock)>;
14751476

14761477
void default_socket_options(socket_t sock);
14771478

1479+
bool set_socket_opt(socket_t sock, int level, int optname, int optval);
1480+
14781481
const char *status_message(int status);
14791482

14801483
std::string to_string(Error error);
@@ -1564,6 +1567,13 @@ ssize_t write_headers(Stream &strm, const Headers &headers);
15641567
bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
15651568
time_t usec);
15661569

1570+
size_t get_multipart_content_length(const UploadFormDataItems &items,
1571+
const std::string &boundary);
1572+
1573+
ContentProvider
1574+
make_multipart_content_provider(const UploadFormDataItems &items,
1575+
const std::string &boundary);
1576+
15671577
} // namespace detail
15681578

15691579
class Server {

0 commit comments

Comments
 (0)