1mod 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#[non_exhaustive]
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum StoreError {
37 QuotaExceeded,
39 NotADirectory,
41 DestinationExists,
43}
44
45impl StoreError {
46 #[cfg(target_has_atomic = "32")]
47 pub(crate) const fn index(self) -> usize {
48 match self {
49 Self::QuotaExceeded => 0,
50 Self::NotADirectory => 1,
51 Self::DestinationExists => 2,
52 }
53 }
54
55 pub(crate) const fn code(self) -> &'static str {
56 match self {
57 Self::QuotaExceeded => "store.quota_exceeded",
58 Self::NotADirectory => "store.not_a_directory",
59 Self::DestinationExists => "store.destination_exists",
60 }
61 }
62
63 pub(crate) const fn describe(self) -> &'static str {
64 match self {
65 Self::QuotaExceeded => "the shared upload budget is full; retry later",
66 Self::NotADirectory => "not a directory, or a link to one",
67 Self::DestinationExists => "the destination exists; nothing was overwritten",
68 }
69 }
70}
71
72#[non_exhaustive]
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
82pub enum SpillMode {
83 #[default]
85 Anonymous,
86 Named,
88}
89
90#[non_exhaustive]
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub struct StoreStats {
94 pub files_in_memory: u64,
96 pub files_on_disk: u64,
98 pub bytes_in_memory: u64,
100 pub bytes_on_disk: u64,
102 pub files_spilled: u64,
104 pub cleanup_failures: u64,
106}
107
108#[derive(Default)]
109struct Counters {
110 files_in_memory: AtomicU64,
111 files_on_disk: AtomicU64,
112 bytes_in_memory: AtomicU64,
113 bytes_on_disk: AtomicU64,
114 files_spilled: AtomicU64,
115 cleanup_failures: AtomicU64,
116}
117
118fn add(c: &AtomicU64, n: u64) {
119 c.fetch_add(n, Ordering::Relaxed);
120}
121
122fn sub(c: &AtomicU64, n: u64) {
123 let mut v = c.load(Ordering::Relaxed);
125 loop {
126 match c.compare_exchange_weak(v, v.saturating_sub(n), Ordering::Relaxed, Ordering::Relaxed)
127 {
128 Ok(_) => return,
129 Err(now) => v = now,
130 }
131 }
132}
133
134struct Shared {
138 dir: OnceLock<PathBuf>,
139 owns_dir: bool,
140 counters: Counters,
141}
142
143impl Drop for Shared {
144 fn drop(&mut self) {
145 if self.owns_dir {
146 if let Some(dir) = self.dir.get() {
147 let _ = std::fs::remove_dir(dir);
148 }
149 }
150 }
151}
152
153impl Shared {
154 fn dir(&self) -> Result<&Path, FormError> {
155 if let Some(d) = self.dir.get() {
156 return Ok(d);
157 }
158 let created = temp::create_private_dir()?;
159 if let Err(lost) = self.dir.set(created) {
160 let _ = std::fs::remove_dir(lost);
162 }
163 self.dir.get().map(PathBuf::as_path).ok_or(FormError::Io {
164 kind: io::ErrorKind::Other,
165 })
166 }
167
168 fn cleanup_failed(&self) {
169 add(&self.counters.cleanup_failures, 1);
170 }
171}
172
173#[derive(Clone)]
191pub struct Store {
192 shared: Arc<Shared>,
193 memory_only: bool,
194 threshold: u64,
195 max_disk: Option<u64>,
196 mode: SpillMode,
197 budget: Option<Arc<dyn SpillBudget>>,
198}
199
200impl fmt::Debug for Store {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 f.debug_struct("Store")
203 .field("memory_only", &self.memory_only)
204 .field("threshold", &self.threshold)
205 .field("mode", &self.mode)
206 .field("quota", &self.budget.is_some())
207 .field("stats", &self.stats())
208 .finish_non_exhaustive()
209 }
210}
211
212const DEFAULT_THRESHOLD: u64 = 64 * 1024;
213
214impl Store {
215 fn with(dir: Option<PathBuf>, owns_dir: bool, memory_only: bool) -> Store {
216 let lock = OnceLock::new();
217 if let Some(d) = dir {
218 let _ = lock.set(d);
219 }
220 Store {
221 shared: Arc::new(Shared {
222 dir: lock,
223 owns_dir,
224 counters: Counters::default(),
225 }),
226 memory_only,
227 threshold: DEFAULT_THRESHOLD,
228 max_disk: None,
229 mode: SpillMode::Anonymous,
230 budget: None,
231 }
232 }
233
234 pub fn temp() -> Result<Store, FormError> {
244 let dir = temp::create_private_dir()?;
245 Ok(Store::with(Some(dir), true, false))
246 }
247
248 pub(crate) fn temp_lazy() -> Store {
251 Store::with(None, true, false)
252 }
253
254 #[must_use]
257 pub fn memory_only() -> Store {
258 let mut s = Store::with(None, false, true);
259 s.threshold = u64::MAX;
260 s
261 }
262
263 pub fn in_dir(dir: impl AsRef<Path>) -> Result<Store, FormError> {
279 let dir = dir.as_ref();
280 temp::check_dir(dir)?;
281 Ok(Store::with(Some(dir.to_owned()), false, false))
282 }
283
284 #[must_use]
287 pub fn memory_threshold(mut self, bytes: u64) -> Store {
288 if !self.memory_only {
289 self.threshold = bytes;
290 }
291 self
292 }
293
294 #[must_use]
297 pub fn max_disk_bytes(mut self, bytes: u64) -> Store {
298 self.max_disk = Some(bytes);
299 self
300 }
301
302 #[must_use]
304 pub fn mode(mut self, mode: SpillMode) -> Store {
305 self.mode = mode;
306 self
307 }
308
309 #[must_use]
311 pub fn dir(&self) -> Option<&Path> {
312 self.shared.dir.get().map(PathBuf::as_path)
313 }
314
315 #[must_use]
317 pub fn cleanup_failures(&self) -> u64 {
318 self.shared
319 .counters
320 .cleanup_failures
321 .load(Ordering::Relaxed)
322 }
323
324 #[must_use]
326 pub fn stats(&self) -> StoreStats {
327 let c = &self.shared.counters;
328 StoreStats {
329 files_in_memory: c.files_in_memory.load(Ordering::Relaxed),
330 files_on_disk: c.files_on_disk.load(Ordering::Relaxed),
331 bytes_in_memory: c.bytes_in_memory.load(Ordering::Relaxed),
332 bytes_on_disk: c.bytes_on_disk.load(Ordering::Relaxed),
333 files_spilled: c.files_spilled.load(Ordering::Relaxed),
334 cleanup_failures: c.cleanup_failures.load(Ordering::Relaxed),
335 }
336 }
337
338 pub fn quota(mut self, budget: impl SpillBudget) -> Store {
357 self.budget = Some(Arc::new(budget));
358 self
359 }
360
361 #[must_use]
375 pub fn memory_bound(&self, limits: &Limits) -> u64 {
376 let in_memory = if self.memory_only {
377 u64::MAX
378 } else {
379 self.threshold
380 };
381 limits.memory_bound(in_memory)
382 }
383
384 #[must_use]
386 pub fn sink(&self, limits: &Limits) -> StoreSink {
387 StoreSink {
388 budget: self.budget.clone(),
389 shared: Arc::clone(&self.shared),
390 memory_only: self.memory_only,
391 threshold: self.threshold,
392 memory_cap: usize::try_from(self.threshold.min(limits.file())).unwrap_or(usize::MAX),
395 mode: self.mode,
396 max_disk: self.max_disk.unwrap_or(limits.max_body_bytes),
397 used_disk: 0,
398 current: None,
399 }
400 }
401}
402
403struct Current {
405 name: String,
406 filename: Option<(Vec<u8>, FileNameSource)>,
407 content_type: Option<Vec<u8>>,
408 memory: Vec<u8>,
409 disk: Option<(File, Option<PathBuf>)>,
410 len: u64,
411 on_disk: u64,
413}
414
415pub struct StoreSink {
417 budget: Option<Arc<dyn SpillBudget>>,
418 shared: Arc<Shared>,
419 memory_only: bool,
420 threshold: u64,
421 memory_cap: usize,
422 mode: SpillMode,
423 max_disk: u64,
424 used_disk: u64,
425 current: Option<Current>,
426}
427
428impl fmt::Debug for StoreSink {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 f.debug_struct("StoreSink")
431 .field("used_disk", &self.used_disk)
432 .field("max_disk", &self.max_disk)
433 .finish_non_exhaustive()
434 }
435}
436
437impl StoreSink {
438 pub(crate) const fn used_disk(&self) -> u64 {
440 self.used_disk
441 }
442
443 pub(crate) const fn with_used_disk(mut self, bytes: u64) -> StoreSink {
445 self.used_disk = bytes;
446 self
447 }
448
449 fn disk_write(&mut self, bytes: &[u8]) -> Result<(), FormError> {
450 let used = self.used_disk.saturating_add(bytes.len() as u64);
451 if used > self.max_disk {
452 return Err(FormError::limit(Limit::Disk, Location::NONE));
453 }
454 let Some(cur) = self.current.as_mut() else {
455 return Ok(());
456 };
457 let Some((file, _)) = cur.disk.as_mut() else {
458 return Ok(());
459 };
460 let n = bytes.len() as u64;
461 if let Some(budget) = &self.budget {
462 if !budget.reserve(n) {
463 return Err(FormError::Store {
464 reason: StoreError::QuotaExceeded,
465 });
466 }
467 }
468 if let Err(e) = file.write_all(bytes) {
469 if let Some(budget) = &self.budget {
473 budget.release(n);
474 }
475 return Err(temp::io(&e));
476 }
477 self.used_disk = used;
478 cur.on_disk = cur.on_disk.saturating_add(bytes.len() as u64);
479 add(&self.shared.counters.bytes_on_disk, bytes.len() as u64);
480 Ok(())
481 }
482
483 fn spill(&mut self) -> Result<(), FormError> {
484 let dir = self.shared.dir()?.to_owned();
485 let anonymous = self.mode == SpillMode::Anonymous;
486 let (file, path) = temp::create_file(&dir, anonymous)?;
487 add(&self.shared.counters.files_on_disk, 1);
488 add(&self.shared.counters.files_spilled, 1);
489 let prefix = match self.current.as_mut() {
490 Some(cur) => {
491 cur.disk = Some((file, path));
492 core::mem::take(&mut cur.memory)
493 }
494 None => Vec::new(),
495 };
496 let mut prefix = prefix;
497 let r = self.disk_write(&prefix);
498 crate::wipe::wipe_vec(&mut prefix);
499 r
500 }
501}
502
503impl Sink for StoreSink {
504 type Output = StoredFile;
505
506 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
507 self.abort();
508 self.current = Some(Current {
509 name: String::from(head.name()),
510 filename: head.filename().map(|f| (f.raw().to_vec(), f.source())),
511 content_type: head.content_type().map(|c| c.raw().to_vec()),
512 memory: Vec::new(),
513 disk: None,
514 len: 0,
515 on_disk: 0,
516 });
517 Ok(())
518 }
519
520 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
521 let (on_disk, len) = match self.current.as_ref() {
522 Some(c) => (c.disk.is_some(), c.len),
523 None => return Ok(()),
524 };
525 let new_len = len.saturating_add(chunk.len() as u64);
526 if !on_disk && !self.memory_only && new_len > self.threshold {
527 self.spill()?;
528 }
529 let on_disk = self.current.as_ref().is_some_and(|c| c.disk.is_some());
530 if on_disk {
531 self.disk_write(chunk)?;
532 } else if let Some(cur) = self.current.as_mut() {
533 crate::wipe::append_wiping(&mut cur.memory, chunk, 4096, self.memory_cap);
534 }
535 if let Some(cur) = self.current.as_mut() {
536 cur.len = new_len;
537 }
538 Ok(())
539 }
540
541 fn close(&mut self) -> Result<StoredFile, FormError> {
542 let Some(mut cur) = self.current.take() else {
543 return Err(FormError::Poisoned);
544 };
545 let on_disk = cur.disk.is_some();
546 let place = match cur.disk.take() {
547 Some((file, path)) => Place::Disk {
548 file: Some(file),
549 path,
550 },
551 None => {
552 add(&self.shared.counters.files_in_memory, 1);
553 add(&self.shared.counters.bytes_in_memory, cur.len);
554 Place::Memory(core::mem::take(&mut cur.memory))
555 }
556 };
557 Ok(StoredFile {
558 name: core::mem::take(&mut cur.name),
559 filename: cur.filename.take(),
560 content_type: cur.content_type.take(),
561 len: cur.len,
562 place,
563 shared: Some(Arc::clone(&self.shared)),
564 budget: if on_disk { self.budget.clone() } else { None },
565 reserved: cur.on_disk,
566 })
567 }
568
569 fn abort(&mut self) {
570 if let Some(mut cur) = self.current.take() {
571 crate::wipe::wipe_vec(&mut cur.memory);
572 wipe_meta(&mut cur.name, &mut cur.filename, &mut cur.content_type);
573 if let Some((file, path)) = cur.disk.take() {
574 drop(file);
577 sub(&self.shared.counters.files_on_disk, 1);
578 sub(&self.shared.counters.bytes_on_disk, cur.on_disk);
579 let mut freed = true;
580 if let Some(p) = path {
581 if std::fs::remove_file(&p).is_err() {
582 freed = false;
583 self.shared.cleanup_failed();
584 }
585 }
586 if freed {
589 if let Some(budget) = &self.budget {
590 budget.release(cur.on_disk);
591 }
592 }
593 }
594 }
595 }
596}
597
598impl Drop for StoreSink {
599 fn drop(&mut self) {
600 self.abort();
601 }
602}
603
604enum Place {
605 Memory(Vec<u8>),
606 Disk {
607 file: Option<File>,
608 path: Option<PathBuf>,
609 },
610 Gone,
611}
612
613#[must_use]
633pub struct StoredFile {
634 name: String,
635 filename: Option<(Vec<u8>, FileNameSource)>,
636 content_type: Option<Vec<u8>>,
637 len: u64,
638 place: Place,
639 shared: Option<Arc<Shared>>,
640 budget: Option<Arc<dyn SpillBudget>>,
642 reserved: u64,
643}
644
645impl fmt::Debug for StoredFile {
646 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
647 f.debug_struct("StoredFile")
648 .field("name", &self.name)
649 .field("len", &self.len)
650 .field("in_memory", &self.is_in_memory())
651 .finish_non_exhaustive()
652 }
653}
654
655impl StoredFile {
656 pub(crate) fn detached(
658 name: &str,
659 filename: Option<FileName<'_>>,
660 content_type: Option<&[u8]>,
661 bytes: &[u8],
662 ) -> StoredFile {
663 StoredFile {
664 name: String::from(name),
665 filename: filename.map(|f| (f.raw().to_vec(), f.source())),
666 content_type: content_type.map(<[u8]>::to_vec),
667 len: bytes.len() as u64,
668 place: Place::Memory(bytes.to_vec()),
669 shared: None,
670 budget: None,
671 reserved: 0,
672 }
673 }
674
675 #[must_use]
677 pub fn name(&self) -> &str {
678 &self.name
679 }
680
681 #[must_use]
683 pub fn filename(&self) -> Option<FileName<'_>> {
684 self.filename
685 .as_ref()
686 .map(|(raw, source)| FileName::new(raw, *source))
687 }
688
689 #[must_use]
691 pub fn content_type(&self) -> Option<ContentType<'_>> {
692 self.content_type.as_deref().and_then(ContentType::parse)
693 }
694
695 #[must_use]
697 pub const fn len(&self) -> u64 {
698 self.len
699 }
700
701 #[must_use]
703 pub const fn is_empty(&self) -> bool {
704 self.len == 0
705 }
706
707 #[must_use]
709 pub const fn is_in_memory(&self) -> bool {
710 matches!(self.place, Place::Memory(_))
711 }
712
713 #[must_use]
715 pub fn bytes(&self) -> Option<&[u8]> {
716 match &self.place {
717 Place::Memory(b) => Some(b),
718 _ => None,
719 }
720 }
721
722 pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
729 match &self.place {
730 Place::Gone => Err(FormError::Io {
731 kind: io::ErrorKind::NotFound,
732 }),
733 _ => Ok(StoredReader { file: self, pos: 0 }),
734 }
735 }
736
737 pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
744 if self.len > max {
745 return Err(FormError::limit(Limit::FileSize, Location::NONE));
746 }
747 let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
748 self.reader()?
749 .read_to_end(&mut out)
750 .map_err(|e| temp::io(&e))?;
751 Ok(out)
752 }
753
754 #[must_use]
756 pub fn path(&self) -> Option<&Path> {
757 match &self.place {
758 Place::Disk { path, .. } => path.as_deref(),
759 _ => None,
760 }
761 }
762
763 fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
764 match &self.place {
765 Place::Memory(b) => {
766 let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
767 let src = b.get(start..).unwrap_or(&[]);
768 let n = src.len().min(buf.len());
769 if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
770 dst.copy_from_slice(src);
771 }
772 Ok(n)
773 }
774 Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
775 _ => Err(io::Error::from(io::ErrorKind::NotFound)),
776 }
777 }
778
779 pub fn persist(mut self, dest: &Path) -> Result<(), FormError> {
790 let exists = |e: io::Error| {
791 if e.kind() == io::ErrorKind::AlreadyExists {
792 FormError::Store {
793 reason: StoreError::DestinationExists,
794 }
795 } else {
796 temp::io(&e)
797 }
798 };
799 if let Place::Disk {
800 file,
801 path: Some(p),
802 } = &mut self.place
803 {
804 drop(file.take());
806 match std::fs::hard_link(&*p, dest) {
807 Ok(()) => {
808 let p = p.clone();
809 self.place = Place::Gone;
810 self.release();
811 self.release_budget();
812 if std::fs::remove_file(&p).is_err() {
813 if let Some(s) = &self.shared {
814 s.cleanup_failed();
815 }
816 }
817 return Ok(());
818 }
819 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => return Err(exists(e)),
820 Err(_) => {
821 let f = File::open(&*p).map_err(|e| temp::io(&e))?;
823 *file = Some(f);
824 }
825 }
826 }
827 let mut out = std::fs::OpenOptions::new()
828 .write(true)
829 .create_new(true)
830 .open(dest)
831 .map_err(exists)?;
832 let mut buf = [0u8; 64 * 1024];
833 let mut hi = 0usize;
834 let mut pos = 0u64;
835 let copied = loop {
836 let n = match self.read_at(&mut buf, pos) {
837 Ok(0) => break Ok(()),
838 Ok(n) => n,
839 Err(e) => break Err(temp::io(&e)),
840 };
841 hi = hi.max(n);
842 if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
843 break Err(temp::io(&e));
844 }
845 pos = pos.saturating_add(n as u64);
846 };
847 crate::wipe::wipe(buf.get_mut(..hi).unwrap_or(&mut []));
848 if copied.is_err() {
849 drop(out);
851 let _ = std::fs::remove_file(dest);
852 }
853 copied
854 }
855
856 fn release_budget(&mut self) {
858 if let Some(budget) = self.budget.take() {
859 budget.release(self.reserved);
860 }
861 }
862
863 fn release(&mut self) {
865 if let Some(s) = &self.shared {
866 match &self.place {
867 Place::Memory(_) => {
868 sub(&s.counters.files_in_memory, 1);
869 sub(&s.counters.bytes_in_memory, self.len);
870 }
871 _ => {
872 sub(&s.counters.files_on_disk, 1);
873 sub(&s.counters.bytes_on_disk, self.len);
874 }
875 }
876 }
877 }
878}
879
880fn wipe_meta(
882 name: &mut String,
883 filename: &mut Option<(Vec<u8>, FileNameSource)>,
884 content_type: &mut Option<Vec<u8>>,
885) {
886 crate::wipe::wipe_string(name);
887 if let Some((mut f, _)) = filename.take() {
888 crate::wipe::wipe_vec(&mut f);
889 }
890 if let Some(mut c) = content_type.take() {
891 crate::wipe::wipe_vec(&mut c);
892 }
893}
894
895impl Drop for StoredFile {
896 fn drop(&mut self) {
897 wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
898 match core::mem::replace(&mut self.place, Place::Gone) {
899 Place::Memory(mut b) => {
900 self.place = Place::Memory(Vec::new());
901 self.release();
902 crate::wipe::wipe_vec(&mut b);
903 }
904 Place::Disk { file, path } => {
905 self.place = Place::Disk {
906 file: None,
907 path: None,
908 };
909 self.release();
910 drop(file);
911 let mut freed = true;
912 if let Some(p) = path {
913 if std::fs::remove_file(&p).is_err() {
914 freed = false;
915 if let Some(s) = &self.shared {
916 s.cleanup_failed();
917 }
918 }
919 }
920 if freed {
923 self.release_budget();
924 }
925 }
926 Place::Gone => {}
927 }
928 }
929}
930
931#[derive(Debug)]
933pub struct StoredReader<'f> {
934 file: &'f StoredFile,
935 pos: u64,
936}
937
938impl Read for StoredReader<'_> {
939 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
940 let n = self.file.read_at(buf, self.pos)?;
941 self.pos = self.pos.saturating_add(n as u64);
942 Ok(n)
943 }
944}
945
946impl Seek for StoredReader<'_> {
947 fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
948 let target = match to {
949 SeekFrom::Start(n) => Some(n),
950 SeekFrom::End(d) => self.file.len.checked_add_signed(d),
951 SeekFrom::Current(d) => self.pos.checked_add_signed(d),
952 };
953 match target {
954 Some(t) => {
955 self.pos = t;
956 Ok(t)
957 }
958 None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
959 }
960 }
961}
962
963#[derive(Clone, Copy)]
965struct Span {
966 name: (usize, usize),
967 value: (usize, usize),
968}
969
970#[must_use]
985#[derive(Default)]
986pub struct Upload {
987 text: Vec<u8>,
988 spans: Vec<Span>,
989 files: Vec<StoredFile>,
990 token: Option<String>,
991}
992
993impl fmt::Debug for Upload {
994 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
995 f.debug_struct("Upload")
996 .field("fields", &self.spans.len())
997 .field("text_len", &self.text_len())
998 .field("files", &self.files)
999 .finish_non_exhaustive()
1000 }
1001}
1002
1003impl Drop for Upload {
1004 fn drop(&mut self) {
1005 crate::wipe::wipe_vec(&mut self.text);
1006 if let Some(t) = self.token.as_mut() {
1007 crate::wipe::wipe_string(t);
1008 }
1009 }
1010}
1011
1012impl Upload {
1013 fn slice(&self, (start, len): (usize, usize)) -> &str {
1014 let b = self
1015 .text
1016 .get(start..start.saturating_add(len))
1017 .unwrap_or(&[]);
1018 core::str::from_utf8(b).unwrap_or("")
1020 }
1021
1022 fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
1023 let n0 = self.text.len();
1024 crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 1024, cap);
1025 let v0 = self.text.len();
1026 crate::wipe::append_wiping(&mut self.text, value, 1024, cap);
1027 self.spans.push(Span {
1028 name: (n0, name.len()),
1029 value: (v0, value.len()),
1030 });
1031 }
1032
1033 #[must_use]
1035 pub fn field(&self, name: &str) -> Option<&str> {
1036 self.spans
1037 .iter()
1038 .find(|s| self.slice(s.name) == name)
1039 .map(|s| self.slice(s.value))
1040 }
1041
1042 pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
1048 let mut found = None;
1049 for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
1050 if found.is_some() {
1051 return Err(FormError::Extract {
1052 reason: ExtractError::Duplicate,
1053 field: None,
1054 });
1055 }
1056 found = Some(self.slice(s.value));
1057 }
1058 Ok(found)
1059 }
1060
1061 pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
1063 self.spans
1064 .iter()
1065 .filter(move |s| self.slice(s.name) == name)
1066 .map(move |s| self.slice(s.value))
1067 }
1068
1069 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1071 self.spans
1072 .iter()
1073 .map(|s| (self.slice(s.name), self.slice(s.value)))
1074 }
1075
1076 #[must_use]
1078 pub fn file(&self, name: &str) -> Option<&StoredFile> {
1079 self.files.iter().find(|f| f.name() == name)
1080 }
1081
1082 pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
1084 self.files.iter().filter(move |f| f.name() == name)
1085 }
1086
1087 pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
1089 let i = self.files.iter().position(|f| f.name() == name)?;
1090 Some(self.files.remove(i))
1091 }
1092
1093 #[must_use]
1095 pub fn into_files(mut self) -> Vec<StoredFile> {
1096 core::mem::take(&mut self.files)
1097 }
1098
1099 pub fn from_form(form: crate::Form, policy: &Policy) -> Result<Upload, FormError> {
1124 let mut upload = Upload::default();
1125 let token = policy.token_field.map(|t| t.name());
1126 for (name, value) in form.iter() {
1127 if Some(name) == token {
1128 if upload.token.is_some() {
1129 return Err(FormError::policy(
1130 crate::error::PolicyRefusal::DuplicateToken,
1131 Location::NONE,
1132 ));
1133 }
1134 upload.token = Some(String::from(value));
1135 }
1136 upload.push_field(name, value.as_bytes(), usize::MAX);
1137 }
1138 Ok(upload)
1139 }
1140
1141 pub(crate) fn files_mut(&mut self) -> &mut Vec<StoredFile> {
1143 &mut self.files
1144 }
1145
1146 pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
1147 let files = core::mem::take(&mut self.files);
1148 (self, files)
1149 }
1150
1151 #[must_use]
1153 pub fn captured_token(&self) -> Option<&str> {
1154 self.token.as_deref()
1155 }
1156
1157 #[must_use]
1159 pub fn text_len(&self) -> u64 {
1160 self.spans.iter().map(|s| s.value.1 as u64).sum()
1161 }
1162
1163 #[must_use]
1165 pub fn file_count(&self) -> usize {
1166 self.files.len()
1167 }
1168
1169 pub fn parse_with<R: Read, I: Inspector>(
1190 content_type: &[u8],
1191 mut reader: R,
1192 limits: &Limits,
1193 policy: &Policy,
1194 store: &Store,
1195 inspector: &mut I,
1196 ) -> Result<Upload, FormError> {
1197 let mut session = match UploadSession::new(content_type, limits, policy, store) {
1200 Ok(s) => s,
1201 Err(e) => {
1202 inspector.failed(&e);
1203 return Err(e);
1204 }
1205 };
1206 let mut buf = crate::wipe::ReadBuf::new(64 * 1024);
1207 loop {
1208 let n = match reader.read(&mut buf.buf) {
1209 Ok(n) => n,
1210 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
1211 buf.failed();
1212 continue;
1213 }
1214 Err(e) => {
1215 buf.failed();
1216 let err = temp::io(&e);
1217 inspector.failed(&err);
1218 return Err(err);
1219 }
1220 };
1221 if n == 0 {
1222 break;
1223 }
1224 buf.filled(n);
1225 session.push(buf.buf.get(..n).unwrap_or(&[]), inspector)?;
1226 }
1227 drop(buf);
1228 session.finish(inspector)
1229 }
1230}
1231
1232pub struct UploadSession {
1256 driver: Driver<Vec<u8>, StoreSink>,
1257 upload: Upload,
1258 text_cap: usize,
1259 ended: bool,
1260 failed: bool,
1261}
1262
1263impl fmt::Debug for UploadSession {
1264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1265 f.debug_struct("UploadSession")
1266 .field("bytes_seen", &self.bytes_seen())
1267 .field("upload", &self.upload)
1268 .finish_non_exhaustive()
1269 }
1270}
1271
1272impl UploadSession {
1273 pub fn new(
1280 content_type: &[u8],
1281 limits: &Limits,
1282 policy: &Policy,
1283 store: &Store,
1284 ) -> Result<Self, FormError> {
1285 let boundary = crate::Boundary::parse(content_type)?;
1286 let parser = MultipartParser::new(boundary, limits, policy)?;
1287 Ok(UploadSession {
1288 driver: Driver::new(parser, store.sink(limits)),
1289 upload: Upload::default(),
1290 text_cap: usize::try_from(limits.text_and_names()).unwrap_or(usize::MAX),
1291 ended: false,
1292 failed: false,
1293 })
1294 }
1295
1296 pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1303 self.feed(chunk, false, inspector)
1304 }
1305
1306 pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1313 if self.failed {
1314 return Err(FormError::Poisoned);
1315 }
1316 if !self.ended {
1317 self.feed(&[], true, inspector)?;
1318 }
1319 let mut upload = core::mem::take(&mut self.upload);
1320 upload.token = self.driver.parser().captured_token().map(String::from);
1321 Ok(upload)
1322 }
1323
1324 #[must_use]
1326 pub fn captured_token(&self) -> Option<&str> {
1327 self.driver.parser().captured_token()
1328 }
1329
1330 #[must_use]
1332 pub fn bytes_seen(&self) -> u64 {
1333 self.driver.parser().bytes_seen()
1334 }
1335
1336 fn feed<I: Inspector>(
1337 &mut self,
1338 chunk: &[u8],
1339 eof: bool,
1340 inspector: &mut I,
1341 ) -> Result<(), FormError> {
1342 if self.failed {
1343 return Err(FormError::Poisoned);
1344 }
1345 let r = self.feed_inner(chunk, eof, inspector);
1346 if let Err(e) = &r {
1347 self.failed = true;
1348 if !self.driver.has_failed() {
1351 inspector.failed(e);
1352 }
1353 self.upload.files.clear();
1355 }
1356 r
1357 }
1358
1359 fn feed_inner<I: Inspector>(
1360 &mut self,
1361 chunk: &[u8],
1362 eof: bool,
1363 inspector: &mut I,
1364 ) -> Result<(), FormError> {
1365 let mut pos = 0usize;
1366 loop {
1367 let rest = chunk.get(pos..).unwrap_or(&[]);
1368 let (n, progress) = self.driver.push(rest, eof, inspector)?;
1369 pos = pos.saturating_add(n);
1370 match progress {
1371 Progress::Field { name, value } => {
1372 let cap = self.text_cap;
1375 self.upload.push_field(name, value, cap);
1376 }
1377 Progress::File(f) => self.upload.files.push(f),
1378 Progress::End => {
1379 self.ended = true;
1380 if pos >= chunk.len() {
1381 return Ok(());
1382 }
1383 }
1384 _ => {
1385 if pos >= chunk.len() {
1386 return Ok(());
1387 }
1388 }
1389 }
1390 }
1391 }
1392}
1393
1394pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1413 let store = Store::temp_lazy();
1414 Upload::parse_with(
1415 content_type,
1416 body,
1417 &Limits::DEFAULT,
1418 &Policy::DEFAULT,
1419 &store,
1420 &mut crate::NoInspector,
1421 )
1422}
1423
1424impl FileName<'_> {
1425 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
1449 pub fn sanitized(&self) -> Result<String, FormError> {
1450 let base = FileName::new(self.basename(), self.source());
1451 let text = base.html_unescaped()?;
1452 #[cfg(feature = "unicode")]
1453 let text = unicode_lang::normalize(text.as_ref(), unicode_lang::Form::Nfc);
1454 let text: &str = text.as_ref();
1455 if text.is_ascii() {
1456 return Ok(path_lib::sanitize(text));
1457 }
1458 let visible: String = text
1459 .chars()
1460 .map(|c| if is_default_ignorable(c) { '_' } else { c })
1461 .collect();
1462 Ok(path_lib::sanitize(visible))
1463 }
1464}
1465
1466const fn is_default_ignorable(c: char) -> bool {
1469 matches!(
1470 c,
1471 '\u{00AD}'
1472 | '\u{034F}'
1473 | '\u{061C}'
1474 | '\u{115F}'..='\u{1160}'
1475 | '\u{17B4}'..='\u{17B5}'
1476 | '\u{180B}'..='\u{180F}'
1477 | '\u{200B}'..='\u{200F}'
1478 | '\u{202A}'..='\u{202E}'
1479 | '\u{2060}'..='\u{206F}'
1480 | '\u{3164}'
1481 | '\u{FE00}'..='\u{FE0F}'
1482 | '\u{FEFF}'
1483 | '\u{FFA0}'
1484 | '\u{FFF0}'..='\u{FFF8}'
1485 | '\u{1BCA0}'..='\u{1BCA3}'
1486 | '\u{1D173}'..='\u{1D17A}'
1487 | '\u{E0000}'..='\u{E0FFF}'
1488 )
1489}
1490
1491impl crate::FromField for StoredFile {
1492 type Slot = (bool, Option<StoredFile>);
1494
1495 fn accept_file(
1496 slot: &mut Self::Slot,
1497 file: crate::FileValue<'_>,
1498 ) -> Result<(), crate::ValueError> {
1499 if slot.0 {
1501 return Err(crate::ValueError::Duplicate);
1502 }
1503 slot.0 = true;
1504 let stored = match file {
1505 crate::FileValue::Stored(f) => f,
1506 crate::FileValue::Buffered(part) => StoredFile::detached(
1507 part.name(),
1508 part.filename(),
1509 part.content_type().map(|c| c.raw()),
1510 part.bytes(),
1511 ),
1512 };
1513 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
1514 return Ok(());
1516 }
1517 slot.1 = Some(stored);
1518 Ok(())
1519 }
1520
1521 fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
1522 slot.1.ok_or(crate::ValueError::Missing)
1523 }
1524}