net/content_disposition — total parser for the mojibake-prone header: RFC 6266 filename (quoted/token), RFC 5987 filename* ext-values (charset'lang'pct-encoded, incl. RFC 2231 continuations), legacy RFC 2047 encoded-words (=?UTF-8?B?..?= / ?Q?), and raw Latin-1 bytes; prefers filename* over filename; strips path components AFTER decoding (a base64 payload can hold '/'). 22-case test table. net/text_codec (internal) — percent-decode, UTF-8 validation, Latin-1-> UTF-8, base64, RFC 2047 — shared by the CD parser and the URL splitter. net/url — a small total URL splitter (scheme/userinfo/host/port/path/ query/fragment, http(s) validity) and url_filename() for the last path segment; used for the filename fallback. net/probe — HEAD then a ranged GET bytes=0-0 that PROVES resumability (206 + matching Content-Range + a validator), rather than trusting Accept-Ranges which servers lie about; the ranged GET is also the HEAD- refused (403/405/501) fallback. 401/407 -> success result with requires_auth, not an error. Runs on its own pool (max_concurrent, default 4) outside the segment budget per ADR 0011 §5. suggest_filename() does the resolution order (explicit -> disposition -> URL -> download.bin) with a light strip; rules/ (stage 9) owns the authoritative sanitize. tools/fuzz — libFuzzer targets for the CD parser and the URL splitter, compiling the parser sources directly so they're fully instrumented; self-guards on VELOX_BUILD_FUZZ + Clang (the top-level CMake adds every tools/* unconditionally). Seed corpora included. Fixed on the way: a p -> Transfer -> State -> cbs -> p reference cycle in Prober that leaked every probe (drop the stored Transfer; the worker keeps State alive). Tests green under ASan/UBSan and TSan. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
41 lines
1.8 KiB
C++
41 lines
1.8 KiB
C++
// vdm/net/text_codec.hpp — internal: small byte/text codecs for header parsing.
|
|
//
|
|
// Not a public header. Everything here is pure, allocation-bounded, and total (no throw,
|
|
// no assert on input): the inputs come off the wire from untrusted servers.
|
|
|
|
#ifndef VDM_NET_TEXT_CODEC_HPP
|
|
#define VDM_NET_TEXT_CODEC_HPP
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
namespace vdm::net::detail {
|
|
|
|
// Percent-decode ("%XX"). A stray '%' or a non-hex digit after it is emitted literally.
|
|
// `plus_as_space` handles application/x-www-form-urlencoded style; off for URL paths.
|
|
[[nodiscard]] std::string percent_decode(std::string_view in, bool plus_as_space = false);
|
|
|
|
// True if `s` is well-formed UTF-8 (no overlong forms, no surrogates, no > U+10FFFF).
|
|
[[nodiscard]] bool is_valid_utf8(std::string_view s) noexcept;
|
|
|
|
// Reinterpret each byte as a Latin-1 (ISO-8859-1) code point and re-encode as UTF-8.
|
|
[[nodiscard]] std::string latin1_to_utf8(std::string_view s);
|
|
|
|
// Decode standard base64 (RFC 4648, '+' '/', optional '=' padding). Whitespace is
|
|
// skipped. Invalid trailing bits are dropped. Returns the decoded bytes.
|
|
[[nodiscard]] std::string base64_decode(std::string_view in);
|
|
|
|
// Decode RFC 2047 "encoded-word" runs: =?charset?B?..?= / =?charset?Q?..?=. Text outside
|
|
// encoded words is passed through. Only UTF-8 and ISO-8859-1/Latin-1 charsets are
|
|
// transcoded; anything else is passed through as-is (best effort). `had_encoded_word`
|
|
// reports whether at least one well-formed word was found.
|
|
[[nodiscard]] std::string decode_rfc2047(std::string_view in, bool *had_encoded_word = nullptr);
|
|
|
|
// If `s` is valid UTF-8, return it unchanged; otherwise treat it as Latin-1 and transcode.
|
|
[[nodiscard]] std::string to_utf8_best_effort(std::string_view s);
|
|
|
|
} // namespace vdm::net::detail
|
|
|
|
#endif // VDM_NET_TEXT_CODEC_HPP
|