form-net 0.3.0

Sans-IO form parsing for Rust: application/x-www-form-urlencoded and streaming multipart/form-data, with budgets checked before allocation, safe upload spilling, and typed extraction. No third-party dependencies.
Documentation
  • Coverage
  • 100%
    186 out of 186 items documented30 out of 30 items with examples
  • Size
  • Source code size: 626.2 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.9 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 3s Average build duration of successful builds.
  • all releases: 2s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • jamesgober/form-net
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • jamesgober

Features

The plan for 1.0. Each item names the version that ships it.

Parsing

  • urlencoded (0.3) - the WHATWG algorithm with strict refusal: & is the only separator, a malformed % and invalid UTF-8 are refused, never replaced. Zero copy when nothing needs decoding; a decoder that writes into your buffer and never allocates.
  • Streaming multipart (0.3) - sans-IO: bytes in, events out, identical for every split of the input. The boundary search is linear and never rescans a byte.
  • Strict framing (0.3) - bare LF, header folding, duplicate headers and parameters, transfer encodings, nested multipart, \" in quoted values, and a missing close delimiter are refused: the constructs where a firewall in front and the application behind could disagree.
  • no_std core (0.3) - the Tier 3 parsers run without alloc, on buffers you provide.

Uploads

  • Safe spilling (0.4) - files over a threshold go to temporary files in a private directory, created with random names and create_new, owner-only, deleted on drop and on abort; in the default mode the operating system removes them if the process dies.
  • Disk budgets (0.4, 0.5) - a limit per request and a budget shared across requests (pluggable, so it can cover a host or a fleet); the request that would cross it is refused with a 503 hint.
  • File names as data (0.3, 0.4) - raw bytes, base name, a sanitized portable name, an NFC form; never a path.

Integration

  • Inspection hooks (0.4) - an inspector sees every part head, the first bytes of every file, and every field before anything is stored, in one pass.
  • Anti-forgery token first (0.4) - the token field is captured, and a file that arrives before it is refused, so a forged upload costs no disk.
  • Typed forms without serde (0.4) - one declarative macro; unknown, repeated, and missing fields refused; errors name the field, never echo input.
  • Client side (0.3, 0.5) - a urlencoded serializer and a multipart writer whose output always parses back to the same fields.

Installation

[dependencies]
form-net = "0.3"

Without default features the crate is no_std and needs no allocator: the allocation-free core (raw_pairs, decode_into, MultipartParser::with_buffer) works on any target. Add features = ["alloc"] for the owned results.


Quick start

The API below is fixed by the design lock; the 0.3 parts work today, the 0.4 parts arrive next.

use form_net as fnet;

// A urlencoded body (0.3)
let form = fnet::parse_urlencoded(b"email=a%40b.example&remember=on")?;
assert_eq!(form.get("email"), Some("a@b.example"));

// A query string (0.3)
let query = fnet::parse_query(b"?q=rust+forms&page=2")?;
assert_eq!(query.get("q"), Some("rust forms"));

// A buffered multipart body (0.3)
let mp = fnet::parse_multipart(content_type, body)?;
let title = mp.field("title").unwrap_or("");

// A typed form, either encoding (0.4)
fnet::form! {
    pub struct Login {
        pub email: String,
        pub password: fnet::Secret,
        pub remember: bool,
    }
    ignore = ["csrf_token"];
}
let login = Login::from_body(content_type, body)?;

// An upload streamed to private temporary files (0.4, feature `fs`)
let upload = fnet::read_multipart(content_type, request_body)?;
if let Some(file) = upload.file("document") {
    file.persist(&destination)?;          // never overwrites
}

Configuration (limits per route, policies, inspectors, stores) and the allocation-free Tier 3 surface are in docs/API.md.


Scope

form-net decides whether bytes are a well-formed form. Whether the form is acceptable for a route (schemas, allowed file types by content, Unicode rules) is customs-sec's job; whether the request came from the site's own page is csrf-sec's. HTTP framing is http-net's. See dev/PLAN.md for the full scope, what is deliberately left to other crates, the threat model, and the decision log.


Documentation

Contributing

See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.