form_net/extract/mod.rs
1//! Typed extraction: plain Rust structs filled from a form, without serde
2//! and without a procedural macro.
3//!
4//! [`form!`](crate::form) generates a struct and its [`FromForm`]
5//! implementation; each field type implements [`FromField`]. Unknown names
6//! are refused, a scalar given twice is refused, a missing required field is
7//! refused, and every error names the field by its Rust-side name.
8
9mod file;
10mod macros;
11mod secret;
12mod value;
13
14pub use file::FileData;
15pub use secret::Secret;
16
17use crate::error::{ExtractError, FormError};
18use crate::hook::{Inspector, NoInspector};
19use crate::kind::FormKind;
20use crate::limits::{Limits, Policy};
21use crate::multipart::Part;
22
23/// Why one field's value did not convert. [`form!`](crate::form) turns it
24/// into `FormError::Extract` with the field's name.
25///
26/// # Examples
27///
28/// ```
29/// use form_net::{FromField, ValueError};
30///
31/// let mut slot = <u8 as FromField>::Slot::default();
32/// assert_eq!(u8::accept_text(&mut slot, "300"), Err(ValueError::Invalid("out of range")));
33/// ```
34#[non_exhaustive]
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum ValueError {
37 /// A required field is absent.
38 Missing,
39 /// A single-valued field was given twice.
40 Duplicate,
41 /// The value does not convert; the text says why.
42 Invalid(&'static str),
43 /// Text was given to a file field.
44 ExpectedFile,
45 /// A file was given to a text field.
46 ExpectedText,
47}
48
49/// A file field's value.
50#[non_exhaustive]
51pub enum FileValue<'a> {
52 /// A part of a buffered body.
53 Buffered(&'a Part<'a>),
54 /// A stored file from an upload.
55 #[cfg(feature = "fs")]
56 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
57 Stored(crate::store::StoredFile),
58}
59
60impl core::fmt::Debug for FileValue<'_> {
61 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62 match self {
63 FileValue::Buffered(p) => f.debug_tuple("Buffered").field(p).finish(),
64 #[cfg(feature = "fs")]
65 FileValue::Stored(s) => f.debug_tuple("Stored").field(s).finish(),
66 }
67 }
68}
69
70/// How one field type takes its values.
71///
72/// The slot holds what has arrived so far, so every arity rule lives in one
73/// place: a scalar's slot is `Option<T>` (a second value is `Duplicate`,
74/// none is `Missing`), `Vec<T>` pushes, `bool` finishes a missing value as
75/// `false`. Implement it for an application type to validate while
76/// extracting.
77///
78/// # Examples
79///
80/// ```
81/// use form_net::{FromField, ValueError};
82///
83/// pub struct Slug(String);
84///
85/// impl FromField for Slug {
86/// type Slot = Option<Slug>;
87/// fn accept_text(slot: &mut Option<Slug>, value: &str) -> Result<(), ValueError> {
88/// if slot.is_some() { return Err(ValueError::Duplicate); }
89/// if value.is_empty() || value.len() > 64 || !value.bytes().all(|b| b.is_ascii_lowercase() || b == b'-') {
90/// return Err(ValueError::Invalid("lowercase letters and hyphens, 1 to 64"));
91/// }
92/// *slot = Some(Slug(value.to_owned()));
93/// Ok(())
94/// }
95/// fn finish(slot: Option<Slug>) -> Result<Slug, ValueError> {
96/// slot.ok_or(ValueError::Missing)
97/// }
98/// }
99/// ```
100pub trait FromField: Sized {
101 /// What has arrived so far.
102 type Slot: Default;
103
104 /// Takes one text value.
105 ///
106 /// # Errors
107 ///
108 /// `ExpectedFile` by default; the type's own conversion errors.
109 fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
110 let _ = (slot, value);
111 Err(ValueError::ExpectedFile)
112 }
113
114 /// Takes one file.
115 ///
116 /// # Errors
117 ///
118 /// `ExpectedText` by default; the type's own errors.
119 fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
120 let _ = (slot, file);
121 Err(ValueError::ExpectedText)
122 }
123
124 /// The value, once every field has arrived.
125 ///
126 /// # Errors
127 ///
128 /// `Missing`, or the type's own errors.
129 fn finish(slot: Self::Slot) -> Result<Self, ValueError>;
130}
131
132/// What [`form!`](crate::form) generates, and what a hand-written
133/// [`FromForm`] implementation provides. Reports problems as
134/// `FormError::Extract { reason, field }`.
135///
136/// # Examples
137///
138/// ```
139/// use form_net::{ExtractError, FileValue, FormBuilder, FormError, FromForm};
140///
141/// /// Accepts any field names, keeping the last `q`.
142/// #[derive(Default)]
143/// struct LastQ(Option<String>);
144///
145/// impl FormBuilder for LastQ {
146/// type Output = Query;
147/// fn text(&mut self, name: &str, value: &str) -> Result<(), FormError> {
148/// if name == "q" { self.0 = Some(value.to_owned()); }
149/// Ok(())
150/// }
151/// fn file(&mut self, _name: &str, _file: FileValue<'_>) -> Result<(), FormError> {
152/// Err(FormError::Extract { reason: ExtractError::WrongKind, field: Some("q") })
153/// }
154/// fn finish(self) -> Result<Query, FormError> {
155/// Ok(Query { q: self.0.unwrap_or_default() })
156/// }
157/// }
158///
159/// struct Query { q: String }
160/// impl FromForm for Query { type Builder = LastQ; }
161///
162/// assert_eq!(Query::from_query(b"q=a&q=b")?.q, "b");
163/// # Ok::<(), FormError>(())
164/// ```
165pub trait FormBuilder {
166 /// The struct being built.
167 type Output;
168
169 /// One text field.
170 ///
171 /// # Errors
172 ///
173 /// `Extract { .. }`.
174 fn text(&mut self, name: &str, value: &str) -> Result<(), FormError>;
175
176 /// One file.
177 ///
178 /// # Errors
179 ///
180 /// `Extract { .. }`.
181 fn file(&mut self, name: &str, file: FileValue<'_>) -> Result<(), FormError>;
182
183 /// The struct, once every field has arrived.
184 ///
185 /// # Errors
186 ///
187 /// `Extract { Missing | Invalid, .. }`.
188 fn finish(self) -> Result<Self::Output, FormError>;
189}
190
191/// The bytes the decoders use without allocating.
192const STACK: usize = 512;
193
194/// Feeds a urlencoded body to `builder`, decoding each name and value into a
195/// stack buffer when it fits and a heap buffer otherwise.
196fn feed_urlencoded<B: FormBuilder, I: Inspector>(
197 builder: &mut B,
198 input: &[u8],
199 limits: &Limits,
200 policy: &Policy,
201 inspector: &mut I,
202) -> Result<(), FormError> {
203 let mut name_stack = [0u8; STACK];
204 let mut value_stack = [0u8; STACK];
205 let mut heap = alloc::vec::Vec::new();
206 let fed = (|| {
207 for pair in crate::urlencoded::raw::raw_pairs(input, limits) {
208 let pair = pair?;
209 let name_len = pair.name_raw().len();
210 let value_len = pair.value_raw().len();
211 let (name_buf, value_buf): (&mut [u8], &mut [u8]) =
212 if name_len <= STACK && value_len <= STACK {
213 (&mut name_stack, &mut value_stack)
214 } else {
215 let needed = name_len.saturating_add(value_len);
216 crate::wipe::wipe(&mut heap);
217 heap.clear();
218 if needed > heap.capacity() {
219 // A new allocation; the old one is wiped, not freed
220 // with a decoded value in it.
221 let mut old =
222 core::mem::replace(&mut heap, alloc::vec::Vec::with_capacity(needed));
223 crate::wipe::wipe_vec(&mut old);
224 }
225 heap.resize(needed, 0);
226 heap.split_at_mut(name_len)
227 };
228 let name = pair.decode_name(name_buf, policy.percent)?;
229 let value = pair.decode_value(value_buf, policy.percent)?;
230 let at = crate::error::Location::at(pair.offset());
231 match inspector.field(name, value.as_bytes()) {
232 crate::Verdict::Refuse(refusal) => return Err(FormError::Refused { refusal, at }),
233 crate::Verdict::Skip => {}
234 _ => builder.text(name, value)?,
235 }
236 }
237 Ok(())
238 })();
239 crate::wipe::wipe(&mut name_stack);
240 crate::wipe::wipe(&mut value_stack);
241 crate::wipe::wipe_vec(&mut heap);
242 fed?;
243 if let crate::Verdict::Refuse(refusal) = inspector.end() {
244 return Err(FormError::Refused {
245 refusal,
246 at: crate::error::Location::at(input.len() as u64),
247 });
248 }
249 Ok(())
250}
251
252fn feed_multipart<B: FormBuilder>(
253 builder: &mut B,
254 mp: &crate::Multipart<'_>,
255) -> Result<(), FormError> {
256 for part in mp.parts() {
257 if part.is_file() {
258 builder.file(part.name(), FileValue::Buffered(part))?;
259 } else {
260 builder.text(part.name(), part.text()?)?;
261 }
262 }
263 Ok(())
264}
265
266/// A struct that can be read from a form. Implemented by
267/// [`form!`](crate::form); every method is provided.
268///
269/// # Examples
270///
271/// ```
272/// use form_net::FromForm;
273///
274/// form_net::form! {
275/// pub struct Login {
276/// pub email: String,
277/// pub password: form_net::Secret,
278/// pub remember: bool,
279/// }
280/// ignore = ["csrf_token"];
281/// }
282///
283/// let login = Login::from_body(b"application/x-www-form-urlencoded", b"email=a%40b.example&password=x&csrf_token=t")?;
284/// assert_eq!((login.email.as_str(), login.remember), ("a@b.example", false));
285/// # Ok::<(), form_net::FormError>(())
286/// ```
287pub trait FromForm: Sized {
288 /// The builder that collects the fields.
289 type Builder: FormBuilder<Output = Self> + Default;
290
291 /// From a urlencoded body, with the default limits and policy.
292 ///
293 /// # Errors
294 ///
295 /// The parser's errors; `Extract { .. }`.
296 fn from_urlencoded(input: &[u8]) -> Result<Self, FormError> {
297 Self::from_urlencoded_with(input, &Limits::DEFAULT, &Policy::DEFAULT)
298 }
299
300 /// From a query string; one leading `?` is stripped.
301 ///
302 /// # Errors
303 ///
304 /// As [`from_urlencoded`](Self::from_urlencoded).
305 fn from_query(query: &[u8]) -> Result<Self, FormError> {
306 Self::from_urlencoded(query.strip_prefix(b"?").unwrap_or(query))
307 }
308
309 /// From a buffered `multipart/form-data` body.
310 ///
311 /// # Errors
312 ///
313 /// The parser's errors; `Extract { .. }`.
314 fn from_multipart(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
315 let mp = crate::parse_multipart(content_type, body)?;
316 let mut b = Self::Builder::default();
317 feed_multipart(&mut b, &mp)?;
318 b.finish()
319 }
320
321 /// From either encoding, by the `Content-Type`.
322 ///
323 /// # Errors
324 ///
325 /// `UnsupportedMediaType`; the parsers' errors; `Extract { .. }`.
326 fn from_body(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
327 Self::from_body_with(content_type, body, &Limits::DEFAULT, &Policy::DEFAULT)
328 }
329
330 /// From a urlencoded body with explicit limits and policy.
331 ///
332 /// # Errors
333 ///
334 /// As [`from_urlencoded`](Self::from_urlencoded); `InvalidLimits`.
335 fn from_urlencoded_with(
336 input: &[u8],
337 limits: &Limits,
338 policy: &Policy,
339 ) -> Result<Self, FormError> {
340 limits.validate()?;
341 let mut b = Self::Builder::default();
342 feed_urlencoded(&mut b, input, limits, policy, &mut NoInspector)?;
343 b.finish()
344 }
345
346 /// From either encoding with explicit limits and policy.
347 ///
348 /// # Errors
349 ///
350 /// As [`from_body`](Self::from_body); `InvalidLimits`.
351 fn from_body_with(
352 content_type: &[u8],
353 body: &[u8],
354 limits: &Limits,
355 policy: &Policy,
356 ) -> Result<Self, FormError> {
357 Self::from_body_inspected(content_type, body, limits, policy, &mut NoInspector)
358 }
359
360 /// From either encoding, with an inspector in the same pass: the
361 /// inspector and the struct see the same bytes.
362 ///
363 /// # Errors
364 ///
365 /// As [`from_body_with`](Self::from_body_with); `Refused`.
366 fn from_body_inspected<I: Inspector>(
367 content_type: &[u8],
368 body: &[u8],
369 limits: &Limits,
370 policy: &Policy,
371 inspector: &mut I,
372 ) -> Result<Self, FormError> {
373 let result = (|| {
374 let mut b = Self::Builder::default();
375 match FormKind::detect(content_type)? {
376 FormKind::Urlencoded => {
377 limits.validate()?;
378 feed_urlencoded(&mut b, body, limits, policy, inspector)?;
379 }
380 FormKind::Multipart(_) => {
381 let mp = crate::multipart::parse_multipart_inspected_raw(
382 content_type,
383 body,
384 limits,
385 policy,
386 inspector,
387 )?;
388 feed_multipart(&mut b, &mp)?;
389 }
390 }
391 b.finish()
392 })();
393 if let Err(e) = &result {
394 inspector.failed(e);
395 }
396 result
397 }
398
399 /// From a finished upload: text fields as text, files moved into the
400 /// struct as `StoredFile`s.
401 ///
402 /// # Errors
403 ///
404 /// `Extract { .. }`.
405 #[cfg(feature = "fs")]
406 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
407 fn from_upload(upload: crate::store::Upload) -> Result<Self, FormError> {
408 let (fields, files) = upload.into_parts();
409 let mut b = Self::Builder::default();
410 for (name, value) in fields.iter() {
411 b.text(name, value)?;
412 }
413 for file in files {
414 let name = alloc::string::String::from(file.name());
415 b.file(&name, FileValue::Stored(file))?;
416 }
417 b.finish()
418 }
419}
420
421/// Maps a field's [`ValueError`] to the error `form!` returns. Not part of
422/// the API: used by the macro expansion.
423#[doc(hidden)]
424#[must_use]
425pub fn __value_error(e: ValueError, field: &'static str) -> FormError {
426 let reason = match e {
427 ValueError::Missing => ExtractError::Missing,
428 ValueError::Duplicate => ExtractError::Duplicate,
429 ValueError::Invalid(why) => ExtractError::Invalid(why),
430 _ => ExtractError::WrongKind,
431 };
432 FormError::Extract {
433 reason,
434 field: Some(field),
435 }
436}
437
438/// The error for a name the struct does not declare. Not part of the API.
439#[doc(hidden)]
440#[must_use]
441pub const fn __unknown() -> FormError {
442 FormError::Extract {
443 reason: ExtractError::Unknown,
444 field: None,
445 }
446}