mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-26 12:24:46 +00:00
Compare commits
20
Commits
latest
...
a210b43e11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a210b43e11 | ||
|
|
7d28b64a03 | ||
|
|
97a972fcef | ||
|
|
276cf08e84 | ||
|
|
b4b24589ad | ||
|
|
70bed02bc9 | ||
|
|
5db950e1e1 | ||
|
|
b8ae3e43d5 | ||
|
|
64b5c68a46 | ||
|
|
1c91d54b7c | ||
|
|
fbd3e2cc9b | ||
|
|
32a0ca8b35 | ||
|
|
b83b385ed4 | ||
|
|
f297616584 | ||
|
|
1e35f39675 | ||
|
|
e121385910 | ||
|
|
fc37d3eae1 | ||
|
|
3b8b9cf9d5 | ||
|
|
3599533c24 | ||
|
|
247a2abe4a |
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,7 +102,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
|
||||
|
||||
@@ -406,7 +406,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 `/`, 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
|
||||
@@ -415,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:
|
||||
|
||||
@@ -522,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) {
|
||||
@@ -916,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++
|
||||
@@ -928,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
|
||||
|
||||
@@ -1449,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`.
|
||||
|
||||
@@ -1590,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:
|
||||
|
||||
@@ -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()`でファイルをそのまま返す場合、デフォルトでは圧縮されません。有効にするには次を呼びます。
|
||||
|
||||
@@ -142,6 +142,12 @@
|
||||
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
|
||||
#endif
|
||||
|
||||
// How much of an unread request body the server skips to reuse the connection,
|
||||
// 0 for no limit
|
||||
#ifndef CPPHTTPLIB_UNREAD_PAYLOAD_MAX_LENGTH
|
||||
#define CPPHTTPLIB_UNREAD_PAYLOAD_MAX_LENGTH (256 * 1024) // 256KB
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_SSE_MAX_EVENT_LENGTH
|
||||
#define CPPHTTPLIB_SSE_MAX_EVENT_LENGTH (16 * 1024 * 1024) // 16MB
|
||||
#endif
|
||||
@@ -2357,6 +2363,8 @@ public:
|
||||
|
||||
Server &set_payload_max_length(size_t length);
|
||||
|
||||
Server &set_compress(bool on);
|
||||
|
||||
Server &set_static_file_compression(bool on);
|
||||
Server &set_static_file_compression_min_length(size_t length);
|
||||
Server &set_static_file_compression_max_length(size_t length);
|
||||
@@ -2392,10 +2400,11 @@ protected:
|
||||
// Runs the per-connection serving loop and stops an exception thrown by a
|
||||
// user callback from escaping the worker thread.
|
||||
//
|
||||
// process_request() wraps only routing() in a try/catch. Content providers,
|
||||
// the post-routing, error, logging and expect-100 handlers and WebSocket
|
||||
// handlers all run outside it, and the task queue calls the job without a
|
||||
// catch, so an exception from any of those would terminate the process.
|
||||
// process_request() wraps only routing() and the hooks before a WebSocket
|
||||
// upgrade in a try/catch. Content providers, the post-routing, error,
|
||||
// logging and expect-100 handlers and WebSocket handlers all run outside
|
||||
// it, and the task queue calls the job without a catch, so an exception
|
||||
// from any of those would terminate the process.
|
||||
//
|
||||
// No 500 is possible here: by the time a content provider runs, the status
|
||||
// line and headers are already on the wire. Report it through the error
|
||||
@@ -2431,6 +2440,7 @@ protected:
|
||||
time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
|
||||
time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
|
||||
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
||||
bool compress_ = true;
|
||||
bool static_file_compression_ = false;
|
||||
size_t static_file_compression_min_length_ =
|
||||
CPPHTTPLIB_STATIC_FILE_COMPRESSION_MIN_LENGTH;
|
||||
@@ -2481,6 +2491,9 @@ private:
|
||||
|
||||
bool routing(Request &req, Response &res, Stream &strm,
|
||||
bool skip_pre_routing);
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
bool handle_exception(Request &req, Response &res);
|
||||
#endif
|
||||
bool handle_file_request(Request &req, Response &res);
|
||||
bool check_if_not_modified(const Request &req, Response &res,
|
||||
const std::string &etag, time_t mtime) const;
|
||||
@@ -2997,6 +3010,15 @@ protected:
|
||||
void close_socket(Socket &socket);
|
||||
void disconnect(bool gracefully);
|
||||
|
||||
// Under socket_mutex_: whether the connection kept alive can take another
|
||||
// request. One the peer has closed, or one set up under TLS settings changed
|
||||
// since, is closed instead.
|
||||
bool socket_reusable();
|
||||
|
||||
// TLS settings apply as a connection is set up, so a change makes the
|
||||
// connection kept alive unfit for the next request.
|
||||
void tls_settings_changed() { tls_settings_version_++; }
|
||||
|
||||
std::string make_request_target(const Request &req) const;
|
||||
bool process_request(Stream &strm, Request &req, Response &res,
|
||||
bool close_connection, Error &error);
|
||||
@@ -3023,6 +3045,9 @@ protected:
|
||||
size_t socket_requests_in_flight_ = 0;
|
||||
std::thread::id socket_requests_are_from_thread_ = std::thread::id();
|
||||
bool socket_should_be_closed_when_request_is_done_ = false;
|
||||
size_t socket_tls_settings_version_ = 0;
|
||||
|
||||
std::atomic<size_t> tls_settings_version_{0};
|
||||
|
||||
// Hostname to connection target map. The value is an IP literal or another
|
||||
// hostname; only the connection target changes, never the identity.
|
||||
@@ -4019,6 +4044,7 @@ struct ChunkedDecoder {
|
||||
char line_buf[64];
|
||||
size_t last_chunk_total = 0;
|
||||
size_t last_chunk_offset = 0;
|
||||
size_t overhead = 0;
|
||||
|
||||
explicit ChunkedDecoder(Stream &s);
|
||||
|
||||
@@ -5962,9 +5988,10 @@ inline bool is_valid_close_payload(const std::string &payload) {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// A path a mount point serves: absolute, with no ".", ".." or empty segment
|
||||
// except a trailing '/', so the file served is the one that req.path names to
|
||||
// the handlers that ran before it.
|
||||
// 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
|
||||
// ".." and names Windows trims into another file's name), 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) {
|
||||
if (path.empty() || path[0] != '/') { return false; }
|
||||
|
||||
@@ -5975,8 +6002,8 @@ inline bool is_valid_path(const std::string &path) {
|
||||
continue;
|
||||
}
|
||||
auto len = i - beg;
|
||||
if ((len == 0 && i < path.size()) || (len == 1 && path[beg] == '.') ||
|
||||
(len == 2 && path[beg] == '.' && path[beg + 1] == '.')) {
|
||||
if ((len == 0 && i < path.size()) ||
|
||||
(len > 0 && (path[i - 1] == '.' || path[i - 1] == ' '))) {
|
||||
return false;
|
||||
}
|
||||
beg = i + 1;
|
||||
@@ -7728,13 +7755,10 @@ inline std::string
|
||||
find_content_type(const std::string &path,
|
||||
const std::map<std::string, std::string> &user_data,
|
||||
const std::string &default_content_type) {
|
||||
auto ext = file_extension(path);
|
||||
// Extensions are matched regardless of case, as web servers do
|
||||
auto ext = case_ignore::to_lower(file_extension(path));
|
||||
|
||||
// Extensions are matched regardless of case, as web servers do, with an
|
||||
// exact match to a user mapping first
|
||||
auto lower_ext = case_ignore::to_lower(ext);
|
||||
auto it = user_data.find(ext);
|
||||
if (it == user_data.end()) { it = user_data.find(lower_ext); }
|
||||
if (it != user_data.end()) { return it->second; }
|
||||
|
||||
static const struct {
|
||||
@@ -7793,7 +7817,7 @@ find_content_type(const std::string &path,
|
||||
};
|
||||
|
||||
for (const auto &t : types) {
|
||||
if (lower_ext == t.ext) { return t.type; }
|
||||
if (ext == t.ext) { return t.type; }
|
||||
}
|
||||
return default_content_type;
|
||||
}
|
||||
@@ -8418,6 +8442,18 @@ inline void set_header(Headers &headers, const std::string &key,
|
||||
if (fields::is_field_valid(key, val)) { headers.emplace(key, val); }
|
||||
}
|
||||
|
||||
// Drops the fields set_header() would refuse, from headers an application can
|
||||
// fill directly: CR/LF in one would split the message.
|
||||
inline void erase_invalid_fields(Headers &headers) {
|
||||
for (auto it = headers.begin(); it != headers.end();) {
|
||||
if (fields::is_field_valid(it->first, it->second)) {
|
||||
++it;
|
||||
} else {
|
||||
it = headers.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
const auto bufsiz = 2048;
|
||||
char buf[bufsiz];
|
||||
@@ -8895,13 +8931,6 @@ inline ssize_t write_response_line(Stream &strm, int status) {
|
||||
inline ssize_t write_headers(Stream &strm, const Headers &headers) {
|
||||
ssize_t write_len = 0;
|
||||
for (const auto &x : headers) {
|
||||
// Skip fields with invalid names or values to prevent response splitting
|
||||
// via CR/LF injection, matching set_header(). The client validates request
|
||||
// headers up front in check_and_write_headers, but the server passes
|
||||
// res.headers straight to this writer, and res.headers is a public field
|
||||
// an application can populate directly with request-derived values.
|
||||
if (!fields::is_field_valid(x.first, x.second)) { continue; }
|
||||
|
||||
std::string s;
|
||||
s = x.first;
|
||||
s += ": ";
|
||||
@@ -10377,15 +10406,6 @@ inline bool has_framed_body(const Request &req) {
|
||||
req.get_header_value_u64("Content-Length") > 0;
|
||||
}
|
||||
|
||||
inline bool is_connection_persistent(const Request &req) {
|
||||
if (has_header_token(req.headers, "Connection", "close")) { return false; }
|
||||
if (req.version == "HTTP/1.0" &&
|
||||
!has_header_token(req.headers, "Connection", "keep-alive")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "DELETE") {
|
||||
@@ -10649,6 +10669,32 @@ inline bool is_ip_address(const std::string &host) {
|
||||
inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
|
||||
}
|
||||
|
||||
// One spelling for an IPv6 address, and the IPv4 one for an IPv4-mapped IPv6
|
||||
// address, which is how a dual-stack socket reports an IPv4 peer. Anything
|
||||
// else, including an address with a zone ID, is returned as it is.
|
||||
inline std::string canonical_ip(const std::string &addr) {
|
||||
struct in6_addr addr6;
|
||||
if (addr.find('%') != std::string::npos ||
|
||||
inet_pton(AF_INET6, addr.c_str(), &addr6) != 1) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
if (IN6_IS_ADDR_V4MAPPED(&addr6)) {
|
||||
return inet_ntop(AF_INET, &addr6.s6_addr[12], buf, sizeof(buf)) ? buf
|
||||
: addr;
|
||||
}
|
||||
return inet_ntop(AF_INET6, &addr6, buf, sizeof(buf)) ? buf : addr;
|
||||
}
|
||||
|
||||
// `trusted_proxies` holds canonical_ip() spellings.
|
||||
inline bool is_trusted_proxy(const std::string &addr,
|
||||
const std::vector<std::string> &trusted_proxies) {
|
||||
if (trusted_proxies.empty()) { return false; }
|
||||
return std::find(trusted_proxies.begin(), trusted_proxies.end(),
|
||||
canonical_ip(addr)) != trusted_proxies.end();
|
||||
}
|
||||
|
||||
// Resolve where a client should connect for `host`, honoring a user-supplied
|
||||
// hostname-to-address map. `host` itself is never rewritten, so it keeps
|
||||
// supplying the Host header and SNI; only the connection target changes.
|
||||
@@ -13694,7 +13740,10 @@ inline bool Server::remove_mount_point(const std::string &mount_point) {
|
||||
inline Server &
|
||||
Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
|
||||
const std::string &mime) {
|
||||
file_extension_and_mimetype_map_[ext] = mime;
|
||||
// Keyed as find_content_type() looks it up
|
||||
auto key = detail::case_ignore::to_lower(ext);
|
||||
if (!key.empty() && key[0] == '.') { key.erase(0, 1); }
|
||||
file_extension_and_mimetype_map_[key] = mime;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -13802,7 +13851,11 @@ inline Server &Server::set_header_writer(
|
||||
|
||||
inline Server &
|
||||
Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
|
||||
trusted_proxies_ = proxies;
|
||||
trusted_proxies_.clear();
|
||||
trusted_proxies_.reserve(proxies.size());
|
||||
for (const auto &proxy : proxies) {
|
||||
trusted_proxies_.push_back(detail::canonical_ip(proxy));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -13848,6 +13901,11 @@ inline Server &Server::set_payload_max_length(size_t length) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_compress(bool on) {
|
||||
compress_ = on;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_static_file_compression(bool on) {
|
||||
static_file_compression_ = on;
|
||||
return *this;
|
||||
@@ -14003,6 +14061,10 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
need_apply_ranges = true;
|
||||
}
|
||||
|
||||
// Before anything below reads res.headers, which the handlers may have
|
||||
// filled directly
|
||||
detail::erase_invalid_fields(res.headers);
|
||||
|
||||
if (!detail::body_allowed_for_status(res.status)) {
|
||||
detail::clear_content(res);
|
||||
}
|
||||
@@ -14046,6 +14108,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
|
||||
if (post_routing_handler_) {
|
||||
post_routing_handler_(req, res);
|
||||
detail::erase_invalid_fields(res.headers);
|
||||
// Again, for a body or framing headers the handler changed.
|
||||
detail::set_framing_headers(req, res, boundary, content_type);
|
||||
}
|
||||
@@ -14258,38 +14321,10 @@ inline bool Server::read_content_core(
|
||||
}
|
||||
|
||||
// RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body.
|
||||
// For non-SSL builds we still scan non-persistent connections for stray
|
||||
// body bytes so the payload limit is enforced (413). On keep-alive,
|
||||
// pending bytes may be the next request (issue #2450), so skip.
|
||||
#if !defined(CPPHTTPLIB_SSL_ENABLED)
|
||||
if (!req.has_header("Content-Length") &&
|
||||
!detail::is_chunked_transfer_encoding(req.headers)) {
|
||||
if (!detail::is_connection_persistent(req) && payload_max_length_ > 0 &&
|
||||
payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
|
||||
auto has_data = strm.is_readable();
|
||||
if (!has_data) {
|
||||
auto s = strm.socket();
|
||||
if (s != INVALID_SOCKET) {
|
||||
has_data = detail::select_read(s, 0, 0) > 0;
|
||||
}
|
||||
}
|
||||
if (has_data) {
|
||||
// Route through the same decompressing reader used by the
|
||||
// length-framed and chunked paths below, so payload_max_length_ is
|
||||
// enforced on the decompressed size here too instead of only on the
|
||||
// compressed wire bytes.
|
||||
return detail::read_content(strm, req, payload_max_length_, res.status,
|
||||
nullptr, out, true);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if (!req.has_header("Content-Length") &&
|
||||
!detail::is_chunked_transfer_encoding(req.headers)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// A second read would start where the first one stopped, or in the next
|
||||
// request.
|
||||
@@ -14636,6 +14671,17 @@ inline bool Server::listen_internal() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
// Call from a catch block. The response is a 500 unless the exception handler
|
||||
// sets another status. Returns whether the exception handler ran.
|
||||
inline bool Server::handle_exception(Request &req, Response &res) {
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
if (!exception_handler_) { return false; }
|
||||
exception_handler_(req, res, std::current_exception());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool Server::routing(Request &req, Response &res, Stream &strm,
|
||||
bool skip_pre_routing) {
|
||||
if (!skip_pre_routing && pre_routing_handler_ &&
|
||||
@@ -14871,7 +14917,8 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
"multipart/byteranges; boundary=" + boundary);
|
||||
}
|
||||
|
||||
auto type = detail::encoding_type(req, res);
|
||||
auto type =
|
||||
compress_ ? detail::encoding_type(req, res) : detail::EncodingType::None;
|
||||
|
||||
if (res.body.empty()) {
|
||||
if (res.content_length_ > 0 && req.ranges.size() == 1 &&
|
||||
@@ -14978,12 +15025,7 @@ get_client_ip(const std::string &x_forwarded_for,
|
||||
// returned as the client.
|
||||
for (size_t i = ip_list.size(); i-- > 0;) {
|
||||
const auto &ip = ip_list[i];
|
||||
|
||||
auto is_trusted_proxy =
|
||||
std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
|
||||
[&](const std::string &proxy) { return ip == proxy; });
|
||||
|
||||
if (!is_trusted_proxy) { return ip; }
|
||||
if (!detail::is_trusted_proxy(ip, trusted_proxies)) { return ip; }
|
||||
}
|
||||
|
||||
// Every hop was a trusted proxy; fall back to the first entry.
|
||||
@@ -15003,7 +15045,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
|
||||
// The read timeout bounds the request line and headers as a whole, so a
|
||||
// client sending them a byte at a time cannot hold the worker any longer.
|
||||
strm.set_max_timeout(read_timeout_sec_ * 1000 + read_timeout_usec_ / 1000);
|
||||
auto read_timeout_msec = read_timeout_sec_ * 1000 + read_timeout_usec_ / 1000;
|
||||
strm.set_max_timeout(read_timeout_msec);
|
||||
|
||||
// Connection has been closed on client
|
||||
if (!line_reader.getline()) { return false; }
|
||||
@@ -15078,6 +15121,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// RFC 9112 6.1: a lone "chunked" is the only transfer coding implemented
|
||||
if (req.has_header("Transfer-Encoding") &&
|
||||
(req.get_header_value_count("Transfer-Encoding") != 1 ||
|
||||
!detail::case_ignore::equal(req.get_header_value("Transfer-Encoding"),
|
||||
"chunked"))) {
|
||||
connection_closed = true;
|
||||
res.status = StatusCode::NotImplemented_501;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// RFC 9112 §6.1: a Transfer-Encoding alongside a Content-Length, or in an
|
||||
// HTTP/1.0 request, is honoured, but the connection closes after the
|
||||
// response.
|
||||
@@ -15115,11 +15168,8 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
// Only honor X-Forwarded-For if the peer on the actual TCP connection is
|
||||
// itself a trusted proxy. Otherwise any direct client could spoof
|
||||
// remote_addr simply by sending an arbitrary X-Forwarded-For header.
|
||||
auto is_trusted_peer = std::any_of(
|
||||
trusted_proxies_.begin(), trusted_proxies_.end(),
|
||||
[&](const std::string &proxy) { return proxy == remote_addr; });
|
||||
|
||||
if (is_trusted_peer && req.has_header("X-Forwarded-For")) {
|
||||
if (detail::is_trusted_proxy(remote_addr, trusted_proxies_) &&
|
||||
req.has_header("X-Forwarded-For")) {
|
||||
// Some proxies append the address they observed as a separate
|
||||
// X-Forwarded-For field line instead of extending the one the client sent
|
||||
// (e.g. HAProxy's "option forwardfor"), so the whole combined value has to
|
||||
@@ -15148,7 +15198,9 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
}
|
||||
}
|
||||
|
||||
if (req.has_header("Range")) {
|
||||
// RFC 9110 14.2: range handling is defined only for GET (and so for HEAD)
|
||||
if ((req.method == "GET" || req.method == "HEAD") &&
|
||||
req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
if (!detail::parse_range_header(range_header_value, req.ranges)) {
|
||||
connection_closed = true;
|
||||
@@ -15204,27 +15256,22 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
auto close_if_answered =
|
||||
close_after_response || detail::has_framed_body(req);
|
||||
|
||||
if (pre_routing_handler_ &&
|
||||
pre_routing_handler_(req, res) == HandlerResponse::Handled) {
|
||||
if (res.status == -1) { res.status = StatusCode::OK_200; }
|
||||
return write_response_with_content(strm, close_if_answered, req, res);
|
||||
}
|
||||
// Find matching WebSocket handler
|
||||
for (const auto &entry : websocket_handlers_) {
|
||||
if (entry.matcher->match(req)) {
|
||||
const WebSocketHandlerEntry *ws_route = nullptr;
|
||||
std::string selected_subprotocol;
|
||||
auto answer_before_upgrade = [&]() -> bool {
|
||||
if (pre_routing_handler_ &&
|
||||
pre_routing_handler_(req, res) == HandlerResponse::Handled) {
|
||||
return true;
|
||||
}
|
||||
for (const auto &entry : websocket_handlers_) {
|
||||
if (!entry.matcher->match(req)) { continue; }
|
||||
req.matched_route = entry.matcher->pattern();
|
||||
if (pre_request_handler_ &&
|
||||
pre_request_handler_(req, res) == HandlerResponse::Handled) {
|
||||
if (res.status == -1) { res.status = StatusCode::OK_200; }
|
||||
return write_response_with_content(strm, close_if_answered, req, res);
|
||||
return true;
|
||||
}
|
||||
ws_route = &entry;
|
||||
|
||||
// Compute accept key
|
||||
auto client_key = req.get_header_value("Sec-WebSocket-Key");
|
||||
auto accept_key = detail::websocket_accept_key(client_key);
|
||||
|
||||
// Negotiate subprotocol
|
||||
std::string selected_subprotocol;
|
||||
if (entry.sub_protocol_selector) {
|
||||
auto protocol_header = detail::get_combined_header_value(
|
||||
req.headers, "Sec-WebSocket-Protocol");
|
||||
@@ -15244,57 +15291,78 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Send 101 Switching Protocols
|
||||
std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: " +
|
||||
accept_key + "\r\n";
|
||||
if (!selected_subprotocol.empty()) {
|
||||
if (!detail::fields::is_field_value(selected_subprotocol)) {
|
||||
return false;
|
||||
}
|
||||
handshake_response +=
|
||||
"Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
|
||||
}
|
||||
handshake_response += "\r\n";
|
||||
if (strm.write(handshake_response.data(), handshake_response.size()) <
|
||||
0) {
|
||||
auto answered = false;
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
answered = answer_before_upgrade();
|
||||
#else
|
||||
try {
|
||||
answered = answer_before_upgrade();
|
||||
} catch (...) {
|
||||
if (!handle_exception(req, res)) {
|
||||
return write_response(strm, close_if_answered, req, res);
|
||||
}
|
||||
answered = true;
|
||||
}
|
||||
#endif
|
||||
if (answered) {
|
||||
if (res.status == -1) { res.status = StatusCode::OK_200; }
|
||||
return write_response_with_content(strm, close_if_answered, req, res);
|
||||
}
|
||||
|
||||
if (ws_route) {
|
||||
auto client_key = req.get_header_value("Sec-WebSocket-Key");
|
||||
auto accept_key = detail::websocket_accept_key(client_key);
|
||||
|
||||
// Send 101 Switching Protocols
|
||||
std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Accept: " +
|
||||
accept_key + "\r\n";
|
||||
if (!selected_subprotocol.empty()) {
|
||||
if (!detail::fields::is_field_value(selected_subprotocol)) {
|
||||
return false;
|
||||
}
|
||||
handshake_response +=
|
||||
"Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
|
||||
}
|
||||
handshake_response += "\r\n";
|
||||
if (strm.write(handshake_response.data(), handshake_response.size()) <
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
connection_closed = true;
|
||||
if (websocket_upgraded) { *websocket_upgraded = true; }
|
||||
connection_closed = true;
|
||||
if (websocket_upgraded) { *websocket_upgraded = true; }
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
if (req.ssl) {
|
||||
// wss: the heartbeat ping thread and the read path enter the same
|
||||
// TLS session from different threads. Hand the WebSocket a stream
|
||||
// that serializes every TLS call, so the shared SSLSocketStream on
|
||||
// the plain HTTP/HTTPS paths stays untouched.
|
||||
auto ws_strm =
|
||||
std::unique_ptr<Stream>(new detail::WebSocketSSLStream(
|
||||
strm.socket(), const_cast<tls::session_t>(req.ssl),
|
||||
CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 0,
|
||||
write_timeout_sec_, write_timeout_usec_));
|
||||
ws::WebSocket ws(std::move(ws_strm), req, true,
|
||||
websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_);
|
||||
entry.handler(req, ws);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// Use WebSocket-specific read timeout instead of HTTP timeout
|
||||
strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND,
|
||||
0);
|
||||
ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_);
|
||||
entry.handler(req, ws);
|
||||
}
|
||||
if (req.ssl) {
|
||||
// wss: the heartbeat ping thread and the read path enter the same
|
||||
// TLS session from different threads. Hand the WebSocket a stream
|
||||
// that serializes every TLS call, so the shared SSLSocketStream on
|
||||
// the plain HTTP/HTTPS paths stays untouched.
|
||||
auto ws_strm = std::unique_ptr<Stream>(new detail::WebSocketSSLStream(
|
||||
strm.socket(), const_cast<tls::session_t>(req.ssl),
|
||||
CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 0,
|
||||
write_timeout_sec_, write_timeout_usec_));
|
||||
ws::WebSocket ws(std::move(ws_strm), req, true,
|
||||
websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_);
|
||||
ws_route->handler(req, ws);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// Use WebSocket-specific read timeout instead of HTTP timeout
|
||||
strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 0);
|
||||
ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
|
||||
websocket_max_missed_pongs_);
|
||||
ws_route->handler(req, ws);
|
||||
return true;
|
||||
}
|
||||
// No WebSocket route: route it as a plain HTTP request
|
||||
}
|
||||
@@ -15306,23 +15374,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
#else
|
||||
try {
|
||||
routed = routing(req, res, strm, is_upgrade);
|
||||
} catch (std::exception &) {
|
||||
if (exception_handler_) {
|
||||
auto ep = std::current_exception();
|
||||
exception_handler_(req, res, ep);
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
}
|
||||
} catch (...) {
|
||||
if (exception_handler_) {
|
||||
auto ep = std::current_exception();
|
||||
exception_handler_(req, res, ep);
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
}
|
||||
}
|
||||
} catch (...) { routed = handle_exception(req, res); }
|
||||
#endif
|
||||
auto ret = false;
|
||||
if (routed) {
|
||||
@@ -15378,14 +15430,18 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
// keep-alive. Without framing there is no body to drain — reading would
|
||||
// consume the next request (issue #2450). If the response has committed the
|
||||
// connection to close, there is no next request to protect. A body read in
|
||||
// part cannot be drained from where it stopped, so the connection closes.
|
||||
// part cannot be drained from where it stopped, so the connection closes,
|
||||
// as it does when the body is longer than the drain limit or does not
|
||||
// arrive within the read timeout.
|
||||
if (!req.body_consumed_ && detail::has_framed_body(req) &&
|
||||
!detail::has_header_token(res.headers, "Connection", "close")) {
|
||||
strm.set_max_timeout(read_timeout_msec);
|
||||
int dummy_status;
|
||||
if (req.body_read_started_ ||
|
||||
!detail::read_content(
|
||||
strm, req, payload_max_length_, dummy_status, nullptr,
|
||||
[](const char *, size_t, size_t, size_t) { return true; }, false)) {
|
||||
strm, req, CPPHTTPLIB_UNREAD_PAYLOAD_MAX_LENGTH, dummy_status,
|
||||
nullptr, [](const char *, size_t, size_t, size_t) { return true; },
|
||||
false)) {
|
||||
connection_closed = true;
|
||||
}
|
||||
}
|
||||
@@ -15526,6 +15582,7 @@ inline bool ClientImpl::create_and_connect_socket(Socket &socket,
|
||||
}
|
||||
|
||||
inline bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
|
||||
socket_tls_settings_version_ = tls_settings_version_;
|
||||
return create_and_connect_socket(socket, error);
|
||||
}
|
||||
|
||||
@@ -15575,6 +15632,28 @@ inline void ClientImpl::disconnect(bool gracefully) {
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::socket_reusable() {
|
||||
if (!socket_.is_open()) { return false; }
|
||||
|
||||
auto is_alive = detail::is_socket_alive(socket_.sock);
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
if (is_alive && is_ssl() && tls::is_peer_closed(socket_.ssl, socket_.sock)) {
|
||||
is_alive = false;
|
||||
}
|
||||
#endif
|
||||
if (!is_alive) {
|
||||
// Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
|
||||
disconnect(/*gracefully=*/false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_ssl() && socket_tls_settings_version_ != tls_settings_version_) {
|
||||
disconnect(/*gracefully=*/true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res,
|
||||
bool skip_100_continue) const {
|
||||
@@ -15652,25 +15731,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
// socket.
|
||||
socket_should_be_closed_when_request_is_done_ = false;
|
||||
|
||||
auto is_alive = false;
|
||||
if (socket_.is_open()) {
|
||||
is_alive = detail::is_socket_alive(socket_.sock);
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
if (is_alive && is_ssl()) {
|
||||
if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
|
||||
is_alive = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!is_alive) {
|
||||
// Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
|
||||
disconnect(/*gracefully=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_alive) {
|
||||
if (!socket_reusable()) {
|
||||
if (!ensure_socket_connection(socket_, error)) { return false; }
|
||||
|
||||
{
|
||||
@@ -15833,20 +15894,7 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
|
||||
auto is_alive = false;
|
||||
if (socket_.is_open()) {
|
||||
is_alive = detail::is_socket_alive(socket_.sock);
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
if (is_alive && is_ssl()) {
|
||||
if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
|
||||
is_alive = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!is_alive) { disconnect(/*gracefully=*/false); }
|
||||
}
|
||||
|
||||
if (!is_alive) {
|
||||
if (!socket_reusable()) {
|
||||
if (!ensure_socket_connection(socket_, handle.error)) {
|
||||
handle.response.reset();
|
||||
return handle;
|
||||
@@ -15894,8 +15942,11 @@ inline ClientImpl::StreamHandle ClientImpl::open_stream_(Request &req) {
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
if (detail::write_request_line(bstrm, req.method,
|
||||
proxy_target_prefix() + req.path) < 0) {
|
||||
// The path follows the authority in absolute-form, so one that does not
|
||||
// begin with '/' (e.g. "@evil.example/x") would name another host.
|
||||
auto prefix = proxy_target_prefix();
|
||||
if ((!prefix.empty() && req.path[0] != '/') ||
|
||||
detail::write_request_line(bstrm, req.method, prefix + req.path) < 0) {
|
||||
handle.error = Error::Write;
|
||||
handle.response.reset();
|
||||
return handle;
|
||||
@@ -16113,6 +16164,15 @@ inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
|
||||
if (!is_space_or_tab(*p) && !fields::is_field_vchar(*p)) { return -1; }
|
||||
}
|
||||
|
||||
// The bytes around the data are no payload, so they are counted on their
|
||||
// own, as Go does: each chunk may spend 16 bytes and twice its data on
|
||||
// them, which lets a stream send a byte at a time, and what goes past that
|
||||
// in padding, leading zeros and extensions builds up to one header field.
|
||||
overhead += lr.size() + 2;
|
||||
overhead = overhead > 16 ? overhead - 16 : 0;
|
||||
overhead = chunk_len > overhead / 2 ? 0 : overhead - 2 * chunk_len;
|
||||
if (overhead > CPPHTTPLIB_HEADER_MAX_LENGTH) { return -1; }
|
||||
|
||||
if (chunk_len == 0) {
|
||||
chunk_remaining = 0;
|
||||
finished = true;
|
||||
@@ -16191,6 +16251,13 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
||||
|
||||
auto prefix = proxy_target_prefix();
|
||||
if (!prefix.empty()) {
|
||||
// The path follows the authority in absolute-form, so one that does not
|
||||
// begin with '/' (e.g. "@evil.example/x") would name another host.
|
||||
if (req.path[0] != '/') {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto req2 = req;
|
||||
req2.path = prefix + req.path;
|
||||
ret = process_request(strm, req2, res, close_connection, error);
|
||||
@@ -18054,11 +18121,15 @@ inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
||||
if (server_certificate_verification_ == enabled) { return; }
|
||||
server_certificate_verification_ = enabled;
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
|
||||
if (server_hostname_verification_ == enabled) { return; }
|
||||
server_hostname_verification_ = enabled;
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
inline void ClientImpl::enable_system_ca(bool enabled) {
|
||||
@@ -19217,25 +19288,32 @@ inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
|
||||
tls::free_ca_store(ca_cert_store);
|
||||
}
|
||||
custom_ca_loaded_ = true;
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
inline void
|
||||
SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
|
||||
if (!ctx_) { return; }
|
||||
server_certificate_verifier_ = verifier;
|
||||
// Under the lock that creating a session from ctx_ takes
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||
tls::set_verify_callback(ctx_, std::move(verifier));
|
||||
{
|
||||
// Under the lock that creating a session from ctx_ takes
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||
tls::set_verify_callback(ctx_, std::move(verifier));
|
||||
}
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
inline void SSLClient::set_session_verifier(
|
||||
std::function<SSLVerifierResponse(tls::session_t)> verifier) {
|
||||
session_verifier_ = std::move(verifier);
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
|
||||
inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
|
||||
if (enable_windows_cert_verification_ == enabled) { return; }
|
||||
enable_windows_cert_verification_ = enabled;
|
||||
tls_settings_changed();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19250,6 +19328,7 @@ inline void SSLClient::load_ca_cert_store(const char *ca_cert,
|
||||
tls::load_ca_pem(ctx_, ca_cert, size);
|
||||
}
|
||||
custom_ca_loaded_ = true;
|
||||
tls_settings_changed();
|
||||
}
|
||||
|
||||
inline bool SSLClient::load_certs() {
|
||||
@@ -21966,24 +22045,23 @@ inline ca_store_t create_ca_store(const char *pem, size_t len) {
|
||||
if (!detail::ensure_mbedtls_psa_crypto()) { return nullptr; }
|
||||
#endif
|
||||
|
||||
if (!pem) { return nullptr; }
|
||||
|
||||
// mbedtls_x509_crt_parse expects null-terminated PEM
|
||||
std::string pem_str(pem, len);
|
||||
|
||||
auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
|
||||
if (!ca_chain) { return nullptr; }
|
||||
|
||||
mbedtls_x509_crt_init(ca_chain);
|
||||
|
||||
// mbedtls_x509_crt_parse expects null-terminated PEM
|
||||
int ret = mbedtls_x509_crt_parse(ca_chain,
|
||||
reinterpret_cast<const unsigned char *>(pem),
|
||||
len + 1); // +1 for null terminator
|
||||
auto ret = mbedtls_x509_crt_parse(
|
||||
ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
|
||||
pem_str.size() + 1);
|
||||
if (ret != 0) {
|
||||
// Try without +1 in case PEM is already null-terminated
|
||||
ret = mbedtls_x509_crt_parse(
|
||||
ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
|
||||
if (ret != 0) {
|
||||
mbedtls_x509_crt_free(ca_chain);
|
||||
delete ca_chain;
|
||||
return nullptr;
|
||||
}
|
||||
mbedtls_x509_crt_free(ca_chain);
|
||||
delete ca_chain;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return static_cast<ca_store_t>(ca_chain);
|
||||
|
||||
+600
-271
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user