diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index aed0f576cc..a8bb6c4ffc 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.35.0" +HTTPLIB_VERSION = "refs/tags/v0.37.0" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index 7f76978fd8..c8f88d87df 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -813,17 +813,13 @@ bool is_websocket_upgrade(const Request &req) { // Check Upgrade: websocket (case-insensitive) auto upgrade_it = req.headers.find("Upgrade"); if (upgrade_it == req.headers.end()) { return false; } - auto upgrade_val = upgrade_it->second; - std::transform(upgrade_val.begin(), upgrade_val.end(), upgrade_val.begin(), - ::tolower); + auto upgrade_val = case_ignore::to_lower(upgrade_it->second); if (upgrade_val != "websocket") { return false; } // Check Connection header contains "Upgrade" auto connection_it = req.headers.find("Connection"); if (connection_it == req.headers.end()) { return false; } - auto connection_val = connection_it->second; - std::transform(connection_val.begin(), connection_val.end(), - connection_val.begin(), ::tolower); + auto connection_val = case_ignore::to_lower(connection_it->second); if (connection_val.find("upgrade") == std::string::npos) { return false; } // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars) @@ -2615,10 +2611,15 @@ bool can_compress_content_type(const std::string &content_type) { switch (tag) { case "image/svg+xml"_t: case "application/javascript"_t: + case "application/x-javascript"_t: case "application/json"_t: + case "application/ld+json"_t: case "application/xml"_t: - case "application/protobuf"_t: - case "application/xhtml+xml"_t: return true; + case "application/xhtml+xml"_t: + case "application/rss+xml"_t: + case "application/atom+xml"_t: + case "application/xslt+xml"_t: + case "application/protobuf"_t: return true; case "text/event-stream"_t: return false; @@ -3038,17 +3039,13 @@ bool read_websocket_upgrade_response(Stream &strm, // Verify Upgrade: websocket (case-insensitive) auto upgrade_it = headers.find("Upgrade"); if (upgrade_it == headers.end()) { return false; } - auto upgrade_val = upgrade_it->second; - std::transform(upgrade_val.begin(), upgrade_val.end(), upgrade_val.begin(), - ::tolower); + auto upgrade_val = case_ignore::to_lower(upgrade_it->second); if (upgrade_val != "websocket") { return false; } // Verify Connection header contains "Upgrade" (case-insensitive) auto connection_it = headers.find("Connection"); if (connection_it == headers.end()) { return false; } - auto connection_val = connection_it->second; - std::transform(connection_val.begin(), connection_val.end(), - connection_val.begin(), ::tolower); + auto connection_val = case_ignore::to_lower(connection_it->second); if (connection_val.find("upgrade") == std::string::npos) { return false; } // Verify Sec-WebSocket-Accept header value @@ -3934,14 +3931,10 @@ public: file_.content_type = trim_copy(header.substr(str_len(header_content_type))); } else { - thread_local const std::regex re_content_disposition( - R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", - std::regex_constants::icase); - - std::smatch m; - if (std::regex_match(header, m, re_content_disposition)) { + std::string disposition_params; + if (parse_content_disposition(header, disposition_params)) { Params params; - parse_disposition_params(m[1], params); + parse_disposition_params(disposition_params, params); auto it = params.find("name"); if (it != params.end()) { @@ -3956,13 +3949,14 @@ public: it = params.find("filename*"); if (it != params.end()) { - // Only allow UTF-8 encoding... - thread_local const std::regex re_rfc5987_encoding( - R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); - - std::smatch m2; - if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { - file_.filename = decode_path_component(m2[1]); // override... + // RFC 5987: only UTF-8 encoding is allowed + const auto &val = it->second; + constexpr const char utf8_prefix[] = "UTF-8''"; + constexpr size_t prefix_len = str_len(utf8_prefix); + if (val.size() > prefix_len && + start_with_case_ignore(val, utf8_prefix)) { + file_.filename = decode_path_component( + val.substr(prefix_len)); // override... } else { is_valid_ = false; return false; @@ -4030,17 +4024,48 @@ private: file_.headers.clear(); } - bool start_with_case_ignore(const std::string &a, const char *b) const { + bool start_with_case_ignore(const std::string &a, const char *b, + size_t offset = 0) const { const auto b_len = strlen(b); - if (a.size() < b_len) { return false; } + if (a.size() < offset + b_len) { return false; } for (size_t i = 0; i < b_len; i++) { - if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { + if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) { return false; } } return true; } + // Parses "Content-Disposition: form-data; " without std::regex. + // Returns true if header matches, with the params portion in `params_out`. + bool parse_content_disposition(const std::string &header, + std::string ¶ms_out) const { + constexpr const char prefix[] = "Content-Disposition:"; + constexpr size_t prefix_len = str_len(prefix); + + if (!start_with_case_ignore(header, prefix)) { return false; } + + // Skip whitespace after "Content-Disposition:" + auto pos = prefix_len; + while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + // Match "form-data;" (case-insensitive) + constexpr const char form_data[] = "form-data;"; + constexpr size_t form_data_len = str_len(form_data); + if (!start_with_case_ignore(header, form_data, pos)) { return false; } + pos += form_data_len; + + // Skip whitespace after "form-data;" + while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) { + pos++; + } + + params_out = header.substr(pos); + return true; + } + const std::string dash_ = "--"; const std::string crlf_ = "\r\n"; std::string boundary_; @@ -4992,9 +5017,10 @@ bool match_hostname(const std::string &pattern, // Verify certificate using Windows CertGetCertificateChain API. // This provides real-time certificate validation with Windows Update // integration, independent of the TLS backend (OpenSSL or MbedTLS). -bool verify_cert_with_windows_schannel( - const std::vector &der_cert, const std::string &hostname, - bool verify_hostname, unsigned long &out_error) { +bool +verify_cert_with_windows_schannel(const std::vector &der_cert, + const std::string &hostname, + bool verify_hostname, uint64_t &out_error) { if (der_cert.empty()) { return false; } out_error = 0; @@ -7987,7 +8013,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr, #else try { routed = routing(req, res, strm); - } catch (std::exception &e) { + } catch (std::exception &) { if (exception_handler_) { auto ep = std::current_exception(); exception_handler_(req, res, ep); @@ -11811,7 +11837,7 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { server_certificate_verification_) { verify_result_ = tls::get_verify_result(session); if (verify_result_ != 0) { - last_backend_error_ = static_cast(verify_result_); + last_backend_error_ = static_cast(verify_result_); error = Error::SSLServerVerification; output_error_log(error, nullptr); return false; @@ -11850,7 +11876,7 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { ca_cert_dir_path_.empty() && ca_cert_pem_.empty()) { std::vector der; if (get_cert_der(server_cert, der)) { - unsigned long wincrypt_error = 0; + uint64_t wincrypt_error = 0; if (!detail::verify_cert_with_windows_schannel( der, host_, server_hostname_verification_, wincrypt_error)) { last_backend_error_ = wincrypt_error; @@ -11974,16 +12000,26 @@ bool is_ipv4_address(const std::string &str) { // Parse IPv4 address string to bytes bool parse_ipv4(const std::string &str, unsigned char *out) { - int parts[4]; - if (sscanf(str.c_str(), "%d.%d.%d.%d", &parts[0], &parts[1], &parts[2], - &parts[3]) != 4) { - return false; - } + const char *p = str.c_str(); for (int i = 0; i < 4; i++) { - if (parts[i] < 0 || parts[i] > 255) return false; - out[i] = static_cast(parts[i]); + if (i > 0) { + if (*p != '.') { return false; } + p++; + } + int val = 0; + int digits = 0; + while (*p >= '0' && *p <= '9') { + val = val * 10 + (*p - '0'); + if (val > 255) { return false; } + p++; + digits++; + } + if (digits == 0) { return false; } + // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity + if (digits > 1 && *(p - digits) == '0') { return false; } + out[i] = static_cast(val); } - return true; + return *p == '\0'; } #ifdef _WIN32 @@ -13285,11 +13321,11 @@ void update_server_certs_from_x509(ctx_t ctx, X509 *cert, EVP_PKEY *key, ctx_t create_client_context_from_x509(X509 *cert, EVP_PKEY *key, const char *password, - unsigned long &out_error) { + uint64_t &out_error) { out_error = 0; auto ctx = create_client_context(); if (!ctx) { - out_error = static_cast(get_error()); + out_error = get_error(); return nullptr; } @@ -13303,7 +13339,7 @@ ctx_t create_client_context_from_x509(X509 *cert, EVP_PKEY *key, } if (!set_client_cert_pem(ctx, cert_pem.c_str(), key_pem.c_str(), password)) { - out_error = static_cast(get_error()); + out_error = get_error(); free_context(ctx); return nullptr; } diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index aea6fd308b..ac1908f421 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.35.0" -#define CPPHTTPLIB_VERSION_NUM "0x002300" +#define CPPHTTPLIB_VERSION "0.37.0" +#define CPPHTTPLIB_VERSION_NUM "0x002500" /* * Platform compatibility check @@ -575,6 +575,14 @@ inline unsigned char to_lower(int c) { return table[(unsigned char)(char)c]; } +inline std::string to_lower(const std::string &s) { + std::string result = s; + std::transform( + result.begin(), result.end(), result.begin(), + [](unsigned char c) { return static_cast(to_lower(c)); }); + return result; +} + inline bool equal(const std::string &a, const std::string &b) { return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { @@ -1859,23 +1867,23 @@ public: : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {} Result(std::unique_ptr &&res, Error err, Headers &&request_headers, - int ssl_error, unsigned long ssl_backend_error) + int ssl_error, uint64_t ssl_backend_error) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)), ssl_error_(ssl_error), ssl_backend_error_(ssl_backend_error) {} int ssl_error() const { return ssl_error_; } - unsigned long ssl_backend_error() const { return ssl_backend_error_; } + uint64_t ssl_backend_error() const { return ssl_backend_error_; } private: int ssl_error_ = 0; - unsigned long ssl_backend_error_ = 0; + uint64_t ssl_backend_error_ = 0; #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use ssl_backend_error() instead")]] - unsigned long ssl_openssl_error() const { + uint64_t ssl_openssl_error() const { return ssl_backend_error_; } #endif @@ -2345,7 +2353,7 @@ protected: bool server_hostname_verification_ = true; std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer int last_ssl_error_ = 0; - unsigned long last_backend_error_ = 0; + uint64_t last_backend_error_ = 0; #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT