Skip to main content

form_net/
lib.rs

1//! # form_net
2//!
3//! Parses what a browser sends when a form is submitted:
4//! `application/x-www-form-urlencoded` fields and `multipart/form-data`
5//! uploads. It does no I/O: feed it bytes, get fields and parts.
6//!
7//! Every budget (field count, name and value length, part count, part
8//! header size, file count and size, total size) is checked before the bytes
9//! it governs are buffered. Input that two parsers could read differently is
10//! refused with a precise error, never repaired. No error, `Debug`, or
11//! `Display` output ever contains a field value or a file name.
12//!
13//! ## Tier 1
14//!
15//! ```
16//! // A urlencoded body
17//! let form = form_net::parse_urlencoded(b"email=a%40b.example&remember=on")?;
18//! assert_eq!(form.get("email"), Some("a@b.example"));
19//!
20//! // A query string
21//! let query = form_net::parse_query(b"?q=rust+forms&page=2")?;
22//! assert_eq!(query.get("q"), Some("rust forms"));
23//!
24//! // Building one
25//! assert_eq!(form_net::serialize_urlencoded([("q", "rust forms")]), "q=rust+forms");
26//!
27//! // A buffered multipart body
28//! let body = b"--X\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nHoliday\r\n--X--";
29//! let mp = form_net::parse_multipart(b"multipart/form-data; boundary=X", body)?;
30//! assert_eq!(mp.field("title"), Some("Holiday"));
31//! # Ok::<(), form_net::FormError>(())
32//! ```
33//!
34//! ## Tiers 2 and 3
35//!
36//! Explicit [`Limits`] and [`Policy`] per route (`parse_urlencoded_with`,
37//! `parse_multipart_with`); the allocation-free core ([`raw_pairs`],
38//! [`decode_into`], [`encode_into`], [`Boundary`], [`MultipartParser`]),
39//! which builds without `alloc`.
40//!
41//! ## Features
42//!
43//! | Feature | Default | Adds |
44//! |---|---|---|
45//! | (none) | | the allocation-free core; `no_std` without `alloc` |
46//! | `alloc` | | owned results and the Tier 1 functions |
47//! | `std` | yes | `FormError::Io`, `FormError::Random`; implies `alloc` |
48//!
49//! The full contract is `docs/API.md` in the
50//! [repository](https://github.com/jamesgober/form-net). This release
51//! (0.3) is the core; upload storage, inspection hooks, and typed
52//! extraction arrive in 0.4.
53
54#![no_std]
55#![doc(html_root_url = "https://docs.rs/form-net")]
56#![cfg_attr(docsrs, feature(doc_cfg))]
57#![forbid(unsafe_code)]
58#![deny(missing_docs)]
59#![cfg_attr(
60    not(test),
61    deny(
62        clippy::unwrap_used,
63        clippy::expect_used,
64        clippy::panic,
65        clippy::indexing_slicing,
66        clippy::arithmetic_side_effects,
67        clippy::todo,
68        clippy::unimplemented,
69        clippy::unreachable,
70        clippy::dbg_macro,
71        clippy::print_stdout,
72        clippy::print_stderr
73    )
74)]
75
76#[cfg(any(feature = "alloc", test))]
77extern crate alloc;
78#[cfg(any(feature = "std", test))]
79extern crate std;
80
81mod error;
82mod hook;
83mod kind;
84mod limits;
85mod multipart;
86mod scan;
87mod urlencoded;
88mod wipe;
89
90pub use error::{
91    EncodingError, ExtractError, FormError, Limit, Location, PolicyRefusal, SyntaxError,
92};
93pub use hook::Refusal;
94pub use kind::FormKind;
95pub use limits::{
96    EmptyFilePolicy, ExtendedFilenamePolicy, HeaderPolicy, Limits, PercentPolicy, Policy,
97    TokenField, TokenPlacement,
98};
99pub use multipart::boundary::{Boundary, MAX_BOUNDARY_LEN, MAX_DELIMITER_LEN};
100pub use multipart::filename::{FileName, FileNameSource};
101pub use multipart::parser::{
102    Charset, ContentType, Event, Headers, MultipartParser, PartHead, PartKind, PartSummary, Step,
103};
104#[cfg(feature = "alloc")]
105#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
106pub use multipart::{Multipart, Part, parse_multipart, parse_multipart_with};
107pub use urlencoded::decode::decode_into;
108pub use urlencoded::encode::encode_into;
109#[cfg(feature = "alloc")]
110#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
111pub use urlencoded::encode::{Serializer, serialize_urlencoded};
112pub use urlencoded::raw::{RawPair, RawPairs, raw_pairs};
113#[cfg(feature = "alloc")]
114#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
115pub use urlencoded::{Form, Pairs, pairs, parse_query, parse_urlencoded, parse_urlencoded_with};
116
117/// Crate version string, populated by Cargo at build time.
118pub const VERSION: &str = env!("CARGO_PKG_VERSION");
119
120#[cfg(test)]
121mod tests {
122    use super::VERSION;
123
124    #[test]
125    fn test_version_matches_manifest() {
126        assert_eq!(VERSION, env!("CARGO_PKG_VERSION"));
127        assert_eq!(VERSION.split('.').count(), 3, "expected MAJOR.MINOR.PATCH");
128    }
129
130    #[test]
131    fn test_public_types_are_send_and_sync() {
132        fn send_sync<T: Send + Sync>() {}
133        send_sync::<super::Limits>();
134        send_sync::<super::Policy>();
135        send_sync::<super::Boundary>();
136        send_sync::<super::FormError>();
137        send_sync::<super::MultipartParser<&mut [u8]>>();
138        #[cfg(feature = "alloc")]
139        {
140            send_sync::<super::MultipartParser<alloc::vec::Vec<u8>>>();
141            send_sync::<super::Form>();
142            send_sync::<super::Multipart<'static>>();
143        }
144    }
145}