Compare commits

..
11 Commits
Author SHA1 Message Date
yhirose 7e5d0683ac Release v0.65.0 2026-11-05 07:39:47 -05:00
yhirose 6df971434e Do not carry Request::params over to a redirect's Location
A request sent with send(Request) and params got those params appended
to the Location of a redirect that had no query, on the same origin or
another one. The Location alone names the next target, as it already
did for Get/Post(path, params).
2026-11-05 07:04:31 -05:00
Eminy 39c98d4671 Ignore a Range header with a unit other than bytes (#2657)
RFC 9110 14.2 says an origin server MUST ignore a Range header field
with a range unit it does not understand. parse_range_header() accepts
only bytes=, so a request such as "Range: items=1-2" got a 416 instead
of the full response. The unit is now checked first, and only a bytes
range is parsed and answered with a 416 when it is invalid.
2026-11-04 22:44:22 -05:00
Wei-Lun Liandyhirose a1ba73a256 Cap a send() or wolfSSL_read() length at INT_MAX (#2655)
* Cap a send() or wolfSSL_read() length at INT_MAX

On 64-bit Windows SocketStream::write() handed size to send(), whose
length is an int, so a single write of 4 GiB or more went out as its low
32 bits: a 4 GiB + 1 byte write sent one byte, and write_data() then
retried the remaining 4 GiB as zero-byte sends forever. The INT_MAX cap
that read() has was limited to 32-bit Windows in df20c27 (#1294), when
the body was still written with one unchecked write(); write_data() now
sends the rest, so the cap applies to every Windows build again.

The wolfSSL tls::read() had the same cast, so a read into a buffer over
INT_MAX failed. Cap it as the OpenSSL backend has done since f80864c.

* Drop the 4 GiB write test and shorten comments

The Windows-only test allocated 4 GiB and took about 5 s, which is too
heavy for CI.

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-11-04 18:06:30 -05:00
metsw24-maxandyhirose 453b1b8613 keep a relative Location on its host in resolve_relative_location (#2654)
* keep a relative Location on its host in resolve_relative_location

* Shorten the comment on on_this_host in resolve_relative_location

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-11-02 13:12:31 -05:00
Yoshiki Matsuda a18895f42e Let ClientImpl::stop() interrupt a request waiting on Windows (#2653)
stop() shuts the socket down to end a request in flight, but on Windows
that does not wake a read waiting in WSAPoll (#1396). The request then
waited until the server answered, the read timeout passed, or the OS
dropped the connection.

stop() now also sets a flag, and send_() gives the request's stream an
interrupt check that reads it, as SSEClient::stop() does since 801bf2e.
The wait for data then ends within 100 ms on every platform. A request
started after stop() clears the flag. A redirect or a digest retry sent
inside the request keeps it.

select_read_interruptible() now rounds its last wait up to a whole
millisecond. poll() takes milliseconds, and the wait ended up to 1 ms
before the deadline, which MaxTimeoutTest caught once every client
request waited through this function.
2026-10-30 13:40:50 -04:00
yhirose 24e58ce5b2 Release v0.64.0 2026-10-29 17:08:34 -04:00
Kim, Hyuk a0ab09fc0a Add set_basic_auth and set_bearer_token_auth to WebSocketClient (#2652)
ClientImpl and SSLClient let callers set origin credentials with
set_basic_auth()/set_bearer_token_auth(), but WebSocketClient could only
take a fixed Authorization header through its constructor. Add both
setters: each connect() adds the Authorization header to the upgrade
request from the current credentials (Basic over Bearer), unless the
constructor headers already carry one, and never to the proxy's CONNECT
request.
2026-10-28 16:15:17 -04:00
Kim, Hyuk 290c4c0bbe Add set_tunnel_hostname_addr_map() and deprecate set_hostname_addr_map() (#2647)
Through a CONNECT proxy it is the proxy that connects to the origin, and the CONNECT target was always the original hostname, so there was no way to send the tunnel to another address. set_tunnel_hostname_addr_map() sets that target on SSLClient, Client and WebSocketClient. The CONNECT request's Host follows it, including the Digest retry, while the Host header, the SNI and the certificate check inside the tunnel keep the original hostname. The map is also copied to a redirect client.

set_hostname_addr_map() is deprecated in favor of set_direct_hostname_addr_map(), which behaves the same and names the connection it applies to. The two maps are independent and correspond to curl's --resolve and --connect-to; neither applies to the proxy's own address.
2026-10-27 21:09:25 -04:00
metsw24-max 25ba40f6fe Reject an EOF without close_notify in the Mbed TLS and wolfSSL read (#2649)
Mbed TLS and wolfSSL read a TLS EOF without close_notify as a clean end, so a response delimited by the connection close could be cut short and still come back complete. Both reads now end cleanly only on a close_notify, as on OpenSSL.
2026-10-27 18:31:55 -04:00
metsw24-max 578e4815b7 Treat ':' as a path separator in sanitize_filename on Windows (#2651)
On Windows a ':' ends a drive prefix or starts an NTFS stream name, so a filename such as "C:.." or "a.txt:stream" survived sanitize_filename and could replace the upload directory or write to an alternate data stream. Cut at ':' on Windows as well.
2026-10-27 18:16:21 -04:00
7 changed files with 986 additions and 62 deletions

No files matched your search

+22 -1
View File
@@ -200,6 +200,10 @@ void close(CloseStatus status = CloseStatus::Normal,
const std::string &reason = ""); const std::string &reason = "");
bool is_open() const; bool is_open() const;
// Authentication - the same setters Client has (Basic wins over Bearer).
void set_basic_auth(const std::string &username, const std::string &password);
void set_bearer_token_auth(const std::string &token);
// Proxy - the same setters Client has. connect() opens a CONNECT tunnel // Proxy - the same setters Client has. connect() opens a CONNECT tunnel
// through the proxy for ws:// as well as wss:// (RFC 6455 4.1); the proxy // through the proxy for ws:// as well as wss:// (RFC 6455 4.1); the proxy
// credentials are sent only on the CONNECT request, never through the tunnel. // credentials are sent only on the CONNECT request, never through the tunnel.
@@ -233,7 +237,8 @@ void set_address_family(int family);
void set_ipv6_v6only(bool on); void set_ipv6_v6only(bool on);
void set_socket_options(SocketOptions socket_options); void set_socket_options(SocketOptions socket_options);
void set_interface(const std::string &intf); void set_interface(const std::string &intf);
void set_hostname_addr_map(std::map<std::string, std::string> addr_map); void set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);
// SSL configuration (wss:// only, SSL builds only) // SSL configuration (wss:// only, SSL builds only)
void set_ca_cert_path(const std::string &ca_cert_file_path, void set_ca_cert_path(const std::string &ca_cert_file_path,
@@ -403,6 +408,22 @@ svr.set_pre_request_handler([](const httplib::Request &req, httplib::Response &r
The server accepts a handshake from any `Origin`. A browser sends its cookies with a handshake another site starts, so if cookies authenticate the connection, also check `req.get_header_value("Origin")` against your own origins in one of these handlers (cross-site WebSocket hijacking). The server accepts a handshake from any `Origin`. A browser sends its cookies with a handshake another site starts, so if cookies authenticate the connection, also check `req.get_header_value("Origin")` against your own origins in one of these handlers (cross-site WebSocket hijacking).
### Authentication
```cpp
httplib::ws::WebSocketClient ws("wss://example.com/ws");
ws.set_bearer_token_auth("token123"); // Authorization: Bearer token123
// or
ws.set_basic_auth("user", "pass"); // Authorization: Basic dXNlcjpwYXNz
if (ws.connect()) { /* ... */ }
```
The credentials are read on every `connect()`, so a new token takes effect on
the next reconnect. An `Authorization` header passed to the constructor wins
over them. Through a proxy they are sent only inside the tunnel, never on
`CONNECT`.
### Custom Headers and Timeouts ### Custom Headers and Timeouts
```cpp ```cpp
+33 -9
View File
@@ -742,7 +742,7 @@ svr.Post("/multipart", [&](const Request& req, Response& res) {
auto safe = httplib::sanitize_filename(file.filename); auto safe = httplib::sanitize_filename(file.filename);
``` ```
This function keeps only the part after the last `/` or `\`, removes null bytes, and trims leading and trailing spaces and tabs. It returns an empty string if what is left is empty, `.` or `..`. This function keeps only the part after the last `/` or `\` (or, on Windows, `:`, which starts a drive prefix such as `C:` or an NTFS stream name), removes null bytes, and trims leading and trailing spaces and tabs. It returns an empty string if what is left is empty, `.` or `..`.
> [!NOTE] > [!NOTE]
> `name` and `filename` are read as quoted-strings (RFC 9110 §5.6.4), where `\"` and `\\` stand for `"` and `\`. Browsers send a backslash as it is, so from a browser, a value with `\\` in it arrives with a single `\`, and one that ends in `\` is not read correctly. > `name` and `filename` are read as quoted-strings (RFC 9110 §5.6.4), where `\"` and `\\` stand for `"` and `\`. Browsers send a backslash as it is, so from a browser, a value with `\\` in it arrives with a single `\`, and one that ends in `\` is not read correctly.
@@ -1477,17 +1477,17 @@ The same method is available on `httplib::ws::WebSocketClient`.
### Override the connection target for a hostname ### Override the connection target for a hostname
`set_hostname_addr_map` redirects where the socket connects, without changing `set_direct_hostname_addr_map` redirects where the socket connects, without
the identity of the request. The hostname the client was constructed with keeps changing the identity of the request. The hostname the client was constructed
supplying the `Host` header, the SNI, and the name that the server certificate with keeps supplying the `Host` header, the SNI, and the name that the server
is verified against, so this is a connection-level override only, not a way to certificate is verified against, so this is a connection-level override only,
talk to a different origin. not a way to talk to a different origin.
```cpp ```cpp
httplib::Client cli("https://example.com"); httplib::Client cli("https://example.com");
// Connect to this IP address instead of resolving "example.com" // Connect to this IP address instead of resolving "example.com"
cli.set_hostname_addr_map({{"example.com", "192.168.1.10"}}); cli.set_direct_hostname_addr_map({{"example.com", "192.168.1.10"}});
``` ```
A mapped value may be an IP literal or another hostname. An IP literal is used A mapped value may be an IP literal or another hostname. An IP literal is used
@@ -1495,13 +1495,37 @@ as-is; anything else is resolved as a name, so a host that is only reachable
under a different name works too: under a different name works too:
```cpp ```cpp
cli.set_hostname_addr_map({{"example.com", "internal.example.lan"}}); cli.set_direct_hostname_addr_map({{"example.com", "internal.example.lan"}});
``` ```
An empty value is ignored, leaving the original hostname as the connection An empty value is ignored, leaving the original hostname as the connection
target. target.
The same method is available on `httplib::ws::WebSocketClient`. The direct map applies only when the client connects to the origin itself.
Through a proxy, use `set_tunnel_hostname_addr_map`: its value becomes the
`CONNECT` target (and that request's `Host`) that the proxy is asked to connect
to (HTTPS and WebSocket). The `Host` header, the SNI and the certificate check
inside the tunnel keep the original hostname:
```cpp
httplib::Client cli("https://sni.example");
cli.set_tunnel_hostname_addr_map({{"sni.example", "server.example"}});
cli.set_proxy("proxy.example", 3128);
// CONNECT server.example:443, SNI sni.example
```
The two maps are independent; to use the same target on both paths, set both.
They correspond to curl's `--resolve` and `--connect-to`.
Neither map applies to the proxy's own address ("direct" means not through a
proxy, as in a PAC file's `DIRECT`). To connect to the proxy at a fixed
address, pass an IP literal to `set_proxy()`.
`set_hostname_addr_map` is deprecated and works the same as
`set_direct_hostname_addr_map`.
These methods are also available on `httplib::ws::WebSocketClient`.
### Automatic Path Encoding ### Automatic Path Encoding
+1 -1
View File
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
[site] [site]
title = "cpp-httplib" title = "cpp-httplib"
version = "0.63.0" version = "0.65.0"
hostname = "https://yhirose.github.io" hostname = "https://yhirose.github.io"
base_path = "/cpp-httplib" base_path = "/cpp-httplib"
footer_message = "© 2026 Yuji Hirose. All rights reserved." footer_message = "© 2026 Yuji Hirose. All rights reserved."
+1 -1
View File
@@ -214,7 +214,7 @@ svr.listen("/tmp/httplib.sock", 80); // The port is unused, but must not be 0
// Client // Client
httplib::Client cli("http://localhost"); httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX); cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}}); cli.set_direct_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
auto res = cli.Get("/"); auto res = cli.Get("/");
``` ```
+1 -1
View File
@@ -214,7 +214,7 @@ svr.listen("/tmp/httplib.sock", 80); // ポート番号は使われない(0
// クライアント // クライアント
httplib::Client cli("http://localhost"); httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX); cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}}); cli.set_direct_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
auto res = cli.Get("/"); auto res = cli.Get("/");
``` ```
+156 -29
View File
@@ -8,8 +8,8 @@
#ifndef CPPHTTPLIB_HTTPLIB_H #ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.63.0" #define CPPHTTPLIB_VERSION "0.65.0"
#define CPPHTTPLIB_VERSION_NUM "0x003f00" #define CPPHTTPLIB_VERSION_NUM "0x004100"
#ifdef _WIN32 #ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
@@ -1029,18 +1029,27 @@ inline std::string resolve_relative_location(const std::string &location,
// A ':' in the first segment means the value has a scheme. // A ':' in the first segment means the value has a scheme.
if (location.find(':') < location.find_first_of("/?#")) { return location; } if (location.find(':') < location.find_first_of("/?#")) { return location; }
if (location[0] == '#') { return base.substr(0, base.find('#')) + location; } // A path starting with "//" would be re-parsed as an authority. Prefix "/."
// (as the URL Standard does); remove_dot_segments strips it later.
auto on_this_host = [](std::string path) -> std::string {
if (path.compare(0, 2, "//") == 0) { path.insert(0, "/."); }
return path;
};
if (location[0] == '#') {
return on_this_host(base.substr(0, base.find('#')) + location);
}
auto base_path = base.substr(0, base.find_first_of("?#")); auto base_path = base.substr(0, base.find_first_of("?#"));
if (location[0] == '?') { return base_path + location; } if (location[0] == '?') { return on_this_host(base_path + location); }
if (base_path.empty() || base_path[0] != '/') { base_path = "/"; } if (base_path.empty() || base_path[0] != '/') { base_path = "/"; }
auto merged = base_path.substr(0, base_path.rfind('/') + 1) + location; auto merged = base_path.substr(0, base_path.rfind('/') + 1) + location;
// Remove "." and ".." segments from the merged path. // Remove "." and ".." segments from the merged path.
auto path_end = (std::min)(merged.find_first_of("?#"), merged.size()); auto path_end = (std::min)(merged.find_first_of("?#"), merged.size());
return remove_dot_segments(merged.substr(0, path_end)) + return on_this_host(remove_dot_segments(merged.substr(0, path_end)) +
merged.substr(path_end); merged.substr(path_end));
} }
} // namespace detail } // namespace detail
@@ -2927,7 +2936,12 @@ public:
size_t is_socket_open() const; size_t is_socket_open() const;
socket_t socket() const; socket_t socket() const;
[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map); void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_default_headers(Headers headers); void set_default_headers(Headers headers);
@@ -3053,12 +3067,19 @@ protected:
bool socket_should_be_closed_when_request_is_done_ = false; bool socket_should_be_closed_when_request_is_done_ = false;
size_t socket_tls_settings_version_ = 0; size_t socket_tls_settings_version_ = 0;
// Set by stop() while a request is in flight. The request's reads poll it,
// because shutting the socket down does not wake a read on Windows.
std::atomic<bool> stop_requested_{false};
std::atomic<size_t> tls_settings_version_{0}; std::atomic<size_t> tls_settings_version_{0};
// Hostname to connection target map. The value is an IP literal or another // Hostname to connection target map. The value is an IP literal or another
// hostname; only the connection target changes, never the identity. // hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_; std::map<std::string, std::string> addr_map_;
// Hostname to CONNECT target map, used only through a proxy.
std::map<std::string, std::string> tunnel_addr_map_;
// Default headers // Default headers
Headers default_headers_; Headers default_headers_;
@@ -3348,7 +3369,12 @@ public:
size_t is_socket_open() const; size_t is_socket_open() const;
socket_t socket() const; socket_t socket() const;
[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map); void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_default_headers(Headers headers); void set_default_headers(Headers headers);
@@ -4748,7 +4774,15 @@ public:
set_connection_timeout(const std::chrono::duration<Rep, Period> &duration); set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
void set_interface(const std::string &intf); void set_interface(const std::string &intf);
[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map); void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_basic_auth(const std::string &username, const std::string &password);
void set_bearer_token_auth(const std::string &token);
void set_proxy(const std::string &host, int port); void set_proxy(const std::string &host, int port);
void set_proxy_basic_auth(const std::string &username, void set_proxy_basic_auth(const std::string &username,
@@ -4820,6 +4854,13 @@ private:
// hostname; only the connection target changes, never the identity. // hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_; std::map<std::string, std::string> addr_map_;
// Hostname to CONNECT target map, used only through a proxy.
std::map<std::string, std::string> tunnel_addr_map_;
std::string basic_auth_username_;
std::string basic_auth_password_;
std::string bearer_token_auth_token_;
std::string proxy_host_; std::string proxy_host_;
int proxy_port_ = -1; int proxy_port_ = -1;
std::string proxy_basic_auth_username_; std::string proxy_basic_auth_username_;
@@ -6812,10 +6853,14 @@ select_read_interruptible(socket_t sock, time_t sec, time_t usec,
while (!interrupted()) { while (!interrupted()) {
auto left = std::chrono::duration_cast<std::chrono::microseconds>( auto left = std::chrono::duration_cast<std::chrono::microseconds>(
deadline - std::chrono::steady_clock::now()); deadline - std::chrono::steady_clock::now());
auto wait = auto last = left < slice;
(std::max)((std::min)(left, slice), std::chrono::microseconds(0)); // poll() takes whole milliseconds, so round the last wait up to keep it
auto ret = select_impl(sock, POLLIN, 0, static_cast<time_t>(wait.count())); // from ending before the deadline.
if (ret != 0 || wait < slice) { return ret; } auto wait_usec = last ? (std::max)((left.count() + 999) / 1000 * 1000,
static_cast<decltype(left.count())>(0))
: slice.count();
auto ret = select_impl(sock, POLLIN, 0, static_cast<time_t>(wait_usec));
if (ret != 0 || last) { return ret; }
} }
return 0; return 0;
} }
@@ -9307,6 +9352,7 @@ inline bool redirect(T &cli, Request &req, Response &res,
Error &error) { Error &error) {
Request new_req = req; Request new_req = req;
new_req.path = path; new_req.path = path;
new_req.params.clear();
new_req.redirect_count_ -= 1; new_req.redirect_count_ -= 1;
if (res.status == StatusCode::SeeOther_303 && if (res.status == StatusCode::SeeOther_303 &&
@@ -11975,8 +12021,13 @@ inline std::string decode_query_component(const std::string &component,
} }
inline std::string sanitize_filename(const std::string &filename) { inline std::string sanitize_filename(const std::string &filename) {
// Extract basename: find the last path separator (/ or \) // Extract basename: find the last path separator (/ or \) and, on Windows,
// ':', which ends a drive prefix ("C:..") or names an NTFS stream ("a:b")
#ifdef _WIN32
auto pos = filename.find_last_of("/\\:");
#else
auto pos = filename.find_last_of("/\\"); auto pos = filename.find_last_of("/\\");
#endif
auto result = auto result =
(pos != std::string::npos) ? filename.substr(pos + 1) : filename; (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
@@ -12826,7 +12877,8 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
inline ssize_t SocketStream::write(const char *ptr, size_t size) { inline ssize_t SocketStream::write(const char *ptr, size_t size) {
if (!wait_writable()) { return -1; } if (!wait_writable()) { return -1; }
#if defined(_WIN32) && !defined(_WIN64) // Winsock's send() takes an int length, even on 64-bit Windows
#ifdef _WIN32
size = size =
(std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)())); (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
#endif #endif
@@ -13041,6 +13093,17 @@ make_host_and_port_string_always_port(const std::string &host, int port) {
return prepare_host_string(host) + ":" + std::to_string(port); return prepare_host_string(host) + ":" + std::to_string(port);
} }
// Request target of a CONNECT for `host`, with `addr_map` applied.
inline std::string
make_connect_request_target(const std::map<std::string, std::string> &addr_map,
const std::string &host, int port) {
std::string connect_host;
std::string ip;
apply_addr_map(addr_map, host, connect_host, ip);
return make_host_and_port_string_always_port(ip.empty() ? connect_host : ip,
port);
}
// Value for the Host header a client sends when the caller supplied none. // Value for the Host header a client sends when the caller supplied none.
// Only the value: callers decide where in their header list it goes. // Only the value: callers decide where in their header list it goes.
inline std::string make_default_host_header_value(const std::string &host, inline std::string make_default_host_header_value(const std::string &host,
@@ -15230,7 +15293,10 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
if ((req.method == "GET" || req.method == "HEAD") && if ((req.method == "GET" || req.method == "HEAD") &&
req.has_header("Range")) { req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range"); const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) { // RFC 9110 14.2: a range unit other than bytes must be ignored
auto unit = range_header_value.substr(0, range_header_value.find('='));
if (detail::case_ignore::equal(unit, "bytes") &&
!detail::parse_range_header(range_header_value, req.ranges)) {
connection_closed = true; connection_closed = true;
res.status = StatusCode::RangeNotSatisfiable_416; res.status = StatusCode::RangeNotSatisfiable_416;
output_error_log(Error::InvalidRangeHeader, &req); output_error_log(Error::InvalidRangeHeader, &req);
@@ -15759,6 +15825,9 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
// socket. // socket.
socket_should_be_closed_when_request_is_done_ = false; socket_should_be_closed_when_request_is_done_ = false;
// A redirect or a retry sent inside a request keeps its stop()
if (socket_requests_in_flight_ == 0) { stop_requested_ = false; }
if (!socket_reusable()) { if (!socket_reusable()) {
if (!ensure_socket_connection(socket_, error)) { return false; } if (!ensure_socket_connection(socket_, error)) { return false; }
@@ -15806,6 +15875,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
}); });
ret = process_socket(socket_, req.start_time_, [&](Stream &strm) { ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
strm.set_read_interrupt([this] { return stop_requested_.load(); });
return handle_request(strm, req, res, close_connection, error); return handle_request(strm, req, res, close_connection, error);
}); });
@@ -16569,6 +16639,7 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) {
// Copy network and socket settings // Copy network and socket settings
client.addr_map_ = addr_map_; client.addr_map_ = addr_map_;
client.tunnel_addr_map_ = tunnel_addr_map_;
client.set_address_family(address_family_); client.set_address_family(address_family_);
client.set_tcp_nodelay(tcp_nodelay_); client.set_tcp_nodelay(tcp_nodelay_);
client.set_ipv6_v6only(ipv6_v6only_); client.set_ipv6_v6only(ipv6_v6only_);
@@ -18023,6 +18094,9 @@ inline void ClientImpl::stop() {
// are not thread-safe. // are not thread-safe.
if (socket_requests_in_flight_ > 0) { if (socket_requests_in_flight_ > 0) {
shutdown_socket(socket_); shutdown_socket(socket_);
// shutdown() does not wake a read waiting in WSAPoll on Windows, so the
// request's reads also give up once they see this flag.
stop_requested_ = true;
// Aside from that, we set a flag for the socket to be closed when we're // Aside from that, we set a flag for the socket to be closed when we're
// done. // done.
@@ -18081,9 +18155,19 @@ inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
inline void inline void
ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) { ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
set_direct_hostname_addr_map(std::move(addr_map));
}
inline void ClientImpl::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
addr_map_ = std::move(addr_map); addr_map_ = std::move(addr_map);
} }
inline void ClientImpl::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
tunnel_addr_map_ = std::move(addr_map);
}
inline void ClientImpl::set_default_headers(Headers headers) { inline void ClientImpl::set_default_headers(Headers headers) {
default_headers_ = std::move(headers); default_headers_ = std::move(headers);
} }
@@ -18792,7 +18876,17 @@ inline socket_t Client::socket() const { return cli_->socket(); }
inline void inline void
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) { Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
cli_->set_hostname_addr_map(std::move(addr_map)); cli_->set_direct_hostname_addr_map(std::move(addr_map));
}
inline void Client::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
cli_->set_direct_hostname_addr_map(std::move(addr_map));
}
inline void Client::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
cli_->set_tunnel_hostname_addr_map(std::move(addr_map));
} }
inline void Client::set_default_headers(Headers headers) { inline void Client::set_default_headers(Headers headers) {
@@ -19160,6 +19254,10 @@ inline bool SSLClient::connect_with_proxy(
std::chrono::time_point<std::chrono::steady_clock> start_time, std::chrono::time_point<std::chrono::steady_clock> start_time,
Response &res, bool &success, Error &error) { Response &res, bool &success, Error &error) {
success = true; success = true;
auto connect_target =
detail::make_connect_request_target(tunnel_addr_map_, host_, port_);
auto retargeted = connect_target !=
detail::make_host_and_port_string_always_port(host_, port_);
Response proxy_res; Response proxy_res;
if (!detail::process_client_socket( if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_, socket.sock, read_timeout_sec_, read_timeout_usec_,
@@ -19167,8 +19265,8 @@ inline bool SSLClient::connect_with_proxy(
start_time, [&](Stream &strm) { start_time, [&](Stream &strm) {
Request req2; Request req2;
req2.method = "CONNECT"; req2.method = "CONNECT";
req2.path = req2.path = connect_target;
detail::make_host_and_port_string_always_port(host_, port_); if (retargeted) { req2.set_header("Host", connect_target); }
if (max_timeout_msec_ > 0) { if (max_timeout_msec_ > 0) {
req2.start_time_ = std::chrono::steady_clock::now(); req2.start_time_ = std::chrono::steady_clock::now();
} }
@@ -19207,8 +19305,8 @@ inline bool SSLClient::connect_with_proxy(
start_time, [&](Stream &strm) { start_time, [&](Stream &strm) {
Request req3; Request req3;
req3.method = "CONNECT"; req3.method = "CONNECT";
req3.path = detail::make_host_and_port_string_always_port( req3.path = connect_target;
host_, port_); if (retargeted) { req3.set_header("Host", connect_target); }
req3.headers.insert(detail::make_digest_authentication_header( req3.headers.insert(detail::make_digest_authentication_header(
req3, make_request_target(req3), auth, 1, req3, make_request_target(req3), auth, 1,
detail::random_string(10), proxy_digest_auth_username_, detail::random_string(10), proxy_digest_auth_username_,
@@ -21040,6 +21138,11 @@ inline ErrorCode map_mbedtls_error(int ret, int &out_errno,
out_errno = errno; out_errno = errno;
return ErrorCode::SyscallError; return ErrorCode::SyscallError;
} }
if (ret == MBEDTLS_ERR_SSL_CONN_EOF) {
// EOF without close_notify
out_errno = 0;
return ErrorCode::SyscallError;
}
if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
// Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into // Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into
// the handshake's chain verification (see set_sni()); a mismatch there // the handshake's chain verification (see set_sni()); a mismatch there
@@ -21127,7 +21230,7 @@ inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
return MBEDTLS_ERR_NET_RECV_FAILED; return MBEDTLS_ERR_NET_RECV_FAILED;
} }
#endif #endif
if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; } // EOF is MBEDTLS_ERR_SSL_CONN_EOF; only a close_notify is a clean end
return static_cast<int>(ret); return static_cast<int>(ret);
} }
@@ -21783,10 +21886,8 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
return static_cast<ssize_t>(ret); return static_cast<ssize_t>(ret);
} }
if (ret == 0) { // 0 means EOF without close_notify, which is an error as on OpenSSL
err.code = ErrorCode::PeerClosed; if (ret == 0) { ret = MBEDTLS_ERR_SSL_CONN_EOF; }
return 0;
}
err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0); err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0);
err.backend_code = static_cast<uint64_t>(-ret); err.backend_code = static_cast<uint64_t>(-ret);
@@ -22393,6 +22494,8 @@ inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite; case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed; case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError; case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
// EOF without close_notify
case SOCKET_PEER_CLOSED_E: out_errno = 0; return ErrorCode::SyscallError;
default: default:
if (ssl) { if (ssl) {
// wolfSSL stores the low-level error code as a negative value. // wolfSSL stores the low-level error code as a negative value.
@@ -22956,6 +23059,9 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
} }
auto wsession = static_cast<impl::WolfSSLSession *>(session); auto wsession = static_cast<impl::WolfSSLSession *>(session);
constexpr auto max_len =
static_cast<size_t>((std::numeric_limits<int>::max)());
if (len > max_len) { len = max_len; }
int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len)); int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
if (ret > 0) { if (ret > 0) {
@@ -22963,13 +23069,10 @@ inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
return static_cast<ssize_t>(ret); return static_cast<ssize_t>(ret);
} }
if (ret == 0) { // 0 is also returned for EOF without close_notify; get_error tells them apart
err.code = ErrorCode::PeerClosed;
return 0;
}
int ssl_error = wolfSSL_get_error(wsession->ssl, ret); int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno); err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
if (err.code == ErrorCode::PeerClosed) { return 0; }
err.backend_code = static_cast<uint64_t>(ssl_error); err.backend_code = static_cast<uint64_t>(ssl_error);
impl::wolfssl_last_error() = err.backend_code; impl::wolfssl_last_error() = err.backend_code;
return -1; return -1;
@@ -23926,6 +24029,9 @@ inline void WebSocketClient::prepare_default_headers(Request &req) {
} }
detail::add_default_user_agent_header(req); detail::add_default_user_agent_header(req);
detail::add_authorization_header(req, basic_auth_username_,
basic_auth_password_,
bearer_token_auth_token_, false);
} }
inline Result WebSocketClient::connect() { inline Result WebSocketClient::connect() {
@@ -24051,9 +24157,19 @@ inline void WebSocketClient::set_interface(const std::string &intf) {
inline void WebSocketClient::set_hostname_addr_map( inline void WebSocketClient::set_hostname_addr_map(
std::map<std::string, std::string> addr_map) { std::map<std::string, std::string> addr_map) {
set_direct_hostname_addr_map(std::move(addr_map));
}
inline void WebSocketClient::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
addr_map_ = std::move(addr_map); addr_map_ = std::move(addr_map);
} }
inline void WebSocketClient::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
tunnel_addr_map_ = std::move(addr_map);
}
inline bool WebSocketClient::is_proxy_enabled() const { inline bool WebSocketClient::is_proxy_enabled() const {
if (proxy_host_.empty() || proxy_port_ == -1) { return false; } if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
if (no_proxy_entries_.empty()) { return true; } if (no_proxy_entries_.empty()) { return true; }
@@ -24103,7 +24219,8 @@ inline bool WebSocketClient::connect_with_proxy(Response &proxy_res,
Error &error) { Error &error) {
Request req; Request req;
req.method = "CONNECT"; req.method = "CONNECT";
req.path = detail::make_host_and_port_string_always_port(host_, port_); req.path =
detail::make_connect_request_target(tunnel_addr_map_, host_, port_);
req.headers.emplace("Host", req.path); req.headers.emplace("Host", req.path);
detail::add_default_user_agent_header(req); detail::add_default_user_agent_header(req);
detail::add_authorization_header(req, proxy_basic_auth_username_, detail::add_authorization_header(req, proxy_basic_auth_username_,
@@ -24137,6 +24254,16 @@ inline bool WebSocketClient::connect_with_proxy(Response &proxy_res,
return true; return true;
} }
inline void WebSocketClient::set_basic_auth(const std::string &username,
const std::string &password) {
basic_auth_username_ = username;
basic_auth_password_ = password;
}
inline void WebSocketClient::set_bearer_token_auth(const std::string &token) {
bearer_token_auth_token_ = token;
}
inline void WebSocketClient::set_proxy(const std::string &host, int port) { inline void WebSocketClient::set_proxy(const std::string &host, int port) {
proxy_host_ = host; proxy_host_ = host;
proxy_port_ = port; proxy_port_ = port;
+772 -20
View File
File diff suppressed because it is too large. Load diff