mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-30 13:00:42 +00:00
Compare commits
14
Commits
a210b43e11
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24e58ce5b2 | ||
|
|
a0ab09fc0a | ||
|
|
290c4c0bbe | ||
|
|
25ba40f6fe | ||
|
|
578e4815b7 | ||
|
|
252dbaa505 | ||
|
|
8710fbd25d | ||
|
|
2f8b9f6f57 | ||
|
|
391de32115 | ||
|
|
ba32b468a4 | ||
|
|
76f84a5bfd | ||
|
|
d23d1b6029 | ||
|
|
7d349cbeed | ||
|
|
5ceef83c54 |
No files matched your search
+1
-1
@@ -100,7 +100,7 @@ sse.stop();
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
bool connected = sse.is_connected();
|
bool connected = sse.is_connected();
|
||||||
const std::string &id = sse.last_event_id();
|
std::string id = sse.last_event_id();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
+22
-1
@@ -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
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
|
|||||||
> A wolfSSL server that verifies client certificates does not resume TLS sessions: wolfSSL shares its session cache between contexts, so a resumed session could carry a certificate checked against another server's client CA.
|
> A wolfSSL server that verifies client certificates does not resume TLS sessions: wolfSSL shares its session cache between contexts, so a resumed session could carry a certificate checked against another server's client CA.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. Two known behavioral differences vs OpenSSL: (1) BoringSSL's public headers require C++14 or later, so consumers must compile accordingly; (2) hostname verification is SAN-only per RFC 6125 §6.4.4 (no CN fallback).
|
> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. One known difference vs OpenSSL: BoringSSL's public headers require C++14 or later, so consumers must compile accordingly.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Hostname verification is SAN-only on every backend** (RFC 9525). A host is matched against the certificate's subjectAltName entries, never its Common Name, so a certificate that names its host only in the CN is rejected. Reissue it with a subjectAltName.
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
||||||
@@ -240,7 +243,7 @@ cpp-httplib automatically integrates with the OS certificate store on macOS and
|
|||||||
|
|
||||||
| Platform | Behavior | Disable (compile time) |
|
| Platform | Behavior | Disable (compile time) |
|
||||||
| :------- | :------- | :--------------------- |
|
| :------- | :------- | :--------------------- |
|
||||||
| macOS | Loads system certs from Keychain (link `CoreFoundation` and `Security` with `-framework`). Requires Apple Clang; GCC is not supported for this feature. | `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES` |
|
| macOS | Loads system certs from Keychain, honouring their Trust Settings: a certificate set to "Never Trust", or trusted only for another policy such as S/MIME, is not loaded (link `CoreFoundation` and `Security` with `-framework`). Requires Apple Clang; GCC is not supported for this feature. | `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES` |
|
||||||
| Windows | Verifies the certificate chain with CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) instead of the TLS backend. Revocation checking is best-effort: a revoked server certificate is rejected, while one whose revocation status cannot be determined is accepted. Windows fetches missing roots and intermediates on demand. With a custom CA, the TLS backend verifies the chain instead; with `set_server_certificate_verifier()`, both do. | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` |
|
| Windows | Verifies the certificate chain with CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) instead of the TLS backend. Revocation checking is best-effort: a revoked server certificate is rejected, while one whose revocation status cannot be determined is accepted. Windows fetches missing roots and intermediates on demand. With a custom CA, the TLS backend verifies the chain instead; with `set_server_certificate_verifier()`, both do. | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` |
|
||||||
|
|
||||||
On Windows, the CryptoAPI check can also be turned off at runtime. The TLS backend then verifies the chain instead, as on other platforms:
|
On Windows, the CryptoAPI check can also be turned off at runtime. The TLS backend then verifies the chain instead, as on other platforms:
|
||||||
@@ -406,7 +409,7 @@ ret = svr.remove_mount_point("/");
|
|||||||
ret = svr.remove_mount_point("/public");
|
ret = svr.remove_mount_point("/public");
|
||||||
```
|
```
|
||||||
|
|
||||||
A mount point serves a path only when it has no `.`, `..` or empty segment other than a trailing `/`, and no segment ending in `.` or a space, so a check on `req.path` in `set_pre_routing_handler()` sees the path of the file that is served. A path ending in `/`, and the mount point itself such as `/public`, serve the `index.html` in that directory. The file system can still give a file other names: another case on a case-insensitive file system, such as the default ones on macOS and Windows, and on Windows an 8.3 short name such as `PRIVAT~1`. Compare paths without regard to case on such a file system, and on Windows keep files that need a guard out of mounted directories.
|
A mount point serves a path only when it has no `.`, `..` or empty segment other than a trailing `/`, no segment ending in `.` or a space and, on Windows, no `:` (it names an NTFS stream), so a check on `req.path` in `set_pre_routing_handler()` sees the path of the file that is served. A path ending in `/`, and the mount point itself such as `/public`, serve the `index.html` in that directory. The file system can still give a file other names: another case on a case-insensitive file system, such as the default ones on macOS and Windows, and on Windows an 8.3 short name such as `PRIVAT~1`. Compare paths without regard to case on such a file system, and on Windows keep files that need a guard out of mounted directories.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// User defined file extension and MIME type mappings
|
// User defined file extension and MIME type mappings
|
||||||
@@ -739,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.
|
||||||
@@ -1474,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
|
||||||
@@ -1492,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
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
|
|||||||
|
|
||||||
[site]
|
[site]
|
||||||
title = "cpp-httplib"
|
title = "cpp-httplib"
|
||||||
version = "0.63.0"
|
version = "0.64.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."
|
||||||
|
|||||||
@@ -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("/");
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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("/");
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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.64.0"
|
||||||
#define CPPHTTPLIB_VERSION_NUM "0x003f00"
|
#define CPPHTTPLIB_VERSION_NUM "0x004000"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
|
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
|
||||||
@@ -995,6 +995,30 @@ inline bool parse_url(const std::string &url, UrlComponents &uc) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes "." and ".." segments from a path that starts with '/' (RFC 3986
|
||||||
|
// section 5.2.4).
|
||||||
|
inline std::string remove_dot_segments(const std::string &path) {
|
||||||
|
std::string out;
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < path.size()) {
|
||||||
|
auto next = (std::min)(path.find('/', i + 1), path.size());
|
||||||
|
auto segment = path.substr(i + 1, next - i - 1);
|
||||||
|
auto is_last = next == path.size();
|
||||||
|
if (segment == "." || segment == "..") {
|
||||||
|
if (segment == "..") {
|
||||||
|
out.erase((std::min)(out.rfind('/'), out.size()));
|
||||||
|
}
|
||||||
|
if (is_last) { out += '/'; }
|
||||||
|
} else {
|
||||||
|
out += '/';
|
||||||
|
out += segment;
|
||||||
|
}
|
||||||
|
i = next;
|
||||||
|
}
|
||||||
|
if (out.empty()) { out = "/"; }
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// Resolves a relative-path or query-only Location value against the path of
|
// Resolves a relative-path or query-only Location value against the path of
|
||||||
// the request being redirected (RFC 3986 section 5.2). Absolute URIs and
|
// the request being redirected (RFC 3986 section 5.2). Absolute URIs and
|
||||||
// references starting with '/' are returned unchanged.
|
// references starting with '/' are returned unchanged.
|
||||||
@@ -1015,26 +1039,8 @@ inline std::string resolve_relative_location(const std::string &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());
|
||||||
std::string path;
|
return remove_dot_segments(merged.substr(0, path_end)) +
|
||||||
size_t i = 0;
|
merged.substr(path_end);
|
||||||
while (i < path_end) {
|
|
||||||
auto next = (std::min)(merged.find('/', i + 1), path_end);
|
|
||||||
auto segment = merged.substr(i + 1, next - i - 1);
|
|
||||||
auto is_last = next == path_end;
|
|
||||||
if (segment == "." || segment == "..") {
|
|
||||||
if (segment == "..") {
|
|
||||||
path.erase((std::min)(path.rfind('/'), path.size()));
|
|
||||||
}
|
|
||||||
if (is_last) { path += '/'; }
|
|
||||||
} else {
|
|
||||||
path += '/';
|
|
||||||
path += segment;
|
|
||||||
}
|
|
||||||
i = next;
|
|
||||||
}
|
|
||||||
if (path.empty()) { path = "/"; }
|
|
||||||
|
|
||||||
return path + merged.substr(path_end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@@ -2921,7 +2927,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,6 +3064,9 @@ protected:
|
|||||||
// 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_;
|
||||||
|
|
||||||
@@ -3342,7 +3356,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);
|
||||||
|
|
||||||
@@ -3797,6 +3816,8 @@ template <typename T>
|
|||||||
bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer,
|
bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer,
|
||||||
Error &error);
|
Error &error);
|
||||||
|
|
||||||
|
bool write_data(Stream &strm, const char *d, size_t l);
|
||||||
|
|
||||||
std::string trim_copy(const std::string &s);
|
std::string trim_copy(const std::string &s);
|
||||||
|
|
||||||
void divide(
|
void divide(
|
||||||
@@ -4496,7 +4517,7 @@ public:
|
|||||||
|
|
||||||
// State accessors
|
// State accessors
|
||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
const std::string &last_event_id() const;
|
std::string last_event_id() const;
|
||||||
|
|
||||||
// Blocking start - runs event loop with auto-reconnect
|
// Blocking start - runs event loop with auto-reconnect
|
||||||
void start();
|
void start();
|
||||||
@@ -4534,7 +4555,9 @@ private:
|
|||||||
// State
|
// State
|
||||||
std::atomic<bool> running_{false};
|
std::atomic<bool> running_{false};
|
||||||
std::atomic<bool> connected_{false};
|
std::atomic<bool> connected_{false};
|
||||||
|
// Written by the event loop, which start_async() runs on its own thread
|
||||||
std::string last_event_id_;
|
std::string last_event_id_;
|
||||||
|
mutable std::mutex last_event_id_mutex_;
|
||||||
|
|
||||||
// Async support
|
// Async support
|
||||||
std::thread async_thread_;
|
std::thread async_thread_;
|
||||||
@@ -4738,7 +4761,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,
|
||||||
@@ -4810,6 +4841,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_;
|
||||||
@@ -5032,7 +5070,8 @@ inline SSEClient &SSEClient::set_headers(const Headers &headers) {
|
|||||||
|
|
||||||
inline bool SSEClient::is_connected() const { return connected_.load(); }
|
inline bool SSEClient::is_connected() const { return connected_.load(); }
|
||||||
|
|
||||||
inline const std::string &SSEClient::last_event_id() const {
|
inline std::string SSEClient::last_event_id() const {
|
||||||
|
std::lock_guard<std::mutex> lock(last_event_id_mutex_);
|
||||||
return last_event_id_;
|
return last_event_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5207,7 +5246,10 @@ inline void SSEClient::run_event_loop() {
|
|||||||
if (event_complete) {
|
if (event_complete) {
|
||||||
// Update last_event_id for reconnection, even for an event that
|
// Update last_event_id for reconnection, even for an event that
|
||||||
// has no data. An empty id clears it.
|
// has no data. An empty id clears it.
|
||||||
if (has_id) { last_event_id_ = current_msg.id; }
|
if (has_id) {
|
||||||
|
std::lock_guard<std::mutex> lock(last_event_id_mutex_);
|
||||||
|
last_event_id_ = current_msg.id;
|
||||||
|
}
|
||||||
|
|
||||||
// An event without a data field is not dispatched
|
// An event without a data field is not dispatched
|
||||||
if (has_data) { dispatch_event(current_msg); }
|
if (has_data) { dispatch_event(current_msg); }
|
||||||
@@ -5344,6 +5386,8 @@ cert_t get_peer_cert(const_session_t session);
|
|||||||
// do not use them after free_session(), as with get_peer_cert().
|
// do not use them after free_session(), as with get_peer_cert().
|
||||||
size_t get_peer_certs(const_session_t session, std::vector<cert_t> &certs);
|
size_t get_peer_certs(const_session_t session, std::vector<cert_t> &certs);
|
||||||
void free_cert(cert_t cert);
|
void free_cert(cert_t cert);
|
||||||
|
// Matches the host against the subjectAltName entries only, never the Common
|
||||||
|
// Name (RFC 9525).
|
||||||
bool verify_hostname(cert_t cert, const char *hostname);
|
bool verify_hostname(cert_t cert, const char *hostname);
|
||||||
uint64_t hostname_mismatch_code();
|
uint64_t hostname_mismatch_code();
|
||||||
long get_verify_result(const_session_t session);
|
long get_verify_result(const_session_t session);
|
||||||
@@ -5813,32 +5857,40 @@ inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
|
|||||||
if (len < 126) {
|
if (len < 126) {
|
||||||
header[1] = static_cast<uint8_t>(len);
|
header[1] = static_cast<uint8_t>(len);
|
||||||
if (mask) { header[1] |= 0x80; }
|
if (mask) { header[1] |= 0x80; }
|
||||||
if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(header), 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else if (len <= 0xFFFF) {
|
} else if (len <= 0xFFFF) {
|
||||||
header[1] = 126;
|
header[1] = 126;
|
||||||
if (mask) { header[1] |= 0x80; }
|
if (mask) { header[1] |= 0x80; }
|
||||||
if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(header), 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
uint8_t ext[2];
|
uint8_t ext[2];
|
||||||
ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
|
ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
|
||||||
ext[1] = static_cast<uint8_t>(len & 0xFF);
|
ext[1] = static_cast<uint8_t>(len & 0xFF);
|
||||||
if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(ext), 2)) { return false; }
|
||||||
} else {
|
} else {
|
||||||
header[1] = 127;
|
header[1] = 127;
|
||||||
if (mask) { header[1] |= 0x80; }
|
if (mask) { header[1] |= 0x80; }
|
||||||
if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(header), 2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
uint8_t ext[8];
|
uint8_t ext[8];
|
||||||
for (int i = 7; i >= 0; i--) {
|
for (int i = 7; i >= 0; i--) {
|
||||||
ext[7 - i] =
|
ext[7 - i] =
|
||||||
static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
|
static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
|
||||||
}
|
}
|
||||||
if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(ext), 8)) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
// Generate random mask key
|
// Generate random mask key
|
||||||
uint8_t mask_key[4];
|
uint8_t mask_key[4];
|
||||||
fill_random_bytes(mask_key, sizeof(mask_key));
|
fill_random_bytes(mask_key, sizeof(mask_key));
|
||||||
if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
|
if (!write_data(strm, reinterpret_cast<char *>(mask_key), 4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Write masked payload in chunks
|
// Write masked payload in chunks
|
||||||
const size_t chunk_size = 4096;
|
const size_t chunk_size = 4096;
|
||||||
@@ -5849,11 +5901,11 @@ inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
|
|||||||
buf[i] =
|
buf[i] =
|
||||||
data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
|
data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
|
||||||
}
|
}
|
||||||
if (strm.write(buf.data(), n) < 0) { return false; }
|
if (!write_data(strm, buf.data(), n)) { return false; }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
if (strm.write(data, len) < 0) { return false; }
|
if (!write_data(strm, data, len)) { return false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5989,9 +6041,10 @@ inline bool is_valid_close_payload(const std::string &payload) {
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// A path a mount point serves: absolute, with no empty segment except a
|
// A path a mount point serves: absolute, with no empty segment except a
|
||||||
// trailing '/' and no segment ending in '.' or a space (which covers "." and
|
// trailing '/', no segment ending in '.' or a space (which covers "." and
|
||||||
// ".." and names Windows trims into another file's name), so the file served
|
// ".." and names Windows trims into another file's name) and, on Windows, no
|
||||||
// is the one that req.path names to the handlers that ran before it.
|
// ':' (which names an NTFS stream, so "a.txt::$DATA" is a.txt), so the file
|
||||||
|
// served is the one that req.path names to the handlers that ran before it.
|
||||||
inline bool is_valid_path(const std::string &path) {
|
inline bool is_valid_path(const std::string &path) {
|
||||||
if (path.empty() || path[0] != '/') { return false; }
|
if (path.empty() || path[0] != '/') { return false; }
|
||||||
|
|
||||||
@@ -5999,6 +6052,9 @@ inline bool is_valid_path(const std::string &path) {
|
|||||||
for (auto i = beg; i <= path.size(); i++) {
|
for (auto i = beg; i <= path.size(); i++) {
|
||||||
if (i < path.size() && path[i] != '/') {
|
if (i < path.size() && path[i] != '/') {
|
||||||
if (path[i] == '\0' || path[i] == '\\') { return false; }
|
if (path[i] == '\0' || path[i] == '\\') { return false; }
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (path[i] == ':') { return false; }
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto len = i - beg;
|
auto len = i - beg;
|
||||||
@@ -11947,8 +12003,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;
|
||||||
|
|
||||||
@@ -13013,6 +13074,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,
|
||||||
@@ -15967,7 +16039,7 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.body.empty()) {
|
if (!req.body.empty()) {
|
||||||
if (strm.write(req.body.data(), req.body.size()) < 0) {
|
if (!detail::write_data(strm, req.body.data(), req.body.size())) {
|
||||||
handle.error = Error::Write;
|
handle.error = Error::Write;
|
||||||
handle.response.reset();
|
handle.response.reset();
|
||||||
return handle;
|
return handle;
|
||||||
@@ -16018,7 +16090,7 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) {
|
|||||||
|
|
||||||
auto content_encoding = detail::get_combined_header_value(
|
auto content_encoding = detail::get_combined_header_value(
|
||||||
handle.response->headers, "Content-Encoding");
|
handle.response->headers, "Content-Encoding");
|
||||||
if (!content_encoding.empty()) {
|
if (decompress_ && !content_encoding.empty()) {
|
||||||
// Same policy as prepare_content_receiver(): reject a coding we know about
|
// Same policy as prepare_content_receiver(): reject a coding we know about
|
||||||
// but were not built with, pass an unrecognized one through as-is.
|
// but were not built with, pass an unrecognized one through as-is.
|
||||||
handle.decompressor_ = detail::create_decompressor(content_encoding);
|
handle.decompressor_ = detail::create_decompressor(content_encoding);
|
||||||
@@ -16213,10 +16285,18 @@ inline bool ChunkedDecoder::parse_trailers_into(Headers &dest,
|
|||||||
return parse_trailers(lr, dest, src_headers);
|
return parse_trailers(lr, dest, src_headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A 3xx without a Location, such as a 304, is a final response
|
// Only 301, 302, 303, 307 and 308 redirect. Any other 3xx, such as a 304, or
|
||||||
|
// a redirect without a Location, is a final response.
|
||||||
inline bool is_redirect(const Response &res) {
|
inline bool is_redirect(const Response &res) {
|
||||||
return 300 < res.status && res.status < 400 &&
|
switch (res.status) {
|
||||||
!res.get_header_value("Location").empty();
|
case StatusCode::MovedPermanently_301:
|
||||||
|
case StatusCode::Found_302:
|
||||||
|
case StatusCode::SeeOther_303:
|
||||||
|
case StatusCode::TemporaryRedirect_307:
|
||||||
|
case StatusCode::PermanentRedirect_308:
|
||||||
|
return !res.get_header_value("Location").empty();
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@@ -16278,9 +16358,10 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|||||||
// to call it from a different thread since it's a thread-safety issue
|
// to call it from a different thread since it's a thread-safety issue
|
||||||
// to do these things to the socket if another thread is using the socket.
|
// to do these things to the socket if another thread is using the socket.
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
// Also when the request asked for it (e.g. a body that was never sent),
|
// Also when the request asked for it (e.g. a body that was never sent,
|
||||||
// so that a redirect or an auth retry below does not reuse the socket.
|
// or "Connection: close"), so that a redirect or an auth retry below does
|
||||||
if (socket_should_be_closed_when_request_is_done_ ||
|
// not reuse the socket.
|
||||||
|
if (socket_should_be_closed_when_request_is_done_ || close_connection ||
|
||||||
detail::has_header_token(res.headers, "Connection", "close") ||
|
detail::has_header_token(res.headers, "Connection", "close") ||
|
||||||
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
||||||
disconnect(/*gracefully=*/true);
|
disconnect(/*gracefully=*/true);
|
||||||
@@ -16374,6 +16455,10 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
|||||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||||
if (next_host.empty()) { next_host = host_; }
|
if (next_host.empty()) { next_host = host_; }
|
||||||
if (next_path.empty()) { next_path = "/"; }
|
if (next_path.empty()) { next_path = "/"; }
|
||||||
|
// An absolute or absolute-path location can have dot segments too.
|
||||||
|
if (next_path[0] == '/') {
|
||||||
|
next_path = detail::remove_dot_segments(next_path);
|
||||||
|
}
|
||||||
|
|
||||||
auto path = std::move(next_path) + next_query;
|
auto path = std::move(next_path) + next_query;
|
||||||
|
|
||||||
@@ -16528,6 +16613,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_);
|
||||||
@@ -17025,8 +17111,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto redirect = follow_location_ && detail::is_redirect(res) &&
|
auto redirect = follow_location_ && detail::is_redirect(res);
|
||||||
res.status != StatusCode::NotModified_304;
|
|
||||||
|
|
||||||
if (req.response_handler && !redirect) {
|
if (req.response_handler && !redirect) {
|
||||||
if (!req.response_handler(res)) {
|
if (!req.response_handler(res)) {
|
||||||
@@ -18041,9 +18126,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);
|
||||||
}
|
}
|
||||||
@@ -18752,7 +18847,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) {
|
||||||
@@ -19120,6 +19225,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_,
|
||||||
@@ -19127,8 +19236,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();
|
||||||
}
|
}
|
||||||
@@ -19167,8 +19276,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_,
|
||||||
@@ -19582,15 +19691,75 @@ inline bool enumerate_windows_system_certs(Callback cb) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
|
||||||
// Enumerate macOS Keychain certificates and call callback with DER data
|
// The trust a certificate's Trust Settings give it for SSL server
|
||||||
|
// verification (SecTrustSettings.h). An empty array trusts it as a root.
|
||||||
|
// Otherwise the first usage-constraints dictionary that applies decides: one
|
||||||
|
// naming a policy other than SSL, a host name or an application does not
|
||||||
|
// apply, and a result left out means TrustRoot. Keychain Access writes "Never
|
||||||
|
// Trust" as kSecTrustSettingsResultDeny.
|
||||||
|
inline SecTrustSettingsResult
|
||||||
|
macos_ssl_trust_settings_result(CFArrayRef trust_settings) {
|
||||||
|
auto count = CFArrayGetCount(trust_settings);
|
||||||
|
if (count == 0) { return kSecTrustSettingsResultTrustRoot; }
|
||||||
|
|
||||||
|
for (CFIndex i = 0; i < count; i++) {
|
||||||
|
auto value = CFArrayGetValueAtIndex(trust_settings, i);
|
||||||
|
if (!value || CFGetTypeID(value) != CFDictionaryGetTypeID()) { continue; }
|
||||||
|
auto constraint = static_cast<CFDictionaryRef>(value);
|
||||||
|
|
||||||
|
auto policy_value =
|
||||||
|
CFDictionaryGetValue(constraint, kSecTrustSettingsPolicy);
|
||||||
|
if (policy_value) {
|
||||||
|
auto policy = static_cast<SecPolicyRef>(const_cast<void *>(policy_value));
|
||||||
|
auto is_ssl = false;
|
||||||
|
if (auto properties = SecPolicyCopyProperties(policy)) {
|
||||||
|
auto oid = CFDictionaryGetValue(properties, kSecPolicyOid);
|
||||||
|
is_ssl = oid && CFEqual(oid, kSecPolicyAppleSSL);
|
||||||
|
CFRelease(properties);
|
||||||
|
}
|
||||||
|
if (!is_ssl) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CFDictionaryContainsKey(constraint, kSecTrustSettingsPolicyString) ||
|
||||||
|
CFDictionaryContainsKey(constraint, kSecTrustSettingsApplication)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = kSecTrustSettingsResultTrustRoot;
|
||||||
|
auto number = static_cast<CFNumberRef>(
|
||||||
|
CFDictionaryGetValue(constraint, kSecTrustSettingsResult));
|
||||||
|
if (number) {
|
||||||
|
SInt32 number_value = 0;
|
||||||
|
if (!CFNumberGetValue(number, kCFNumberSInt32Type, &number_value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result = static_cast<SecTrustSettingsResult>(number_value);
|
||||||
|
}
|
||||||
|
if (result == kSecTrustSettingsResultTrustRoot ||
|
||||||
|
result == kSecTrustSettingsResultTrustAsRoot ||
|
||||||
|
result == kSecTrustSettingsResultDeny) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kSecTrustSettingsResultUnspecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerate the macOS Keychain certificates trusted for SSL and call callback
|
||||||
|
// with DER data. A certificate with Trust Settings is not trusted just by
|
||||||
|
// having them: the settings say whether it is, and a per-user setting
|
||||||
|
// overrides the admin domain's, which overrides the system domain's (whose
|
||||||
|
// roots are all trusted). So a system root the user set to "Never Trust" is
|
||||||
|
// left out, as is one trusted only for S/MIME or code signing.
|
||||||
template <typename Callback>
|
template <typename Callback>
|
||||||
inline bool enumerate_macos_keychain_certs(Callback cb) {
|
inline bool enumerate_macos_keychain_certs(Callback cb) {
|
||||||
bool loaded = false;
|
|
||||||
const SecTrustSettingsDomain domains[] = {
|
const SecTrustSettingsDomain domains[] = {
|
||||||
kSecTrustSettingsDomainSystem,
|
|
||||||
kSecTrustSettingsDomainAdmin,
|
|
||||||
kSecTrustSettingsDomainUser,
|
kSecTrustSettingsDomainUser,
|
||||||
|
kSecTrustSettingsDomainAdmin,
|
||||||
|
kSecTrustSettingsDomainSystem,
|
||||||
};
|
};
|
||||||
|
std::set<std::string> decided;
|
||||||
|
std::vector<std::string> trusted;
|
||||||
for (auto domain : domains) {
|
for (auto domain : domains) {
|
||||||
CFArrayRef certs = nullptr;
|
CFArrayRef certs = nullptr;
|
||||||
OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
|
OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
|
||||||
@@ -19603,16 +19772,43 @@ inline bool enumerate_macos_keychain_certs(Callback cb) {
|
|||||||
SecCertificateRef cert =
|
SecCertificateRef cert =
|
||||||
(SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
|
(SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
|
||||||
CFDataRef data = SecCertificateCopyData(cert);
|
CFDataRef data = SecCertificateCopyData(cert);
|
||||||
if (data) {
|
if (!data) { continue; }
|
||||||
if (cb(CFDataGetBytePtr(data),
|
std::string der(reinterpret_cast<const char *>(CFDataGetBytePtr(data)),
|
||||||
static_cast<size_t>(CFDataGetLength(data)))) {
|
static_cast<size_t>(CFDataGetLength(data)));
|
||||||
loaded = true;
|
CFRelease(data);
|
||||||
|
|
||||||
|
// Decided by a domain that overrides this one
|
||||||
|
if (decided.find(der) != decided.end()) { continue; }
|
||||||
|
|
||||||
|
auto result = kSecTrustSettingsResultTrustRoot;
|
||||||
|
if (domain != kSecTrustSettingsDomainSystem) {
|
||||||
|
result = kSecTrustSettingsResultUnspecified;
|
||||||
|
CFArrayRef trust_settings = nullptr;
|
||||||
|
if (SecTrustSettingsCopyTrustSettings(cert, domain, &trust_settings) ==
|
||||||
|
errSecSuccess &&
|
||||||
|
trust_settings) {
|
||||||
|
result = macos_ssl_trust_settings_result(trust_settings);
|
||||||
}
|
}
|
||||||
CFRelease(data);
|
if (trust_settings) CFRelease(trust_settings);
|
||||||
|
}
|
||||||
|
// Neither trusted nor distrusted here; a domain below may decide
|
||||||
|
if (result == kSecTrustSettingsResultUnspecified) { continue; }
|
||||||
|
|
||||||
|
decided.insert(der);
|
||||||
|
if (result == kSecTrustSettingsResultTrustRoot ||
|
||||||
|
result == kSecTrustSettingsResultTrustAsRoot) {
|
||||||
|
trusted.push_back(std::move(der));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CFRelease(certs);
|
CFRelease(certs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool loaded = false;
|
||||||
|
for (const auto &der : trusted) {
|
||||||
|
if (cb(reinterpret_cast<const unsigned char *>(der.data()), der.size())) {
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -20046,36 +20242,14 @@ inline bool load_system_certs(ctx_t ctx) {
|
|||||||
auto store = SSL_CTX_get_cert_store(ssl_ctx);
|
auto store = SSL_CTX_get_cert_store(ssl_ctx);
|
||||||
if (!store) return false;
|
if (!store) return false;
|
||||||
|
|
||||||
bool loaded_any = false;
|
auto loaded_any = impl::enumerate_macos_keychain_certs(
|
||||||
const SecTrustSettingsDomain domains[] = {
|
[&](const unsigned char *data, size_t len) {
|
||||||
kSecTrustSettingsDomainSystem,
|
auto x509 = d2i_X509(nullptr, &data, static_cast<long>(len));
|
||||||
kSecTrustSettingsDomainAdmin,
|
if (!x509) { return false; }
|
||||||
kSecTrustSettingsDomainUser,
|
auto added = X509_STORE_add_cert(store, x509) == 1;
|
||||||
};
|
X509_free(x509);
|
||||||
for (auto domain : domains) {
|
return added;
|
||||||
CFArrayRef certs = nullptr;
|
});
|
||||||
if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
|
|
||||||
!certs) {
|
|
||||||
if (certs) CFRelease(certs);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto count = CFArrayGetCount(certs);
|
|
||||||
for (CFIndex i = 0; i < count; i++) {
|
|
||||||
auto cert = reinterpret_cast<SecCertificateRef>(
|
|
||||||
const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
|
|
||||||
CFDataRef der = SecCertificateCopyData(cert);
|
|
||||||
if (der) {
|
|
||||||
const unsigned char *data = CFDataGetBytePtr(der);
|
|
||||||
auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
|
|
||||||
if (x509) {
|
|
||||||
if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
|
|
||||||
X509_free(x509);
|
|
||||||
}
|
|
||||||
CFRelease(der);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CFRelease(certs);
|
|
||||||
}
|
|
||||||
return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
||||||
#else
|
#else
|
||||||
return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
|
||||||
@@ -20189,10 +20363,12 @@ inline bool set_sni(session_t session, const char *hostname,
|
|||||||
auto ssl = static_cast<SSL *>(session);
|
auto ssl = static_cast<SSL *>(session);
|
||||||
|
|
||||||
// The handshake's chain verification then checks the name as well
|
// The handshake's chain verification then checks the name as well
|
||||||
if (verify_hostname &&
|
if (verify_hostname) {
|
||||||
X509_VERIFY_PARAM_set1_host(SSL_get0_param(ssl), hostname,
|
auto param = SSL_get0_param(ssl);
|
||||||
strlen(hostname)) != 1) {
|
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NEVER_CHECK_SUBJECT);
|
||||||
return false;
|
if (X509_VERIFY_PARAM_set1_host(param, hostname, strlen(hostname)) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(OPENSSL_IS_BORINGSSL)
|
#if defined(OPENSSL_IS_BORINGSSL)
|
||||||
@@ -20441,7 +20617,8 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
if (detail::is_ip_address(hostname)) {
|
if (detail::is_ip_address(hostname)) {
|
||||||
return X509_check_ip_asc(x509, hostname, 0) == 1;
|
return X509_check_ip_asc(x509, hostname, 0) == 1;
|
||||||
}
|
}
|
||||||
return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
|
return X509_check_host(x509, hostname, strlen(hostname),
|
||||||
|
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, nullptr) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64_t hostname_mismatch_code() {
|
inline uint64_t hostname_mismatch_code() {
|
||||||
@@ -20932,6 +21109,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
|
||||||
@@ -21019,7 +21201,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21675,10 +21857,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);
|
||||||
@@ -21827,7 +22007,6 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
|
|
||||||
// Check Subject Alternative Names (SAN). Mbed TLS keeps the GeneralName type
|
// Check Subject Alternative Names (SAN). Mbed TLS keeps the GeneralName type
|
||||||
// in buf.tag and the raw value in buf.p / buf.len.
|
// in buf.tag and the raw value in buf.p / buf.len.
|
||||||
auto has_dns_san = false;
|
|
||||||
for (auto san = &mcert->subject_alt_names; san; san = san->next) {
|
for (auto san = &mcert->subject_alt_names; san; san = san->next) {
|
||||||
if (!san->buf.p || san->buf.len == 0) { continue; }
|
if (!san->buf.p || san->buf.len == 0) { continue; }
|
||||||
const unsigned char *p = san->buf.p;
|
const unsigned char *p = san->buf.p;
|
||||||
@@ -21842,24 +22021,11 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (san_type == MBEDTLS_X509_SAN_DNS_NAME) {
|
} else if (san_type == MBEDTLS_X509_SAN_DNS_NAME) {
|
||||||
has_dns_san = true;
|
|
||||||
std::string san_name(reinterpret_cast<const char *>(p), len);
|
std::string san_name(reinterpret_cast<const char *>(p), len);
|
||||||
if (detail::match_hostname(san_name, host_str)) { return true; }
|
if (detail::match_hostname(san_name, host_str)) { return true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: the Common Names (CN) in the subject, as X509_check_host does it:
|
|
||||||
// never for an IP host, which only an iPAddress SAN names, and never next to
|
|
||||||
// a DNS SAN (RFC 6125 6.4.4).
|
|
||||||
if (is_ip || has_dns_san) { return false; }
|
|
||||||
for (auto name = &mcert->subject; name; name = name->next) {
|
|
||||||
if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
|
|
||||||
std::string cn(reinterpret_cast<const char *>(name->val.p),
|
|
||||||
name->val.len);
|
|
||||||
if (detail::match_hostname(cn, host_str)) { return true; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22299,6 +22465,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.
|
||||||
@@ -22869,13 +23037,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;
|
||||||
@@ -23007,7 +23172,6 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
auto is_ip = ip_len > 0;
|
auto is_ip = ip_len > 0;
|
||||||
|
|
||||||
// Check Subject Alternative Names
|
// Check Subject Alternative Names
|
||||||
auto has_dns_san = false;
|
|
||||||
auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
|
auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
|
||||||
wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
|
wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
|
||||||
|
|
||||||
@@ -23020,7 +23184,6 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
|
|
||||||
if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
|
if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
|
||||||
// DNS name
|
// DNS name
|
||||||
has_dns_san = true;
|
|
||||||
unsigned char *dns_name = nullptr;
|
unsigned char *dns_name = nullptr;
|
||||||
int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
|
int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
|
||||||
if (dns_name && dns_len > 0) {
|
if (dns_name && dns_len > 0) {
|
||||||
@@ -23047,12 +23210,7 @@ inline bool verify_hostname(cert_t cert, const char *hostname) {
|
|||||||
wolfSSL_sk_free(san_names);
|
wolfSSL_sk_free(san_names);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: the Common Name (CN) in the subject, as X509_check_host does it:
|
return false;
|
||||||
// never for an IP host, which only an iPAddress SAN names, and never next to
|
|
||||||
// a DNS SAN (RFC 6125 6.4.4).
|
|
||||||
if (is_ip || has_dns_san) { return false; }
|
|
||||||
auto cn = get_cert_subject_cn(cert);
|
|
||||||
return !cn.empty() && detail::match_hostname(cn, host_str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint64_t hostname_mismatch_code() {
|
inline uint64_t hostname_mismatch_code() {
|
||||||
@@ -23839,6 +23997,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() {
|
||||||
@@ -23964,9 +24125,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; }
|
||||||
@@ -24016,7 +24187,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_,
|
||||||
@@ -24050,6 +24222,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;
|
||||||
|
|||||||
@@ -153,6 +153,14 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
|||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMAND_ERROR_IS_FATAL ANY
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
)
|
)
|
||||||
|
# cert_dns_cn.pem: CN is the DNS name the tests connect to, with NO
|
||||||
|
# subjectAltName. A host is named by a SAN only (RFC 9525),
|
||||||
|
# so verifying it against this cert must fail.
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -x509 -key key.pem -sha256 -days 3650 -nodes -subj /CN=localhost -out cert_dns_cn.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not
|
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not
|
||||||
# leftmost. "*.leftmost.example.test" matches a single
|
# leftmost. "*.leftmost.example.test" matches a single
|
||||||
# label; the wildcard in "www.*.example.test" must not be
|
# label; the wildcard in "www.*.example.test" must not be
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=::1" -addext
|
|||||||
# DNS:a.zz is 97.46.122.122, IP:42.46.122.122 is "*.zz".
|
# DNS:a.zz is 97.46.122.122, IP:42.46.122.122 is "*.zz".
|
||||||
openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=san-types" -addext "subjectAltName=DNS:a.zz,IP:42.46.122.122" -out cert_san_types.pem
|
openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=san-types" -addext "subjectAltName=DNS:a.zz,IP:42.46.122.122" -out cert_san_types.pem
|
||||||
|
|
||||||
|
# cert_dns_cn.pem: CN is the DNS name the tests connect to, with NO
|
||||||
|
# subjectAltName. A host is named by a SAN only (RFC 9525), so
|
||||||
|
# verifying it against this cert must fail.
|
||||||
|
openssl req -x509 -key key.pem -sha256 -days 3650 -nodes -subj "/CN=localhost" -out cert_dns_cn.pem
|
||||||
|
|
||||||
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not leftmost.
|
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not leftmost.
|
||||||
# "*.leftmost.example.test" matches a single label; the
|
# "*.leftmost.example.test" matches a single label; the
|
||||||
# wildcard in "www.*.example.test" must not be honoured.
|
# wildcard in "www.*.example.test" must not be honoured.
|
||||||
|
|||||||
@@ -146,6 +146,16 @@ cert_san_types_pem = custom_target(
|
|||||||
command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=san-types', '-addext', 'subjectAltName=DNS:a.zz,IP:42.46.122.122', '-out', '@OUTPUT@']
|
command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=san-types', '-addext', 'subjectAltName=DNS:a.zz,IP:42.46.122.122', '-out', '@OUTPUT@']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# cert_dns_cn.pem: CN is the DNS name the tests connect to, with NO
|
||||||
|
# subjectAltName. A host is named by a SAN only (RFC 9525), so verifying it
|
||||||
|
# against this cert must fail.
|
||||||
|
cert_dns_cn_pem = custom_target(
|
||||||
|
'cert_dns_cn_pem',
|
||||||
|
input: key_pem,
|
||||||
|
output: 'cert_dns_cn.pem',
|
||||||
|
command: [openssl, 'req', '-x509', '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-subj', '/CN=localhost', '-out', '@OUTPUT@']
|
||||||
|
)
|
||||||
|
|
||||||
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not leftmost.
|
# cert_wildcard_san.pem: a leftmost wildcard next to one that is not leftmost.
|
||||||
# "*.leftmost.example.test" matches a single label; the wildcard in
|
# "*.leftmost.example.test" matches a single label; the wildcard in
|
||||||
# "www.*.example.test" must not be honoured.
|
# "www.*.example.test" must not be honoured.
|
||||||
@@ -197,6 +207,7 @@ test(
|
|||||||
client_encrypted_pbes1_key_pem,
|
client_encrypted_pbes1_key_pem,
|
||||||
client_encrypted_cert_pem,
|
client_encrypted_cert_pem,
|
||||||
cert_ip_cn_pem,
|
cert_ip_cn_pem,
|
||||||
|
cert_dns_cn_pem,
|
||||||
cert_ipv6_pem,
|
cert_ipv6_pem,
|
||||||
cert_san_types_pem,
|
cert_san_types_pem,
|
||||||
cert_wildcard_san_pem
|
cert_wildcard_san_pem
|
||||||
|
|||||||
+1027
-38
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user