15 Commits
Author SHA1 Message Date
metsw24-maxandyhirose 8b872605e0 reject control characters in chunk extensions in read_payload (#2585)
* reject control characters in chunk extensions in read_payload

* Bound every chunk-size line scan by the line terminator

read_payload() ended its scans of one line buffer two different ways: the
hex-size parse and the space skip that follows stopped on the NUL that
stream_line_reader::append() writes, while the new chunk-ext check walked
to an explicit end pointer. Compute that end pointer first and bound all
of them by it, so no scan depends on the buffer's NUL and the terminator
can never be read as line content.

The bare-LF branch is reachable only under
CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR, where getline() ends the line on
an LF that is the terminator rather than extension text. Say so: the
comment below it explains why a bare LF inside the line is rejected, and
without that note the two read as contradictory. Its guard no longer
depends on the scan cursor either, since all it ever needed was a check
that there is a byte to look at.

* Reuse the chunked-body helper in the chunk-ext acceptance test

AcceptsChunkExtension repeated expect_chunked_body_rejected()'s body
verbatim apart from the expected status, so parameterise the helper on
the status and keep the rejection wrapper for the existing callers. The
decoded body is already checked by the /chunked handler, so asserting
the status is all the new test needs.

Also record why the control-character literal stays split: a hex escape
consumes every hex digit that follows it, so "\x01b" would be the single
byte \x1b rather than \x01 followed by 'b', and joining the halves would
quietly change what the test sends.

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-09-20 20:22:52 -04:00
metsw24-maxandyhirose 0480ff77b8 reject ambiguously framed responses in client read paths (#2581)
* reject ambiguously framed responses in client read paths

* Accept non-chunked Transfer-Encoding responses in the client framing guard

RFC 9112 §6.3 treats requests and responses differently when the final
transfer coding is not chunked: a request's body length cannot be
determined and the server must answer 400, but a response's body simply
runs until the server closes the connection. read_content() and the
open_stream() body reader already do that, so such a response is not
ambiguous and rejecting it broke valid responses such as
"Transfer-Encoding: gzip" followed by a close.

Keep rejecting a Transfer-Encoding paired with a non-zero Content-Length,
which is the actual ambiguity, and drop the non-chunked clause from both
client read paths.

Tests: check that rejection surfaces as Error::Read, that a non-chunked
Transfer-Encoding response is read until close on both paths, and that
HEAD, 204 and 304 responses with both framing headers are not rejected.

Claude-Session: https://claude.ai/code/session_01JYPWKpbp4a881EdpEf2xSi

* Share the framing check and reuse existing test helpers

Factor "Transfer-Encoding with a non-zero Content-Length" into
detail::has_conflicting_content_length() next to
is_chunked_transfer_encoding(), and call it from the server request
guard and both client read paths so the rule and its RFC 9112 §6.3
rationale live in one place.

In the tests, drop the POSIX-only raw socket helper in favour of the
existing serve_single_response() and read_all(), which also lets the
tests run on Windows. Fold the stream-only test into the buffered one so
each case checks both Get() and open_stream(), and cover the HEAD/204/304
exclusion on the open_stream() path too.

Claude-Session: https://claude.ai/code/session_01JYPWKpbp4a881EdpEf2xSi

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-09-11 15:55:28 -04:00
metsw24-maxandyhirose 515b8f84af send each credential only to its own hop in write_request (#2579)
* send each credential only to its own hop in write_request

An SSLClient behind a proxy sent Proxy-Authorization inside the TLS tunnel, where the origin reads it, and sent the origin's Authorization on the CONNECT request the proxy reads. Attach each only on the message its hop actually reads.

* Keep default headers off the CONNECT request

set_default_headers() is typically used for origin credentials such as
Authorization, Cookie or API keys, but they were also attached to the
CONNECT request an SSLClient sends to its proxy, in plaintext before the
TLS tunnel exists. Default headers now go only on requests the origin
reads, the same split the previous commit makes for set_basic_auth and
set_bearer_token_auth.

Claude-Session: https://claude.ai/code/session_01JYPWKpbp4a881EdpEf2xSi

* Simplify per-hop credential handling and its tests

Flatten the Authorization insertion in write_request into one guard with
an else-if (Basic already took precedence over Bearer), and shorten the
comments around it. Fold DefaultHeadersStayOffConnect into the
CredentialsStayWithTheirHop helper, which now takes the list of headers
that must reach only the origin.

Claude-Session: https://claude.ai/code/session_01JYPWKpbp4a881EdpEf2xSi

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-09-11 13:25:48 -04:00
metsw24-max 2a068def54 validate bearer scheme in get_bearer_token_auth (#2544) 2026-08-19 19:04:46 -04:00
metsw24-max 2d8e49dd9b reject trailing bytes after IPv6 host literal in parse_url (#2536) 2026-08-14 21:27:40 -04:00
metsw24-max 2b8658fa99 match mount points on a segment boundary in handle_file_request (#2529) 2026-08-03 17:51:50 -04:00
Sayed Kaif f4fce42e77 fail mmap::open when ::mmap returns MAP_FAILED 2026-07-30 00:46:08 +05:30
Sayed Kaif 23fef15e07 apply Range only to a 206 response in write_content_with_provider 2026-07-28 13:19:35 +05:30
Sayed Kaif 695961f8ae skip invalid fields in write_headers to prevent response splitting 2026-07-23 21:42:37 +05:30
metsw24-max 6c4cbd49a2 scan X-Forwarded-For right-to-left in get_client_ip (#2503) 2026-07-21 18:12:40 -04:00
Sayed Kaif a415362dff update OpenStreamMalformedContentLength.OutOfRange for stricter parsing 2026-07-18 18:29:16 -04:00
Sayed Kaif 982235c0a9 flag out-of-range Content-Length in get_header_value_u64 2026-07-18 18:29:16 -04:00
metsw24-max a7b886b9cb Use an unsigned accumulator in base64_encode (#2477)
* use an unsigned accumulator in base64_encode

* Forward-declare detail::base64_encode for split builds
2026-07-01 20:12:00 -04:00
metsw24-max 28d95937b5 use strict hex parsing in decode_query_component (#2472) 2026-06-12 13:54:04 -04:00
metsw24-max 018ce8e4ca cast to unsigned char before ctype calls in is_hex and is_token_char (#2469) 2026-06-09 19:34:43 -04:00