Skip to main content

form_net/store/
mod.rs

1//! Upload storage: files kept in memory up to a threshold and spilled to
2//! private temporary files above it. No client byte is ever part of a path.
3//!
4//! Spilling is blocking file I/O: a server with an event loop runs a parse
5//! that stores uploads where blocking is allowed, or gives the driver its
6//! own [`Sink`](crate::Sink).
7
8mod quota;
9mod sweep;
10mod temp;
11
12pub use quota::{SpillBudget, SpillQuota};
13pub use sweep::sweep;
14
15use alloc::borrow::ToOwned;
16use alloc::string::String;
17use alloc::sync::Arc;
18use alloc::vec::Vec;
19use core::fmt;
20use core::sync::atomic::{AtomicU64, Ordering};
21use std::fs::File;
22use std::io::{self, Read, Seek, SeekFrom, Write};
23use std::path::{Path, PathBuf};
24use std::sync::OnceLock;
25
26use crate::error::{ExtractError, FormError, Limit, Location};
27use crate::hook::{Inspector, Sink};
28use crate::limits::{Limits, Policy};
29use crate::multipart::driver::{Driver, Progress};
30use crate::multipart::filename::{FileName, FileNameSource};
31use crate::multipart::parser::{ContentType, MultipartParser, PartHead};
32
33/// What went wrong with storage.
34///
35/// # Examples
36///
37/// ```
38/// use form_net::{FormError, Store, StoreError};
39///
40/// # let dir = std::env::temp_dir().join(format!("form-net-doc-store-error-{}", std::process::id()));
41/// # std::fs::create_dir_all(&dir).unwrap();
42/// std::fs::write(dir.join("file"), b"")?;
43/// let e = Store::in_dir(dir.join("file")).unwrap_err();
44/// assert!(matches!(e, FormError::Store { reason: StoreError::NotADirectory }));
45/// assert_eq!(e.code(), "store.not_a_directory");
46/// # std::fs::remove_dir_all(&dir).unwrap();
47/// # Ok::<(), Box<dyn std::error::Error>>(())
48/// ```
49#[non_exhaustive]
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum StoreError {
52    /// The shared spill budget is full (status hint 503).
53    QuotaExceeded,
54    /// `Store::in_dir` or `sweep` was given a non-directory or a link.
55    NotADirectory,
56    /// `persist`'s destination exists; nothing was overwritten.
57    DestinationExists,
58}
59
60impl StoreError {
61    #[cfg(target_has_atomic = "32")]
62    pub(crate) const fn index(self) -> usize {
63        match self {
64            Self::QuotaExceeded => 0,
65            Self::NotADirectory => 1,
66            Self::DestinationExists => 2,
67        }
68    }
69
70    pub(crate) const fn code(self) -> &'static str {
71        match self {
72            Self::QuotaExceeded => "store.quota_exceeded",
73            Self::NotADirectory => "store.not_a_directory",
74            Self::DestinationExists => "store.destination_exists",
75        }
76    }
77
78    pub(crate) const fn describe(self) -> &'static str {
79        match self {
80            Self::QuotaExceeded => "the shared upload budget is full; retry later",
81            Self::NotADirectory => "not a directory, or a link to one",
82            Self::DestinationExists => "the destination exists; nothing was overwritten",
83        }
84    }
85}
86
87/// How spilled files are kept.
88///
89/// | | `Anonymous` (default) | `Named` |
90/// |---|---|---|
91/// | Unix | unlinked right after creation | keeps its random name until dropped or persisted |
92/// | Windows | delete-on-close, no sharing | deleted on drop |
93/// | after a crash | nothing is left | leftovers are removed by `sweep` (0.5) |
94/// | `persist` | a copy | a hard link on the same file system, else a copy |
95///
96/// # Examples
97///
98/// ```
99/// use form_net::{Limits, Policy, SpillMode, Store};
100///
101/// # let dir = std::env::temp_dir().join(format!("form-net-doc-spill-mode-{}", std::process::id()));
102/// # std::fs::create_dir_all(&dir).unwrap();
103/// let store = Store::in_dir(&dir)?.memory_threshold(0).mode(SpillMode::Named);
104/// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
105/// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
106/// let path = upload.file("f").unwrap().path().unwrap().to_owned();
107/// assert!(path.starts_with(&dir));
108/// drop(upload); // a named file is deleted when it drops
109/// assert!(!path.exists());
110/// # std::fs::remove_dir_all(&dir).unwrap();
111/// # Ok::<(), form_net::FormError>(())
112/// ```
113#[non_exhaustive]
114#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
115pub enum SpillMode {
116    /// The file has no name the moment it exists.
117    #[default]
118    Anonymous,
119    /// The file keeps a random name in the store's directory.
120    Named,
121}
122
123/// A snapshot of what a store holds, read from atomic counters.
124///
125/// # Examples
126///
127/// ```
128/// use form_net::{Limits, Policy, Store};
129///
130/// let store = Store::memory_only();
131/// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
132/// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
133/// let stats = store.stats();
134/// assert_eq!((stats.files_in_memory, stats.bytes_in_memory, stats.files_on_disk), (1, 5, 0));
135/// drop(upload);
136/// assert_eq!(store.stats().files_in_memory, 0);
137/// # Ok::<(), form_net::FormError>(())
138/// ```
139#[non_exhaustive]
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub struct StoreStats {
142    /// Files held in memory now.
143    pub files_in_memory: u64,
144    /// Files on disk now.
145    pub files_on_disk: u64,
146    /// Bytes held in memory now.
147    pub bytes_in_memory: u64,
148    /// Bytes on disk now.
149    pub bytes_on_disk: u64,
150    /// Files spilled to disk since the store was created.
151    pub files_spilled: u64,
152    /// Deletions that failed during a drop, since the store was created.
153    pub cleanup_failures: u64,
154}
155
156#[derive(Default)]
157struct Counters {
158    files_in_memory: AtomicU64,
159    files_on_disk: AtomicU64,
160    bytes_in_memory: AtomicU64,
161    bytes_on_disk: AtomicU64,
162    files_spilled: AtomicU64,
163    cleanup_failures: AtomicU64,
164}
165
166fn add(c: &AtomicU64, n: u64) {
167    c.fetch_add(n, Ordering::Relaxed);
168}
169
170fn sub(c: &AtomicU64, n: u64) {
171    // Saturating: a counter never wraps below zero.
172    let mut v = c.load(Ordering::Relaxed);
173    loop {
174        match c.compare_exchange_weak(v, v.saturating_sub(n), Ordering::Relaxed, Ordering::Relaxed)
175        {
176            Ok(_) => return,
177            Err(now) => v = now,
178        }
179    }
180}
181
182/// What files share with their store: the directory and the counters. A
183/// store's own directory is removed (if empty) when the store and all its
184/// files are gone.
185struct Shared {
186    dir: OnceLock<PathBuf>,
187    owns_dir: bool,
188    counters: Counters,
189}
190
191impl Drop for Shared {
192    fn drop(&mut self) {
193        if self.owns_dir {
194            if let Some(dir) = self.dir.get() {
195                let _ = std::fs::remove_dir(dir);
196            }
197        }
198    }
199}
200
201impl Shared {
202    fn dir(&self) -> Result<&Path, FormError> {
203        if let Some(d) = self.dir.get() {
204            return Ok(d);
205        }
206        let created = temp::create_private_dir()?;
207        if let Err(lost) = self.dir.set(created) {
208            // Another thread created one first; remove ours.
209            let _ = std::fs::remove_dir(lost);
210        }
211        self.dir.get().map(PathBuf::as_path).ok_or(FormError::Io {
212            kind: io::ErrorKind::Other,
213        })
214    }
215
216    fn cleanup_failed(&self) {
217        add(&self.counters.cleanup_failures, 1);
218    }
219}
220
221/// Where uploads are kept. `Send + Sync`; create one per process or per
222/// route and share it by reference.
223///
224/// # Examples
225///
226/// ```
227/// use form_net::{SpillMode, Store};
228///
229/// // Files over 64 KiB spill to anonymous temporary files.
230/// let store = Store::temp()?;
231/// assert!(store.dir().is_some());
232///
233/// // Never touch the file system.
234/// let memory = Store::memory_only().memory_threshold(1 << 20);
235/// assert!(memory.dir().is_none());
236/// # Ok::<(), form_net::FormError>(())
237/// ```
238#[derive(Clone)]
239pub struct Store {
240    shared: Arc<Shared>,
241    memory_only: bool,
242    threshold: u64,
243    max_disk: Option<u64>,
244    mode: SpillMode,
245    budget: Option<Arc<dyn SpillBudget>>,
246}
247
248impl fmt::Debug for Store {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.debug_struct("Store")
251            .field("memory_only", &self.memory_only)
252            .field("threshold", &self.threshold)
253            .field("mode", &self.mode)
254            .field("quota", &self.budget.is_some())
255            .field("stats", &self.stats())
256            .finish_non_exhaustive()
257    }
258}
259
260const DEFAULT_THRESHOLD: u64 = 64 * 1024;
261
262impl Store {
263    fn with(dir: Option<PathBuf>, owns_dir: bool, memory_only: bool) -> Store {
264        let lock = OnceLock::new();
265        if let Some(d) = dir {
266            let _ = lock.set(d);
267        }
268        Store {
269            shared: Arc::new(Shared {
270                dir: lock,
271                owns_dir,
272                counters: Counters::default(),
273            }),
274            memory_only,
275            threshold: DEFAULT_THRESHOLD,
276            max_disk: None,
277            mode: SpillMode::Anonymous,
278            budget: None,
279        }
280    }
281
282    /// A store with its own private directory under the system temporary
283    /// directory, created now: `form-net-<32 hex>`, owner-only on Unix,
284    /// failing if the path exists. Files up to 64 KiB stay in memory;
285    /// larger ones spill to anonymous temporary files.
286    ///
287    /// # Errors
288    ///
289    /// `Io { kind }` when the directory cannot be created; `Random` when the
290    /// OS random source fails (no predictable fallback name).
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// let store = form_net::Store::temp()?;
296    /// let dir = store.dir().unwrap().to_owned();
297    /// assert!(dir.is_dir());
298    /// drop(store); // its own directory goes with it, once empty
299    /// assert!(!dir.exists());
300    /// # Ok::<(), form_net::FormError>(())
301    /// ```
302    pub fn temp() -> Result<Store, FormError> {
303        let dir = temp::create_private_dir()?;
304        Ok(Store::with(Some(dir), true, false))
305    }
306
307    /// A store whose private directory is created only when a file first
308    /// spills (for one-shot parses).
309    pub(crate) fn temp_lazy() -> Store {
310        Store::with(None, true, false)
311    }
312
313    /// A store that never touches the file system: a file larger than
314    /// `max_file_bytes` is refused, never spilled.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// use form_net::{Limits, Policy, Store};
320    ///
321    /// let store = Store::memory_only();
322    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
323    /// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
324    /// assert!(upload.file("f").unwrap().is_in_memory());
325    /// assert_eq!(store.dir(), None);
326    /// # Ok::<(), form_net::FormError>(())
327    /// ```
328    #[must_use]
329    pub fn memory_only() -> Store {
330        let mut s = Store::with(None, false, true);
331        s.threshold = u64::MAX;
332        s
333    }
334
335    /// A store that spills into `dir`, a directory the caller made. It must
336    /// exist and be a directory, not a link to one.
337    ///
338    /// # Errors
339    ///
340    /// `Store { NotADirectory }`; `Io { kind }`.
341    ///
342    /// # Examples
343    ///
344    /// ```
345    /// let dir = std::env::temp_dir();
346    /// let store = form_net::Store::in_dir(&dir)?;
347    /// assert_eq!(store.dir(), Some(dir.as_path()));
348    /// # Ok::<(), form_net::FormError>(())
349    /// ```
350    pub fn in_dir(dir: impl AsRef<Path>) -> Result<Store, FormError> {
351        let dir = dir.as_ref();
352        temp::check_dir(dir)?;
353        Ok(Store::with(Some(dir.to_owned()), false, false))
354    }
355
356    /// Keeps each file in memory up to `bytes`; a file whose next write would
357    /// pass it spills first. `0` spills every file at its first byte. On a
358    /// [`memory_only`](Self::memory_only) store it changes nothing: that
359    /// store never spills, and a file larger than `max_file_bytes` is
360    /// refused (D-160).
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// let dir = std::env::temp_dir();
366    /// let store = form_net::Store::in_dir(&dir)?.memory_threshold(16 * 1024);
367    /// assert_eq!(store.dir(), Some(dir.as_path()));
368    /// # Ok::<(), form_net::FormError>(())
369    /// ```
370    #[must_use]
371    pub fn memory_threshold(mut self, bytes: u64) -> Store {
372        if !self.memory_only {
373            self.threshold = bytes;
374        }
375        self
376    }
377
378    /// Caps the bytes one request may put on disk (`Limit { Disk }` beyond).
379    /// Unset, the cap is the request's `max_body_bytes`.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// let store = form_net::Store::temp()?.max_disk_bytes(50 * 1024 * 1024);
385    /// assert!(store.dir().is_some());
386    /// # Ok::<(), form_net::FormError>(())
387    /// ```
388    #[must_use]
389    pub fn max_disk_bytes(mut self, bytes: u64) -> Store {
390        self.max_disk = Some(bytes);
391        self
392    }
393
394    /// How spilled files are kept.
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// use form_net::{Limits, Policy, SpillMode, Store};
400    ///
401    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-mode-{}", std::process::id()));
402    /// # std::fs::create_dir_all(&dir).unwrap();
403    /// let store = Store::in_dir(&dir)?.memory_threshold(0).mode(SpillMode::Anonymous);
404    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
405    /// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
406    /// let file = upload.file("f").unwrap();
407    /// // Spilled, and nameless from the moment it was created.
408    /// assert_eq!((file.is_in_memory(), file.path()), (false, None));
409    /// # drop(upload);
410    /// # std::fs::remove_dir_all(&dir).unwrap();
411    /// # Ok::<(), form_net::FormError>(())
412    /// ```
413    #[must_use]
414    pub fn mode(mut self, mode: SpillMode) -> Store {
415        self.mode = mode;
416        self
417    }
418
419    /// The directory files spill into, once it exists.
420    ///
421    /// # Examples
422    ///
423    /// ```
424    /// use form_net::Store;
425    ///
426    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-dir-{}", std::process::id()));
427    /// # std::fs::create_dir_all(&dir).unwrap();
428    /// assert_eq!(Store::in_dir(&dir)?.dir(), Some(dir.as_path()));
429    /// assert_eq!(Store::memory_only().dir(), None);
430    /// # std::fs::remove_dir_all(&dir).unwrap();
431    /// # Ok::<(), form_net::FormError>(())
432    /// ```
433    #[must_use]
434    pub fn dir(&self) -> Option<&Path> {
435        self.shared.dir.get().map(PathBuf::as_path)
436    }
437
438    /// Deletions that failed during a drop (they cannot be returned).
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// let store = form_net::Store::memory_only();
444    /// assert_eq!(store.cleanup_failures(), 0);
445    /// assert_eq!(store.cleanup_failures(), store.stats().cleanup_failures);
446    /// ```
447    #[must_use]
448    pub fn cleanup_failures(&self) -> u64 {
449        self.shared
450            .counters
451            .cleanup_failures
452            .load(Ordering::Relaxed)
453    }
454
455    /// What the store holds now.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// use form_net::{Limits, Policy, Store};
461    ///
462    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-stats-{}", std::process::id()));
463    /// # std::fs::create_dir_all(&dir).unwrap();
464    /// let store = Store::in_dir(&dir)?.memory_threshold(0);
465    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
466    /// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
467    /// let stats = store.stats();
468    /// assert_eq!((stats.files_on_disk, stats.bytes_on_disk, stats.files_spilled), (1, 5, 1));
469    /// drop(upload);
470    /// assert_eq!((store.stats().files_on_disk, store.stats().files_spilled), (0, 1));
471    /// # std::fs::remove_dir_all(&dir).unwrap();
472    /// # Ok::<(), form_net::FormError>(())
473    /// ```
474    #[must_use]
475    pub fn stats(&self) -> StoreStats {
476        let c = &self.shared.counters;
477        StoreStats {
478            files_in_memory: c.files_in_memory.load(Ordering::Relaxed),
479            files_on_disk: c.files_on_disk.load(Ordering::Relaxed),
480            bytes_in_memory: c.bytes_in_memory.load(Ordering::Relaxed),
481            bytes_on_disk: c.bytes_on_disk.load(Ordering::Relaxed),
482            files_spilled: c.files_spilled.load(Ordering::Relaxed),
483            cleanup_failures: c.cleanup_failures.load(Ordering::Relaxed),
484        }
485    }
486
487    /// Adds a disk budget shared across requests (and across stores given
488    /// the same budget): bytes are reserved from it before every disk write,
489    /// and a request that would cross it is refused with
490    /// `Store { QuotaExceeded }` (status hint 503). Replaces an earlier one.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use form_net::{Limits, NoInspector, Policy, SpillQuota, Store, Upload};
496    ///
497    /// let quota = SpillQuota::new(4);
498    /// let store = Store::temp()?.memory_threshold(0).quota(quota.clone());
499    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a\r\n\r\n12345\r\n--X--";
500    /// let e = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store).unwrap_err();
501    /// assert_eq!(e.status_hint(), 503);
502    /// assert_eq!(quota.in_use(), 0, "the refused file gave its bytes back");
503    /// # Ok::<(), form_net::FormError>(())
504    /// ```
505    pub fn quota(mut self, budget: impl SpillBudget) -> Store {
506        self.budget = Some(Arc::new(budget));
507        self
508    }
509
510    /// The worst-case bytes one request through this store can hold in
511    /// memory under `limits`: [`Limits::memory_bound`] with this store's
512    /// memory threshold (`u64::MAX` for a memory-only store).
513    ///
514    /// # Examples
515    ///
516    /// ```
517    /// use form_net::{Limits, Store};
518    ///
519    /// let limits = Limits::DEFAULT;
520    /// assert!(Store::temp()?.memory_bound(&limits) < Store::memory_only().memory_bound(&limits));
521    /// # Ok::<(), form_net::FormError>(())
522    /// ```
523    #[must_use]
524    pub fn memory_bound(&self, limits: &Limits) -> u64 {
525        let in_memory = if self.memory_only {
526            u64::MAX
527        } else {
528            self.threshold
529        };
530        limits.memory_bound(in_memory)
531    }
532
533    /// The `Sink` that stores the files of one request under `limits`.
534    ///
535    /// # Examples
536    ///
537    /// ```
538    /// use form_net::{Boundary, Driver, Limits, MultipartParser, NoInspector, Policy, Progress, Store};
539    ///
540    /// let store = Store::memory_only();
541    /// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
542    /// let mut driver = Driver::new(parser, store.sink(&Limits::DEFAULT));
543    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
544    /// let (_, progress) = driver.push(body, true, &mut NoInspector)?;
545    /// assert!(matches!(progress, Progress::File(ref f) if f.bytes() == Some(&b"hello"[..])));
546    /// # Ok::<(), form_net::FormError>(())
547    /// ```
548    #[must_use]
549    pub fn sink(&self, limits: &Limits) -> StoreSink {
550        StoreSink {
551            budget: self.budget.clone(),
552            shared: Arc::clone(&self.shared),
553            memory_only: self.memory_only,
554            threshold: self.threshold,
555            // A memory file never holds more than this: its buffer is
556            // capped here, so `memory_bound` is exact.
557            memory_cap: usize::try_from(self.threshold.min(limits.file())).unwrap_or(usize::MAX),
558            mode: self.mode,
559            max_disk: self.max_disk.unwrap_or(limits.max_body_bytes),
560            used_disk: 0,
561            current: None,
562        }
563    }
564}
565
566/// One file being written.
567struct Current {
568    name: String,
569    filename: Option<(Vec<u8>, FileNameSource)>,
570    content_type: Option<Vec<u8>>,
571    memory: Vec<u8>,
572    disk: Option<(File, Option<PathBuf>)>,
573    len: u64,
574    /// Bytes of this file written to disk (and added to the counters).
575    on_disk: u64,
576}
577
578/// The [`Sink`] a [`Store`] hands out for one request.
579///
580/// # Examples
581///
582/// ```
583/// use form_net::{Boundary, Driver, Limits, MultipartParser, NoInspector, Policy, Progress, Store};
584///
585/// let store = Store::memory_only();
586/// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
587/// let mut driver = Driver::new(parser, store.sink(&Limits::DEFAULT));
588/// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
589/// let (_, progress) = driver.push(body, true, &mut NoInspector)?;
590/// // The sink's output is a `StoredFile`, counted by the store.
591/// assert!(matches!(progress, Progress::File(ref f) if f.name() == "f"));
592/// assert_eq!(store.stats().files_in_memory, 1);
593/// # Ok::<(), form_net::FormError>(())
594/// ```
595pub struct StoreSink {
596    budget: Option<Arc<dyn SpillBudget>>,
597    shared: Arc<Shared>,
598    memory_only: bool,
599    threshold: u64,
600    memory_cap: usize,
601    mode: SpillMode,
602    max_disk: u64,
603    used_disk: u64,
604    current: Option<Current>,
605}
606
607impl fmt::Debug for StoreSink {
608    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609        f.debug_struct("StoreSink")
610            .field("used_disk", &self.used_disk)
611            .field("max_disk", &self.max_disk)
612            .finish_non_exhaustive()
613    }
614}
615
616impl StoreSink {
617    /// Bytes this request has put on disk so far.
618    pub(crate) const fn used_disk(&self) -> u64 {
619        self.used_disk
620    }
621
622    /// Continues a request that already put `bytes` on disk.
623    pub(crate) const fn with_used_disk(mut self, bytes: u64) -> StoreSink {
624        self.used_disk = bytes;
625        self
626    }
627
628    fn disk_write(&mut self, bytes: &[u8]) -> Result<(), FormError> {
629        let used = self.used_disk.saturating_add(bytes.len() as u64);
630        if used > self.max_disk {
631            return Err(FormError::limit(Limit::Disk, Location::NONE));
632        }
633        let Some(cur) = self.current.as_mut() else {
634            return Ok(());
635        };
636        let Some((file, _)) = cur.disk.as_mut() else {
637            return Ok(());
638        };
639        let n = bytes.len() as u64;
640        if let Some(budget) = &self.budget {
641            if !budget.reserve(n) {
642                return Err(FormError::Store {
643                    reason: StoreError::QuotaExceeded,
644                });
645            }
646        }
647        if let Err(e) = file.write_all(bytes) {
648            // Reserved, never written: give the bytes back now. (A partial
649            // write is removed with the file on abort, which releases only
650            // `on_disk`.)
651            if let Some(budget) = &self.budget {
652                budget.release(n);
653            }
654            return Err(temp::io(&e));
655        }
656        self.used_disk = used;
657        cur.on_disk = cur.on_disk.saturating_add(bytes.len() as u64);
658        add(&self.shared.counters.bytes_on_disk, bytes.len() as u64);
659        Ok(())
660    }
661
662    fn spill(&mut self) -> Result<(), FormError> {
663        let dir = self.shared.dir()?.to_owned();
664        let anonymous = self.mode == SpillMode::Anonymous;
665        let (file, path) = temp::create_file(&dir, anonymous)?;
666        add(&self.shared.counters.files_on_disk, 1);
667        add(&self.shared.counters.files_spilled, 1);
668        let prefix = match self.current.as_mut() {
669            Some(cur) => {
670                cur.disk = Some((file, path));
671                core::mem::take(&mut cur.memory)
672            }
673            None => Vec::new(),
674        };
675        let mut prefix = prefix;
676        let r = self.disk_write(&prefix);
677        crate::wipe::wipe_vec(&mut prefix);
678        r
679    }
680}
681
682impl Sink for StoreSink {
683    type Output = StoredFile;
684
685    fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
686        self.abort();
687        self.current = Some(Current {
688            name: String::from(head.name()),
689            filename: head.filename().map(|f| (f.raw().to_vec(), f.source())),
690            content_type: head.content_type().map(|c| c.raw().to_vec()),
691            memory: Vec::new(),
692            disk: None,
693            len: 0,
694            on_disk: 0,
695        });
696        Ok(())
697    }
698
699    fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
700        let (on_disk, len) = match self.current.as_ref() {
701            Some(c) => (c.disk.is_some(), c.len),
702            None => return Ok(()),
703        };
704        let new_len = len.saturating_add(chunk.len() as u64);
705        if !on_disk && !self.memory_only && new_len > self.threshold {
706            self.spill()?;
707        }
708        let on_disk = self.current.as_ref().is_some_and(|c| c.disk.is_some());
709        if on_disk {
710            self.disk_write(chunk)?;
711        } else if let Some(cur) = self.current.as_mut() {
712            crate::wipe::append_wiping(&mut cur.memory, chunk, 4096, self.memory_cap);
713        }
714        if let Some(cur) = self.current.as_mut() {
715            cur.len = new_len;
716        }
717        Ok(())
718    }
719
720    fn close(&mut self) -> Result<StoredFile, FormError> {
721        let Some(mut cur) = self.current.take() else {
722            return Err(FormError::Poisoned);
723        };
724        let on_disk = cur.disk.is_some();
725        let place = match cur.disk.take() {
726            Some((file, path)) => Place::Disk {
727                file: Some(file),
728                path,
729            },
730            None => {
731                add(&self.shared.counters.files_in_memory, 1);
732                add(&self.shared.counters.bytes_in_memory, cur.len);
733                Place::Memory(core::mem::take(&mut cur.memory))
734            }
735        };
736        Ok(StoredFile {
737            name: core::mem::take(&mut cur.name),
738            filename: cur.filename.take(),
739            content_type: cur.content_type.take(),
740            len: cur.len,
741            place,
742            shared: Some(Arc::clone(&self.shared)),
743            budget: if on_disk { self.budget.clone() } else { None },
744            reserved: cur.on_disk,
745        })
746    }
747
748    fn abort(&mut self) {
749        if let Some(mut cur) = self.current.take() {
750            crate::wipe::wipe_vec(&mut cur.memory);
751            wipe_meta(&mut cur.name, &mut cur.filename, &mut cur.content_type);
752            if let Some((file, path)) = cur.disk.take() {
753                // Close first, then remove the name: an open file cannot be
754                // removed on Windows.
755                drop(file);
756                sub(&self.shared.counters.files_on_disk, 1);
757                sub(&self.shared.counters.bytes_on_disk, cur.on_disk);
758                let mut freed = true;
759                if let Some(p) = path {
760                    if std::fs::remove_file(&p).is_err() {
761                        freed = false;
762                        self.shared.cleanup_failed();
763                    }
764                }
765                // As `StoredFile`'s drop: only space that is free again goes
766                // back to the budget (D-101, D-143).
767                if freed {
768                    if let Some(budget) = &self.budget {
769                        budget.release(cur.on_disk);
770                    }
771                }
772            }
773        }
774    }
775}
776
777impl Drop for StoreSink {
778    fn drop(&mut self) {
779        self.abort();
780    }
781}
782
783enum Place {
784    Memory(Vec<u8>),
785    Disk {
786        file: Option<File>,
787        path: Option<PathBuf>,
788    },
789    Gone,
790}
791
792/// One received file, in memory or in a temporary file.
793///
794/// Dropping it without [`persist`](Self::persist) deletes it (closing the
795/// handle first) and wipes memory-held bytes. `Debug` never prints the file
796/// name or the data.
797///
798/// # Examples
799///
800/// ```
801/// use std::io::Read;
802///
803/// let body = b"--X\r\nContent-Disposition: form-data; name=doc; filename=a.txt\r\n\r\nhello\r\n--X--";
804/// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
805/// let file = upload.file("doc").unwrap();
806/// let mut text = String::new();
807/// file.reader()?.read_to_string(&mut text).unwrap();
808/// assert_eq!((text.as_str(), file.len(), file.is_in_memory()), ("hello", 5, true));
809/// # Ok::<(), form_net::FormError>(())
810/// ```
811#[must_use]
812pub struct StoredFile {
813    name: String,
814    filename: Option<(Vec<u8>, FileNameSource)>,
815    content_type: Option<Vec<u8>>,
816    len: u64,
817    place: Place,
818    shared: Option<Arc<Shared>>,
819    /// The budget this file's disk bytes were reserved from.
820    budget: Option<Arc<dyn SpillBudget>>,
821    reserved: u64,
822}
823
824impl fmt::Debug for StoredFile {
825    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826        f.debug_struct("StoredFile")
827            .field("name", &self.name)
828            .field("len", &self.len)
829            .field("in_memory", &self.is_in_memory())
830            .finish_non_exhaustive()
831    }
832}
833
834impl StoredFile {
835    /// A memory-held file not tied to a store (from a buffered part).
836    pub(crate) fn detached(
837        name: &str,
838        filename: Option<FileName<'_>>,
839        content_type: Option<&[u8]>,
840        bytes: &[u8],
841    ) -> StoredFile {
842        StoredFile {
843            name: String::from(name),
844            filename: filename.map(|f| (f.raw().to_vec(), f.source())),
845            content_type: content_type.map(<[u8]>::to_vec),
846            len: bytes.len() as u64,
847            place: Place::Memory(bytes.to_vec()),
848            shared: None,
849            budget: None,
850            reserved: 0,
851        }
852    }
853
854    /// The form field name.
855    ///
856    /// # Examples
857    ///
858    /// ```
859    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
860    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
861    /// let file = upload.file("f").unwrap();
862    /// assert_eq!(file.name(), "f");
863    /// # Ok::<(), form_net::FormError>(())
864    /// ```
865    #[must_use]
866    pub fn name(&self) -> &str {
867        &self.name
868    }
869
870    /// The client's file name: a hint, never a path.
871    ///
872    /// # Examples
873    ///
874    /// ```
875    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
876    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
877    /// let file = upload.file("f").unwrap();
878    /// assert_eq!(file.filename().unwrap().as_str()?, "a.txt");
879    /// # Ok::<(), form_net::FormError>(())
880    /// ```
881    #[must_use]
882    pub fn filename(&self) -> Option<FileName<'_>> {
883        self.filename
884            .as_ref()
885            .map(|(raw, source)| FileName::new(raw, *source))
886    }
887
888    /// The declared content type: a hint, never trusted.
889    ///
890    /// # Examples
891    ///
892    /// ```
893    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\nContent-Type: text/plain\r\n\r\nhello\r\n--X--";
894    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
895    /// let file = upload.file("f").unwrap();
896    /// assert!(file.content_type().unwrap().is("text", "plain"));
897    /// # Ok::<(), form_net::FormError>(())
898    /// ```
899    #[must_use]
900    pub fn content_type(&self) -> Option<ContentType<'_>> {
901        self.content_type.as_deref().and_then(ContentType::parse)
902    }
903
904    /// The length in bytes.
905    ///
906    /// # Examples
907    ///
908    /// ```
909    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
910    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
911    /// let file = upload.file("f").unwrap();
912    /// assert_eq!(file.len(), 5);
913    /// # Ok::<(), form_net::FormError>(())
914    /// ```
915    #[must_use]
916    pub const fn len(&self) -> u64 {
917        self.len
918    }
919
920    /// Whether the file is empty.
921    ///
922    /// # Examples
923    ///
924    /// ```
925    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n\r\n--X--";
926    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
927    /// let file = upload.file("f").unwrap();
928    /// assert!(file.is_empty());
929    /// # Ok::<(), form_net::FormError>(())
930    /// ```
931    #[must_use]
932    pub const fn is_empty(&self) -> bool {
933        self.len == 0
934    }
935
936    /// Whether the file is held in memory.
937    ///
938    /// # Examples
939    ///
940    /// ```
941    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
942    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
943    /// let file = upload.file("f").unwrap();
944    /// // Files up to the store's memory threshold never reach the disk.
945    /// assert!(file.is_in_memory());
946    /// # Ok::<(), form_net::FormError>(())
947    /// ```
948    #[must_use]
949    pub const fn is_in_memory(&self) -> bool {
950        matches!(self.place, Place::Memory(_))
951    }
952
953    /// The bytes, while the file is in memory.
954    ///
955    /// # Examples
956    ///
957    /// ```
958    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
959    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
960    /// let file = upload.file("f").unwrap();
961    /// assert_eq!(file.bytes(), Some(&b"hello"[..]));
962    /// # Ok::<(), form_net::FormError>(())
963    /// ```
964    #[must_use]
965    pub fn bytes(&self) -> Option<&[u8]> {
966        match &self.place {
967            Place::Memory(b) => Some(b),
968            _ => None,
969        }
970    }
971
972    /// A reader over the file, in memory or on disk. It reads with
973    /// positional reads, so several readers can exist at once.
974    ///
975    /// # Errors
976    ///
977    /// `Io { kind }` when the file is gone.
978    ///
979    /// # Examples
980    ///
981    /// ```
982    /// use std::io::Read;
983    ///
984    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
985    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
986    /// let file = upload.file("f").unwrap();
987    /// let (mut a, mut b) = (file.reader()?, file.reader()?);
988    /// let mut first = [0u8; 2];
989    /// a.read_exact(&mut first)?;
990    /// let mut all = Vec::new();
991    /// b.read_to_end(&mut all)?;
992    /// assert_eq!((&first, all.as_slice()), (b"he", &b"hello"[..]));
993    /// # Ok::<(), Box<dyn std::error::Error>>(())
994    /// ```
995    pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
996        match &self.place {
997            Place::Gone => Err(FormError::Io {
998                kind: io::ErrorKind::NotFound,
999            }),
1000            _ => Ok(StoredReader { file: self, pos: 0 }),
1001        }
1002    }
1003
1004    /// The whole file in a new vector, refusing a file longer than `max`
1005    /// before allocating.
1006    ///
1007    /// # Errors
1008    ///
1009    /// `Limit { FileSize }`; `Io { kind }`.
1010    ///
1011    /// # Examples
1012    ///
1013    /// ```
1014    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1015    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1016    /// let file = upload.file("f").unwrap();
1017    /// assert_eq!(file.read_to_vec(1024)?, b"hello");
1018    /// assert_eq!(file.read_to_vec(4).unwrap_err().code(), "limit.file_size");
1019    /// # Ok::<(), form_net::FormError>(())
1020    /// ```
1021    pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
1022        if self.len > max {
1023            return Err(FormError::limit(Limit::FileSize, Location::NONE));
1024        }
1025        let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
1026        self.reader()?
1027            .read_to_end(&mut out)
1028            .map_err(|e| temp::io(&e))?;
1029        Ok(out)
1030    }
1031
1032    /// The temporary file's path, in `SpillMode::Named` only.
1033    ///
1034    /// # Examples
1035    ///
1036    /// ```
1037    /// use form_net::{Limits, Policy, SpillMode, Store};
1038    ///
1039    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-path-{}", std::process::id()));
1040    /// # std::fs::create_dir_all(&dir).unwrap();
1041    /// let store = Store::in_dir(&dir)?.memory_threshold(0).mode(SpillMode::Named);
1042    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1043    /// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
1044    /// let path = upload.file("f").unwrap().path().unwrap();
1045    /// assert!(path.is_file() && path.parent() == Some(dir.as_path()));
1046    /// # drop(upload);
1047    /// # std::fs::remove_dir_all(&dir).unwrap();
1048    /// # Ok::<(), form_net::FormError>(())
1049    /// ```
1050    #[must_use]
1051    pub fn path(&self) -> Option<&Path> {
1052        match &self.place {
1053            Place::Disk { path, .. } => path.as_deref(),
1054            _ => None,
1055        }
1056    }
1057
1058    fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
1059        match &self.place {
1060            Place::Memory(b) => {
1061                let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
1062                let src = b.get(start..).unwrap_or(&[]);
1063                let n = src.len().min(buf.len());
1064                if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
1065                    dst.copy_from_slice(src);
1066                }
1067                Ok(n)
1068            }
1069            Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
1070            _ => Err(io::Error::from(io::ErrorKind::NotFound)),
1071        }
1072    }
1073
1074    /// Moves the finished upload to `dest`, which the caller built. It never
1075    /// overwrites: a named file is hard-linked when `dest` is on the same file
1076    /// system and copied otherwise; anonymous and memory files are copied
1077    /// into `dest` opened with `create_new`. Whether to fsync `dest` and its
1078    /// directory is the caller's choice.
1079    ///
1080    /// On Unix the persisted file is owner-only (`0600`) however it was
1081    /// made, as the temporary file was; widen it after `persist` if others
1082    /// must read it. On Windows it takes the destination directory's
1083    /// inherited ACL (D-155).
1084    ///
1085    /// On failure the upload is handed back in the error, untouched, so the
1086    /// caller can try another name; `?` into a `FormError` drops it, which
1087    /// deletes the temporary file (D-154).
1088    ///
1089    /// # Errors
1090    ///
1091    /// `Store { DestinationExists }`; `Io { kind }`; each with the file.
1092    ///
1093    /// # Examples
1094    ///
1095    /// ```
1096    /// # let dir = std::env::temp_dir().join(format!("form-net-persist-doc-{}", std::process::id()));
1097    /// # std::fs::create_dir_all(&dir).unwrap();
1098    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1099    /// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1100    /// let file = upload.take_file("f").unwrap();
1101    /// std::fs::write(dir.join("a.txt"), b"taken")?;
1102    /// // The name is taken: the upload comes back, and another name works.
1103    /// let file = match file.persist(&dir.join("a.txt")) {
1104    ///     Ok(()) => unreachable!(),
1105    ///     Err(e) => {
1106    ///         assert_eq!(e.error().code(), "store.destination_exists");
1107    ///         e.into_file()
1108    ///     }
1109    /// };
1110    /// file.persist(&dir.join("a (2).txt"))?;
1111    /// assert_eq!(std::fs::read(dir.join("a (2).txt"))?, b"hello");
1112    /// # std::fs::remove_dir_all(&dir).unwrap();
1113    /// # Ok::<(), Box<dyn std::error::Error>>(())
1114    /// ```
1115    pub fn persist(mut self, dest: &Path) -> Result<(), PersistError> {
1116        match self.persist_ref(dest) {
1117            Ok(()) => Ok(()),
1118            Err(error) => Err(PersistError {
1119                error,
1120                file: alloc::boxed::Box::new(self),
1121            }),
1122        }
1123    }
1124
1125    /// `persist`, leaving `self` intact on failure.
1126    fn persist_ref(&mut self, dest: &Path) -> Result<(), FormError> {
1127        let exists = |e: io::Error| {
1128            if e.kind() == io::ErrorKind::AlreadyExists {
1129                FormError::Store {
1130                    reason: StoreError::DestinationExists,
1131                }
1132            } else {
1133                temp::io(&e)
1134            }
1135        };
1136        if let Place::Disk {
1137            file,
1138            path: Some(p),
1139        } = &mut self.place
1140        {
1141            // Close before linking and removing: Windows needs it.
1142            drop(file.take());
1143            match std::fs::hard_link(&*p, dest) {
1144                Ok(()) => {
1145                    let p = p.clone();
1146                    self.place = Place::Gone;
1147                    self.release();
1148                    self.release_budget();
1149                    if std::fs::remove_file(&p).is_err() {
1150                        if let Some(s) = &self.shared {
1151                            s.cleanup_failed();
1152                        }
1153                    }
1154                    return Ok(());
1155                }
1156                Err(e) => {
1157                    // Reopened either way: on `AlreadyExists` the upload goes
1158                    // back to the caller readable; on another file system it
1159                    // is copied below, from the name.
1160                    let f = File::open(&*p).map_err(|e| temp::io(&e))?;
1161                    *file = Some(f);
1162                    if e.kind() == io::ErrorKind::AlreadyExists {
1163                        return Err(exists(e));
1164                    }
1165                }
1166            }
1167        }
1168        let mut options = std::fs::OpenOptions::new();
1169        options.write(true).create_new(true);
1170        #[cfg(unix)]
1171        {
1172            use std::os::unix::fs::OpenOptionsExt;
1173            // Owner-only, as a hard link to the 0600 temporary file is (D-155).
1174            options.mode(0o600);
1175        }
1176        let mut out = options.open(dest).map_err(exists)?;
1177        let mut buf = [0u8; 64 * 1024];
1178        let mut hi = 0usize;
1179        let mut pos = 0u64;
1180        let copied = loop {
1181            let n = match self.read_at(&mut buf, pos) {
1182                Ok(0) => break Ok(()),
1183                Ok(n) => n,
1184                Err(e) => break Err(temp::io(&e)),
1185            };
1186            hi = hi.max(n);
1187            if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
1188                break Err(temp::io(&e));
1189            }
1190            pos = pos.saturating_add(n as u64);
1191        };
1192        crate::wipe::wipe(buf.get_mut(..hi).unwrap_or(&mut []));
1193        if copied.is_err() {
1194            // Never leave a partial copy under the caller's name.
1195            drop(out);
1196            let _ = std::fs::remove_file(dest);
1197        }
1198        copied
1199    }
1200
1201    /// Gives the file's disk bytes back to the shared budget, once.
1202    fn release_budget(&mut self) {
1203        if let Some(budget) = self.budget.take() {
1204            budget.release(self.reserved);
1205        }
1206    }
1207
1208    /// Takes the file off the store's counters.
1209    fn release(&mut self) {
1210        if let Some(s) = &self.shared {
1211            match &self.place {
1212                Place::Memory(_) => {
1213                    sub(&s.counters.files_in_memory, 1);
1214                    sub(&s.counters.bytes_in_memory, self.len);
1215                }
1216                _ => {
1217                    sub(&s.counters.files_on_disk, 1);
1218                    sub(&s.counters.bytes_on_disk, self.len);
1219                }
1220            }
1221        }
1222    }
1223}
1224
1225/// Wipes a file's name, file name, and declared content type.
1226fn wipe_meta(
1227    name: &mut String,
1228    filename: &mut Option<(Vec<u8>, FileNameSource)>,
1229    content_type: &mut Option<Vec<u8>>,
1230) {
1231    crate::wipe::wipe_string(name);
1232    if let Some((mut f, _)) = filename.take() {
1233        crate::wipe::wipe_vec(&mut f);
1234    }
1235    if let Some(mut c) = content_type.take() {
1236        crate::wipe::wipe_vec(&mut c);
1237    }
1238}
1239
1240/// A failed [`StoredFile::persist`]: the error, and the upload, untouched,
1241/// so the caller can try another name (D-154). Converting it into a
1242/// `FormError` (as `?` does) drops the file, which deletes it.
1243///
1244/// # Examples
1245///
1246/// ```
1247/// fn save(file: form_net::StoredFile, dest: &std::path::Path) -> Result<(), form_net::FormError> {
1248///     file.persist(dest)?; // on failure the file is dropped and deleted
1249///     Ok(())
1250/// }
1251/// ```
1252pub struct PersistError {
1253    error: FormError,
1254    file: alloc::boxed::Box<StoredFile>,
1255}
1256
1257impl PersistError {
1258    /// Why `persist` failed.
1259    ///
1260    /// # Examples
1261    ///
1262    /// ```
1263    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-persist-error-{}", std::process::id()));
1264    /// # std::fs::create_dir_all(&dir).unwrap();
1265    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1266    /// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1267    /// std::fs::write(dir.join("a.txt"), b"taken")?;
1268    /// let e = upload.take_file("f").unwrap().persist(&dir.join("a.txt")).unwrap_err();
1269    /// assert_eq!(e.error().code(), "store.destination_exists");
1270    /// # drop(e);
1271    /// # std::fs::remove_dir_all(&dir).unwrap();
1272    /// # Ok::<(), Box<dyn std::error::Error>>(())
1273    /// ```
1274    #[must_use]
1275    pub const fn error(&self) -> FormError {
1276        self.error
1277    }
1278
1279    /// The upload, to persist under another name.
1280    ///
1281    /// # Examples
1282    ///
1283    /// ```
1284    /// # let dir = std::env::temp_dir().join(format!("form-net-doc-into-file-{}", std::process::id()));
1285    /// # std::fs::create_dir_all(&dir).unwrap();
1286    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1287    /// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1288    /// std::fs::write(dir.join("a.txt"), b"taken")?;
1289    /// let e = upload.take_file("f").unwrap().persist(&dir.join("a.txt")).unwrap_err();
1290    /// e.into_file().persist(&dir.join("b.txt"))?;
1291    /// assert_eq!(std::fs::read(dir.join("b.txt"))?, b"hello");
1292    /// # std::fs::remove_dir_all(&dir).unwrap();
1293    /// # Ok::<(), Box<dyn std::error::Error>>(())
1294    /// ```
1295    pub fn into_file(self) -> StoredFile {
1296        *self.file
1297    }
1298}
1299
1300impl From<PersistError> for FormError {
1301    fn from(e: PersistError) -> FormError {
1302        e.error
1303    }
1304}
1305
1306impl fmt::Debug for PersistError {
1307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1308        f.debug_struct("PersistError")
1309            .field("error", &self.error)
1310            .field("file", &self.file)
1311            .finish()
1312    }
1313}
1314
1315impl fmt::Display for PersistError {
1316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1317        fmt::Display::fmt(&self.error, f)
1318    }
1319}
1320
1321impl std::error::Error for PersistError {
1322    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1323        Some(&self.error)
1324    }
1325}
1326
1327impl Drop for StoredFile {
1328    fn drop(&mut self) {
1329        wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
1330        match core::mem::replace(&mut self.place, Place::Gone) {
1331            Place::Memory(mut b) => {
1332                self.place = Place::Memory(Vec::new());
1333                self.release();
1334                crate::wipe::wipe_vec(&mut b);
1335            }
1336            Place::Disk { file, path } => {
1337                self.place = Place::Disk {
1338                    file: None,
1339                    path: None,
1340                };
1341                self.release();
1342                drop(file);
1343                let mut freed = true;
1344                if let Some(p) = path {
1345                    if std::fs::remove_file(&p).is_err() {
1346                        freed = false;
1347                        if let Some(s) = &self.shared {
1348                            s.cleanup_failed();
1349                        }
1350                    }
1351                }
1352                // Only space that is free again goes back to the budget: a
1353                // file that could not be removed still uses its bytes.
1354                if freed {
1355                    self.release_budget();
1356                }
1357            }
1358            Place::Gone => {}
1359        }
1360    }
1361}
1362
1363/// A reader over a [`StoredFile`]; `Read + Seek` with its own position.
1364///
1365/// # Examples
1366///
1367/// ```
1368/// use std::io::{Read, Seek, SeekFrom};
1369///
1370/// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1371/// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1372/// let mut reader = upload.file("f").unwrap().reader()?;
1373/// reader.seek(SeekFrom::Start(2))?;
1374/// let mut rest = String::new();
1375/// reader.read_to_string(&mut rest)?;
1376/// assert_eq!(rest, "llo");
1377/// # Ok::<(), Box<dyn std::error::Error>>(())
1378/// ```
1379#[derive(Debug)]
1380pub struct StoredReader<'f> {
1381    file: &'f StoredFile,
1382    pos: u64,
1383}
1384
1385impl Read for StoredReader<'_> {
1386    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1387        let n = self.file.read_at(buf, self.pos)?;
1388        self.pos = self.pos.saturating_add(n as u64);
1389        Ok(n)
1390    }
1391}
1392
1393impl Seek for StoredReader<'_> {
1394    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
1395        let target = match to {
1396            SeekFrom::Start(n) => Some(n),
1397            SeekFrom::End(d) => self.file.len.checked_add_signed(d),
1398            SeekFrom::Current(d) => self.pos.checked_add_signed(d),
1399        };
1400        match target {
1401            Some(t) => {
1402                self.pos = t;
1403                Ok(t)
1404            }
1405            None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
1406        }
1407    }
1408}
1409
1410/// A position in an upload's text arena.
1411#[derive(Clone, Copy)]
1412struct Span {
1413    name: (usize, usize),
1414    value: (usize, usize),
1415}
1416
1417/// The result of a completed multipart parse with storage: text fields,
1418/// owned and wiped on drop, and files.
1419///
1420/// # Examples
1421///
1422/// ```
1423/// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=photo; filename=a.jpg\r\n\r\nJPEG\r\n--X--";
1424/// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1425/// assert_eq!(upload.field("title"), Some("Holiday"));
1426/// let photo = upload.take_file("photo").unwrap();
1427/// assert_eq!(photo.bytes(), Some(&b"JPEG"[..]));
1428/// assert_eq!(upload.file_count(), 0);
1429/// # Ok::<(), form_net::FormError>(())
1430/// ```
1431#[must_use]
1432#[derive(Default)]
1433pub struct Upload {
1434    text: Vec<u8>,
1435    spans: Vec<Span>,
1436    files: Vec<StoredFile>,
1437    token: Option<String>,
1438}
1439
1440impl fmt::Debug for Upload {
1441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1442        f.debug_struct("Upload")
1443            .field("fields", &self.spans.len())
1444            .field("text_len", &self.text_len())
1445            .field("files", &self.files)
1446            .finish_non_exhaustive()
1447    }
1448}
1449
1450impl Drop for Upload {
1451    fn drop(&mut self) {
1452        crate::wipe::wipe_vec(&mut self.text);
1453        if let Some(t) = self.token.as_mut() {
1454            crate::wipe::wipe_string(t);
1455        }
1456    }
1457}
1458
1459impl Upload {
1460    fn slice(&self, (start, len): (usize, usize)) -> &str {
1461        let b = self
1462            .text
1463            .get(start..start.saturating_add(len))
1464            .unwrap_or(&[]);
1465        // Every value was checked as text when it was stored.
1466        core::str::from_utf8(b).unwrap_or("")
1467    }
1468
1469    fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
1470        let n0 = self.text.len();
1471        crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 1024, cap);
1472        let v0 = self.text.len();
1473        crate::wipe::append_wiping(&mut self.text, value, 1024, cap);
1474        self.spans.push(Span {
1475            name: (n0, name.len()),
1476            value: (v0, value.len()),
1477        });
1478    }
1479
1480    /// The first text field named `name`.
1481    ///
1482    /// # Examples
1483    ///
1484    /// ```
1485    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\na\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\nb\r\n--X--";
1486    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1487    /// assert_eq!(upload.field("title"), Some("Holiday"));
1488    /// assert_eq!(upload.field("tag"), Some("a"));
1489    /// assert_eq!(upload.field("missing"), None);
1490    /// # Ok::<(), form_net::FormError>(())
1491    /// ```
1492    #[must_use]
1493    pub fn field(&self, name: &str) -> Option<&str> {
1494        self.spans
1495            .iter()
1496            .find(|s| self.slice(s.name) == name)
1497            .map(|s| self.slice(s.value))
1498    }
1499
1500    /// The text field named `name`, refusing a name given twice.
1501    ///
1502    /// # Errors
1503    ///
1504    /// `Extract { Duplicate }`.
1505    ///
1506    /// # Examples
1507    ///
1508    /// ```
1509    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\na\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\nb\r\n--X--";
1510    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1511    /// assert_eq!(upload.field_one("title")?, Some("Holiday"));
1512    /// assert_eq!(upload.field_one("tag").unwrap_err().code(), "extract.duplicate");
1513    /// # Ok::<(), form_net::FormError>(())
1514    /// ```
1515    pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
1516        let mut found = None;
1517        for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
1518            if found.is_some() {
1519                return Err(FormError::Extract {
1520                    reason: ExtractError::Duplicate,
1521                    field: None,
1522                });
1523            }
1524            found = Some(self.slice(s.value));
1525        }
1526        Ok(found)
1527    }
1528
1529    /// Every text field named `name`, in body order.
1530    ///
1531    /// # Examples
1532    ///
1533    /// ```
1534    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\na\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\nb\r\n--X--";
1535    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1536    /// assert_eq!(upload.fields("tag").collect::<Vec<_>>(), ["a", "b"]);
1537    /// # Ok::<(), form_net::FormError>(())
1538    /// ```
1539    pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
1540        self.spans
1541            .iter()
1542            .filter(move |s| self.slice(s.name) == name)
1543            .map(move |s| self.slice(s.value))
1544    }
1545
1546    /// Every text field `(name, value)`, in body order.
1547    ///
1548    /// # Examples
1549    ///
1550    /// ```
1551    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\na\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\nb\r\n--X--";
1552    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1553    /// let pairs: Vec<_> = upload.iter().collect();
1554    /// assert_eq!(pairs, [("title", "Holiday"), ("tag", "a"), ("tag", "b")]);
1555    /// # Ok::<(), form_net::FormError>(())
1556    /// ```
1557    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1558        self.spans
1559            .iter()
1560            .map(|s| (self.slice(s.name), self.slice(s.value)))
1561    }
1562
1563    /// The first file named `name`.
1564    ///
1565    /// # Examples
1566    ///
1567    /// ```
1568    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n1\r\n--X\r\nContent-Disposition: form-data; name=f; filename=b.txt\r\n\r\n22\r\n--X--";
1569    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1570    /// assert_eq!(upload.file("f").map(|f| f.len()), Some(1));
1571    /// assert!(upload.file("title").is_none(), "a text field is not a file");
1572    /// # Ok::<(), form_net::FormError>(())
1573    /// ```
1574    #[must_use]
1575    pub fn file(&self, name: &str) -> Option<&StoredFile> {
1576        self.files.iter().find(|f| f.name() == name)
1577    }
1578
1579    /// Every file named `name`, in body order.
1580    ///
1581    /// # Examples
1582    ///
1583    /// ```
1584    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n1\r\n--X\r\nContent-Disposition: form-data; name=f; filename=b.txt\r\n\r\n22\r\n--X--";
1585    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1586    /// let lens: Vec<u64> = upload.files("f").map(|f| f.len()).collect();
1587    /// assert_eq!(lens, [1, 2]);
1588    /// # Ok::<(), form_net::FormError>(())
1589    /// ```
1590    pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
1591        self.files.iter().filter(move |f| f.name() == name)
1592    }
1593
1594    /// Removes and returns the first file named `name`.
1595    ///
1596    /// # Examples
1597    ///
1598    /// ```
1599    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n1\r\n--X\r\nContent-Disposition: form-data; name=f; filename=b.txt\r\n\r\n22\r\n--X--";
1600    /// let mut upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1601    /// let first = upload.take_file("f").unwrap();
1602    /// assert_eq!(first.bytes(), Some(&b"1"[..]));
1603    /// assert_eq!(upload.file("f").map(|f| f.len()), Some(2));
1604    /// assert_eq!(upload.file_count(), 1);
1605    /// # Ok::<(), form_net::FormError>(())
1606    /// ```
1607    pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
1608        let i = self.files.iter().position(|f| f.name() == name)?;
1609        Some(self.files.remove(i))
1610    }
1611
1612    /// Every file, in body order.
1613    ///
1614    /// # Examples
1615    ///
1616    /// ```
1617    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n1\r\n--X\r\nContent-Disposition: form-data; name=f; filename=b.txt\r\n\r\n22\r\n--X--";
1618    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1619    /// let files = upload.into_files();
1620    /// assert_eq!(files.iter().map(|f| f.len()).collect::<Vec<_>>(), [1, 2]);
1621    /// # Ok::<(), form_net::FormError>(())
1622    /// ```
1623    #[must_use]
1624    pub fn into_files(mut self) -> Vec<StoredFile> {
1625        core::mem::take(&mut self.files)
1626    }
1627
1628    /// One owned type for both encodings (D-090, D-098): the fields of a
1629    /// urlencoded form as an upload with no files, so a server keeps one
1630    /// `Upload` per request whatever the `Content-Type`. The policy's token
1631    /// field is captured as a multipart parse would capture it, so
1632    /// [`captured_token`](Self::captured_token) means the same for both.
1633    /// The form is wiped as it drops.
1634    ///
1635    /// # Errors
1636    ///
1637    /// `Policy { DuplicateToken }` when the token field appears twice.
1638    ///
1639    /// # Examples
1640    ///
1641    /// ```
1642    /// use form_net::{Policy, TokenField, Upload};
1643    ///
1644    /// let policy = Policy::DEFAULT.with_token_field(TokenField::new("csrf"));
1645    /// let form = form_net::parse_urlencoded(b"email=a%40b.example&csrf=t0k&tag=x&tag=y")?;
1646    /// let upload = Upload::from_form(form, &policy)?;
1647    /// assert_eq!(upload.field("email"), Some("a@b.example"));
1648    /// assert_eq!(upload.captured_token(), Some("t0k"));
1649    /// assert_eq!((upload.fields("tag").count(), upload.file_count()), (2, 0));
1650    /// # Ok::<(), form_net::FormError>(())
1651    /// ```
1652    pub fn from_form(form: crate::Form, policy: &Policy) -> Result<Upload, FormError> {
1653        let mut upload = Upload::default();
1654        let token = policy.token_field.map(|t| t.name());
1655        for (name, value) in form.iter() {
1656            if Some(name) == token {
1657                if upload.token.is_some() {
1658                    return Err(FormError::policy(
1659                        crate::error::PolicyRefusal::DuplicateToken,
1660                        Location::NONE,
1661                    ));
1662                }
1663                upload.token = Some(String::from(value));
1664            }
1665            upload.push_field(name, value.as_bytes(), usize::MAX);
1666        }
1667        Ok(upload)
1668    }
1669
1670    /// The stored files, for extraction that moves some of them out.
1671    pub(crate) fn files_mut(&mut self) -> &mut Vec<StoredFile> {
1672        &mut self.files
1673    }
1674
1675    pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
1676        let files = core::mem::take(&mut self.files);
1677        (self, files)
1678    }
1679
1680    /// The anti-forgery token field's value, when a token field was set.
1681    ///
1682    /// # Examples
1683    ///
1684    /// ```
1685    /// use form_net::{Limits, Policy, Store, TokenField};
1686    ///
1687    /// let store = Store::memory_only();
1688    /// let policy = Policy::DEFAULT.with_token_field(TokenField::new("csrf"));
1689    /// let body = b"--X\r\nContent-Disposition: form-data; name=csrf\r\n\r\nt0k\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1690    /// let upload = form_net::read_multipart_with(b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &policy, &store)?;
1691    /// assert_eq!(upload.captured_token(), Some("t0k"));
1692    /// # Ok::<(), form_net::FormError>(())
1693    /// ```
1694    #[must_use]
1695    pub fn captured_token(&self) -> Option<&str> {
1696        self.token.as_deref()
1697    }
1698
1699    /// The bytes of all text values together.
1700    ///
1701    /// # Examples
1702    ///
1703    /// ```
1704    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\na\r\n--X\r\nContent-Disposition: form-data; name=tag\r\n\r\nb\r\n--X--";
1705    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1706    /// assert_eq!(upload.text_len(), 9); // "Holiday", "a", "b"
1707    /// # Ok::<(), form_net::FormError>(())
1708    /// ```
1709    #[must_use]
1710    pub fn text_len(&self) -> u64 {
1711        self.spans.iter().map(|s| s.value.1 as u64).sum()
1712    }
1713
1714    /// The number of files.
1715    ///
1716    /// # Examples
1717    ///
1718    /// ```
1719    /// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHoliday\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\n1\r\n--X\r\nContent-Disposition: form-data; name=f; filename=b.txt\r\n\r\n22\r\n--X--";
1720    /// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1721    /// assert_eq!(upload.file_count(), 2);
1722    /// # Ok::<(), form_net::FormError>(())
1723    /// ```
1724    #[must_use]
1725    pub fn file_count(&self) -> usize {
1726        self.files.len()
1727    }
1728}
1729
1730/// The push-style form for event-driven servers: hand each body chunk to
1731/// [`push`](Self::push) as reads complete, then [`finish`](Self::finish).
1732///
1733/// Dropping a session without `finish` deletes every file it stored.
1734/// [`captured_token`](Self::captured_token) is available as soon as the
1735/// token field ends, so the token can be verified while files are still
1736/// arriving.
1737///
1738/// # Examples
1739///
1740/// ```
1741/// use form_net::{Limits, NoInspector, Policy, Store, UploadSession};
1742///
1743/// let store = Store::temp()?;
1744/// let body = b"--X\r\nContent-Disposition: form-data; name=a\r\n\r\n1\r\n--X--";
1745/// let mut session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &Policy::DEFAULT, &store)?;
1746/// for chunk in body.chunks(5) {
1747///     session.push(chunk, &mut NoInspector)?;
1748/// }
1749/// let upload = session.finish(&mut NoInspector)?;
1750/// assert_eq!(upload.field("a"), Some("1"));
1751/// # Ok::<(), form_net::FormError>(())
1752/// ```
1753pub struct UploadSession {
1754    driver: Driver<Vec<u8>, StoreSink>,
1755    upload: Upload,
1756    text_cap: usize,
1757    ended: bool,
1758    failed: bool,
1759}
1760
1761impl fmt::Debug for UploadSession {
1762    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1763        f.debug_struct("UploadSession")
1764            .field("bytes_seen", &self.bytes_seen())
1765            .field("upload", &self.upload)
1766            .finish_non_exhaustive()
1767    }
1768}
1769
1770impl UploadSession {
1771    /// A session for one request body.
1772    ///
1773    /// # Errors
1774    ///
1775    /// `UnsupportedMediaType` and the boundary errors of
1776    /// [`FormKind::detect`](crate::FormKind::detect); `InvalidLimits`.
1777    ///
1778    /// # Examples
1779    ///
1780    /// ```
1781    /// use form_net::{Limits, Policy, Store, UploadSession};
1782    ///
1783    /// let (store, policy) = (Store::memory_only(), Policy::DEFAULT);
1784    /// let session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &policy, &store)?;
1785    /// assert_eq!(session.bytes_seen(), 0);
1786    /// let e = UploadSession::new(b"text/plain", &Limits::DEFAULT, &policy, &store).unwrap_err();
1787    /// assert_eq!(e.code(), "unsupported_media_type");
1788    /// # Ok::<(), form_net::FormError>(())
1789    /// ```
1790    pub fn new(
1791        content_type: &[u8],
1792        limits: &Limits,
1793        policy: &Policy,
1794        store: &Store,
1795    ) -> Result<Self, FormError> {
1796        let boundary = crate::Boundary::parse(content_type)?;
1797        let parser = MultipartParser::new(boundary, limits, policy)?;
1798        Ok(UploadSession {
1799            driver: Driver::new(parser, store.sink(limits)),
1800            upload: Upload::default(),
1801            text_cap: usize::try_from(limits.text_and_names()).unwrap_or(usize::MAX),
1802            ended: false,
1803            failed: false,
1804        })
1805    }
1806
1807    /// Feeds one chunk; consumes all of it or fails.
1808    ///
1809    /// # Errors
1810    ///
1811    /// Every multipart error, `Refused`, `Io`, `Store`, `Limit { Disk }`;
1812    /// `Poisoned` after an error.
1813    ///
1814    /// # Examples
1815    ///
1816    /// ```
1817    /// use form_net::{Limits, NoInspector, Policy, Store, UploadSession};
1818    ///
1819    /// let (store, policy) = (Store::memory_only(), Policy::DEFAULT);
1820    /// let mut session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &policy, &store)?;
1821    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1822    /// for chunk in body.chunks(7) {
1823    ///     session.push(chunk, &mut NoInspector)?;
1824    /// }
1825    /// let upload = session.finish(&mut NoInspector)?;
1826    /// assert_eq!(upload.file("f").unwrap().bytes(), Some(&b"hello"[..]));
1827    /// # Ok::<(), form_net::FormError>(())
1828    /// ```
1829    pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1830        self.feed(chunk, false, inspector)
1831    }
1832
1833    /// Ends the body and returns the upload.
1834    ///
1835    /// # Errors
1836    ///
1837    /// `Syntax { Truncated }` if the close delimiter was not seen; as
1838    /// [`push`](Self::push).
1839    ///
1840    /// # Examples
1841    ///
1842    /// ```
1843    /// use form_net::{Limits, NoInspector, Policy, Store, UploadSession};
1844    ///
1845    /// let (store, policy) = (Store::memory_only(), Policy::DEFAULT);
1846    /// let mut session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &policy, &store)?;
1847    /// // The body stops before its close delimiter.
1848    /// session.push(b"--X\r\nContent-Disposition: form-data; name=a\r\n\r\n1", &mut NoInspector)?;
1849    /// let e = session.finish(&mut NoInspector).unwrap_err();
1850    /// assert_eq!(e.code(), "syntax.truncated");
1851    /// # Ok::<(), form_net::FormError>(())
1852    /// ```
1853    pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1854        if self.failed {
1855            return Err(FormError::Poisoned);
1856        }
1857        if !self.ended {
1858            self.feed(&[], true, inspector)?;
1859        }
1860        let mut upload = core::mem::take(&mut self.upload);
1861        upload.token = self.driver.parser().captured_token().map(String::from);
1862        Ok(upload)
1863    }
1864
1865    /// The token field's value, once its part has ended.
1866    ///
1867    /// # Examples
1868    ///
1869    /// ```
1870    /// use form_net::{Limits, NoInspector, Policy, Store, TokenField, UploadSession};
1871    ///
1872    /// let store = Store::memory_only();
1873    /// let policy = Policy::DEFAULT.with_token_field(TokenField::new("csrf"));
1874    /// let mut session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &policy, &store)?;
1875    /// let body = b"--X\r\nContent-Disposition: form-data; name=csrf\r\n\r\nt0k\r\n--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1876    /// session.push(body, &mut NoInspector)?;
1877    /// // Known before `finish`, so the token can be checked first.
1878    /// assert_eq!(session.captured_token(), Some("t0k"));
1879    /// # Ok::<(), form_net::FormError>(())
1880    /// ```
1881    #[must_use]
1882    pub fn captured_token(&self) -> Option<&str> {
1883        self.driver.parser().captured_token()
1884    }
1885
1886    /// Body bytes consumed so far.
1887    ///
1888    /// # Examples
1889    ///
1890    /// ```
1891    /// use form_net::{Limits, NoInspector, Policy, Store, UploadSession};
1892    ///
1893    /// let (store, policy) = (Store::memory_only(), Policy::DEFAULT);
1894    /// let mut session = UploadSession::new(b"multipart/form-data; boundary=X", &Limits::DEFAULT, &policy, &store)?;
1895    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.txt\r\n\r\nhello\r\n--X--";
1896    /// session.push(&body[..10], &mut NoInspector)?;
1897    /// assert_eq!(session.bytes_seen(), 10);
1898    /// # Ok::<(), form_net::FormError>(())
1899    /// ```
1900    #[must_use]
1901    pub fn bytes_seen(&self) -> u64 {
1902        self.driver.parser().bytes_seen()
1903    }
1904
1905    fn feed<I: Inspector>(
1906        &mut self,
1907        chunk: &[u8],
1908        eof: bool,
1909        inspector: &mut I,
1910    ) -> Result<(), FormError> {
1911        if self.failed {
1912            return Err(FormError::Poisoned);
1913        }
1914        let r = self.feed_inner(chunk, eof, inspector);
1915        if let Err(e) = &r {
1916            self.failed = true;
1917            // The driver reports its own failures; a field that fails its
1918            // text check fails here, after the driver returned.
1919            if !self.driver.has_failed() {
1920                inspector.failed(e);
1921            }
1922            // Nothing of a refused body survives.
1923            self.upload.files.clear();
1924        }
1925        r
1926    }
1927
1928    fn feed_inner<I: Inspector>(
1929        &mut self,
1930        chunk: &[u8],
1931        eof: bool,
1932        inspector: &mut I,
1933    ) -> Result<(), FormError> {
1934        let mut pos = 0usize;
1935        loop {
1936            let rest = chunk.get(pos..).unwrap_or(&[]);
1937            let (n, progress) = self.driver.push(rest, eof, inspector)?;
1938            pos = pos.saturating_add(n);
1939            match progress {
1940                Progress::Field { name, value } => {
1941                    // Checked as text by the driver before the inspector
1942                    // saw it, as the buffered parser checks it.
1943                    let cap = self.text_cap;
1944                    self.upload.push_field(name, value, cap);
1945                }
1946                Progress::File(f) => self.upload.files.push(f),
1947                Progress::End => {
1948                    self.ended = true;
1949                    if pos >= chunk.len() {
1950                        return Ok(());
1951                    }
1952                }
1953                _ => {
1954                    if pos >= chunk.len() {
1955                        return Ok(());
1956                    }
1957                }
1958            }
1959        }
1960    }
1961}
1962
1963/// The blocking convenience: reads `body` to the end in 64 KiB chunks with
1964/// the default limits and policy, keeps files up to 64 KiB in memory, and
1965/// spills larger ones to anonymous temporary files in a private directory
1966/// that is created only when a file first spills and removed when the last
1967/// file from it is dropped. There is no process-wide state.
1968///
1969/// # Errors
1970///
1971/// Every multipart error; `Io { kind }`; `Limit { Disk }`; `Random`.
1972///
1973/// # Examples
1974///
1975/// ```
1976/// let body = b"--X\r\nContent-Disposition: form-data; name=title\r\n\r\nHi\r\n--X--";
1977/// let upload = form_net::read_multipart(b"multipart/form-data; boundary=X", &body[..])?;
1978/// assert_eq!(upload.field("title"), Some("Hi"));
1979/// # Ok::<(), form_net::FormError>(())
1980/// ```
1981pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1982    let store = Store::temp_lazy();
1983    read_multipart_with(
1984        content_type,
1985        body,
1986        &Limits::DEFAULT,
1987        &Policy::DEFAULT,
1988        &store,
1989    )
1990}
1991
1992/// [`read_multipart`] with explicit limits, policy, and store: reads the
1993/// whole body from `body` in 64 KiB chunks (replaces `Upload::parse_with`,
1994/// D-148).
1995///
1996/// # Errors
1997///
1998/// Every multipart error; `Io { kind }`; `Store { .. }`; `Limit { Disk }`;
1999/// `InvalidLimits`.
2000///
2001/// # Examples
2002///
2003/// ```
2004/// use form_net::{Limits, Policy, Store, TokenField};
2005///
2006/// let store = Store::temp()?;
2007/// let policy = Policy::DEFAULT.with_token_field(TokenField::new("csrf_token"));
2008/// let body = b"--X\r\nContent-Disposition: form-data; name=csrf_token\r\n\r\ntok\r\n--X--";
2009/// let upload = form_net::read_multipart_with(
2010///     b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &policy, &store,
2011/// )?;
2012/// assert_eq!(upload.captured_token(), Some("tok"));
2013/// # Ok::<(), form_net::FormError>(())
2014/// ```
2015pub fn read_multipart_with<R: Read>(
2016    content_type: &[u8],
2017    body: R,
2018    limits: &Limits,
2019    policy: &Policy,
2020    store: &Store,
2021) -> Result<Upload, FormError> {
2022    read_multipart_inspected(
2023        content_type,
2024        body,
2025        limits,
2026        policy,
2027        store,
2028        &mut crate::NoInspector,
2029    )
2030}
2031
2032/// [`read_multipart_with`] with an inspector: every part passes through it
2033/// in the hook order, before any file reaches the store. `failed` is called
2034/// once on every failed read, setup errors included (D-144).
2035///
2036/// # Errors
2037///
2038/// Every multipart error; `Refused`; `Io { kind }`; `Store { .. }`;
2039/// `Limit { Disk }`; `InvalidLimits`.
2040///
2041/// # Examples
2042///
2043/// ```
2044/// use form_net::{Limits, Policy, Store, TokenCheck, TokenField};
2045///
2046/// let store = Store::temp()?;
2047/// let policy = Policy::DEFAULT.with_token_field(TokenField::new("csrf_token"));
2048/// let mut check = TokenCheck::new("csrf_token", |t| {
2049///     if t == "tok" { Ok(()) } else { Err(form_net::Refusal::new(403, "csrf.bad")) }
2050/// });
2051/// let body = b"--X\r\nContent-Disposition: form-data; name=csrf_token\r\n\r\ntok\r\n--X--";
2052/// let upload = form_net::read_multipart_inspected(
2053///     b"multipart/form-data; boundary=X", &body[..], &Limits::DEFAULT, &policy, &store, &mut check,
2054/// )?;
2055/// assert!(check.verified());
2056/// assert_eq!(upload.captured_token(), Some("tok"));
2057/// # Ok::<(), form_net::FormError>(())
2058/// ```
2059pub fn read_multipart_inspected<R: Read, I: Inspector>(
2060    content_type: &[u8],
2061    mut reader: R,
2062    limits: &Limits,
2063    policy: &Policy,
2064    store: &Store,
2065    inspector: &mut I,
2066) -> Result<Upload, FormError> {
2067    // `failed` on every failed parse, setup included, as the buffered
2068    // entry points call it (D-043, D-144).
2069    let mut session = match UploadSession::new(content_type, limits, policy, store) {
2070        Ok(s) => s,
2071        Err(e) => {
2072            inspector.failed(&e);
2073            return Err(e);
2074        }
2075    };
2076    let mut buf = crate::wipe::ReadBuf::new(64 * 1024);
2077    loop {
2078        let n = match reader.read(&mut buf.buf) {
2079            Ok(n) => n,
2080            Err(e) if e.kind() == io::ErrorKind::Interrupted => {
2081                buf.failed();
2082                continue;
2083            }
2084            Err(e) => {
2085                buf.failed();
2086                // The client's side, not the store's (D-159).
2087                let err = FormError::BodyRead { kind: e.kind() };
2088                inspector.failed(&err);
2089                return Err(err);
2090            }
2091        };
2092        if n == 0 {
2093            break;
2094        }
2095        buf.filled(n);
2096        session.push(buf.buf.get(..n).unwrap_or(&[]), inspector)?;
2097    }
2098    drop(buf);
2099    session.finish(inspector)
2100}
2101
2102impl FileName<'_> {
2103    /// One portable file name from the client's: the HTML-unescaped base
2104    /// name with every default-ignorable character (zero width spaces and
2105    /// joiners, soft hyphens, variation selectors, tags, bidirectional
2106    /// controls) replaced by `_` (D-084), then passed through
2107    /// `path_lib::sanitize`, which replaces separators, control characters,
2108    /// Windows-reserved characters and device names, and trims trailing dots
2109    /// and spaces. With the `unicode` feature the name is put in Unicode NFC
2110    /// first, so an NFD name from a macOS client sanitizes to the same bytes.
2111    ///
2112    /// The result is at most 255 bytes. A longer name loses bytes from the
2113    /// end of its stem, never its extension: when
2114    /// [`extension`](Self::extension) is `Some(e)`, the result ends with
2115    /// `.e`, so the name an extension check saw is the name saved (D-156). A
2116    /// leading `.` becomes `_`, so `.htaccess` is never kept as a dot file
2117    /// (D-157). It is still a name the client chose: never let it overwrite
2118    /// anything (`StoredFile::persist` refuses to).
2119    ///
2120    /// # Errors
2121    ///
2122    /// `Encoding { InvalidUtf8 }`.
2123    ///
2124    /// # Examples
2125    ///
2126    /// ```
2127    /// let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=\"C:\\x\\Q3 report: final?.pdf\"\r\n\r\n\r\n--X--";
2128    /// let mp = form_net::parse_multipart(b"multipart/form-data; boundary=X", body)?;
2129    /// let name = mp.file("f").unwrap().filename().unwrap();
2130    /// assert_eq!(name.sanitized()?, "Q3 report_ final_.pdf");
2131    /// # Ok::<(), form_net::FormError>(())
2132    /// ```
2133    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
2134    pub fn sanitized(&self) -> Result<String, FormError> {
2135        let base = FileName::new(self.basename(), self.source());
2136        let text = base.html_unescaped()?;
2137        #[cfg(feature = "unicode")]
2138        let text = unicode_lang::normalize(text.as_ref(), unicode_lang::Form::Nfc);
2139        let text: &str = text.as_ref();
2140        let out = if text.is_ascii() {
2141            path_lib::sanitize(text)
2142        } else {
2143            let visible: String = text
2144                .chars()
2145                .map(|c| if is_default_ignorable(c) { '_' } else { c })
2146                .collect();
2147            path_lib::sanitize(visible)
2148        };
2149        Ok(keep_extension(no_leading_dot(out), self.extension()))
2150    }
2151}
2152
2153/// The longest name `sanitized` returns, in bytes: what common file systems
2154/// allow for one name, and what `path_lib::sanitize` cuts to.
2155const MAX_NAME_BYTES: usize = 255;
2156
2157/// A leading `.` replaced by `_`: a dot file (`.htaccess`, `.user.ini`) can
2158/// change how a directory is served (D-157).
2159fn no_leading_dot(mut name: String) -> String {
2160    if name.starts_with('.') {
2161        name.replace_range(..1, "_");
2162    }
2163    name
2164}
2165
2166/// `name`, ending with `.ext` when the client's name had that extension.
2167/// `path_lib::sanitize` keeps the first 255 bytes, so a long name can lose
2168/// its extension or end in another one (`A…A.php.jpg` saved as `A…A.php`);
2169/// here the stem is cut instead (D-156).
2170fn keep_extension(name: String, ext: Option<&str>) -> String {
2171    let Some(ext) = ext else {
2172        return name;
2173    };
2174    let tail_len = ext.len().saturating_add(1);
2175    let has_tail = name
2176        .len()
2177        .checked_sub(tail_len)
2178        .and_then(|i| name.get(i..))
2179        .is_some_and(|tail| tail.strip_prefix('.') == Some(ext));
2180    if has_tail {
2181        return name;
2182    }
2183    let mut keep = name.len().min(MAX_NAME_BYTES.saturating_sub(tail_len));
2184    while keep > 0 && !name.is_char_boundary(keep) {
2185        keep = keep.saturating_sub(1);
2186    }
2187    let mut out = String::with_capacity(keep.saturating_add(tail_len));
2188    out.push_str(name.get(..keep).unwrap_or(""));
2189    out.push('.');
2190    out.push_str(ext);
2191    // Once more through the sanitizer: the cut can leave a reserved name or
2192    // a trailing space before the dot. The result fits, so nothing is cut.
2193    no_leading_dot(path_lib::sanitize(out))
2194}
2195
2196/// Unicode's `Default_Ignorable_Code_Point` property (DerivedCoreProperties):
2197/// characters that render as nothing, so a name can hide them.
2198const fn is_default_ignorable(c: char) -> bool {
2199    matches!(
2200        c,
2201        '\u{00AD}'
2202            | '\u{034F}'
2203            | '\u{061C}'
2204            | '\u{115F}'..='\u{1160}'
2205            | '\u{17B4}'..='\u{17B5}'
2206            | '\u{180B}'..='\u{180F}'
2207            | '\u{200B}'..='\u{200F}'
2208            | '\u{202A}'..='\u{202E}'
2209            | '\u{2060}'..='\u{206F}'
2210            | '\u{3164}'
2211            | '\u{FE00}'..='\u{FE0F}'
2212            | '\u{FEFF}'
2213            | '\u{FFA0}'
2214            | '\u{FFF0}'..='\u{FFF8}'
2215            | '\u{1BCA0}'..='\u{1BCA3}'
2216            | '\u{1D173}'..='\u{1D17A}'
2217            | '\u{E0000}'..='\u{E0FFF}'
2218    )
2219}
2220
2221impl crate::FromField for StoredFile {
2222    /// Whether the field arrived (an empty file input counts), and the file.
2223    type Slot = (bool, Option<StoredFile>);
2224
2225    fn accept_file(
2226        slot: &mut Self::Slot,
2227        file: crate::FileValue<'_>,
2228    ) -> Result<(), crate::ValueError> {
2229        // Before the empty-input shortcut, as for `FileData` (D-137).
2230        if slot.0 {
2231            return Err(crate::ValueError::Duplicate);
2232        }
2233        slot.0 = true;
2234        let stored = match file {
2235            crate::FileValue::Stored(f) => f,
2236            crate::FileValue::Buffered(part) => StoredFile::detached(
2237                part.name(),
2238                part.filename(),
2239                part.content_type().map(|c| c.raw()),
2240                part.bytes(),
2241            ),
2242        };
2243        if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
2244            // A file input left empty: as if it were absent.
2245            return Ok(());
2246        }
2247        slot.1 = Some(stored);
2248        Ok(())
2249    }
2250
2251    fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
2252        slot.1.ok_or(crate::ValueError::Missing)
2253    }
2254}