vendor : update cpp-httplib to 0.49.0 (#25218)
This commit is contained in:
parent
fdb1db877c
commit
c8ae9a750c
Vendored
+127
-35
@@ -478,7 +478,7 @@ bool set_socket_opt_time(socket_t sock, int level, int optname,
|
||||
}
|
||||
|
||||
bool is_hex(char c, int &v) {
|
||||
if (isdigit(static_cast<unsigned char>(c))) {
|
||||
if (is_ascii_digit(c)) {
|
||||
v = c - '0';
|
||||
return true;
|
||||
} else if ('A' <= c && c <= 'F') {
|
||||
@@ -695,7 +695,11 @@ std::string base64_encode(const std::string &in) {
|
||||
std::string out;
|
||||
out.reserve(in.size());
|
||||
|
||||
auto val = 0;
|
||||
// Unsigned: the accumulator is never masked, so with a signed int the
|
||||
// `val << 8` below overflows once enough bytes are folded in (undefined
|
||||
// behaviour before C++20). Only the low bits are ever emitted, so the
|
||||
// wrap-around of an unsigned accumulator does not affect the output.
|
||||
uint32_t val = 0;
|
||||
auto valb = -6;
|
||||
|
||||
for (auto c : in) {
|
||||
@@ -3887,8 +3891,7 @@ bool parse_range_header(const std::string &s, Ranges &ranges) {
|
||||
bool parse_range_header(const std::string &s, Ranges &ranges) try {
|
||||
#endif
|
||||
auto is_valid = [](const std::string &str) {
|
||||
return std::all_of(str.cbegin(), str.cend(),
|
||||
[](unsigned char c) { return std::isdigit(c); });
|
||||
return std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
|
||||
};
|
||||
|
||||
if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
|
||||
@@ -4336,7 +4339,7 @@ bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
auto valid = true;
|
||||
for (size_t i = 0; i < boundary.size(); i++) {
|
||||
auto c = boundary[i];
|
||||
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '-' && c != '_') {
|
||||
if (!is_ascii_alnum(c) && c != '-' && c != '_') {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
@@ -4344,18 +4347,47 @@ bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
// Escape a multipart field name/filename following the WHATWG HTML standard
|
||||
// ("escape a multipart form-data name"), which is what browsers send:
|
||||
// '"' -> %22, CR -> %0D, LF -> %0A
|
||||
// With escape_quote = false, only CR and LF are escaped; this is for header
|
||||
// values outside a quoted-string (e.g. Content-Type), where '"' is legal.
|
||||
std::string escape_multipart_field(const std::string &s,
|
||||
bool escape_quote = true) {
|
||||
std::string result;
|
||||
result.reserve(s.size());
|
||||
for (auto c : s) {
|
||||
switch (c) {
|
||||
case '"':
|
||||
if (escape_quote) {
|
||||
result += "%22";
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
break;
|
||||
case '\r': result += "%0D"; break;
|
||||
case '\n': result += "%0A"; break;
|
||||
default: result += c; break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string
|
||||
serialize_multipart_formdata_item_begin(const T &item,
|
||||
const std::string &boundary) {
|
||||
std::string body = "--" + boundary + "\r\n";
|
||||
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
|
||||
body += "Content-Disposition: form-data; name=\"" +
|
||||
escape_multipart_field(item.name) + "\"";
|
||||
if (!item.filename.empty()) {
|
||||
body += "; filename=\"" + item.filename + "\"";
|
||||
body += "; filename=\"" + escape_multipart_field(item.filename) + "\"";
|
||||
}
|
||||
body += "\r\n";
|
||||
if (!item.content_type.empty()) {
|
||||
body += "Content-Type: " + item.content_type + "\r\n";
|
||||
body +=
|
||||
"Content-Type: " + escape_multipart_field(item.content_type, false) +
|
||||
"\r\n";
|
||||
}
|
||||
body += "\r\n";
|
||||
|
||||
@@ -4821,10 +4853,9 @@ private:
|
||||
namespace fields {
|
||||
|
||||
bool is_token_char(char c) {
|
||||
return std::isalnum(static_cast<unsigned char>(c)) || c == '!' || c == '#' ||
|
||||
c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
|
||||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
|
||||
c == '|' || c == '~';
|
||||
return is_ascii_alnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
|
||||
c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
|
||||
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
|
||||
}
|
||||
|
||||
bool is_token(const std::string &s) {
|
||||
@@ -4873,7 +4904,8 @@ bool is_field_value(const std::string &s) { return is_field_content(s); }
|
||||
} // namespace fields
|
||||
|
||||
bool perform_websocket_handshake(Stream &strm, const std::string &host,
|
||||
int port, const std::string &path,
|
||||
int port, bool is_ssl,
|
||||
const std::string &path,
|
||||
const Headers &headers,
|
||||
std::string &selected_subprotocol) {
|
||||
// Validate path and host
|
||||
@@ -4899,7 +4931,7 @@ bool perform_websocket_handshake(Stream &strm, const std::string &host,
|
||||
|
||||
// Build upgrade request
|
||||
std::string req_str = "GET " + path + " HTTP/1.1\r\n";
|
||||
req_str += "Host: " + host + ":" + std::to_string(port) + "\r\n";
|
||||
req_str += "Host: " + make_host_and_port_string(host, port, is_ssl) + "\r\n";
|
||||
req_str += "Upgrade: websocket\r\n";
|
||||
req_str += "Connection: Upgrade\r\n";
|
||||
req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
|
||||
@@ -5599,9 +5631,8 @@ std::string encode_uri_component(const std::string &value) {
|
||||
escaped << std::hex;
|
||||
|
||||
for (auto c : value) {
|
||||
if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
|
||||
c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
|
||||
c == ')') {
|
||||
if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
|
||||
c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
|
||||
escaped << c;
|
||||
} else {
|
||||
escaped << std::uppercase;
|
||||
@@ -5620,10 +5651,10 @@ std::string encode_uri(const std::string &value) {
|
||||
escaped << std::hex;
|
||||
|
||||
for (auto c : value) {
|
||||
if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
|
||||
c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
|
||||
c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
|
||||
c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
|
||||
if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
|
||||
c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' ||
|
||||
c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' ||
|
||||
c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
|
||||
escaped << c;
|
||||
} else {
|
||||
escaped << std::uppercase;
|
||||
@@ -5684,7 +5715,8 @@ std::string encode_path_component(const std::string &component) {
|
||||
auto c = static_cast<unsigned char>(component[i]);
|
||||
|
||||
// Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
|
||||
if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
|
||||
c == '_' || c == '~') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
|
||||
@@ -5757,7 +5789,8 @@ std::string encode_query_component(const std::string &component,
|
||||
auto c = static_cast<unsigned char>(component[i]);
|
||||
|
||||
// Unreserved characters per RFC 3986
|
||||
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
|
||||
if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
|
||||
c == '_' || c == '~') {
|
||||
result += static_cast<char>(c);
|
||||
}
|
||||
// Space handling
|
||||
@@ -6010,6 +6043,48 @@ size_t MultipartFormData::get_file_count(const std::string &key) const {
|
||||
return static_cast<size_t>(std::distance(r.first, r.second));
|
||||
}
|
||||
|
||||
// Multipart FormData writer implementation
|
||||
bool is_valid_multipart_boundary(const std::string &boundary) {
|
||||
return detail::is_multipart_boundary_chars_valid(boundary);
|
||||
}
|
||||
|
||||
MultipartFormDataWriter::MultipartFormDataWriter()
|
||||
: boundary_(detail::make_multipart_data_boundary()) {}
|
||||
|
||||
MultipartFormDataWriter::MultipartFormDataWriter(std::string boundary)
|
||||
: boundary_(std::move(boundary)) {}
|
||||
|
||||
const std::string &MultipartFormDataWriter::boundary() const {
|
||||
return boundary_;
|
||||
}
|
||||
|
||||
std::string MultipartFormDataWriter::content_type() const {
|
||||
return detail::serialize_multipart_formdata_get_content_type(boundary_);
|
||||
}
|
||||
|
||||
std::string
|
||||
MultipartFormDataWriter::serialize(const UploadFormDataItems &items) const {
|
||||
return detail::serialize_multipart_formdata(items, boundary_);
|
||||
}
|
||||
|
||||
size_t MultipartFormDataWriter::content_length(
|
||||
const UploadFormDataItems &items) const {
|
||||
return detail::get_multipart_content_length(items, boundary_);
|
||||
}
|
||||
|
||||
std::string
|
||||
MultipartFormDataWriter::item_begin(const UploadFormData &item) const {
|
||||
return detail::serialize_multipart_formdata_item_begin(item, boundary_);
|
||||
}
|
||||
|
||||
std::string MultipartFormDataWriter::item_end() {
|
||||
return detail::serialize_multipart_formdata_item_end();
|
||||
}
|
||||
|
||||
std::string MultipartFormDataWriter::finish() const {
|
||||
return detail::serialize_multipart_formdata_finish(boundary_);
|
||||
}
|
||||
|
||||
// Response implementation
|
||||
size_t Response::get_header_value_u64(const std::string &key, size_t def,
|
||||
size_t id) const {
|
||||
@@ -6229,8 +6304,10 @@ ssize_t detail::BodyReader::read(char *buf, size_t len) {
|
||||
}
|
||||
|
||||
// ThreadPool implementation
|
||||
ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr)
|
||||
: base_thread_count_(n), max_queued_requests_(mqr), idle_thread_count_(0),
|
||||
ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr,
|
||||
time_t idle_timeout_sec)
|
||||
: base_thread_count_(n), max_queued_requests_(mqr),
|
||||
idle_timeout_sec_(idle_timeout_sec), idle_thread_count_(0),
|
||||
shutdown_(false) {
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
if (max_n != 0 && max_n < n) {
|
||||
@@ -6340,9 +6417,9 @@ void ThreadPool::worker(bool is_dynamic) {
|
||||
idle_thread_count_++;
|
||||
|
||||
if (is_dynamic) {
|
||||
auto has_work = cond_.wait_for(
|
||||
lock, std::chrono::seconds(CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT),
|
||||
[&] { return !jobs_.empty() || shutdown_; });
|
||||
auto has_work =
|
||||
cond_.wait_for(lock, std::chrono::seconds(idle_timeout_sec_),
|
||||
[&] { return !jobs_.empty() || shutdown_; });
|
||||
if (!has_work) {
|
||||
// Timed out with no work - exit this dynamic thread
|
||||
idle_thread_count_--;
|
||||
@@ -9687,9 +9764,18 @@ bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
|
||||
if (!query_part.empty()) {
|
||||
// Normalize the query string (decode then re-encode) while preserving
|
||||
// the original parameter order.
|
||||
auto normalized = detail::normalize_query_string(query_part);
|
||||
if (!normalized.empty()) { path_with_query += '?' + normalized; }
|
||||
// the original parameter order. When path encoding is disabled the
|
||||
// caller has supplied an already-encoded target and expects the exact
|
||||
// bytes to be sent on the wire, so skip normalization for the query
|
||||
// too. Normalizing here would decode-then-re-encode the query and
|
||||
// corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`,
|
||||
// which a strict RFC 3986 server decodes back as `+`, not a space).
|
||||
if (path_encode_) {
|
||||
auto normalized = detail::normalize_query_string(query_part);
|
||||
if (!normalized.empty()) { path_with_query += '?' + normalized; }
|
||||
} else {
|
||||
path_with_query += '?' + query_part;
|
||||
}
|
||||
|
||||
// Still populate req.params for handlers/users who read them.
|
||||
detail::parse_query_text(query_part, req.params);
|
||||
@@ -12518,7 +12604,7 @@ bool is_ipv4_address(const std::string &str) {
|
||||
for (char c : str) {
|
||||
if (c == '.') {
|
||||
dots++;
|
||||
} else if (!isdigit(static_cast<unsigned char>(c))) {
|
||||
} else if (!detail::is_ascii_digit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -12535,7 +12621,7 @@ bool parse_ipv4(const std::string &str, unsigned char *out) {
|
||||
}
|
||||
int val = 0;
|
||||
int digits = 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
while (detail::is_ascii_digit(*p)) {
|
||||
val = val * 10 + (*p - '0');
|
||||
if (val > 255) { return false; }
|
||||
p++;
|
||||
@@ -16487,9 +16573,15 @@ bool WebSocketClient::connect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
auto is_ssl = is_ssl_;
|
||||
#else
|
||||
auto is_ssl = false;
|
||||
#endif
|
||||
|
||||
std::string selected_subprotocol;
|
||||
if (!detail::perform_websocket_handshake(*strm, host_, port_, path_, headers_,
|
||||
selected_subprotocol)) {
|
||||
if (!detail::perform_websocket_handshake(*strm, host_, port_, is_ssl, path_,
|
||||
headers_, selected_subprotocol)) {
|
||||
shutdown_and_close();
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user