Skip to content

Commit 4720819

Browse files
authored
vendor : update cpp-httplib to 0.35.0 (ggml-org#19969)
Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>
1 parent d979f2b commit 4720819

4 files changed

Lines changed: 144 additions & 80 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.34.0"
8+
HTTPLIB_VERSION = "refs/tags/v0.35.0"
99

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

vendor/cpp-httplib/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ endif()
171171
if (CPPHTTPLIB_OPENSSL_SUPPORT)
172172
target_compile_definitions(${TARGET} PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT) # used in server.cpp
173173
if (APPLE AND CMAKE_SYSTEM_NAME STREQUAL "Darwin")
174-
target_compile_definitions(${TARGET} PRIVATE CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
175174
find_library(CORE_FOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
176175
find_library(SECURITY_FRAMEWORK Security REQUIRED)
177176
target_link_libraries(${TARGET} PUBLIC ${CORE_FOUNDATION_FRAMEWORK} ${SECURITY_FRAMEWORK})

vendor/cpp-httplib/httplib.cpp

Lines changed: 112 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,10 +2571,46 @@ find_content_type(const std::string &path,
25712571
}
25722572
}
25732573

2574+
std::string
2575+
extract_media_type(const std::string &content_type,
2576+
std::map<std::string, std::string> *params = nullptr) {
2577+
// Extract type/subtype from Content-Type value (RFC 2045)
2578+
// e.g. "application/json; charset=utf-8" -> "application/json"
2579+
auto media_type = content_type;
2580+
auto semicolon_pos = media_type.find(';');
2581+
if (semicolon_pos != std::string::npos) {
2582+
auto param_str = media_type.substr(semicolon_pos + 1);
2583+
media_type = media_type.substr(0, semicolon_pos);
2584+
2585+
if (params) {
2586+
// Parse parameters: key=value pairs separated by ';'
2587+
split(param_str.data(), param_str.data() + param_str.size(), ';',
2588+
[&](const char *b, const char *e) {
2589+
std::string key;
2590+
std::string val;
2591+
split(b, e, '=', [&](const char *b2, const char *e2) {
2592+
if (key.empty()) {
2593+
key.assign(b2, e2);
2594+
} else {
2595+
val.assign(b2, e2);
2596+
}
2597+
});
2598+
if (!key.empty()) {
2599+
params->emplace(trim_copy(key), trim_double_quotes_copy(val));
2600+
}
2601+
});
2602+
}
2603+
}
2604+
2605+
// Trim whitespace from media type
2606+
return trim_copy(media_type);
2607+
}
2608+
25742609
bool can_compress_content_type(const std::string &content_type) {
25752610
using udl::operator""_t;
25762611

2577-
auto tag = str2tag(content_type);
2612+
auto mime_type = extract_media_type(content_type);
2613+
auto tag = str2tag(mime_type);
25782614

25792615
switch (tag) {
25802616
case "image/svg+xml"_t:
@@ -2586,7 +2622,7 @@ bool can_compress_content_type(const std::string &content_type) {
25862622

25872623
case "text/event-stream"_t: return false;
25882624

2589-
default: return !content_type.rfind("text/", 0);
2625+
default: return !mime_type.rfind("text/", 0);
25902626
}
25912627
}
25922628

@@ -3141,7 +3177,8 @@ bool is_chunked_transfer_encoding(const Headers &headers) {
31413177
template <typename T, typename U>
31423178
bool prepare_content_receiver(T &x, int &status,
31433179
ContentReceiverWithProgress receiver,
3144-
bool decompress, U callback) {
3180+
bool decompress, size_t payload_max_length,
3181+
bool &exceed_payload_max_length, U callback) {
31453182
if (decompress) {
31463183
std::string encoding = x.get_header_value("Content-Encoding");
31473184
std::unique_ptr<decompressor> decompressor;
@@ -3157,12 +3194,22 @@ bool prepare_content_receiver(T &x, int &status,
31573194

31583195
if (decompressor) {
31593196
if (decompressor->is_valid()) {
3197+
size_t decompressed_size = 0;
31603198
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
31613199
size_t off, size_t len) {
3162-
return decompressor->decompress(buf, n,
3163-
[&](const char *buf2, size_t n2) {
3164-
return receiver(buf2, n2, off, len);
3165-
});
3200+
return decompressor->decompress(
3201+
buf, n, [&](const char *buf2, size_t n2) {
3202+
// Guard against zip-bomb: check
3203+
// decompressed size against limit.
3204+
if (payload_max_length > 0 &&
3205+
(decompressed_size >= payload_max_length ||
3206+
n2 > payload_max_length - decompressed_size)) {
3207+
exceed_payload_max_length = true;
3208+
return false;
3209+
}
3210+
decompressed_size += n2;
3211+
return receiver(buf2, n2, off, len);
3212+
});
31663213
};
31673214
return callback(std::move(out));
31683215
} else {
@@ -3183,11 +3230,14 @@ template <typename T>
31833230
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
31843231
DownloadProgress progress,
31853232
ContentReceiverWithProgress receiver, bool decompress) {
3233+
bool exceed_payload_max_length = false;
31863234
return prepare_content_receiver(
3187-
x, status, std::move(receiver), decompress,
3188-
[&](const ContentReceiverWithProgress &out) {
3235+
x, status, std::move(receiver), decompress, payload_max_length,
3236+
exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
31893237
auto ret = true;
3190-
auto exceed_payload_max_length = false;
3238+
// Note: exceed_payload_max_length may also be set by the decompressor
3239+
// wrapper in prepare_content_receiver when the decompressed payload
3240+
// size exceeds the limit.
31913241

31923242
if (is_chunked_transfer_encoding(x.headers)) {
31933243
auto result = read_content_chunked(strm, x, payload_max_length, out);
@@ -3603,12 +3653,11 @@ std::string normalize_query_string(const std::string &query) {
36033653

36043654
bool parse_multipart_boundary(const std::string &content_type,
36053655
std::string &boundary) {
3606-
auto boundary_keyword = "boundary=";
3607-
auto pos = content_type.find(boundary_keyword);
3608-
if (pos == std::string::npos) { return false; }
3609-
auto end = content_type.find(';', pos);
3610-
auto beg = pos + strlen(boundary_keyword);
3611-
boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
3656+
std::map<std::string, std::string> params;
3657+
extract_media_type(content_type, &params);
3658+
auto it = params.find("boundary");
3659+
if (it == params.end()) { return false; }
3660+
boundary = it->second;
36123661
return !boundary.empty();
36133662
}
36143663

@@ -3776,11 +3825,7 @@ bool parse_accept_header(const std::string &s,
37763825
}
37773826

37783827
// Remove additional parameters from media type
3779-
auto param_pos = accept_entry.media_type.find(';');
3780-
if (param_pos != std::string::npos) {
3781-
accept_entry.media_type =
3782-
trim_copy(accept_entry.media_type.substr(0, param_pos));
3783-
}
3828+
accept_entry.media_type = extract_media_type(accept_entry.media_type);
37843829

37853830
// Basic validation of media type format
37863831
if (accept_entry.media_type.empty()) {
@@ -5610,7 +5655,7 @@ size_t Request::get_param_value_count(const std::string &key) const {
56105655

56115656
bool Request::is_multipart_form_data() const {
56125657
const auto &content_type = get_header_value("Content-Type");
5613-
return !content_type.rfind("multipart/form-data", 0);
5658+
return detail::extract_media_type(content_type) == "multipart/form-data";
56145659
}
56155660

56165661
// Multipart FormData implementation
@@ -7092,7 +7137,8 @@ bool Server::read_content(Stream &strm, Request &req, Response &res) {
70927137
return true;
70937138
})) {
70947139
const auto &content_type = req.get_header_value("Content-Type");
7095-
if (!content_type.find("application/x-www-form-urlencoded")) {
7140+
if (detail::extract_media_type(content_type) ==
7141+
"application/x-www-form-urlencoded") {
70967142
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
70977143
res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
70987144
output_error_log(Error::ExceedMaxPayloadSize, &req);
@@ -7479,45 +7525,63 @@ bool Server::routing(Request &req, Response &res, Stream &strm) {
74797525
if (detail::expect_content(req)) {
74807526
// Content reader handler
74817527
{
7528+
// Track whether the ContentReader was aborted due to the decompressed
7529+
// payload exceeding `payload_max_length_`.
7530+
// The user handler runs after the lambda returns, so we must restore the
7531+
// 413 status if the handler overwrites it.
7532+
bool content_reader_payload_too_large = false;
7533+
74827534
ContentReader reader(
74837535
[&](ContentReceiver receiver) {
74847536
auto result = read_content_with_content_receiver(
74857537
strm, req, res, std::move(receiver), nullptr, nullptr);
7486-
if (!result) { output_error_log(Error::Read, &req); }
7538+
if (!result) {
7539+
output_error_log(Error::Read, &req);
7540+
if (res.status == StatusCode::PayloadTooLarge_413) {
7541+
content_reader_payload_too_large = true;
7542+
}
7543+
}
74877544
return result;
74887545
},
74897546
[&](FormDataHeader header, ContentReceiver receiver) {
74907547
auto result = read_content_with_content_receiver(
74917548
strm, req, res, nullptr, std::move(header),
74927549
std::move(receiver));
7493-
if (!result) { output_error_log(Error::Read, &req); }
7550+
if (!result) {
7551+
output_error_log(Error::Read, &req);
7552+
if (res.status == StatusCode::PayloadTooLarge_413) {
7553+
content_reader_payload_too_large = true;
7554+
}
7555+
}
74947556
return result;
74957557
});
74967558

7559+
bool dispatched = false;
74977560
if (req.method == "POST") {
7498-
if (dispatch_request_for_content_reader(
7499-
req, res, std::move(reader),
7500-
post_handlers_for_content_reader_)) {
7501-
return true;
7502-
}
7561+
dispatched = dispatch_request_for_content_reader(
7562+
req, res, std::move(reader), post_handlers_for_content_reader_);
75037563
} else if (req.method == "PUT") {
7504-
if (dispatch_request_for_content_reader(
7505-
req, res, std::move(reader),
7506-
put_handlers_for_content_reader_)) {
7507-
return true;
7508-
}
7564+
dispatched = dispatch_request_for_content_reader(
7565+
req, res, std::move(reader), put_handlers_for_content_reader_);
75097566
} else if (req.method == "PATCH") {
7510-
if (dispatch_request_for_content_reader(
7511-
req, res, std::move(reader),
7512-
patch_handlers_for_content_reader_)) {
7513-
return true;
7514-
}
7567+
dispatched = dispatch_request_for_content_reader(
7568+
req, res, std::move(reader), patch_handlers_for_content_reader_);
75157569
} else if (req.method == "DELETE") {
7516-
if (dispatch_request_for_content_reader(
7517-
req, res, std::move(reader),
7518-
delete_handlers_for_content_reader_)) {
7519-
return true;
7570+
dispatched = dispatch_request_for_content_reader(
7571+
req, res, std::move(reader), delete_handlers_for_content_reader_);
7572+
}
7573+
7574+
if (dispatched) {
7575+
if (content_reader_payload_too_large) {
7576+
// Enforce the limit: override any status the handler may have set
7577+
// and return false so the error path sends a plain 413 response.
7578+
res.status = StatusCode::PayloadTooLarge_413;
7579+
res.body.clear();
7580+
res.content_length_ = 0;
7581+
res.content_provider_ = nullptr;
7582+
return false;
75207583
}
7584+
return true;
75217585
}
75227586
}
75237587

@@ -7930,16 +7994,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
79307994
routed = true;
79317995
} else {
79327996
res.status = StatusCode::InternalServerError_500;
7933-
std::string val;
7934-
auto s = e.what();
7935-
for (size_t i = 0; s[i]; i++) {
7936-
switch (s[i]) {
7937-
case '\r': val += "\\r"; break;
7938-
case '\n': val += "\\n"; break;
7939-
default: val += s[i]; break;
7940-
}
7941-
}
7942-
res.set_header("EXCEPTION_WHAT", val);
79437997
}
79447998
} catch (...) {
79457999
if (exception_handler_) {
@@ -7948,7 +8002,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
79488002
routed = true;
79498003
} else {
79508004
res.status = StatusCode::InternalServerError_500;
7951-
res.set_header("EXCEPTION_WHAT", "UNKNOWN");
79528005
}
79538006
}
79548007
#endif
@@ -11629,8 +11682,7 @@ void SSLClient::set_session_verifier(
1162911682
session_verifier_ = std::move(verifier);
1163011683
}
1163111684

11632-
#if defined(_WIN32) && \
11633-
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
11685+
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1163411686
void SSLClient::enable_windows_certificate_verification(bool enabled) {
1163511687
enable_windows_cert_verification_ = enabled;
1163611688
}
@@ -11788,8 +11840,7 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
1178811840
}
1178911841
}
1179011842

11791-
#if defined(_WIN32) && \
11792-
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
11843+
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1179311844
// Additional Windows Schannel verification.
1179411845
// This provides real-time certificate validation with Windows Update
1179511846
// integration, working with both OpenSSL and MbedTLS backends.
@@ -11835,8 +11886,7 @@ void Client::enable_server_hostname_verification(bool enabled) {
1183511886
cli_->enable_server_hostname_verification(enabled);
1183611887
}
1183711888

11838-
#if defined(_WIN32) && \
11839-
!defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
11889+
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
1184011890
void Client::enable_windows_certificate_verification(bool enabled) {
1184111891
if (is_ssl_) {
1184211892
static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
@@ -11959,7 +12009,7 @@ bool enumerate_windows_system_certs(Callback cb) {
1195912009
}
1196012010
#endif
1196112011

11962-
#if defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
12012+
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
1196312013
// Enumerate macOS Keychain certificates and call callback with DER data
1196412014
template <typename Callback>
1196512015
bool enumerate_macos_keychain_certs(Callback cb) {

0 commit comments

Comments
 (0)