Skip to content

Commit e6e934c

Browse files
authored
vendor: update cpp-httplib version (ggml-org#19313)
Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
1 parent b536eb0 commit e6e934c

3 files changed

Lines changed: 285 additions & 59 deletions

File tree

scripts/sync_vendor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
# "https://github.com/mackron/miniaudio/raw/refs/tags/0.11.23/miniaudio.h": "vendor/miniaudio/miniaudio.h",
1313
"https://github.com/mackron/miniaudio/raw/669ed3e844524fcd883231b13095baee9f6de304/miniaudio.h": "vendor/miniaudio/miniaudio.h",
1414

15-
"https://raw.githubusercontent.com/yhirose/cpp-httplib/refs/tags/v0.30.1/httplib.h": "vendor/cpp-httplib/httplib.h",
16-
"https://raw.githubusercontent.com/yhirose/cpp-httplib/refs/tags/v0.30.1/LICENSE": "vendor/cpp-httplib/LICENSE",
15+
"https://raw.githubusercontent.com/yhirose/cpp-httplib/refs/tags/v0.30.2/httplib.h": "vendor/cpp-httplib/httplib.h",
16+
"https://raw.githubusercontent.com/yhirose/cpp-httplib/refs/tags/v0.30.2/LICENSE": "vendor/cpp-httplib/LICENSE",
1717

1818
"https://raw.githubusercontent.com/sheredom/subprocess.h/b49c56e9fe214488493021017bf3954b91c7c1f5/subprocess.h": "vendor/sheredom/subprocess.h",
1919
}

vendor/cpp-httplib/httplib.cpp

