form_net/hook.rs
1//! Hooks for other crates to take part in a parse without a second pass:
2//! an [`Inspector`] sees every part before it is stored, and a [`Sink`]
3//! decides where file data goes.
4//!
5//! Hooks are generic parameters, so a hook that does nothing
6//! ([`NoInspector`], [`DiscardSink`]) compiles to nothing.
7
8use crate::error::FormError;
9use crate::multipart::parser::{PartHead, PartSummary};
10
11/// Why an inspector refused the input: the HTTP status it wants and a
12/// stable, machine-readable code for the reason.
13///
14/// [`FormError::status_hint`](crate::FormError::status_hint) returns the
15/// status (400 when it is outside 400 to 599: a refusal is never a success);
16/// [`FormError::code`](crate::FormError::code) returns the code. The code
17/// must never contain input.
18///
19/// # Examples
20///
21/// ```
22/// use form_net::Refusal;
23///
24/// let r = Refusal::new(415, "customs.avatar_not_image");
25/// assert_eq!((r.status, r.code), (415, "customs.avatar_not_image"));
26/// ```
27#[non_exhaustive]
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct Refusal {
30 /// The HTTP status the inspector wants.
31 pub status: u16,
32 /// The inspector's stable code for the reason.
33 pub code: &'static str,
34}
35
36impl Refusal {
37 /// A refusal with this status and code.
38 #[must_use]
39 pub const fn new(status: u16, code: &'static str) -> Refusal {
40 Refusal { status, code }
41 }
42}
43
44/// What an inspector says about what it saw.
45///
46/// # Examples
47///
48/// ```
49/// use form_net::{Refusal, Verdict};
50///
51/// let v = Verdict::Refuse(Refusal::new(422, "example.not_allowed"));
52/// assert!(matches!(v, Verdict::Refuse(r) if r.status == 422));
53/// ```
54#[must_use]
55#[non_exhaustive]
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum Verdict {
58 /// Go on.
59 Continue,
60 /// Discard the rest of this part (or this field); it still counts
61 /// against the budgets and is left out of the result.
62 Skip,
63 /// Stop the parse with this refusal.
64 Refuse(Refusal),
65}
66
67/// Takes part in a parse: called for every part head, the first bytes of
68/// every file, every data chunk, every complete text field, every part end,
69/// the end of the body, and once on any failure.
70///
71/// Every method has a default, so an inspector implements only what it
72/// checks.
73///
74/// **Order guarantee**, per part and at every split of the input:
75///
76/// ```text
77/// part_start ─▶ sniff (file parts, when sniff_bytes > 0) ─▶ data* ─▶ field (text fields) ─▶ part_end
78/// ... after the last part: end
79/// ... on any error, at any point: failed (once), then the error is returned
80/// ```
81///
82/// For urlencoded bodies only `field`, `end`, and `failed` are called.
83///
84/// # Examples
85///
86/// ```
87/// use form_net::{Inspector, PartHead, Refusal, Verdict};
88///
89/// /// Allows only PNG and JPEG avatars, judged by their bytes.
90/// struct ImagesOnly;
91///
92/// impl Inspector for ImagesOnly {
93/// fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
94/// match (head.name(), head.is_file()) {
95/// ("avatar", true) | ("title", false) => Verdict::Continue,
96/// _ => Verdict::Refuse(Refusal::new(422, "example.unexpected_field")),
97/// }
98/// }
99/// fn sniff(&mut self, _head: &PartHead<'_>, first: &[u8]) -> Verdict {
100/// if first.starts_with(b"\x89PNG\r\n\x1a\n") || first.starts_with(b"\xff\xd8\xff") {
101/// Verdict::Continue
102/// } else {
103/// Verdict::Refuse(Refusal::new(415, "example.avatar_not_image"))
104/// }
105/// }
106/// }
107/// ```
108pub trait Inspector {
109 /// A part's headers were read and passed every rule. Called before any
110 /// byte of the part reaches a sink.
111 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
112 let _ = head;
113 Verdict::Continue
114 }
115
116 /// The first `min(sniff_bytes, part length)` bytes of a file part, as one
117 /// contiguous slice however the input was split, before any `data` call
118 /// for that part.
119 fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict {
120 let _ = (head, first);
121 Verdict::Continue
122 }
123
124 /// Bytes of part `part`'s data, text fields included.
125 fn data(&mut self, part: u32, chunk: &[u8]) -> Verdict {
126 let _ = (part, chunk);
127 Verdict::Continue
128 }
129
130 /// A complete text field (multipart) or a decoded pair (urlencoded): the
131 /// value as bytes, so the inspector can apply its own charset rules.
132 /// `Skip` leaves the field out of the result.
133 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
134 let _ = (name, value);
135 Verdict::Continue
136 }
137
138 /// A part ended. `Skip` here is treated as `Continue`.
139 fn part_end(&mut self, summary: &PartSummary) -> Verdict {
140 let _ = summary;
141 Verdict::Continue
142 }
143
144 /// The body ended. `Skip` here is treated as `Continue`.
145 fn end(&mut self) -> Verdict {
146 Verdict::Continue
147 }
148
149 /// The parse failed with `error` (for any reason, this inspector's
150 /// refusal included). Called once; it cannot change the outcome. The
151 /// hook for security signals: a stream of refusals from one client is a
152 /// probe.
153 fn failed(&mut self, error: &FormError) {
154 let _ = error;
155 }
156}
157
158impl<I: Inspector + ?Sized> Inspector for &mut I {
159 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
160 (**self).part_start(head)
161 }
162 fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict {
163 (**self).sniff(head, first)
164 }
165 fn data(&mut self, part: u32, chunk: &[u8]) -> Verdict {
166 (**self).data(part, chunk)
167 }
168 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
169 (**self).field(name, value)
170 }
171 fn part_end(&mut self, summary: &PartSummary) -> Verdict {
172 (**self).part_end(summary)
173 }
174 fn end(&mut self) -> Verdict {
175 (**self).end()
176 }
177 fn failed(&mut self, error: &FormError) {
178 (**self).failed(error);
179 }
180}
181
182/// Two inspectors in one pass, `self.0` first (D-088). A refusal from either
183/// wins and the second is not asked; otherwise the part is skipped if either
184/// says `Skip`. `failed` reaches both. Nest pairs for longer chains:
185/// `(customs, (sentinel, app))`.
186///
187/// # Examples
188///
189/// ```
190/// use form_net::{Inspector, Limits, Policy, Refusal, Verdict};
191///
192/// struct NoAdmin;
193/// impl Inspector for NoAdmin {
194/// fn field(&mut self, _name: &str, value: &[u8]) -> Verdict {
195/// if value == b"admin" { Verdict::Refuse(Refusal::new(403, "app.admin")) } else { Verdict::Continue }
196/// }
197/// }
198/// struct Count(u32);
199/// impl Inspector for Count {
200/// fn field(&mut self, _name: &str, _value: &[u8]) -> Verdict {
201/// self.0 += 1;
202/// Verdict::Continue
203/// }
204/// }
205///
206/// let mut chain = (Count(0), NoAdmin);
207/// let r = form_net::parse_urlencoded_inspected(b"a=1&b=admin", &Limits::DEFAULT, &Policy::DEFAULT, &mut chain);
208/// assert_eq!(r.unwrap_err().status_hint(), 403);
209/// assert_eq!(chain.0 .0, 2);
210/// ```
211impl<A: Inspector, B: Inspector> Inspector for (A, B) {
212 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
213 both(self.0.part_start(head), || self.1.part_start(head))
214 }
215 fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict {
216 both(self.0.sniff(head, first), || self.1.sniff(head, first))
217 }
218 fn data(&mut self, part: u32, chunk: &[u8]) -> Verdict {
219 both(self.0.data(part, chunk), || self.1.data(part, chunk))
220 }
221 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
222 both(self.0.field(name, value), || self.1.field(name, value))
223 }
224 fn part_end(&mut self, summary: &PartSummary) -> Verdict {
225 both(self.0.part_end(summary), || self.1.part_end(summary))
226 }
227 fn end(&mut self) -> Verdict {
228 both(self.0.end(), || self.1.end())
229 }
230 fn failed(&mut self, error: &FormError) {
231 self.0.failed(error);
232 self.1.failed(error);
233 }
234}
235
236/// Combines two verdicts: the first refusal wins, then any skip.
237fn both(first: Verdict, second: impl FnOnce() -> Verdict) -> Verdict {
238 if matches!(first, Verdict::Refuse(_)) {
239 return first;
240 }
241 match second() {
242 Verdict::Refuse(r) => Verdict::Refuse(r),
243 Verdict::Skip => Verdict::Skip,
244 _ => first,
245 }
246}
247
248/// A boxed inspector, for a chain chosen at run time (D-088).
249#[cfg(feature = "alloc")]
250impl<I: Inspector + ?Sized> Inspector for alloc::boxed::Box<I> {
251 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
252 (**self).part_start(head)
253 }
254 fn sniff(&mut self, head: &PartHead<'_>, first: &[u8]) -> Verdict {
255 (**self).sniff(head, first)
256 }
257 fn data(&mut self, part: u32, chunk: &[u8]) -> Verdict {
258 (**self).data(part, chunk)
259 }
260 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
261 (**self).field(name, value)
262 }
263 fn part_end(&mut self, summary: &PartSummary) -> Verdict {
264 (**self).part_end(summary)
265 }
266 fn end(&mut self) -> Verdict {
267 (**self).end()
268 }
269 fn failed(&mut self, error: &FormError) {
270 (**self).failed(error);
271 }
272}
273
274/// An inspector that accepts everything; it compiles to nothing.
275///
276/// # Examples
277///
278/// ```
279/// use form_net::{Limits, NoInspector, Policy};
280///
281/// let form = form_net::parse_urlencoded_inspected(b"a=1", &Limits::DEFAULT, &Policy::DEFAULT, &mut NoInspector)?;
282/// assert_eq!(form.get("a"), Some("1"));
283/// # Ok::<(), form_net::FormError>(())
284/// ```
285#[derive(Clone, Copy, Debug, Default)]
286pub struct NoInspector;
287
288impl Inspector for NoInspector {}
289
290/// Verifies the anti-forgery token the moment its field ends, before any
291/// later part starts: a forged upload is refused before its first file
292/// opens a sink, so it costs no disk.
293///
294/// `verify` is called once, with the value of the text field named `name`
295/// (byte for byte, as [`TokenField`](crate::TokenField) matches it); it
296/// does the comparison (csrf-sec's `Csrf::verify`, constant time) and
297/// returns `Err` with the refusal to report. The check refuses, with
298/// status 403:
299///
300/// - `csrf.missing`: a file part starts before a token was verified, or the
301/// body ends without one;
302/// - `csrf.duplicate`: a second field with the token's name (two tokens
303/// would let an attacker choose which one is verified);
304/// - `csrf.not_text`: a token that is not UTF-8;
305/// - whatever `verify` returns.
306///
307/// Another inspector in the pair must not skip the token's part: the check
308/// never sees a value that was not read, so it refuses the body with
309/// `csrf.token_skipped` and status 500, a configuration error to fix in the
310/// other inspector, not a client's.
311///
312/// It works for multipart and urlencoded bodies alike, and composes with
313/// other inspectors as a pair: `(customs, check)`. Browsers send fields in
314/// document order, so the form puts the hidden token input before its file
315/// inputs. With `TokenField::anywhere`, where files may come first, verify
316/// [`captured_token`](crate::MultipartParser::captured_token) (or the
317/// upload's) after the parse instead. The token field stays in the result; leave it out of a typed
318/// form with `ignore`.
319///
320/// Cost: one name comparison per text field, and `verify` once.
321///
322/// # Examples
323///
324/// ```
325/// use form_net::{Limits, Policy, Refusal, TokenCheck};
326///
327/// # struct Csrf;
328/// # impl Csrf { fn verify(&self, _session: &str, token: &str) -> bool { token == "good" } }
329/// # let (csrf, session) = (Csrf, "session-id");
330/// let mut check = TokenCheck::new("csrf_token", |token| {
331/// if csrf.verify(session, token) { Ok(()) } else { Err(Refusal::new(403, "csrf.rejected")) }
332/// });
333/// let body = b"csrf_token=good&email=a%40b.example";
334/// let form = form_net::parse_urlencoded_inspected(body, &Limits::DEFAULT, &Policy::DEFAULT, &mut check)?;
335/// assert!(check.verified());
336/// assert_eq!(form.get("email"), Some("a@b.example"));
337///
338/// let mut check = TokenCheck::new("csrf_token", |_| Err(Refusal::new(403, "csrf.rejected")));
339/// let err = form_net::parse_urlencoded_inspected(b"csrf_token=bad", &Limits::DEFAULT, &Policy::DEFAULT, &mut check)
340/// .unwrap_err();
341/// assert_eq!((err.status_hint(), err.code()), (403, "csrf.rejected"));
342/// # Ok::<(), form_net::FormError>(())
343/// ```
344pub struct TokenCheck<V> {
345 name: &'static str,
346 verify: V,
347 seen: bool,
348 verified: bool,
349 /// The token's part started and its value has not come yet.
350 pending: bool,
351}
352
353impl<V: FnMut(&str) -> Result<(), Refusal>> TokenCheck<V> {
354 /// A check of the field `name` with `verify`.
355 #[must_use]
356 pub const fn new(name: &'static str, verify: V) -> TokenCheck<V> {
357 TokenCheck {
358 name,
359 verify,
360 seen: false,
361 verified: false,
362 pending: false,
363 }
364 }
365
366 /// Whether the token was seen and `verify` accepted it.
367 #[must_use]
368 pub const fn verified(&self) -> bool {
369 self.verified
370 }
371}
372
373impl<V> core::fmt::Debug for TokenCheck<V> {
374 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 f.debug_struct("TokenCheck")
376 .field("name", &self.name)
377 .field("seen", &self.seen)
378 .field("verified", &self.verified)
379 .finish_non_exhaustive()
380 }
381}
382
383const TOKEN_MISSING: Refusal = Refusal::new(403, "csrf.missing");
384const TOKEN_DUPLICATE: Refusal = Refusal::new(403, "csrf.duplicate");
385const TOKEN_NOT_TEXT: Refusal = Refusal::new(403, "csrf.not_text");
386const TOKEN_SKIPPED: Refusal = Refusal::new(500, "csrf.token_skipped");
387
388impl<V: FnMut(&str) -> Result<(), Refusal>> Inspector for TokenCheck<V> {
389 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
390 if head.is_file() && !self.verified {
391 return Verdict::Refuse(TOKEN_MISSING);
392 }
393 self.pending = !head.is_file() && head.name() == self.name;
394 Verdict::Continue
395 }
396
397 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
398 if name != self.name {
399 return Verdict::Continue;
400 }
401 self.pending = false;
402 if self.seen {
403 return Verdict::Refuse(TOKEN_DUPLICATE);
404 }
405 self.seen = true;
406 let Ok(token) = core::str::from_utf8(value) else {
407 return Verdict::Refuse(TOKEN_NOT_TEXT);
408 };
409 match (self.verify)(token) {
410 Ok(()) => {
411 self.verified = true;
412 Verdict::Continue
413 }
414 Err(refusal) => Verdict::Refuse(refusal),
415 }
416 }
417
418 fn part_end(&mut self, _summary: &PartSummary) -> Verdict {
419 // The token's part ended without its value reaching `field`:
420 // another inspector skipped it.
421 if core::mem::take(&mut self.pending) {
422 return Verdict::Refuse(TOKEN_SKIPPED);
423 }
424 Verdict::Continue
425 }
426
427 fn end(&mut self) -> Verdict {
428 if self.verified {
429 Verdict::Continue
430 } else {
431 Verdict::Refuse(TOKEN_MISSING)
432 }
433 }
434}
435
436/// Where file parts go: one part at a time, `open`, any number of `write`,
437/// then `close` or `abort`.
438///
439/// The [`Driver`](crate::Driver) opens a sink only after the inspector has
440/// accepted the part's head and sniff window and the first chunk of data
441/// (or at the end of an empty file), so a refused or skipped part never
442/// reaches storage. Budgets are enforced before `write` is called, so a sink
443/// never has to count. `abort` must release everything, cannot fail, and
444/// must do nothing when no part is open; drivers call it on every error
445/// path.
446///
447/// # Examples
448///
449/// ```
450/// use form_net::{FormError, PartHead, Sink};
451///
452/// /// Counts bytes per file without keeping them.
453/// struct Counter(u64);
454///
455/// impl Sink for Counter {
456/// type Output = u64;
457/// fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
458/// self.0 = 0;
459/// Ok(())
460/// }
461/// fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
462/// self.0 += chunk.len() as u64;
463/// Ok(())
464/// }
465/// fn close(&mut self) -> Result<u64, FormError> {
466/// Ok(self.0)
467/// }
468/// fn abort(&mut self) {}
469/// }
470/// ```
471pub trait Sink {
472 /// What a closed part becomes.
473 type Output;
474
475 /// A file part starts.
476 ///
477 /// # Errors
478 ///
479 /// The sink's own errors (storage, I/O).
480 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError>;
481
482 /// More bytes of the current file part.
483 ///
484 /// # Errors
485 ///
486 /// The sink's own errors.
487 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError>;
488
489 /// The current file part ended.
490 ///
491 /// # Errors
492 ///
493 /// The sink's own errors.
494 fn close(&mut self) -> Result<Self::Output, FormError>;
495
496 /// Discards the current file part, if any, and releases everything.
497 fn abort(&mut self);
498}
499
500/// A sink that discards file data and reports how many bytes it saw.
501///
502/// # Examples
503///
504/// ```
505/// use form_net::{DiscardSink, Sink};
506///
507/// let mut s = DiscardSink::default();
508/// s.write(b"abc")?;
509/// assert_eq!(s.close()?, 3);
510/// # Ok::<(), form_net::FormError>(())
511/// ```
512#[derive(Clone, Copy, Debug, Default)]
513pub struct DiscardSink {
514 bytes: u64,
515}
516
517impl Sink for DiscardSink {
518 type Output = u64;
519
520 fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
521 self.bytes = 0;
522 Ok(())
523 }
524
525 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
526 self.bytes = self.bytes.saturating_add(chunk.len() as u64);
527 Ok(())
528 }
529
530 fn close(&mut self) -> Result<u64, FormError> {
531 Ok(core::mem::take(&mut self.bytes))
532 }
533
534 fn abort(&mut self) {
535 self.bytes = 0;
536 }
537}
538
539#[cfg(feature = "alloc")]
540mod memory {
541 use alloc::string::String;
542 use alloc::vec::Vec;
543
544 use super::Sink;
545 use crate::error::FormError;
546 use crate::extract::FileData;
547 use crate::multipart::parser::PartHead;
548
549 /// A sink that keeps each file in memory as a [`FileData`].
550 ///
551 /// # Examples
552 ///
553 /// ```
554 /// use form_net::{Driver, MemorySink, MultipartParser, Boundary, Limits, Policy, NoInspector, Progress};
555 ///
556 /// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
557 /// let mut driver = Driver::new(parser, MemorySink::new());
558 /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhi\r\n--X--";
559 /// let (_, progress) = driver.push(body, true, &mut NoInspector)?;
560 /// match progress {
561 /// Progress::File(file) => assert_eq!(file.bytes(), b"hi"),
562 /// _ => unreachable!(),
563 /// }
564 /// # Ok::<(), form_net::FormError>(())
565 /// ```
566 #[derive(Debug, Default)]
567 pub struct MemorySink {
568 current: Option<FileData>,
569 }
570
571 impl MemorySink {
572 /// An empty sink.
573 #[must_use]
574 pub fn new() -> MemorySink {
575 MemorySink { current: None }
576 }
577 }
578
579 impl Sink for MemorySink {
580 type Output = FileData;
581
582 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
583 // Text only when it is text: a file name that is not UTF-8 is
584 // reported as absent rather than repaired.
585 let filename = head
586 .filename()
587 .and_then(|f| core::str::from_utf8(f.raw()).ok().map(String::from));
588 let content_type = head
589 .content_type()
590 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
591 self.current = Some(FileData::new(Vec::new(), filename, content_type));
592 Ok(())
593 }
594
595 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
596 if let Some(f) = self.current.as_mut() {
597 f.append(chunk);
598 }
599 Ok(())
600 }
601
602 fn close(&mut self) -> Result<FileData, FormError> {
603 Ok(self.current.take().unwrap_or_default())
604 }
605
606 fn abort(&mut self) {
607 self.current = None;
608 }
609 }
610}
611
612#[cfg(feature = "alloc")]
613pub use memory::MemorySink;