What's Changed
Security fixes
- Escape
", CR, and LF in multipart field names and filenames when serializingmultipart/form-databodies. Previously these values were concatenated into theContent-Dispositionquoted-string unescaped, so a"silently terminated the quoted-string early, and embedded CR/LF allowed injecting arbitrary part headers or forging part boundaries when a filename comes from external input. Escaping follows the WHATWG HTML standard ("→%22, CR →%0D, LF →%0A), matching what browsers send; behavior only changes for inputs that previously produced malformed HTTP - Close the same header-injection vector for part content types: CR/LF in
MultipartFormData::content_typeare now escaped (%0D/%0A) before being written into the part'sContent-Typeheader."is left intact since it is legal inContent-Typevalues (e.g. quoted charset parameters)
New features
- Add public
MultipartFormDataWriterfor multipart body serialization outsideClient::Post/Put/Patch(e.g. to feed a custom content provider or compose with other body sources), which previously required calling unstabledetail::functions directly. Supports whole-body serialization with known content length and per-part framing (item_begin/item_end/finish) for streaming. Also exposesis_valid_multipart_boundary()so callers using an explicit boundary can validate it without exceptions - Make the
ThreadPoolidle timeout for dynamic threads configurable at runtime via a new fourth constructor parameter:ThreadPool(base_threads, max_threads, max_queued_requests, idle_timeout_sec)(Fix #2481)
Bug fixes
- Classify ASCII bytes without consulting the global C locale.
std::isalnum/std::isdigitread the locale, so a byte like0xC5could classify as alphanumeric once an embedder calledsetlocale()(observed on macOS). Newdetail::is_ascii_digit/is_ascii_alpha/is_ascii_alnumhelpers are now used at every classification site — multipart boundary validation, token checks, URI encoding, range header parsing, and IPv4 host detection — and the<cctype>include is dropped (Fix #2482, Fix #2483) - Omit the default port from the WebSocket handshake
Hostheader, per RFC 6455 Section 4.1 (include the port only when it is not 80 forws/ 443 forwss). Some CDNs alter routing when theHostheader carries an explicit default port. IPv6 literal hosts are now bracketed correctly as well (Fix #2480) - Send the query string verbatim when path encoding is disabled.
set_path_encode(false)only suppressed encoding of the path; the query was still decoded and re-encoded, which is lossy for pre-encoded payloads (%2C→,,%20→+, etc.) and could corrupt binary query data on strict RFC 3986 servers (#2479) - Use an unsigned accumulator in
base64_encodeto avoid implementation-defined behavior with high-bit input bytes (#2477)
Build
- meson: fix build failure on glibc >= 2.34, where
getaddrinfo_ais built into libc and no standalonelibanlexists (also fixes architectures like loongarch64/riscv64 that never shipped one) (#2484)
Full Changelog: v0.48.0...v0.49.0