github yhirose/cpp-httplib v0.57.0

3 hours ago

What's Changed

Security-relevant fixes

  • Reject control characters in the chunk-size line (#2585). ChunkedDecoder::read_payload() checked only the first byte after the chunk-size, and getline() reads up to the CRLF, so a bare LF (or CR, or any other control byte) after the size or inside a chunk-ext was swallowed as extension text, while an intermediary that ends the line on that LF delimits the chunks differently, and the two disagree on where the body ends (request smuggling, RFC 9112 §7.1.1). The rest of the line is now validated as chunk-ext where it is parsed, which covers the server and both client read paths since they share this decoder. Ordinary extensions are still accepted. Every scan of the line is also bounded by the line terminator instead of by the buffer's NUL, so the terminator can never be read as line content.
  • Bound the trailer declaration set and count every received trailer field (#2583). parse_trailers() combined all Trailer field values and inserted each declared name into a local case-insensitive set with no cap, so a peer could grow that set well past the 100-field header limit (case_ignore::hash is unkeyed, so colliding names turn lookups quadratic). The set is now capped at CPPHTTPLIB_HEADER_MAX_COUNT. Separately, the received-trailer counter advanced only for declared fields that were stored, so undeclared fields could keep the loop running past the limit; every received field now counts.

Bug fixes

  • Run pre_request_handler for WebSocket routes. The upgrade path matched the route and switched protocols without setting req.matched_route or calling pre_request_handler, so a check placed there (authentication, for instance) never ran for WebSocket routes. matched_route is now set and the handler runs before the upgrade; if it handles the request, the server replies with a regular HTTP response instead of 101. Rejected upgrade responses (from pre_routing_handler too) are now written with Content-Length, so a client reading the body no longer waits for the keep-alive timeout.
  • Send 100 Continue only when the request body is about to be read. The server wrote the interim response as soon as it saw the expectation, before pre_routing_handler, pre_request_handler or routing ran, so a request that those handlers rejected, or one that matched no route, still invited the client to send a body the server would never read. If the request is answered without reading the body, 100 Continue is never sent and the connection is closed, since whether and when the client sends the body is unknown. A 417 returned by expect_100_continue_handler is now the final response; it used to be written as a bare status line, after which the request was processed and a second response was written.
  • Don't wait for the peer's close_notify on OpenSSL shutdown. tls::shutdown() called SSL_shutdown() a second time to wait for it, but an idle keep-alive client never sends one, so closing its connection held the worker thread until the read timeout and Server::stop() waited for it. The backend now sends close_notify and returns, as the Mbed TLS and wolfSSL backends already do.

Documentation

  • README and cookbook S18 (en/ja): document the default port-sharing behavior (SO_REUSEPORT on Linux/macOS, SO_REUSEADDR on Windows) and how to bind exclusively with set_socket_options(), including why SO_REUSEADDR alone is not enough on Windows. bind_to_port() returning false is no longer described as "port already in use", which it is not by default.
  • README, README-websocket and the tour/cookbook pages (en/ja): document that pre_request_handler runs for WebSocket routes.

Development

  • scripts/release.sh: update the version files with sed -i.bak, which GNU sed accepts too. sed -i '' is BSD-only, so just release --run failed on Linux before touching anything.

Full Changelog: v0.56.0...v0.57.0

Don't miss a new cpp-httplib release

NewReleases is sending notifications on new releases.