|
|
|
@@ -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.
|
|
|
|
|
|
|
|
|
|
> [!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++
|
|
|
|
|
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
|
|
|
@@ -102,7 +105,7 @@ cli.enable_server_hostname_verification(false);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> [!NOTE]
|
|
|
|
|
> The CA certificates are loaded when the client makes its first connection. Changing `set_ca_cert_path()` or `enable_system_ca()` after that has no effect, so make the settings before the first request, or use a new client. `set_ca_cert_store()` and `load_ca_cert_store()` take effect at once. `WebSocketClient` works the same way.
|
|
|
|
|
> The CA certificates are loaded when the client makes its first connection. Changing `set_ca_cert_path()` or `enable_system_ca()` after that has no effect, so make the settings before the first request, or use a new client. `set_ca_cert_store()` and `load_ca_cert_store()` take effect at once. `WebSocketClient` works the same way. On `Client` and `SSLClient`, a setter that changes a TLS setting also closes a connection kept alive under the old one, so the next request connects again.
|
|
|
|
|
|
|
|
|
|
### SSL Error Handling
|
|
|
|
|
|
|
|
|
@@ -240,7 +243,7 @@ cpp-httplib automatically integrates with the OS certificate store on macOS and
|
|
|
|
|
|
|
|
|
|
| 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` |
|
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A mount point serves a path only when it has no `.`, `..` or empty segment other than a trailing `/`, 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 a name with a trailing `.` or space, or 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
|
|
|
|
|
// User defined file extension and MIME type mappings
|
|
|
|
@@ -415,7 +418,7 @@ svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
|
|
|
|
|
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Extensions match regardless of case, so `photo.JPG` is served as `image/jpeg`. A user mapping is looked up by the exact extension first, then by the extension in lowercase.
|
|
|
|
|
Extensions match regardless of case, so `photo.JPG` is served as `image/jpeg`. The extension of a user mapping can be given in any case, with or without a leading `.`.
|
|
|
|
|
|
|
|
|
|
The following are built-in mappings:
|
|
|
|
|
|
|
|
|
@@ -522,7 +525,7 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Exception handler
|
|
|
|
|
The exception handler gets called if a user routing handler throws an error. This covers the pre-routing handler, the file request handler, the pre-request handler, route handlers and content readers. An exception from the post-routing handler, the error handler, the `Expect: 100-continue` handler, a content provider, a logger or a WebSocket handler does not reach it: the connection is dropped, and the error logger receives `Error::UserCallbackException`.
|
|
|
|
|
The exception handler gets called if a user routing handler throws an error. This covers the pre-routing handler, the file request handler, the pre-request handler, route handlers, content readers and, on a WebSocket upgrade, the sub-protocol selector. The response is a 500 unless the exception handler sets another status. An exception from the post-routing handler, the error handler, the `Expect: 100-continue` handler, a content provider, a logger or a WebSocket handler does not reach it: the connection is dropped, and the error logger receives `Error::UserCallbackException`.
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) {
|
|
|
|
@@ -916,6 +919,14 @@ svr.set_keep_alive_max_count(2); // Default is 100, 0 for no limit
|
|
|
|
|
svr.set_keep_alive_timeout(10); // Default is 5
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Client address behind a reverse proxy
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
svr.set_trusted_proxies({"10.0.0.2"});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A request that comes straight from one of these addresses gets `req.remote_addr` from `X-Forwarded-For`: the rightmost address in it that is not a trusted proxy. A request from anywhere else keeps the address of its connection, whatever `X-Forwarded-For` says. Addresses match as IP addresses, so an IPv4 peer that a server listening on both IPv4 and IPv6 sees as `::ffff:10.0.0.2` matches `10.0.0.2`. Host names and CIDR ranges are not supported.
|
|
|
|
|
|
|
|
|
|
### Timeout
|
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
@@ -928,7 +939,7 @@ svr.set_read_timeout(std::chrono::seconds(5));
|
|
|
|
|
svr.set_keep_alive_timeout(std::chrono::seconds(10));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The read timeout bounds the request line and headers as a whole, and each read of a request body.
|
|
|
|
|
The read timeout bounds the request line and headers as a whole, and each read of a request body. An error response closes the connection. Any other response to a request whose body was left unread, such as a redirect from a pre-routing handler, keeps the connection only when the rest of the body is at most `CPPHTTPLIB_UNREAD_PAYLOAD_MAX_LENGTH` (256KB, `0` for no limit) and arrives within the read timeout, since the server has to read past it first.
|
|
|
|
|
|
|
|
|
|
### Set maximum payload length for reading a request body
|
|
|
|
|
|
|
|
|
@@ -1449,7 +1460,7 @@ res->status; // 200
|
|
|
|
|
|
|
|
|
|
A URL with userinfo (`http://user:pass@host/`) is not supported, neither for a `Client` nor in a `Location`; use `set_basic_auth()` for the credentials. A `Client` constructed from a URL it cannot use, such as one with userinfo, a backslash in the authority or an invalid port, is not valid: `is_valid()` returns `false`, and every request fails with `Error::Connection`.
|
|
|
|
|
|
|
|
|
|
On a redirect to another origin (a different scheme, host or port), `Authorization`, `Cookie` and `Proxy-Authorization` are dropped along with the credentials set with `set_basic_auth()`, `set_bearer_token_auth()` and `set_digest_auth()`, and the client certificate is not presented. Other headers, the default ones included, go along (RFC 9110 §15.4), and so do the timeouts, the payload limit and the TLS verification settings. A `303` that turns a request into a `GET` drops its headers and body; only on the same origin are the default headers sent again.
|
|
|
|
|
On a redirect to another origin (a different scheme, host or port), `Authorization`, `Cookie` and `Proxy-Authorization` are dropped along with the credentials set with `set_basic_auth()`, `set_bearer_token_auth()` and `set_digest_auth()`, and the client certificate is not presented. Other headers, the default ones included, go along (RFC 9110 §15.4), and so do the timeouts, the payload limit and the TLS verification settings. The TLS settings pass only from one HTTPS hop to the next: an HTTPS hop that follows a plain HTTP one starts over with the defaults, verifying against the system CAs with no verifier set. A `303` that turns a request into a `GET` drops its headers and body; only on the same origin are the default headers sent again.
|
|
|
|
|
|
|
|
|
|
Up to `CPPHTTPLIB_REDIRECT_MAX_COUNT` (20 by default) redirects are followed for a request; one more fails it with `Error::ExceedRedirectCount`. Interim `1xx` responses before the final response are skipped, up to `CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT` (100 by default); one more fails the request with `Error::Read`.
|
|
|
|
|
|
|
|
|
@@ -1590,6 +1601,8 @@ This holds for every kind of response, including the file-backed ones below.
|
|
|
|
|
|
|
|
|
|
`Vary: Accept-Encoding` is added only to responses the server encoded itself. A handler that chooses between an encoded and an identity representation by reading `Accept-Encoding` should set the field itself, so that shared caches keep the two apart.
|
|
|
|
|
|
|
|
|
|
Turn this compression off with `svr.set_compress(false)`. Over HTTPS, the length of a compressed response can give away a secret in it, such as a CSRF token, when the same response also carries text an attacker can choose (the BREACH attack). A server whose responses mix the two should turn compression off, or keep such secrets out of those responses. Static file compression, below, is set separately.
|
|
|
|
|
|
|
|
|
|
### Static file compression
|
|
|
|
|
|
|
|
|
|
Responses served from a file, whether through `set_mount_point()` or `Response::set_file_content()`, are sent as is by default. Turn compression on for them with:
|
|
|
|
|