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/// It works for multipart and urlencoded bodies alike, and composes with
308/// other inspectors as a pair: `(customs, check)`. Browsers send fields in
309/// document order, so the form puts the hidden token input before its file
310/// inputs. With `TokenField::anywhere`, where files may come first, verify
311/// [`captured_token`](crate::MultipartParser::captured_token) (or the
312/// upload's) after the parse instead. The token field stays in the result; leave it out of a typed
313/// form with `ignore`.
314///
315/// Cost: one name comparison per text field, and `verify` once.
316///
317/// # Examples
318///
319/// ```
320/// use form_net::{Limits, Policy, Refusal, TokenCheck};
321///
322/// # struct Csrf;
323/// # impl Csrf { fn verify(&self, _session: &str, token: &str) -> bool { token == "good" } }
324/// # let (csrf, session) = (Csrf, "session-id");
325/// let mut check = TokenCheck::new("csrf_token", |token| {
326/// if csrf.verify(session, token) { Ok(()) } else { Err(Refusal::new(403, "csrf.rejected")) }
327/// });
328/// let body = b"csrf_token=good&email=a%40b.example";
329/// let form = form_net::parse_urlencoded_inspected(body, &Limits::DEFAULT, &Policy::DEFAULT, &mut check)?;
330/// assert!(check.verified());
331/// assert_eq!(form.get("email"), Some("a@b.example"));
332///
333/// let mut check = TokenCheck::new("csrf_token", |_| Err(Refusal::new(403, "csrf.rejected")));
334/// let err = form_net::parse_urlencoded_inspected(b"csrf_token=bad", &Limits::DEFAULT, &Policy::DEFAULT, &mut check)
335/// .unwrap_err();
336/// assert_eq!((err.status_hint(), err.code()), (403, "csrf.rejected"));
337/// # Ok::<(), form_net::FormError>(())
338/// ```
339pub struct TokenCheck<V> {
340 name: &'static str,
341 verify: V,
342 seen: bool,
343 verified: bool,
344}
345
346impl<V: FnMut(&str) -> Result<(), Refusal>> TokenCheck<V> {
347 /// A check of the field `name` with `verify`.
348 #[must_use]
349 pub const fn new(name: &'static str, verify: V) -> TokenCheck<V> {
350 TokenCheck {
351 name,
352 verify,
353 seen: false,
354 verified: false,
355 }
356 }
357
358 /// Whether the token was seen and `verify` accepted it.
359 #[must_use]
360 pub const fn verified(&self) -> bool {
361 self.verified
362 }
363}
364
365impl<V> core::fmt::Debug for TokenCheck<V> {
366 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
367 f.debug_struct("TokenCheck")
368 .field("name", &self.name)
369 .field("seen", &self.seen)
370 .field("verified", &self.verified)
371 .finish_non_exhaustive()
372 }
373}
374
375const TOKEN_MISSING: Refusal = Refusal::new(403, "csrf.missing");
376const TOKEN_DUPLICATE: Refusal = Refusal::new(403, "csrf.duplicate");
377const TOKEN_NOT_TEXT: Refusal = Refusal::new(403, "csrf.not_text");
378
379impl<V: FnMut(&str) -> Result<(), Refusal>> Inspector for TokenCheck<V> {
380 fn part_start(&mut self, head: &PartHead<'_>) -> Verdict {
381 if head.is_file() && !self.verified {
382 return Verdict::Refuse(TOKEN_MISSING);
383 }
384 Verdict::Continue
385 }
386
387 fn field(&mut self, name: &str, value: &[u8]) -> Verdict {
388 if name != self.name {
389 return Verdict::Continue;
390 }
391 if self.seen {
392 return Verdict::Refuse(TOKEN_DUPLICATE);
393 }
394 self.seen = true;
395 let Ok(token) = core::str::from_utf8(value) else {
396 return Verdict::Refuse(TOKEN_NOT_TEXT);
397 };
398 match (self.verify)(token) {
399 Ok(()) => {
400 self.verified = true;
401 Verdict::Continue
402 }
403 Err(refusal) => Verdict::Refuse(refusal),
404 }
405 }
406
407 fn end(&mut self) -> Verdict {
408 if self.verified {
409 Verdict::Continue
410 } else {
411 Verdict::Refuse(TOKEN_MISSING)
412 }
413 }
414}
415
416/// Where file parts go: one part at a time, `open`, any number of `write`,
417/// then `close` or `abort`.
418///
419/// The [`Driver`](crate::Driver) opens a sink only after the inspector has
420/// accepted the part's head and sniff window and the first chunk of data
421/// (or at the end of an empty file), so a refused or skipped part never
422/// reaches storage. Budgets are enforced before `write` is called, so a sink
423/// never has to count. `abort` must release everything, cannot fail, and
424/// must do nothing when no part is open; drivers call it on every error
425/// path.
426///
427/// # Examples
428///
429/// ```
430/// use form_net::{FormError, PartHead, Sink};
431///
432/// /// Counts bytes per file without keeping them.
433/// struct Counter(u64);
434///
435/// impl Sink for Counter {
436/// type Output = u64;
437/// fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
438/// self.0 = 0;
439/// Ok(())
440/// }
441/// fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
442/// self.0 += chunk.len() as u64;
443/// Ok(())
444/// }
445/// fn close(&mut self) -> Result<u64, FormError> {
446/// Ok(self.0)
447/// }
448/// fn abort(&mut self) {}
449/// }
450/// ```
451pub trait Sink {
452 /// What a closed part becomes.
453 type Output;
454
455 /// A file part starts.
456 ///
457 /// # Errors
458 ///
459 /// The sink's own errors (storage, I/O).
460 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError>;
461
462 /// More bytes of the current file part.
463 ///
464 /// # Errors
465 ///
466 /// The sink's own errors.
467 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError>;
468
469 /// The current file part ended.
470 ///
471 /// # Errors
472 ///
473 /// The sink's own errors.
474 fn close(&mut self) -> Result<Self::Output, FormError>;
475
476 /// Discards the current file part, if any, and releases everything.
477 fn abort(&mut self);
478}
479
480/// A sink that discards file data and reports how many bytes it saw.
481///
482/// # Examples
483///
484/// ```
485/// use form_net::{DiscardSink, Sink};
486///
487/// let mut s = DiscardSink::default();
488/// s.write(b"abc")?;
489/// assert_eq!(s.close()?, 3);
490/// # Ok::<(), form_net::FormError>(())
491/// ```
492#[derive(Clone, Copy, Debug, Default)]
493pub struct DiscardSink {
494 bytes: u64,
495}
496
497impl Sink for DiscardSink {
498 type Output = u64;
499
500 fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
501 self.bytes = 0;
502 Ok(())
503 }
504
505 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
506 self.bytes = self.bytes.saturating_add(chunk.len() as u64);
507 Ok(())
508 }
509
510 fn close(&mut self) -> Result<u64, FormError> {
511 Ok(core::mem::take(&mut self.bytes))
512 }
513
514 fn abort(&mut self) {
515 self.bytes = 0;
516 }
517}
518
519#[cfg(feature = "alloc")]
520mod memory {
521 use alloc::string::String;
522 use alloc::vec::Vec;
523
524 use super::Sink;
525 use crate::error::FormError;
526 use crate::extract::FileData;
527 use crate::multipart::parser::PartHead;
528
529 /// A sink that keeps each file in memory as a [`FileData`].
530 ///
531 /// # Examples
532 ///
533 /// ```
534 /// use form_net::{Driver, MemorySink, MultipartParser, Boundary, Limits, Policy, NoInspector, Progress};
535 ///
536 /// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
537 /// let mut driver = Driver::new(parser, MemorySink::new());
538 /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhi\r\n--X--";
539 /// let (_, progress) = driver.push(body, true, &mut NoInspector)?;
540 /// match progress {
541 /// Progress::File(file) => assert_eq!(file.bytes(), b"hi"),
542 /// _ => unreachable!(),
543 /// }
544 /// # Ok::<(), form_net::FormError>(())
545 /// ```
546 #[derive(Debug, Default)]
547 pub struct MemorySink {
548 current: Option<FileData>,
549 }
550
551 impl MemorySink {
552 /// An empty sink.
553 #[must_use]
554 pub fn new() -> MemorySink {
555 MemorySink { current: None }
556 }
557 }
558
559 impl Sink for MemorySink {
560 type Output = FileData;
561
562 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
563 // Text only when it is text: a file name that is not UTF-8 is
564 // reported as absent rather than repaired.
565 let filename = head
566 .filename()
567 .and_then(|f| core::str::from_utf8(f.raw()).ok().map(String::from));
568 let content_type = head
569 .content_type()
570 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
571 self.current = Some(FileData::new(Vec::new(), filename, content_type));
572 Ok(())
573 }
574
575 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
576 if let Some(f) = self.current.as_mut() {
577 f.append(chunk);
578 }
579 Ok(())
580 }
581
582 fn close(&mut self) -> Result<FileData, FormError> {
583 Ok(self.current.take().unwrap_or_default())
584 }
585
586 fn abort(&mut self) {
587 self.current = None;
588 }
589 }
590}
591
592#[cfg(feature = "alloc")]
593pub use memory::MemorySink;