Lines changed: 190 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ time_t parse_http_date(const std::string &date_str) {
117117

118118
#ifdef _WIN32
119119
return _mkgmtime(&tm_buf);
120+
#elif defined _AIX
121+
return mktime(&tm_buf);
120122
#else
121123
return timegm(&tm_buf);
122124
#endif
@@ -1376,7 +1378,7 @@ int getaddrinfo_with_timeout(const char *node, const char *service,
13761378

13771379
// Allocate on the heap, so the resolver thread can keep using the data.
13781380
auto state = std::make_shared<GetAddrInfoState>();
1379-
state->node = node;
1381+
if (node) { state->node = node; }
13801382
state->service = service;
13811383
state->hints = *hints;
13821384

@@ -2896,10 +2898,20 @@ bool parse_range_header(const std::string &s, Ranges &ranges) try {
28962898
return;
28972899
}
28982900

2899-
const auto first =
2900-
static_cast<ssize_t>(lhs.empty() ? -1 : std::stoll(lhs));
2901-
const auto last =
2902-
static_cast<ssize_t>(rhs.empty() ? -1 : std::stoll(rhs));
2901+
ssize_t first = -1;
2902+
if (!lhs.empty()) {
2903+
ssize_t v;
2904+
auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
2905+
if (res.ec == std::errc{}) { first = v; }
2906+
}
2907+
2908+
ssize_t last = -1;
2909+
if (!rhs.empty()) {
2910+
ssize_t v;
2911+
auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
2912+
if (res.ec == std::errc{}) { last = v; }
2913+
}
2914+
29032915
if ((first == -1 && last == -1) ||
29042916
(first != -1 && last != -1 && first > last)) {
29052917
all_valid_ranges = false;
@@ -2974,25 +2986,17 @@ bool parse_accept_header(const std::string &s,
29742986
return;
29752987
}
29762988

2977-
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
29782989
{
2979-
std::istringstream iss(quality_str);
2980-
iss >> accept_entry.quality;
2981-
2982-
// Check if conversion was successful and entire string was consumed
2983-
if (iss.fail() || !iss.eof()) {
2990+
double v = 0.0;
2991+
auto res = detail::from_chars(
2992+
quality_str.data(), quality_str.data() + quality_str.size(), v);
2993+
if (res.ec == std::errc{}) {
2994+
accept_entry.quality = v;
2995+
} else {
29842996
has_invalid_entry = true;
29852997
return;
29862998
}
29872999
}
2988-
#else
2989-
try {
2990-
accept_entry.quality = std::stod(quality_str);
2991-
} catch (...) {
2992-
has_invalid_entry = true;
2993-
return;
2994-
}
2995-
#endif
29963000
// Check if quality is in valid range [0.0, 1.0]
29973001
if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
29983002
has_invalid_entry = true;
@@ -5570,13 +5574,26 @@ bool Server::read_content(Stream &strm, Request &req, Response &res) {
55705574
strm, req, res,
55715575
// Regular
55725576
[&](const char *buf, size_t n) {
5577+
// Prevent arithmetic overflow when checking sizes.
5578+
// Avoid computing (req.body.size() + n) directly because
5579+
// adding two unsigned `size_t` values can wrap around and
5580+
// produce a small result instead of indicating overflow.
5581+
// Instead, check using subtraction: ensure `n` does not
5582+
// exceed the remaining capacity `max_size() - size()`.
5583+
if (req.body.size() >= req.body.max_size() ||
5584+
n > req.body.max_size() - req.body.size()) {
5585+
return false;
5586+
}
5587+
55735588
// Limit decompressed body size to payload_max_length_ to protect
55745589
// against "zip bomb" attacks where a small compressed payload
55755590
// decompresses to a massive size.
5576-
if (req.body.size() + n > payload_max_length_ ||
5577-
req.body.size() + n > req.body.max_size()) {
5591+
if (payload_max_length_ > 0 &&
5592+
(req.body.size() >= payload_max_length_ ||
5593+
n > payload_max_length_ - req.body.size())) {
55785594
return false;
55795595
}
5596+
55805597
req.body.append(buf, n);
55815598
return true;
55825599
},
@@ -5666,22 +5683,29 @@ bool Server::read_content_core(
56665683
// oversized request and fail early (causing connection close). For SSL
56675684
// builds we cannot reliably peek the decrypted application bytes, so keep
56685685
// the original behaviour.
5669-
#if !defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(_WIN32)
5686+
#if !defined(CPPHTTPLIB_OPENSSL_SUPPORT)
56705687
if (!req.has_header("Content-Length") &&
56715688
!detail::is_chunked_transfer_encoding(req.headers)) {
5672-
socket_t s = strm.socket();
5673-
if (s != INVALID_SOCKET) {
5674-
// Peek up to payload_max_length_ + 1 bytes. If more than
5675-
// payload_max_length_ bytes are pending, reject the request.
5676-
size_t to_peek =
5677-
(payload_max_length_ > 0)
5678-
? (std::min)(payload_max_length_ + 1, static_cast<size_t>(4096))
5679-
: 1;
5680-
std::vector<char> peekbuf(to_peek);
5681-
ssize_t n = ::recv(s, peekbuf.data(), to_peek, MSG_PEEK);
5682-
if (n > 0 && static_cast<size_t>(n) > payload_max_length_) {
5683-
// Indicate failure so connection will be closed.
5684-
return false;
5689+
// Only peek if payload_max_length is set to a finite value
5690+
if (payload_max_length_ > 0 &&
5691+
payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
5692+
socket_t s = strm.socket();
5693+
if (s != INVALID_SOCKET) {
5694+
// Peek to check if there is any pending data
5695+
char peekbuf[1];
5696+
ssize_t n = ::recv(s, peekbuf, 1, MSG_PEEK);
5697+
if (n > 0) {
5698+
// There is data, so read it with payload limit enforcement
5699+
auto result = detail::read_content_without_length(
5700+
strm, payload_max_length_, out);
5701+
if (result == detail::ReadContentResult::PayloadTooLarge) {
5702+
res.status = StatusCode::PayloadTooLarge_413;
5703+
return false;
5704+
} else if (result != detail::ReadContentResult::Success) {
5705+
return false;
5706+
}
5707+
return true;
5708+
}
56855709
}
56865710
}
56875711
return true;
@@ -6656,7 +6680,8 @@ void ClientImpl::close_socket(Socket &socket) {
66566680
}
66576681

66586682
bool ClientImpl::read_response_line(Stream &strm, const Request &req,
6659-
Response &res) const {
6683+
Response &res,
6684+
bool skip_100_continue) const {
66606685
std::array<char, 2048> buf{};
66616686

66626687
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
@@ -6677,8 +6702,8 @@ bool ClientImpl::read_response_line(Stream &strm, const Request &req,
66776702
res.status = std::stoi(std::string(m[2]));
66786703
res.reason = std::string(m[3]);
66796704

6680-
// Ignore '100 Continue'
6681-
while (res.status == StatusCode::Continue_100) {
6705+
// Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
6706+
while (skip_100_continue && res.status == StatusCode::Continue_100) {
66826707
if (!line_reader.getline()) { return false; } // CRLF
66836708
if (!line_reader.getline()) { return false; } // next response line
66846709

@@ -7463,7 +7488,8 @@ bool ClientImpl::write_content_with_provider(Stream &strm,
74637488
}
74647489

74657490
bool ClientImpl::write_request(Stream &strm, Request &req,
7466-
bool close_connection, Error &error) {
7491+
bool close_connection, Error &error,
7492+
bool skip_body) {
74677493
// Prepare additional headers
74687494
if (close_connection) {
74697495
if (!req.has_header("Connection")) {
@@ -7582,7 +7608,59 @@ bool ClientImpl::write_request(Stream &strm, Request &req,
75827608
}
75837609
}
75847610

7611+
// After sending request line and headers, wait briefly for an early server
7612+
// response (e.g. 4xx) and avoid sending a potentially large request body
7613+
// unnecessarily. This workaround is only enabled on Windows because Unix
7614+
// platforms surface write errors (EPIPE) earlier; on Windows kernel send
7615+
// buffering can accept large writes even when the peer already responded.
7616+
// Check the stream first (which covers SSL via `is_readable()`), then
7617+
// fall back to select on the socket. Only perform the wait for very large
7618+
// request bodies to avoid interfering with normal small requests and
7619+
// reduce side-effects. Poll briefly (up to 50ms as default) for an early
7620+
// response. Skip this check when using Expect: 100-continue, as the protocol
7621+
// handles early responses properly.
7622+
#if defined(_WIN32)
7623+
if (!skip_body &&
7624+
req.body.size() > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD &&
7625+
req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
7626+
auto start = std::chrono::high_resolution_clock::now();
7627+
7628+
for (;;) {
7629+
// Prefer socket-level readiness to avoid SSL_pending() false-positives
7630+
// from SSL internals. If the underlying socket is readable, assume an
7631+
// early response may be present.
7632+
auto sock = strm.socket();
7633+
if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
7634+
return false;
7635+
}
7636+
7637+
// Fallback to stream-level check for non-socket streams or when the
7638+
// socket isn't reporting readable. Avoid using `is_readable()` for
7639+
// SSL, since `SSL_pending()` may report buffered records that do not
7640+
// indicate a complete application-level response yet.
7641+
if (!is_ssl() && strm.is_readable()) { return false; }
7642+
7643+
auto now = std::chrono::high_resolution_clock::now();
7644+
auto elapsed =
7645+
std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
7646+
.count();
7647+
if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) {
7648+
break;
7649+
}
7650+
7651+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
7652+
}
7653+
}
7654+
#endif
7655+
75857656
// Body
7657+
if (skip_body) { return true; }
7658+
7659+
return write_request_body(strm, req, error);
7660+
}
7661+
7662+
bool ClientImpl::write_request_body(Stream &strm, Request &req,
7663+
Error &error) {
75867664
if (req.body.empty()) {
75877665
return write_content_with_provider(strm, req, error);
75887666
}
@@ -7758,8 +7836,20 @@ void ClientImpl::output_error_log(const Error &err,
77587836
bool ClientImpl::process_request(Stream &strm, Request &req,
77597837
Response &res, bool close_connection,
77607838
Error &error) {
7761-
// Send request
7762-
if (!write_request(strm, req, close_connection, error)) { return false; }
7839+
// Auto-add Expect: 100-continue for large bodies
7840+
if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
7841+
auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
7842+
if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
7843+
req.set_header("Expect", "100-continue");
7844+
}
7845+
}
7846+
7847+
// Check for Expect: 100-continue
7848+
auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
7849+
7850+
// Send request (skip body if using Expect: 100-continue)
7851+
auto write_request_success =
7852+
write_request(strm, req, close_connection, error, expect_100_continue);
77637853

77647854
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
77657855
if (is_ssl()) {
@@ -7774,14 +7864,48 @@ bool ClientImpl::process_request(Stream &strm, Request &req,
77747864
}
77757865
#endif
77767866

7867+
// Handle Expect: 100-continue with timeout
7868+
if (expect_100_continue && CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
7869+
time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
7870+
time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
7871+
auto ret = detail::select_read(strm.socket(), sec, usec);
7872+
if (ret <= 0) {
7873+
// Timeout or error: send body anyway (server didn't respond in time)
7874+
if (!write_request_body(strm, req, error)) { return false; }
7875+
expect_100_continue = false; // Switch to normal response handling
7876+
}
7877+
}
7878+
77777879
// Receive response and headers
7778-
if (!read_response_line(strm, req, res) ||
7880+
// When using Expect: 100-continue, don't auto-skip `100 Continue` response
7881+
if (!read_response_line(strm, req, res, !expect_100_continue) ||
77797882
!detail::read_headers(strm, res.headers)) {
7780-
error = Error::Read;
7883+
if (write_request_success) { error = Error::Read; }
77817884
output_error_log(error, &req);
77827885
return false;
77837886
}
77847887

7888+
if (!write_request_success) { return false; }
7889+
7890+
// Handle Expect: 100-continue response
7891+
if (expect_100_continue) {
7892+
if (res.status == StatusCode::Continue_100) {
7893+
// Server accepted, send the body
7894+
if (!write_request_body(strm, req, error)) { return false; }
7895+
7896+
// Read the actual response
7897+
res.headers.clear();
7898+
res.body.clear();
7899+
if (!read_response_line(strm, req, res) ||
7900+
!detail::read_headers(strm, res.headers)) {
7901+
error = Error::Read;
7902+
output_error_log(error, &req);
7903+
return false;
7904+
}
7905+
}
7906+
// If not 100 Continue, server returned an error; proceed with that response
7907+
}
7908+
77857909
// Body
77867910
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
77877911
req.method != "CONNECT") {
@@ -9543,7 +9667,7 @@ bool SSLClient::load_certs() {
95439667
last_openssl_error_ = ERR_get_error();
95449668
ret = false;
95459669
}
9546-
} else {
9670+
} else if (!ca_cert_store_) {
95479671
auto loaded = false;
95489672
#ifdef _WIN32
95499673
loaded =
@@ -9790,7 +9914,11 @@ bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
97909914

97919915
bool SSLClient::check_host_name(const char *pattern,
97929916
size_t pattern_len) const {
9793-
if (host_.size() == pattern_len && host_ == pattern) { return true; }
9917+
// Exact match (case-insensitive)
9918+
if (host_.size() == pattern_len &&
9919+
detail::case_ignore::equal(host_, std::string(pattern, pattern_len))) {
9920+
return true;
9921+
}
97949922

97959923
// Wildcard match
97969924
// https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
@@ -9805,9 +9933,23 @@ bool SSLClient::check_host_name(const char *pattern,
98059933
auto itr = pattern_components.begin();
98069934
for (const auto &h : host_components_) {
98079935
auto &p = *itr;
9808-
if (p != h && p != "*") {
9809-
auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
9810-
!p.compare(0, p.size() - 1, h));
9936+
if (!httplib::detail::case_ignore::equal(p, h) && p != "*") {
9937+
bool partial_match = false;
9938+
if (!p.empty() && p[p.size() - 1] == '*') {
9939+
const auto prefix_length = p.size() - 1;
9940+
if (prefix_length == 0) {
9941+
partial_match = true;
9942+
} else if (h.size() >= prefix_length) {
9943+
partial_match =
9944+
std::equal(p.begin(),
9945+
p.begin() + static_cast<std::string::difference_type>(
9946+
prefix_length),
9947+
h.begin(), [](const char ca, const char cb) {
9948+
return httplib::detail::case_ignore::to_lower(ca) ==
9949+
httplib::detail::case_ignore::to_lower(cb);
9950+
});
9951+
}
9952+
}
98119953
if (!partial_match) { return false; }
98129954
}
98139955
++itr;

0 commit comments

Comments
 (0)