mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-27 12:31:28 +00:00
Compare commits
30
Commits
v0.62.0
...
a210b43e11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a210b43e11 | ||
|
|
7d28b64a03 | ||
|
|
97a972fcef | ||
|
|
276cf08e84 | ||
|
|
b4b24589ad | ||
|
|
70bed02bc9 | ||
|
|
5db950e1e1 | ||
|
|
b8ae3e43d5 | ||
|
|
64b5c68a46 | ||
|
|
1c91d54b7c | ||
|
|
fbd3e2cc9b | ||
|
|
32a0ca8b35 | ||
|
|
b83b385ed4 | ||
|
|
f297616584 | ||
|
|
1e35f39675 | ||
|
|
e121385910 | ||
|
|
fc37d3eae1 | ||
|
|
3b8b9cf9d5 | ||
|
|
3599533c24 | ||
|
|
247a2abe4a | ||
|
|
5bcfea7b90 | ||
|
|
fd71c11e0e | ||
|
|
fd5dd79bd2 | ||
|
|
6304fc6abb | ||
|
|
8772d10c90 | ||
|
|
98647e059d | ||
|
|
7a9cee0e05 | ||
|
|
71b5f0a1c8 | ||
|
|
67b88030e8 | ||
|
|
41c85c0733 |
No files matched your search
@@ -28,10 +28,13 @@ jobs:
|
||||
test-arm32:
|
||||
name: ARM 32-bit (cross-compile)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
# Loose, because apt on the runner mirror can crawl for minutes; the build
|
||||
# step has its own tight limit.
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install cross compiler
|
||||
run: sudo apt-get update && sudo apt-get install -y g++-arm-linux-gnueabihf
|
||||
- name: Build (ARM 32-bit)
|
||||
timeout-minutes: 2
|
||||
run: arm-linux-gnueabihf-g++ -std=c++11 -Wall -Wextra -Wno-psabi -Werror -c -o /dev/null test/test_32bit_build.cpp
|
||||
@@ -209,7 +209,11 @@ jobs:
|
||||
git clone https://boringssl.googlesource.com/boringssl boringssl
|
||||
cd boringssl
|
||||
git checkout "${{ steps.boringssl-rev.outputs.sha }}"
|
||||
# GCC 15 reports a false -Wstringop-overflow in BoringSSL's Keccak
|
||||
# code, which its -Werror makes fatal. Clang is its primary compiler.
|
||||
cmake -S . -B build \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
@@ -319,10 +323,9 @@ jobs:
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
name: issue-2431 repro (Linux + ASAN)
|
||||
# Bound the whole job in case anything in the test harness hangs
|
||||
# unexpectedly. With the fixture in place a normal run is well under
|
||||
# a minute either way (ASAN abort on broken HEAD, clean pass on fix).
|
||||
timeout-minutes: 5
|
||||
# Loose, because apt on the runner mirror can crawl for minutes; the test
|
||||
# step has its own tight limit.
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
DNS_FIXTURE_PORT: "15353"
|
||||
DNS_FIXTURE_DELAY: "3"
|
||||
@@ -370,6 +373,8 @@ jobs:
|
||||
- name: build test binary
|
||||
run: cd test && make test
|
||||
- name: run GetAddrInfoAsyncCancelTest
|
||||
# A normal run takes about 80s.
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
cd test
|
||||
ARCH=$(uname -m)
|
||||
|
||||
@@ -102,13 +102,13 @@ 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
|
||||
|
||||
When SSL operations fail, cpp-httplib provides detailed error information through `ssl_error()` and `ssl_backend_error()`:
|
||||
|
||||
- `ssl_error()` - Returns the TLS-level error as a backend-independent `httplib::tls::ErrorCode` value (e.g., `Fatal`, `CertVerifyFailed`), cast to `int`. `CertVerifyFailed` comes from Mbed TLS and wolfSSL. OpenSSL checks the chain and the host name after the handshake, so a failure there leaves this `0`
|
||||
- `ssl_error()` - Returns the TLS-level error as a backend-independent `httplib::tls::ErrorCode` value (e.g., `Fatal`, `CertVerifyFailed`), cast to `int`. A server certificate that the built-in verification rejects reports `CertVerifyFailed`, or `HostnameMismatch` when only its name does not match
|
||||
- `ssl_backend_error()` - Returns the backend-specific error code (e.g., `ERR_get_error()` for OpenSSL/wolfSSL, return value for Mbed TLS). With OpenSSL, a certificate verification failure reports the verify result (`X509_V_ERR_*`) here instead. On Windows, when CryptoAPI verifies the chain, a failure there reports the CryptoAPI error code here
|
||||
|
||||
A request that fails after the handshake with `Error::Read` or `Error::Write` also carries the TLS error behind it in both values, such as a TLS 1.3 "certificate required" alert from the server. Both values are reset for each request.
|
||||
@@ -210,6 +210,8 @@ client_pem.key_pem_len = client_key.size();
|
||||
httplib::SSLClient cli("api.example.com", 443, client_pem);
|
||||
```
|
||||
|
||||
A length of 0 means the field is a NUL-terminated string.
|
||||
|
||||
`httplib::ws::WebSocketClient` has the same `PemMemory` constructor for `wss://` connections. See [README-websocket.md](README-websocket.md) for details.
|
||||
|
||||
### Peer Certificate Inspection
|
||||
@@ -404,6 +406,8 @@ 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 `/`, 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.
|
||||
|
||||
```cpp
|
||||
// User defined file extension and MIME type mappings
|
||||
svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
|
||||
@@ -411,7 +415,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:
|
||||
|
||||
@@ -518,7 +522,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) {
|
||||
@@ -912,6 +916,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++
|
||||
@@ -924,7 +936,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
|
||||
|
||||
@@ -1445,7 +1457,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`.
|
||||
|
||||
@@ -1586,6 +1598,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:
|
||||
|
||||
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
|
||||
|
||||
[site]
|
||||
title = "cpp-httplib"
|
||||
version = "0.62.0"
|
||||
version = "0.63.0"
|
||||
hostname = "https://yhirose.github.io"
|
||||
base_path = "/cpp-httplib"
|
||||
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
||||
|
||||
@@ -48,6 +48,14 @@ svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
});
|
||||
```
|
||||
|
||||
## Turning it off
|
||||
|
||||
```cpp
|
||||
svr.set_compress(false);
|
||||
```
|
||||
|
||||
Handler responses, buffered or streamed, are then sent as they are. 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). Static file compression is set separately.
|
||||
|
||||
## Static files need to be opted in
|
||||
|
||||
Files served as they are, through `set_mount_point()` or `Response::set_file_content()`, are not compressed by default. Turn it on with:
|
||||
|
||||
@@ -48,6 +48,14 @@ svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
||||
});
|
||||
```
|
||||
|
||||
## 圧縮を止める
|
||||
|
||||
```cpp
|
||||
svr.set_compress(false);
|
||||
```
|
||||
|
||||
ハンドラのレスポンスは、通常のボディもストリーミングも、そのまま送られます。HTTPSでは、攻撃者が選べる文字列とCSRFトークンなどの秘密が同じレスポンスに載ると、圧縮後の長さから秘密が漏れることがあります(BREACH攻撃)。静的ファイルの圧縮は別に設定します。
|
||||
|
||||
## 静的ファイルは明示的に有効にする
|
||||
|
||||
`set_mount_point()`や`Response::set_file_content()`でファイルをそのまま返す場合、デフォルトでは圧縮されません。有効にするには次を呼びます。
|
||||
|
||||
+882
-292
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user