What's Changed
Security-relevant fixes
- Reject an invalid
Content-Lengthand honorConnection: close. A request whoseContent-Lengthwas present but not a valid decimal length (e.g.42, 42,+42,0x2eor empty) was treated as having no body unless a handler read it, so the bytes after the header block were parsed as the next request on the keep-alive connection. Such a request is now rejected with 400 and the connection is closed before routing (RFC 9112 §6.3). The server also kept reading after a response that announcedConnection: close, so a rejected request line or header block left the rest of the message to be parsed as a new request. The connection is now closed whenever the final response carriesConnection: close(RFC 9112 §9.6). - Reject control characters in the request-target.
parse_request_line()accepted them, so e.g.GET /a\rb HTTP/1.1was routed normally. Any byte that is not VCHAR or obs-text is now rejected with 400 (RFC 9112 §3.2, §2.2). obs-text is still allowed since some clients send raw UTF-8 in the target. The client now applies the same check when writing the request line (it used to let an embedded SP or HTAB through), andencode_path()percent-encodes every control character (0x00-0x1F, 0x7F) instead of only CR/LF. - Escape quoted-string auth-params in the Digest
Authorizationheader (#2597).realm,nonceandopaquefrom the server's challenge were written back without escaping, andalgorithmwas emitted verbatim, so a crafted challenge could close the quoted-string early and inject extra auth-params into the request. Quoted values are now escaped as quoted-pairs, and a non-tokenalgorithmfalls back toMD5. A realm that legitimately contains a quote now round-trips correctly. - Escape request data in the docker server's access and error logs.
req.pathis percent-decoded, so a request likeGET /%0D%0A...put a literal CR/LF into the log lines and let a client forge extra entries. The server now logs the rawreq.target(as NGINX's$requestdoes) and escapes",\, control and non-ASCII bytes as\xHH.
Bug fixes
- Serve pipelined requests without waiting for the keep-alive timeout (#2599). The server read the following request(s) into a per-request buffer, discarded them after the first response, and then waited for socket data that never came, closing the connection after the keep-alive timeout. Over TLS the bytes stayed decrypted in the TLS library, out of sight too. The stream is now kept for the whole connection, and a request that is already buffered is served immediately. One empty line before the request-line is ignored, as RFC 9112 §2.2 recommends, since some clients send an extra CRLF after a request body.
- Windows certificate verification (#2602, #2604, refs #2596):
- Pass the intermediates the server sent to CryptoAPI. It got only the leaf and fetched an issuer from the leaf's AIA URL instead, which could chain to a root Windows does not trust while the server's own chain was fine. New
tls::get_peer_certs()returns the peer's certificates for every backend (wolfSSL needsSESSION_CERTSfor this; without it CryptoAPI still gets the leaf alone). - Make CryptoAPI the only chain verifier when Windows verification is on. Windows adds a root to its stores only when CryptoAPI needs it to build a chain, so on a machine that had not needed that root yet, the TLS backend rejected the chain before CryptoAPI ran (e.g. OpenSSL error 20). The backend's chain verdict is no longer used in this mode, and the CryptoAPI check is mandatory: a leaf that cannot be encoded now fails the connection, and the chain must allow server authentication. With a custom CA the backend still verifies the chain, and with
set_server_certificate_verifier()both do, as before.
- Pass the intermediates the server sent to CryptoAPI. It got only the leaf and fetched an issuer from the leaf's AIA URL instead, which could chain to a root Windows does not trust while the server's own chain was fine. New
- Resolve relative
Locationreferences on redirect (#2586). ALocationwithout a leading slash (next,./next,../other,?x=1) was read as a host name or sent as an invalid request target. It is now resolved against the current request path with dot segments removed (RFC 3986 §5.2). Absolute URLs and paths behave as before. - SSE client parsing fixes:
- Preserve empty
datafields (#2594). An event with an emptydatafield (ordatawithout a colon) is now dispatched, and leading empty lines are kept. - A field line without a colon kept the
\rof a CRLF line ending in its name, sodata\r\nwas not recognized. - An event without a
datafield was neither dispatched nor cleared, so its event type leaked into the next event and itsidreachedlast_event_idonly after a later event. The message is now reset on every blank line and theidis recorded even when nothing is dispatched. - Ignore a
retryfield that is not all digits (#2591).retry: -1made the client reconnect without waiting andretry: 10sset 10 ms. - Enforce a minimum reconnect wait of 100 ms (#2592). With an interval of 0 (
retry: 0from the server, orset_reconnect_interval(0)), a server that closes the stream made the client reconnect in a tight loop. Intervals of 1-99 ms already waited 100 ms, so only 0 and negative values change behavior.
- Preserve empty
- Reject trailing characters in URL port numbers (#2593).
http://host:80abcwas accepted as port 80, and a redirectLocationwith such a port was followed. - Reject trailing characters in HTTP quality values (#2590).
q=0.5junkwas accepted, so malformedAcceptvalues are now rejected and invalidAccept-Encodingweights are ignored.
Performance
- Send small static files with the headers, and large bodies without a copy (#2589). A file served from a mount point or through
set_file_content()used to leave in two writes; a small file is now read into the header buffer so the whole response leaves in a single write. Aset_content()body ofCPPHTTPLIB_SEND_BUFSIZor more is now written directly after the headers instead of being copied into the header buffer.
Build
- CMake: detect
GetAddrInfoExCancelon Windows and disableCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFOwith a warning when it is unavailable (#2578). Downstream projects on such toolchains no longer fail to compile with the default settings.
Documentation
- README: describe how Windows certificate verification works with a custom CA and with
set_server_certificate_verifier(). - README: note that
req.pathis percent-decoded and may contain control characters, so request data should be escaped before logging. - README-sse: document the minimum reconnect wait.
Full Changelog: v0.58.0...v0.59.0