vendor : update cpp-httplib to 0.51.0 (#26067)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
This commit is contained in:
@@ -5,7 +5,7 @@ import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
HTTPLIB_VERSION = "refs/tags/v0.50.1"
|
||||
HTTPLIB_VERSION = "refs/tags/v0.51.0"
|
||||
|
||||
vendor = {
|
||||
"https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp",
|
||||
|
||||
Vendored
+258
-63
@@ -3161,9 +3161,7 @@ get_multimap_value(const Map &m, const std::string &key, size_t id) {
|
||||
|
||||
void set_header(Headers &headers, const std::string &key,
|
||||
const std::string &val) {
|
||||
if (fields::is_field_name(key) && fields::is_field_value(val)) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
if (fields::is_field_valid(key, val)) { headers.emplace(key, val); }
|
||||
}
|
||||
|
||||
bool read_headers(Stream &strm, Headers &headers) {
|
||||
@@ -3370,8 +3368,46 @@ ReadContentResult read_content_chunked(Stream &strm, T &x,
|
||||
}
|
||||
|
||||
bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
return case_ignore::equal(
|
||||
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
|
||||
// RFC 9112 6.1: a message is framed with the chunked coding when "chunked"
|
||||
// is the final transfer coding. A single field value may list several
|
||||
// codings ("gzip, chunked"), and the list may be split across multiple
|
||||
// Transfer-Encoding header lines (RFC 9110 5.3). Match the last coding token
|
||||
// case-insensitively rather than comparing the whole value against "chunked".
|
||||
//
|
||||
// Security: reading a chunked message as unframed leaves its body in the
|
||||
// socket, where a keep-alive connection parses it as a smuggled request.
|
||||
// Headers is an unordered_multimap whose iteration order for duplicate keys
|
||||
// is not portable, so when there is more than one Transfer-Encoding line we
|
||||
// cannot tell which coding is truly final. In that ambiguous case we fail
|
||||
// safe by treating the message as chunked (a mis-parse just closes the
|
||||
// connection, whereas the opposite error enables smuggling).
|
||||
auto rng = headers.equal_range("Transfer-Encoding");
|
||||
|
||||
size_t line_count = 0;
|
||||
bool chunked_present = false;
|
||||
bool last_line_ends_with_chunked = false;
|
||||
|
||||
for (auto it = rng.first; it != rng.second; ++it) {
|
||||
line_count++;
|
||||
const auto &value = it->second;
|
||||
|
||||
std::string last_coding;
|
||||
bool line_has_chunked = false;
|
||||
split(value.data(), value.data() + value.size(), ',',
|
||||
[&](const char *b, const char *e) {
|
||||
last_coding.assign(b, e);
|
||||
if (case_ignore::equal(last_coding, "chunked")) {
|
||||
line_has_chunked = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (line_has_chunked) { chunked_present = true; }
|
||||
last_line_ends_with_chunked = case_ignore::equal(last_coding, "chunked");
|
||||
}
|
||||
|
||||
if (line_count == 0) { return false; }
|
||||
if (line_count == 1) { return last_line_ends_with_chunked; }
|
||||
return chunked_present;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -3488,6 +3524,13 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
|
||||
ssize_t write_request_line(Stream &strm, const std::string &method,
|
||||
const std::string &path) {
|
||||
// A request target must not carry CR/LF (or other control octets); otherwise
|
||||
// a value smuggled into it splits the request line and injects headers or a
|
||||
// whole request. The same field-value check already guards header values in
|
||||
// check_and_write_headers and the request target in
|
||||
// perform_websocket_handshake; apply it here too.
|
||||
if (!fields::is_field_value(path)) { return -1; }
|
||||
|
||||
std::string s = method;
|
||||
s += ' ';
|
||||
s += path;
|
||||
@@ -3507,6 +3550,13 @@ ssize_t write_response_line(Stream &strm, int status) {
|
||||
ssize_t write_headers(Stream &strm, const Headers &headers) {
|
||||
ssize_t write_len = 0;
|
||||
for (const auto &x : headers) {
|
||||
// Skip fields with invalid names or values to prevent response splitting
|
||||
// via CR/LF injection, matching set_header(). The client validates request
|
||||
// headers up front in check_and_write_headers, but the server passes
|
||||
// res.headers straight to this writer, and res.headers is a public field
|
||||
// an application can populate directly with request-derived values.
|
||||
if (!fields::is_field_valid(x.first, x.second)) { continue; }
|
||||
|
||||
std::string s;
|
||||
s = x.first;
|
||||
s += ": ";
|
||||
@@ -3707,10 +3757,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
for (const auto &kv : *trailer) {
|
||||
// Skip fields with invalid names or values to prevent response
|
||||
// splitting via CR/LF injection, matching set_header().
|
||||
if (!fields::is_field_name(kv.first) ||
|
||||
!fields::is_field_value(kv.second)) {
|
||||
continue;
|
||||
}
|
||||
if (!fields::is_field_valid(kv.first, kv.second)) { continue; }
|
||||
std::string field_line = kv.first + ": " + kv.second + "\r\n";
|
||||
if (!write_data(strm, field_line.data(), field_line.size())) {
|
||||
ok = false;
|
||||
@@ -4079,6 +4126,13 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check header count limit
|
||||
if (header_count_ >= CPPHTTPLIB_HEADER_MAX_COUNT) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
header_count_++;
|
||||
|
||||
const auto header = buf_head(pos);
|
||||
|
||||
if (!parse_header(header.data(), header.data() + header.size(),
|
||||
@@ -4194,6 +4248,7 @@ private:
|
||||
file_.filename.clear();
|
||||
file_.content_type.clear();
|
||||
file_.headers.clear();
|
||||
header_count_ = 0;
|
||||
}
|
||||
|
||||
bool start_with_case_ignore(const std::string &a, const char *b,
|
||||
@@ -4247,6 +4302,7 @@ private:
|
||||
size_t state_ = 0;
|
||||
bool is_valid_ = false;
|
||||
FormData file_;
|
||||
size_t header_count_ = 0;
|
||||
|
||||
// Buffer
|
||||
bool start_with(const std::string &a, size_t spos, size_t epos,
|
||||
@@ -4907,6 +4963,10 @@ bool is_field_content(const std::string &s) {
|
||||
|
||||
bool is_field_value(const std::string &s) { return is_field_content(s); }
|
||||
|
||||
bool is_field_valid(const std::string &name, const std::string &value) {
|
||||
return is_field_name(name) && is_field_value(value);
|
||||
}
|
||||
|
||||
} // namespace fields
|
||||
|
||||
bool perform_websocket_handshake(Stream &strm, const std::string &host,
|
||||
@@ -4921,9 +4981,7 @@ bool perform_websocket_handshake(Stream &strm, const std::string &host,
|
||||
|
||||
// Validate user-provided headers
|
||||
for (const auto &h : headers) {
|
||||
if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) {
|
||||
return false;
|
||||
}
|
||||
if (!fields::is_field_valid(h.first, h.second)) { return false; }
|
||||
}
|
||||
|
||||
// Generate random Sec-WebSocket-Key
|
||||
@@ -5042,9 +5100,31 @@ std::string hash_to_hex(const unsigned char (&hash)[N]) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Mbed TLS 4.x provides hashing (and TLS RNG) via PSA Crypto, which must be
|
||||
// initialized once. PSA state is process-global; do not free it.
|
||||
bool ensure_mbedtls_psa_crypto() {
|
||||
static std::once_flag once;
|
||||
static bool ok = false;
|
||||
std::call_once(once, []() { ok = (psa_crypto_init() == PSA_SUCCESS); });
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool psa_hash(psa_algorithm_t alg, const std::string &s,
|
||||
unsigned char *out, size_t out_size) {
|
||||
if (!ensure_mbedtls_psa_crypto()) { return false; }
|
||||
size_t olen = 0;
|
||||
return psa_hash_compute(alg, reinterpret_cast<const uint8_t *>(s.data()),
|
||||
s.size(), out, out_size, &olen) == PSA_SUCCESS &&
|
||||
olen == out_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string MD5(const std::string &s) {
|
||||
unsigned char hash[16];
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
if (!psa_hash(PSA_ALG_MD5, s, hash, sizeof(hash))) { return {}; }
|
||||
#elif defined(CPPHTTPLIB_MBEDTLS_V3)
|
||||
mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
|
||||
hash);
|
||||
#else
|
||||
@@ -5056,7 +5136,9 @@ std::string MD5(const std::string &s) {
|
||||
|
||||
std::string SHA_256(const std::string &s) {
|
||||
unsigned char hash[32];
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
if (!psa_hash(PSA_ALG_SHA_256, s, hash, sizeof(hash))) { return {}; }
|
||||
#elif defined(CPPHTTPLIB_MBEDTLS_V3)
|
||||
mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
|
||||
hash, 0);
|
||||
#else
|
||||
@@ -5068,7 +5150,9 @@ std::string SHA_256(const std::string &s) {
|
||||
|
||||
std::string SHA_512(const std::string &s) {
|
||||
unsigned char hash[64];
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
if (!psa_hash(PSA_ALG_SHA_512, s, hash, sizeof(hash))) { return {}; }
|
||||
#elif defined(CPPHTTPLIB_MBEDTLS_V3)
|
||||
mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
|
||||
hash, 0);
|
||||
#else
|
||||
@@ -6949,8 +7033,7 @@ template <typename T>
|
||||
bool check_and_write_headers(Stream &strm, Headers &headers,
|
||||
T header_writer, Error &error) {
|
||||
for (const auto &h : headers) {
|
||||
if (!detail::fields::is_field_name(h.first) ||
|
||||
!detail::fields::is_field_value(h.second)) {
|
||||
if (!detail::fields::is_field_valid(h.first, h.second)) {
|
||||
error = Error::InvalidHeaders;
|
||||
return false;
|
||||
}
|
||||
@@ -8348,25 +8431,25 @@ get_client_ip(const std::string &x_forwarded_for,
|
||||
// caller can fall back to the connection-level remote address.
|
||||
if (ip_list.empty()) { return std::string(); }
|
||||
|
||||
for (size_t i = 0; i < ip_list.size(); ++i) {
|
||||
auto ip = ip_list[i];
|
||||
// Each hop appends the address it received the request from, so the rightmost
|
||||
// entries are the ones written by our own infrastructure while the leftmost
|
||||
// are whatever the original client chose to send. Walk from the right and
|
||||
// skip trusted proxies; the first address that is not a trusted proxy is the
|
||||
// furthest point still attributable to a real hop, i.e. the client. Scanning
|
||||
// from the left instead lets a client forge an arbitrary address by following
|
||||
// it with a trusted proxy's address, which the left-to-right scan then
|
||||
// returned as the client.
|
||||
for (size_t i = ip_list.size(); i-- > 0;) {
|
||||
const auto &ip = ip_list[i];
|
||||
|
||||
auto is_trusted_proxy =
|
||||
std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
|
||||
[&](const std::string &proxy) { return ip == proxy; });
|
||||
|
||||
if (is_trusted_proxy) {
|
||||
if (i == 0) {
|
||||
// If the trusted proxy is the first IP, there's no preceding client IP
|
||||
return ip;
|
||||
} else {
|
||||
// Return the IP immediately before the trusted proxy
|
||||
return ip_list[i - 1];
|
||||
}
|
||||
}
|
||||
if (!is_trusted_proxy) { return ip; }
|
||||
}
|
||||
|
||||
// If no trusted proxy is found, return the first IP in the list
|
||||
// Every hop was a trusted proxy; fall back to the first entry.
|
||||
return ip_list.front();
|
||||
}
|
||||
|
||||
@@ -8436,7 +8519,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
connection_closed = true;
|
||||
}
|
||||
|
||||
if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
|
||||
// Only honor X-Forwarded-For if the peer on the actual TCP connection is
|
||||
// itself a trusted proxy. Otherwise any direct client could spoof
|
||||
// remote_addr simply by sending an arbitrary X-Forwarded-For header.
|
||||
auto is_trusted_peer = std::any_of(
|
||||
trusted_proxies_.begin(), trusted_proxies_.end(),
|
||||
[&](const std::string &proxy) { return proxy == remote_addr; });
|
||||
|
||||
if (is_trusted_peer && req.has_header("X-Forwarded-For")) {
|
||||
auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
|
||||
auto derived = get_client_ip(x_forwarded_for, trusted_proxies_);
|
||||
req.remote_addr = derived.empty() ? remote_addr : derived;
|
||||
@@ -8643,13 +8733,19 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
|
||||
// Drain any unconsumed framed body to prevent request smuggling on
|
||||
// keep-alive. Without framing there is no body to drain — reading would
|
||||
// consume the next request (issue #2450).
|
||||
// consume the next request (issue #2450). If the response has committed the
|
||||
// connection to close, there is no next request to protect.
|
||||
if (!req.body_consumed_ && detail::has_framed_body(req)) {
|
||||
int dummy_status;
|
||||
if (!detail::read_content(
|
||||
strm, req, payload_max_length_, dummy_status, nullptr,
|
||||
[](const char *, size_t, size_t, size_t) { return true; }, false)) {
|
||||
if (res.get_header_value("Connection") == "close") {
|
||||
connection_closed = true;
|
||||
} else {
|
||||
int dummy_status;
|
||||
if (!detail::read_content(
|
||||
strm, req, payload_max_length_, dummy_status, nullptr,
|
||||
[](const char *, size_t, size_t, size_t) { return true; },
|
||||
false)) {
|
||||
connection_closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9210,9 +9306,8 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
|
||||
handle.body_reader_.content_length = content_length;
|
||||
}
|
||||
|
||||
auto transfer_encoding =
|
||||
handle.response->get_header_value("Transfer-Encoding");
|
||||
handle.body_reader_.chunked = (transfer_encoding == "chunked");
|
||||
handle.body_reader_.chunked =
|
||||
detail::is_chunked_transfer_encoding(handle.response->headers);
|
||||
|
||||
auto content_encoding = handle.response->get_header_value("Content-Encoding");
|
||||
if (!content_encoding.empty()) {
|
||||
@@ -9541,8 +9636,8 @@ bool ClientImpl::create_redirect_client(
|
||||
|
||||
// Clean up request headers that are host/client specific
|
||||
// Remove headers that should not be carried over to new host
|
||||
auto headers_to_remove =
|
||||
std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
|
||||
auto headers_to_remove = std::vector<std::string>{
|
||||
"Host", "Proxy-Authorization", "Authorization", "Cookie", "Cookie2"};
|
||||
|
||||
for (const auto &header_name : headers_to_remove) {
|
||||
auto it = req.headers.find(header_name);
|
||||
@@ -9796,7 +9891,14 @@ bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
}
|
||||
|
||||
// Write request line and headers
|
||||
detail::write_request_line(bstrm, req.method, path_with_query);
|
||||
if (detail::write_request_line(bstrm, req.method, path_with_query) < 0) {
|
||||
// A rejected target (e.g. CR/LF smuggled in via a decoded redirect
|
||||
// Location under set_path_encode(false)) must fail the request cleanly
|
||||
// instead of emitting a request-line-less, header-injecting request.
|
||||
error = Error::Write;
|
||||
output_error_log(error, &req);
|
||||
return false;
|
||||
}
|
||||
if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_,
|
||||
error)) {
|
||||
output_error_log(error, &req);
|
||||
@@ -13893,6 +13995,13 @@ struct MbedTlsSession {
|
||||
std::string hostname; // For client: set via set_sni
|
||||
std::string sni_hostname; // For server: received from client via SNI callback
|
||||
|
||||
// Mbed TLS has no SSL_peek() equivalent, so is_peer_closed() must probe with
|
||||
// a real 1-byte mbedtls_ssl_read(). If that probe lands on application data
|
||||
// (e.g. a response that arrived while this side was still in its post-write
|
||||
// check), the byte is pushed back here and served by the next read().
|
||||
unsigned char peeked_byte = 0;
|
||||
bool has_peeked_byte = false;
|
||||
|
||||
MbedTlsSession() { mbedtls_ssl_init(&ssl); }
|
||||
|
||||
~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
|
||||
@@ -13927,6 +14036,20 @@ ErrorCode map_mbedtls_error(int ret, int &out_errno) {
|
||||
return ErrorCode::Fatal;
|
||||
}
|
||||
|
||||
// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a
|
||||
// non-fatal notification delivered between records, not an error and not
|
||||
// application data, so I/O calls that see it should just be retried. Kept in
|
||||
// one helper so the retry loops keep an intact "do { } while (...)" instead of
|
||||
// splitting the closing brace across an #if.
|
||||
bool mbedtls_is_session_ticket(int ret) {
|
||||
#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
|
||||
return ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET;
|
||||
#else
|
||||
(void)ret;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// BIO-like send callback for Mbed TLS
|
||||
int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
|
||||
size_t len) {
|
||||
@@ -13978,8 +14101,10 @@ int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
|
||||
// MbedTlsContext constructor/destructor implementations
|
||||
MbedTlsContext::MbedTlsContext() {
|
||||
mbedtls_ssl_config_init(&conf);
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
#endif
|
||||
mbedtls_x509_crt_init(&ca_chain);
|
||||
mbedtls_x509_crt_init(&own_cert);
|
||||
mbedtls_pk_init(&own_key);
|
||||
@@ -13989,8 +14114,10 @@ MbedTlsContext::~MbedTlsContext() {
|
||||
mbedtls_pk_free(&own_key);
|
||||
mbedtls_x509_crt_free(&own_cert);
|
||||
mbedtls_x509_crt_free(&ca_chain);
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
#endif
|
||||
mbedtls_ssl_config_free(&conf);
|
||||
}
|
||||
|
||||
@@ -14064,6 +14191,14 @@ ctx_t create_client_context() {
|
||||
|
||||
ctx->is_server = false;
|
||||
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready.
|
||||
if (!detail::ensure_mbedtls_psa_crypto()) {
|
||||
delete ctx;
|
||||
return nullptr;
|
||||
}
|
||||
int ret;
|
||||
#else
|
||||
// Seed the random number generator
|
||||
const char *pers = "httplib_client";
|
||||
int ret = mbedtls_ctr_drbg_seed(
|
||||
@@ -14074,6 +14209,7 @@ ctx_t create_client_context() {
|
||||
delete ctx;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set up SSL config for client
|
||||
ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
|
||||
@@ -14085,8 +14221,10 @@ ctx_t create_client_context() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set random number generator
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly)
|
||||
mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
|
||||
#endif
|
||||
|
||||
// Default: verify peer certificate
|
||||
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
@@ -14108,6 +14246,14 @@ ctx_t create_server_context() {
|
||||
|
||||
ctx->is_server = true;
|
||||
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready.
|
||||
if (!detail::ensure_mbedtls_psa_crypto()) {
|
||||
delete ctx;
|
||||
return nullptr;
|
||||
}
|
||||
int ret;
|
||||
#else
|
||||
// Seed the random number generator
|
||||
const char *pers = "httplib_server";
|
||||
int ret = mbedtls_ctr_drbg_seed(
|
||||
@@ -14118,6 +14264,7 @@ ctx_t create_server_context() {
|
||||
delete ctx;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set up SSL config for server
|
||||
ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
|
||||
@@ -14129,8 +14276,10 @@ ctx_t create_server_context() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set random number generator
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly)
|
||||
mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
|
||||
#endif
|
||||
|
||||
// Default: don't verify client
|
||||
mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
@@ -14290,7 +14439,7 @@ bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
|
||||
password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
|
||||
size_t pwd_len = password ? strlen(password) : 0;
|
||||
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
|
||||
ret = mbedtls_pk_parse_key(
|
||||
&mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
|
||||
key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
|
||||
@@ -14305,7 +14454,10 @@ bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the certificate and private key match
|
||||
// Verify that the certificate and private key match.
|
||||
// Mbed TLS 4.x: mbedtls_pk_check_pair() reports a spurious mismatch for
|
||||
// PSA-backed keys, so skip it and let the handshake surface a real mismatch.
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
|
||||
mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
|
||||
@@ -14316,6 +14468,7 @@ bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
|
||||
impl::mbedtls_last_error() = ret;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
|
||||
if (ret != 0) {
|
||||
@@ -14339,7 +14492,7 @@ bool set_client_cert_file(ctx_t ctx, const char *cert_path,
|
||||
}
|
||||
|
||||
// Parse private key file
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
|
||||
ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
|
||||
mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
|
||||
#else
|
||||
@@ -14350,7 +14503,9 @@ bool set_client_cert_file(ctx_t ctx, const char *cert_path,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the certificate and private key match
|
||||
// Verify that the certificate and private key match.
|
||||
// Mbed TLS 4.x: see set_client_cert() — skip the spurious check_pair.
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
|
||||
mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
|
||||
@@ -14361,6 +14516,7 @@ bool set_client_cert_file(ctx_t ctx, const char *cert_path,
|
||||
impl::mbedtls_last_error() = ret;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
|
||||
if (ret != 0) {
|
||||
@@ -14455,7 +14611,10 @@ TlsError connect(session_t session) {
|
||||
}
|
||||
|
||||
auto msession = static_cast<impl::MbedTlsSession *>(session);
|
||||
int ret = mbedtls_ssl_handshake(&msession->ssl);
|
||||
int ret;
|
||||
do {
|
||||
ret = mbedtls_ssl_handshake(&msession->ssl);
|
||||
} while (impl::mbedtls_is_session_ticket(ret));
|
||||
|
||||
if (ret == 0) {
|
||||
err.code = ErrorCode::Success;
|
||||
@@ -14499,6 +14658,8 @@ bool connect_nonblocking(session_t session, socket_t sock,
|
||||
|
||||
int ret;
|
||||
while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
|
||||
// Non-fatal TLS 1.3 ticket; retry immediately.
|
||||
if (impl::mbedtls_is_session_ticket(ret)) { continue; }
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
||||
if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
|
||||
continue;
|
||||
@@ -14546,8 +14707,28 @@ ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
|
||||
}
|
||||
|
||||
auto msession = static_cast<impl::MbedTlsSession *>(session);
|
||||
int ret =
|
||||
mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len);
|
||||
|
||||
// Serve a byte consumed by the is_peer_closed() probe before reading more.
|
||||
if (msession->has_peeked_byte) {
|
||||
if (len == 0) { return 0; }
|
||||
auto p = static_cast<unsigned char *>(buf);
|
||||
p[0] = msession->peeked_byte;
|
||||
msession->has_peeked_byte = false;
|
||||
size_t n = 1;
|
||||
// Top up with any already-decrypted bytes without risking a block.
|
||||
if (len > 1 && mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) {
|
||||
int extra = mbedtls_ssl_read(&msession->ssl, p + 1, len - 1);
|
||||
if (extra > 0) { n += static_cast<size_t>(extra); }
|
||||
}
|
||||
err.code = ErrorCode::Success;
|
||||
return static_cast<ssize_t>(n);
|
||||
}
|
||||
|
||||
int ret;
|
||||
do {
|
||||
ret = mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf),
|
||||
len);
|
||||
} while (impl::mbedtls_is_session_ticket(ret));
|
||||
|
||||
if (ret > 0) {
|
||||
err.code = ErrorCode::Success;
|
||||
@@ -14576,8 +14757,11 @@ ssize_t write(session_t session, const void *buf, size_t len,
|
||||
}
|
||||
|
||||
auto msession = static_cast<impl::MbedTlsSession *>(session);
|
||||
int ret = mbedtls_ssl_write(&msession->ssl,
|
||||
static_cast<const unsigned char *>(buf), len);
|
||||
int ret;
|
||||
do {
|
||||
ret = mbedtls_ssl_write(&msession->ssl,
|
||||
static_cast<const unsigned char *>(buf), len);
|
||||
} while (impl::mbedtls_is_session_ticket(ret));
|
||||
|
||||
if (ret > 0) {
|
||||
err.code = ErrorCode::Success;
|
||||
@@ -14599,7 +14783,8 @@ int pending(const_session_t session) {
|
||||
if (!session) { return 0; }
|
||||
auto msession =
|
||||
static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
|
||||
return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl));
|
||||
return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl)) +
|
||||
(msession->has_peeked_byte ? 1 : 0);
|
||||
}
|
||||
|
||||
void shutdown(session_t session, bool graceful) {
|
||||
@@ -14625,24 +14810,34 @@ bool is_peer_closed(session_t session, socket_t sock) {
|
||||
if (!session || sock == INVALID_SOCKET) { return true; }
|
||||
auto msession = static_cast<impl::MbedTlsSession *>(session);
|
||||
|
||||
// Check if there's already decrypted data available in the TLS buffer
|
||||
// If so, the connection is definitely alive
|
||||
if (mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { return false; }
|
||||
// Check if there's already decrypted or pushed-back data available.
|
||||
// If so, the connection is definitely alive.
|
||||
if (msession->has_peeked_byte ||
|
||||
mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set socket to non-blocking to avoid blocking on read
|
||||
detail::set_nonblocking(sock, true);
|
||||
auto cleanup =
|
||||
detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
|
||||
|
||||
// Try a 1-byte read to check connection status
|
||||
// Note: This will consume the byte if data is available, but for the
|
||||
// purpose of checking if peer is closed, this should be acceptable
|
||||
// since we're only called when we expect the connection might be closing
|
||||
// Probe with a 1-byte read (Mbed TLS has no peek API). If the probe lands
|
||||
// on application data — e.g. a response that already arrived — push the
|
||||
// byte back so the next read() delivers it instead of losing it.
|
||||
unsigned char buf;
|
||||
int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
|
||||
int ret;
|
||||
do {
|
||||
ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
|
||||
} while (impl::mbedtls_is_session_ticket(ret));
|
||||
|
||||
// If we got data or WANT_READ (would block), connection is alive
|
||||
if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
|
||||
if (ret > 0) {
|
||||
msession->peeked_byte = buf;
|
||||
msession->has_peeked_byte = true;
|
||||
return false;
|
||||
}
|
||||
if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
|
||||
|
||||
// If we get a peer close notify or a connection reset, the peer is closed
|
||||
return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
|
||||
@@ -15049,7 +15244,7 @@ bool update_server_cert(ctx_t ctx, const char *cert_pem,
|
||||
}
|
||||
|
||||
// Parse private key PEM
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_V3
|
||||
#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
|
||||
ret = mbedtls_pk_parse_key(
|
||||
&mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
|
||||
strlen(key_pem) + 1,
|
||||
|
||||
Vendored
+37
-10
@@ -8,8 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.50.1"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x003201"
|
||||
#define CPPHTTPLIB_VERSION "0.51.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x003300"
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
|
||||
@@ -420,18 +420,26 @@ using socket_t = int;
|
||||
#endif // CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
// version.h defines MBEDTLS_VERSION_MAJOR (on 2.x/3.x/4.x alike); it is pulled
|
||||
// in with this first include group so the version gating below can use it.
|
||||
#include <mbedtls/error.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/net_sockets.h>
|
||||
#include <mbedtls/oid.h>
|
||||
#include <mbedtls/pk.h>
|
||||
#include <mbedtls/ssl.h>
|
||||
#include <mbedtls/version.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#if MBEDTLS_VERSION_MAJOR >= 4
|
||||
// Mbed TLS 4.x moved hashing/RNG to PSA Crypto and removed these headers.
|
||||
#include <psa/crypto.h>
|
||||
#else
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/sha512.h>
|
||||
#include <mbedtls/ssl.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <wincrypt.h>
|
||||
#ifdef _MSC_VER
|
||||
@@ -444,7 +452,11 @@ using socket_t = int;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Mbed TLS 3.x API compatibility
|
||||
// Mbed TLS version API compatibility. Note: V4 implies V3 (both defined on
|
||||
// 4.x), so version-specific 3.x-only code must check V3 && !V4.
|
||||
#if MBEDTLS_VERSION_MAJOR >= 4
|
||||
#define CPPHTTPLIB_MBEDTLS_V4
|
||||
#endif
|
||||
#if MBEDTLS_VERSION_MAJOR >= 3
|
||||
#define CPPHTTPLIB_MBEDTLS_V3
|
||||
#endif
|
||||
@@ -696,7 +708,7 @@ inline from_chars_result<T> from_chars(const char *first, const char *last,
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
value = negative ? -result : result;
|
||||
value = negative ? T(0) - result : result;
|
||||
return {p, std::errc{}};
|
||||
}
|
||||
|
||||
@@ -2957,7 +2969,18 @@ inline size_t get_header_value_u64(const Headers &headers,
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
if (it != rng.second) {
|
||||
if (is_numeric(it->second)) {
|
||||
return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
|
||||
// Parse at size_t width so an out-of-range Content-Length is reported
|
||||
// rather than silently saturated/truncated (a value above 2^32 would
|
||||
// otherwise wrap to a small framing length on 32-bit builds). Flag it
|
||||
// and return SIZE_MAX so the existing oversized-value guards reject it.
|
||||
size_t val = 0;
|
||||
const auto &s = it->second;
|
||||
auto r = from_chars(s.data(), s.data() + s.size(), val);
|
||||
if (r.ec == std::errc::result_out_of_range) {
|
||||
is_invalid_value = true;
|
||||
return (std::numeric_limits<size_t>::max)();
|
||||
}
|
||||
return val;
|
||||
} else {
|
||||
is_invalid_value = true;
|
||||
}
|
||||
@@ -3403,6 +3426,7 @@ bool is_obs_text(char c);
|
||||
bool is_field_vchar(char c);
|
||||
bool is_field_content(const std::string &s);
|
||||
bool is_field_value(const std::string &s);
|
||||
bool is_field_valid(const std::string &name, const std::string &value);
|
||||
|
||||
} // namespace fields
|
||||
} // namespace detail
|
||||
@@ -3422,8 +3446,11 @@ namespace impl {
|
||||
// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
|
||||
struct MbedTlsContext {
|
||||
mbedtls_ssl_config conf;
|
||||
#ifndef CPPHTTPLIB_MBEDTLS_V4
|
||||
// Mbed TLS 4.x uses PSA Crypto's internal RNG; no explicit entropy/DRBG.
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
#endif
|
||||
mbedtls_x509_crt ca_chain;
|
||||
mbedtls_x509_crt own_cert;
|
||||
mbedtls_pk_context own_key;
|
||||
|
||||
Reference in New Issue
Block a user