github yhirose/cpp-httplib v0.59.0

3 hours ago

What's Changed

Security-relevant fixes

  • Reject an invalid Content-Length and honor Connection: close. A request whose Content-Length was present but not a valid decimal length (e.g. 42, 42, +42, 0x2e or 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 announced Connection: 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 carries Connection: 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.1 was 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), and encode_path() percent-encodes every control character (0x00-0x1F, 0x7F) instead of only CR/LF.
  • Escape quoted-string auth-params in the Digest Authorization header (#2597). realm, nonce and opaque from the server's challenge were written back without escaping, and algorithm was 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-token algorithm falls back to MD5. A realm that legitimately contains a quote now round-trips correctly.
  • Escape request data in the docker server's access and error logs. req.path is percent-decoded, so a request like GET /%0D%0A... put a literal CR/LF into the log lines and let a client forge extra entries. The server now logs the raw req.target (as NGINX's $request does) 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 needs SESSION_CERTS for 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.
  • Resolve relative Location references on redirect (#2586). A Location without 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 data fields (#2594). An event with an empty data field (or data without a colon) is now dispatched, and leading empty lines are kept.
    • A field line without a colon kept the \r of a CRLF line ending in its name, so data\r\n was not recognized.
    • An event without a data field was neither dispatched nor cleared, so its event type leaked into the next event and its id reached last_event_id only after a later event. The message is now reset on every blank line and the id is recorded even when nothing is dispatched.
    • Ignore a retry field that is not all digits (#2591). retry: -1 made the client reconnect without waiting and retry: 10s set 10 ms.
    • Enforce a minimum reconnect wait of 100 ms (#2592). With an interval of 0 (retry: 0 from the server, or set_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.
  • Reject trailing characters in URL port numbers (#2593). http://host:80abc was accepted as port 80, and a redirect Location with such a port was followed.
  • Reject trailing characters in HTTP quality values (#2590). q=0.5junk was accepted, so malformed Accept values are now rejected and invalid Accept-Encoding weights 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. A set_content() body of CPPHTTPLIB_SEND_BUFSIZ or more is now written directly after the headers instead of being copied into the header buffer.

Build

  • CMake: detect GetAddrInfoExCancel on Windows and disable CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO with 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.path is 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

Don't miss a new cpp-httplib release

NewReleases is sending notifications on new releases.