parse-net 0.9.3

Grammar for Internet message fields: RFC 9110 field syntax, media types, RFC 9651 Structured Fields, HTTP and RFC 5322 dates, negotiation, entity tags, ranges, Content-Disposition, Cache-Control, Forwarded, auth challenges, and the email lexical layer. no_std, no allocation, sans-IO, zero dependencies.
Documentation
# parse-net v0.4.0 - Extended

**Date:** 2026-10-22
**Compare:** [`v0.3.0...v0.4.0`](https://github.com/jamesgober/parse-net/compare/v0.3.0...v0.4.0)

**The web layer.** v0.4.0 added content negotiation, entity tags and conditional requests, byte ranges, RFC 8187 values and `Content-Disposition`, `Cache-Control`, `Forwarded` and `X-Forwarded-*` with client-address resolution through trusted proxies, `Authorization` and `WWW-Authenticate`, `Origin`, and RFC 9651 Structured Field parsing. It is still `no_std` without an allocator, with zero dependencies and no `unsafe`. The httpwg structured-field-tests suite passed in full, and every case of the tc2231 `Content-Disposition` suite has a written verdict.

## Highlights

- Structured Fields with all eight bare item types (Date and Display String included), as views or through a single-pass visitor, passing all 1598 cases of the httpwg suite, and again with every list and dictionary split into one field line per member.
- `client_address` reads `Forwarded` or `X-Forwarded-For` only when the connection's peer is a trusted proxy, walks from the right, and stops at the first address it cannot trust; a malformed chain is an error, so the caller falls back to the peer.
- `Range` resolution refuses the abusive shapes RFC 9110 section 14.2 names (too many ranges, more than two overlapping, more than two descents) and tells the caller to serve the whole representation.
- File names: `filename*` preferred and decoded at parse time, decoded control characters refused, path separators and bidi controls reported for the caller's policy.
- Credentials and authentication parameters never appear in `Debug` output.
- `docs/GRAMMAR.md`: for every field, the ABNF from its RFC, the case rules, every leniency or strictness and the RFC text behind it, and the function that parses it.

## Added

- **Negotiation.** `best_media`, `best_language`, `best_encoding`, and `best_charset` for the common case; `Negotiator` for RFC 4647 lookup or basic filtering, several `Accept` lines, and a choice between treating a malformed header as absent (reported as `Outcome::HeaderIgnored`) or refusing it (`Outcome::Refused`, for a 400). `QValue` parses exactly the RFC 9110 `qvalue` grammar. `media::parse_range` and `MediaRange` with `matches` and `specificity`.
- **Entity tags.** `EntityTag` with the RFC 9110 section 8.8.3.2 strong and weak comparisons, `W/` case-sensitive; `parse_condition`, `if_match`, `if_none_match`.
- **Ranges.** `range::parse`, `resolve` and `byte_ranges` (with coalescing), `parse_content_range`, `write_content_range`, `write_unsatisfied`, `parse_if_range` (IMF-fixdate only, D-035).
- **RFC 8187 and Content-Disposition.** `ExtValue` with UTF-8 and ISO-8859-1 (transcoded), `write_ext_value` (UTF-8 only, as producers must); `parse_content_disposition`, `Filename` with `decode_into`, `decode_percent_into` (RFC 7578 percent-encoded names), `has_path_separator`, `has_bidi_control`; `write_attachment` with an ASCII fallback beside `filename*`.
- **Cache-Control.** `response_cache_control` and `request_cache_control` into typed directive sets that report duplicates, invalid arguments, and unknown directives; the raw directive list; `cache::Writer`.
- **Forwarded.** `client_address`, `client_address_detailed` (which also reports whether the other header kind was present, a likely spoofing attempt), `forwarded::parse` with `NodePort` for numeric and obfuscated ports, and the three `X-Forwarded-*` parsers.
- **Authentication.** `parse_credentials`, `bearer`, `basic`, `parse_challenges` (the one-pass algorithm that finds where each challenge ends), `write_challenge`, `write_challenge_token68`.
- **Origin.** `parse_origin`, `same_origin` (whole-value comparison, never a prefix or suffix), `effective_port`, `write_to`.
- **Structured Fields.** `sfv::parse_item`, `parse_list`, `parse_dictionary`, `parse_list_lines`, `parse_dictionary_lines`; `Parser` with `Limits` and `visit_item`, `visit_list`, `visit_dictionary`; `Dictionary::get` (last wins) and `index_into` (one slot per distinct key, into a caller array).
- **`WriteError::DuplicateName`.**

## Changed

- `media::write` and `auth::write_challenge` refuse a parameter name given twice (`DuplicateName`). Both used to write it, and every parser of the field refuses the result; the writer-parser closure check found this (D-052).
- `docs/API.md` records the design-lock deltas: `Node` ports as `NodePort` (D-047), `AuthParams` for comma-separated auth parameters (D-048), and the `sfv` iterator types and helpers (D-055). `parse_challenges`, `write_challenge_token68`, and `has_bidi_control` shipped here, ahead of 0.5 (D-049).

## Testing

- The httpwg structured-field-tests suite, vendored with its BSD license at a pinned commit and excluded from the published crate: all 1598 parsing cases, 728 accepted and 870 refused, exactly as the suite requires. Of its 8 optional cases parse-net refuses 4, each recorded (D-051).
- tc2231: all 87 cases run from the exact header bytes; 66 agree with the expected browser behaviour and 21 fall in five recorded classes, each tied to an RFC sentence (`tests/differential/tc2231.md`).
- Property tests for every 0.4 parser (no panic on arbitrary bytes) and for every 0.4 writer (its output parses back to what was written); the same split-into-lines property for Structured Field lists and dictionaries built from a generator of every bare item type.
- Counting-allocator tests over every 0.4 parse and every 0.4 write into a `SliceSink`.
- Seventeen new fuzz targets, one per 0.4 parser plus the `diff_sfv` oracle against the `sfv` crate. Each asserts no panic, no allocation, and no input bytes in error text, and checks its round trip where the module has a writer. Every target ran one hour clean in WSL Ubuntu, 6.6 million to 407 million inputs each (the `diff_sfv` oracle 11.6 million), meeting the 0.4 exit. Their first short runs found the writer-parser closure gap in `write_challenge`, fixed before release (D-052).
- Every example in `docs/API.md` for a shipped item was compiled and run against the build (32 examples).

## Security

- The writer-parser closure check, new in this release, found that `write_challenge` and `media::write` produced output their own parsers refuse when given a repeated parameter name; both now refuse it.
- The `sfv` oracle and direct checks recorded three disagreement classes (V-5 to V-7 in `tests/differential/verdicts.md`): partial base64 padding and Dates outside years 1 to 9999, which `sfv` accepts (both optional in the conformance suite), and values beyond the configured limits, which `sfv` does not bound.
- `Origin` accepts a single serialized origin only; a space-separated list, which RFC 6454's grammar allows but browsers never send, is refused, so a CSRF check never has to pick one.
- D-006's reason was wrong: RFC 9110's grammar does name second 60. The strict default stays (D-053) and `docs/GRAMMAR.md` states the deviation.

## Compatibility

- MSRV 1.85, edition 2024; `no_std` without `alloc` is the base build, built for `thumbv7em-none-eabi`.
- Zero runtime dependencies in every feature combination; `proptest` and `serde_json` are dev-dependencies only.
- Pre-1.0: the API follows `docs/API.md` and changes only with a recorded decision. Code written against 0.3.0 compiles unchanged, except a `media::write` call with a repeated parameter name, which now returns `DuplicateName`.

## Verification

- Local gate on Windows and WSL Ubuntu, Rust 1.99 and 1.85: format, clippy with default, all, and no default features under `-D warnings`, tests in the same three modes, examples, documentation with default and all features under `-D warnings`, the bare-metal build, and `cargo package` in a fresh target directory.
- Banned-content and link checks over every shipped file.

## What's next

`0.5.0`: `Link`, Structured Field writing, the email lexical layer (folding, RFC 5322 dates, encoded words, MIME parameters with RFC 2231, `addr-spec`), owned forms with `alloc`, and `docs/ARCHITECTURE.md`.

**Full Changelog:** https://github.com/jamesgober/parse-net/compare/v0.3.0...v0.4.0