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}