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/// Where file parts go: one part at a time, `open`, any number of `write`,
291/// then `close` or `abort`.
292///
293/// The [`Driver`](crate::Driver) opens a sink only after the inspector has
294/// accepted the part's head and sniff window and the first chunk of data
295/// (or at the end of an empty file), so a refused or skipped part never
296/// reaches storage. Budgets are enforced before `write` is called, so a sink
297/// never has to count. `abort` must release everything, cannot fail, and
298/// must do nothing when no part is open; drivers call it on every error
299/// path.
300///
301/// # Examples
302///
303/// ```
304/// use form_net::{FormError, PartHead, Sink};
305///
306/// /// Counts bytes per file without keeping them.
307/// struct Counter(u64);
308///
309/// impl Sink for Counter {
310/// type Output = u64;
311/// fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
312/// self.0 = 0;
313/// Ok(())
314/// }
315/// fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
316/// self.0 += chunk.len() as u64;
317/// Ok(())
318/// }
319/// fn close(&mut self) -> Result<u64, FormError> {
320/// Ok(self.0)
321/// }
322/// fn abort(&mut self) {}
323/// }
324/// ```
325pub trait Sink {
326 /// What a closed part becomes.
327 type Output;
328
329 /// A file part starts.
330 ///
331 /// # Errors
332 ///
333 /// The sink's own errors (storage, I/O).
334 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError>;
335
336 /// More bytes of the current file part.
337 ///
338 /// # Errors
339 ///
340 /// The sink's own errors.
341 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError>;
342
343 /// The current file part ended.
344 ///
345 /// # Errors
346 ///
347 /// The sink's own errors.
348 fn close(&mut self) -> Result<Self::Output, FormError>;
349
350 /// Discards the current file part, if any, and releases everything.
351 fn abort(&mut self);
352}
353
354/// A sink that discards file data and reports how many bytes it saw.
355///
356/// # Examples
357///
358/// ```
359/// use form_net::{DiscardSink, Sink};
360///
361/// let mut s = DiscardSink::default();
362/// s.write(b"abc")?;
363/// assert_eq!(s.close()?, 3);
364/// # Ok::<(), form_net::FormError>(())
365/// ```
366#[derive(Clone, Copy, Debug, Default)]
367pub struct DiscardSink {
368 bytes: u64,
369}
370
371impl Sink for DiscardSink {
372 type Output = u64;
373
374 fn open(&mut self, _head: &PartHead<'_>) -> Result<(), FormError> {
375 self.bytes = 0;
376 Ok(())
377 }
378
379 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
380 self.bytes = self.bytes.saturating_add(chunk.len() as u64);
381 Ok(())
382 }
383
384 fn close(&mut self) -> Result<u64, FormError> {
385 Ok(core::mem::take(&mut self.bytes))
386 }
387
388 fn abort(&mut self) {
389 self.bytes = 0;
390 }
391}
392
393#[cfg(feature = "alloc")]
394mod memory {
395 use alloc::string::String;
396 use alloc::vec::Vec;
397
398 use super::Sink;
399 use crate::error::FormError;
400 use crate::extract::FileData;
401 use crate::multipart::parser::PartHead;
402
403 /// A sink that keeps each file in memory as a [`FileData`].
404 ///
405 /// # Examples
406 ///
407 /// ```
408 /// use form_net::{Driver, MemorySink, MultipartParser, Boundary, Limits, Policy, NoInspector, Progress};
409 ///
410 /// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
411 /// let mut driver = Driver::new(parser, MemorySink::new());
412 /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhi\r\n--X--";
413 /// let (_, progress) = driver.push(body, true, &mut NoInspector)?;
414 /// match progress {
415 /// Progress::File(file) => assert_eq!(file.bytes(), b"hi"),
416 /// _ => unreachable!(),
417 /// }
418 /// # Ok::<(), form_net::FormError>(())
419 /// ```
420 #[derive(Debug, Default)]
421 pub struct MemorySink {
422 current: Option<FileData>,
423 }
424
425 impl MemorySink {
426 /// An empty sink.
427 #[must_use]
428 pub fn new() -> MemorySink {
429 MemorySink { current: None }
430 }
431 }
432
433 impl Sink for MemorySink {
434 type Output = FileData;
435
436 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
437 // Text only when it is text: a file name that is not UTF-8 is
438 // reported as absent rather than repaired.
439 let filename = head
440 .filename()
441 .and_then(|f| core::str::from_utf8(f.raw()).ok().map(String::from));
442 let content_type = head
443 .content_type()
444 .and_then(|c| core::str::from_utf8(c.raw()).ok().map(String::from));
445 self.current = Some(FileData::new(Vec::new(), filename, content_type));
446 Ok(())
447 }
448
449 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
450 if let Some(f) = self.current.as_mut() {
451 f.append(chunk);
452 }
453 Ok(())
454 }
455
456 fn close(&mut self) -> Result<FileData, FormError> {
457 Ok(self.current.take().unwrap_or_default())
458 }
459
460 fn abort(&mut self) {
461 self.current = None;
462 }
463 }
464}
465
466#[cfg(feature = "alloc")]
467pub use memory::MemorySink;