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::{EncodingError, 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::{Charset, 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 if let Some(budget) = &self.budget {
580 budget.release(cur.on_disk);
581 }
582 if let Some(p) = path {
583 if std::fs::remove_file(&p).is_err() {
584 self.shared.cleanup_failed();
585 }
586 }
587 }
588 }
589 }
590}
591
592impl Drop for StoreSink {
593 fn drop(&mut self) {
594 self.abort();
595 }
596}
597
598enum Place {
599 Memory(Vec<u8>),
600 Disk {
601 file: Option<File>,
602 path: Option<PathBuf>,
603 },
604 Gone,
605}
606
607#[must_use]
627pub struct StoredFile {
628 name: String,
629 filename: Option<(Vec<u8>, FileNameSource)>,
630 content_type: Option<Vec<u8>>,
631 len: u64,
632 place: Place,
633 shared: Option<Arc<Shared>>,
634 budget: Option<Arc<dyn SpillBudget>>,
636 reserved: u64,
637}
638
639impl fmt::Debug for StoredFile {
640 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
641 f.debug_struct("StoredFile")
642 .field("name", &self.name)
643 .field("len", &self.len)
644 .field("in_memory", &self.is_in_memory())
645 .finish_non_exhaustive()
646 }
647}
648
649impl StoredFile {
650 pub(crate) fn detached(
652 name: &str,
653 filename: Option<FileName<'_>>,
654 content_type: Option<&[u8]>,
655 bytes: &[u8],
656 ) -> StoredFile {
657 StoredFile {
658 name: String::from(name),
659 filename: filename.map(|f| (f.raw().to_vec(), f.source())),
660 content_type: content_type.map(<[u8]>::to_vec),
661 len: bytes.len() as u64,
662 place: Place::Memory(bytes.to_vec()),
663 shared: None,
664 budget: None,
665 reserved: 0,
666 }
667 }
668
669 #[must_use]
671 pub fn name(&self) -> &str {
672 &self.name
673 }
674
675 #[must_use]
677 pub fn filename(&self) -> Option<FileName<'_>> {
678 self.filename
679 .as_ref()
680 .map(|(raw, source)| FileName::new(raw, *source))
681 }
682
683 #[must_use]
685 pub fn content_type(&self) -> Option<ContentType<'_>> {
686 self.content_type.as_deref().and_then(ContentType::parse)
687 }
688
689 #[must_use]
691 pub const fn len(&self) -> u64 {
692 self.len
693 }
694
695 #[must_use]
697 pub const fn is_empty(&self) -> bool {
698 self.len == 0
699 }
700
701 #[must_use]
703 pub const fn is_in_memory(&self) -> bool {
704 matches!(self.place, Place::Memory(_))
705 }
706
707 #[must_use]
709 pub fn bytes(&self) -> Option<&[u8]> {
710 match &self.place {
711 Place::Memory(b) => Some(b),
712 _ => None,
713 }
714 }
715
716 pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
723 match &self.place {
724 Place::Gone => Err(FormError::Io {
725 kind: io::ErrorKind::NotFound,
726 }),
727 _ => Ok(StoredReader { file: self, pos: 0 }),
728 }
729 }
730
731 pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
738 if self.len > max {
739 return Err(FormError::limit(Limit::FileSize, Location::NONE));
740 }
741 let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
742 self.reader()?
743 .read_to_end(&mut out)
744 .map_err(|e| temp::io(&e))?;
745 Ok(out)
746 }
747
748 #[must_use]
750 pub fn path(&self) -> Option<&Path> {
751 match &self.place {
752 Place::Disk { path, .. } => path.as_deref(),
753 _ => None,
754 }
755 }
756
757 fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
758 match &self.place {
759 Place::Memory(b) => {
760 let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
761 let src = b.get(start..).unwrap_or(&[]);
762 let n = src.len().min(buf.len());
763 if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
764 dst.copy_from_slice(src);
765 }
766 Ok(n)
767 }
768 Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
769 _ => Err(io::Error::from(io::ErrorKind::NotFound)),
770 }
771 }
772
773 pub fn persist(mut self, dest: &Path) -> Result<(), FormError> {
784 let exists = |e: io::Error| {
785 if e.kind() == io::ErrorKind::AlreadyExists {
786 FormError::Store {
787 reason: StoreError::DestinationExists,
788 }
789 } else {
790 temp::io(&e)
791 }
792 };
793 if let Place::Disk {
794 file,
795 path: Some(p),
796 } = &mut self.place
797 {
798 drop(file.take());
800 match std::fs::hard_link(&*p, dest) {
801 Ok(()) => {
802 let p = p.clone();
803 self.place = Place::Gone;
804 self.release();
805 self.release_budget();
806 if std::fs::remove_file(&p).is_err() {
807 if let Some(s) = &self.shared {
808 s.cleanup_failed();
809 }
810 }
811 return Ok(());
812 }
813 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => return Err(exists(e)),
814 Err(_) => {
815 let f = File::open(&*p).map_err(|e| temp::io(&e))?;
817 *file = Some(f);
818 }
819 }
820 }
821 let mut out = std::fs::OpenOptions::new()
822 .write(true)
823 .create_new(true)
824 .open(dest)
825 .map_err(exists)?;
826 let mut buf = [0u8; 64 * 1024];
827 let mut hi = 0usize;
828 let mut pos = 0u64;
829 let copied = loop {
830 let n = match self.read_at(&mut buf, pos) {
831 Ok(0) => break Ok(()),
832 Ok(n) => n,
833 Err(e) => break Err(temp::io(&e)),
834 };
835 hi = hi.max(n);
836 if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
837 break Err(temp::io(&e));
838 }
839 pos = pos.saturating_add(n as u64);
840 };
841 crate::wipe::wipe(buf.get_mut(..hi).unwrap_or(&mut []));
842 if copied.is_err() {
843 drop(out);
845 let _ = std::fs::remove_file(dest);
846 }
847 copied
848 }
849
850 fn release_budget(&mut self) {
852 if let Some(budget) = self.budget.take() {
853 budget.release(self.reserved);
854 }
855 }
856
857 fn release(&mut self) {
859 if let Some(s) = &self.shared {
860 match &self.place {
861 Place::Memory(_) => {
862 sub(&s.counters.files_in_memory, 1);
863 sub(&s.counters.bytes_in_memory, self.len);
864 }
865 _ => {
866 sub(&s.counters.files_on_disk, 1);
867 sub(&s.counters.bytes_on_disk, self.len);
868 }
869 }
870 }
871 }
872}
873
874fn wipe_meta(
876 name: &mut String,
877 filename: &mut Option<(Vec<u8>, FileNameSource)>,
878 content_type: &mut Option<Vec<u8>>,
879) {
880 crate::wipe::wipe_string(name);
881 if let Some((mut f, _)) = filename.take() {
882 crate::wipe::wipe_vec(&mut f);
883 }
884 if let Some(mut c) = content_type.take() {
885 crate::wipe::wipe_vec(&mut c);
886 }
887}
888
889impl Drop for StoredFile {
890 fn drop(&mut self) {
891 wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
892 match core::mem::replace(&mut self.place, Place::Gone) {
893 Place::Memory(mut b) => {
894 self.place = Place::Memory(Vec::new());
895 self.release();
896 crate::wipe::wipe_vec(&mut b);
897 }
898 Place::Disk { file, path } => {
899 self.place = Place::Disk {
900 file: None,
901 path: None,
902 };
903 self.release();
904 drop(file);
905 let mut freed = true;
906 if let Some(p) = path {
907 if std::fs::remove_file(&p).is_err() {
908 freed = false;
909 if let Some(s) = &self.shared {
910 s.cleanup_failed();
911 }
912 }
913 }
914 if freed {
917 self.release_budget();
918 }
919 }
920 Place::Gone => {}
921 }
922 }
923}
924
925#[derive(Debug)]
927pub struct StoredReader<'f> {
928 file: &'f StoredFile,
929 pos: u64,
930}
931
932impl Read for StoredReader<'_> {
933 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
934 let n = self.file.read_at(buf, self.pos)?;
935 self.pos = self.pos.saturating_add(n as u64);
936 Ok(n)
937 }
938}
939
940impl Seek for StoredReader<'_> {
941 fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
942 let target = match to {
943 SeekFrom::Start(n) => Some(n),
944 SeekFrom::End(d) => self.file.len.checked_add_signed(d),
945 SeekFrom::Current(d) => self.pos.checked_add_signed(d),
946 };
947 match target {
948 Some(t) => {
949 self.pos = t;
950 Ok(t)
951 }
952 None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
953 }
954 }
955}
956
957#[derive(Clone, Copy)]
959struct Span {
960 name: (usize, usize),
961 value: (usize, usize),
962}
963
964#[must_use]
979#[derive(Default)]
980pub struct Upload {
981 text: Vec<u8>,
982 spans: Vec<Span>,
983 files: Vec<StoredFile>,
984 token: Option<String>,
985}
986
987impl fmt::Debug for Upload {
988 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
989 f.debug_struct("Upload")
990 .field("fields", &self.spans.len())
991 .field("text_len", &self.text_len())
992 .field("files", &self.files)
993 .finish_non_exhaustive()
994 }
995}
996
997impl Drop for Upload {
998 fn drop(&mut self) {
999 crate::wipe::wipe_vec(&mut self.text);
1000 if let Some(t) = self.token.as_mut() {
1001 crate::wipe::wipe_string(t);
1002 }
1003 }
1004}
1005
1006impl Upload {
1007 fn slice(&self, (start, len): (usize, usize)) -> &str {
1008 let b = self
1009 .text
1010 .get(start..start.saturating_add(len))
1011 .unwrap_or(&[]);
1012 core::str::from_utf8(b).unwrap_or("")
1014 }
1015
1016 fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
1017 let n0 = self.text.len();
1018 crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 1024, cap);
1019 let v0 = self.text.len();
1020 crate::wipe::append_wiping(&mut self.text, value, 1024, cap);
1021 self.spans.push(Span {
1022 name: (n0, name.len()),
1023 value: (v0, value.len()),
1024 });
1025 }
1026
1027 #[must_use]
1029 pub fn field(&self, name: &str) -> Option<&str> {
1030 self.spans
1031 .iter()
1032 .find(|s| self.slice(s.name) == name)
1033 .map(|s| self.slice(s.value))
1034 }
1035
1036 pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
1042 let mut found = None;
1043 for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
1044 if found.is_some() {
1045 return Err(FormError::Extract {
1046 reason: ExtractError::Duplicate,
1047 field: None,
1048 });
1049 }
1050 found = Some(self.slice(s.value));
1051 }
1052 Ok(found)
1053 }
1054
1055 pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
1057 self.spans
1058 .iter()
1059 .filter(move |s| self.slice(s.name) == name)
1060 .map(move |s| self.slice(s.value))
1061 }
1062
1063 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1065 self.spans
1066 .iter()
1067 .map(|s| (self.slice(s.name), self.slice(s.value)))
1068 }
1069
1070 #[must_use]
1072 pub fn file(&self, name: &str) -> Option<&StoredFile> {
1073 self.files.iter().find(|f| f.name() == name)
1074 }
1075
1076 pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
1078 self.files.iter().filter(move |f| f.name() == name)
1079 }
1080
1081 pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
1083 let i = self.files.iter().position(|f| f.name() == name)?;
1084 Some(self.files.remove(i))
1085 }
1086
1087 #[must_use]
1089 pub fn into_files(mut self) -> Vec<StoredFile> {
1090 core::mem::take(&mut self.files)
1091 }
1092
1093 pub fn from_form(form: crate::Form, policy: &Policy) -> Result<Upload, FormError> {
1118 let mut upload = Upload::default();
1119 let token = policy.token_field.map(|t| t.name());
1120 for (name, value) in form.iter() {
1121 if Some(name) == token {
1122 if upload.token.is_some() {
1123 return Err(FormError::policy(
1124 crate::error::PolicyRefusal::DuplicateToken,
1125 Location::NONE,
1126 ));
1127 }
1128 upload.token = Some(String::from(value));
1129 }
1130 upload.push_field(name, value.as_bytes(), usize::MAX);
1131 }
1132 Ok(upload)
1133 }
1134
1135 pub(crate) fn files_mut(&mut self) -> &mut Vec<StoredFile> {
1137 &mut self.files
1138 }
1139
1140 pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
1141 let files = core::mem::take(&mut self.files);
1142 (self, files)
1143 }
1144
1145 #[must_use]
1147 pub fn captured_token(&self) -> Option<&str> {
1148 self.token.as_deref()
1149 }
1150
1151 #[must_use]
1153 pub fn text_len(&self) -> u64 {
1154 self.spans.iter().map(|s| s.value.1 as u64).sum()
1155 }
1156
1157 #[must_use]
1159 pub fn file_count(&self) -> usize {
1160 self.files.len()
1161 }
1162
1163 pub fn parse_with<R: Read, I: Inspector>(
1184 content_type: &[u8],
1185 mut reader: R,
1186 limits: &Limits,
1187 policy: &Policy,
1188 store: &Store,
1189 inspector: &mut I,
1190 ) -> Result<Upload, FormError> {
1191 let mut session = UploadSession::new(content_type, limits, policy, store)?;
1192 let mut buf = crate::wipe::ReadBuf::new(64 * 1024);
1193 loop {
1194 let n = match reader.read(&mut buf.buf) {
1195 Ok(n) => n,
1196 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
1197 buf.failed();
1198 continue;
1199 }
1200 Err(e) => {
1201 buf.failed();
1202 let err = temp::io(&e);
1203 inspector.failed(&err);
1204 return Err(err);
1205 }
1206 };
1207 if n == 0 {
1208 break;
1209 }
1210 buf.filled(n);
1211 session.push(buf.buf.get(..n).unwrap_or(&[]), inspector)?;
1212 }
1213 drop(buf);
1214 session.finish(inspector)
1215 }
1216}
1217
1218pub struct UploadSession {
1242 driver: Driver<Vec<u8>, StoreSink>,
1243 upload: Upload,
1244 declared: Option<Vec<u8>>,
1245 text_cap: usize,
1246 ended: bool,
1247 failed: bool,
1248}
1249
1250impl fmt::Debug for UploadSession {
1251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1252 f.debug_struct("UploadSession")
1253 .field("bytes_seen", &self.bytes_seen())
1254 .field("upload", &self.upload)
1255 .finish_non_exhaustive()
1256 }
1257}
1258
1259impl UploadSession {
1260 pub fn new(
1267 content_type: &[u8],
1268 limits: &Limits,
1269 policy: &Policy,
1270 store: &Store,
1271 ) -> Result<Self, FormError> {
1272 let boundary = crate::Boundary::parse(content_type)?;
1273 let parser = MultipartParser::new(boundary, limits, policy)?;
1274 Ok(UploadSession {
1275 driver: Driver::new(parser, store.sink(limits)),
1276 upload: Upload::default(),
1277 declared: None,
1278 text_cap: usize::try_from(limits.text_and_names()).unwrap_or(usize::MAX),
1279 ended: false,
1280 failed: false,
1281 })
1282 }
1283
1284 pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1291 self.feed(chunk, false, inspector)
1292 }
1293
1294 pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1301 if self.failed {
1302 return Err(FormError::Poisoned);
1303 }
1304 if !self.ended {
1305 self.feed(&[], true, inspector)?;
1306 }
1307 let mut upload = core::mem::take(&mut self.upload);
1308 upload.token = self.driver.parser().captured_token().map(String::from);
1309 Ok(upload)
1310 }
1311
1312 #[must_use]
1314 pub fn captured_token(&self) -> Option<&str> {
1315 self.driver.parser().captured_token()
1316 }
1317
1318 #[must_use]
1320 pub fn bytes_seen(&self) -> u64 {
1321 self.driver.parser().bytes_seen()
1322 }
1323
1324 fn feed<I: Inspector>(
1325 &mut self,
1326 chunk: &[u8],
1327 eof: bool,
1328 inspector: &mut I,
1329 ) -> Result<(), FormError> {
1330 if self.failed {
1331 return Err(FormError::Poisoned);
1332 }
1333 let r = self.feed_inner(chunk, eof, inspector);
1334 if let Err(e) = &r {
1335 self.failed = true;
1336 if !self.driver.has_failed() {
1339 inspector.failed(e);
1340 }
1341 self.upload.files.clear();
1343 }
1344 r
1345 }
1346
1347 fn feed_inner<I: Inspector>(
1348 &mut self,
1349 chunk: &[u8],
1350 eof: bool,
1351 inspector: &mut I,
1352 ) -> Result<(), FormError> {
1353 let mut pos = 0usize;
1354 loop {
1355 let rest = chunk.get(pos..).unwrap_or(&[]);
1356 let (n, progress) = self.driver.push(rest, eof, inspector)?;
1357 pos = pos.saturating_add(n);
1358 let mut stored_field = false;
1359 match progress {
1360 Progress::Field { name, value } => {
1361 let cap = self.text_cap;
1362 self.upload.push_field(name, value, cap);
1363 stored_field = true;
1364 }
1365 Progress::File(f) => self.upload.files.push(f),
1366 Progress::End => {
1367 self.ended = true;
1368 if pos >= chunk.len() {
1369 return Ok(());
1370 }
1371 }
1372 _ => {
1373 if pos >= chunk.len() {
1374 return Ok(());
1375 }
1376 }
1377 }
1378 if stored_field {
1379 self.check_last_field()?;
1380 }
1381 }
1382 }
1383
1384 fn check_last_field(&mut self) -> Result<(), FormError> {
1388 let Some(span) = self.upload.spans.last().copied() else {
1389 return Ok(());
1390 };
1391 let readable = match self.driver.current_head_charset() {
1392 Charset::Utf8 | Charset::Ascii => true,
1393 Charset::Other => false,
1394 Charset::Unspecified => self.declared.as_deref().is_none_or(|l| {
1395 l.eq_ignore_ascii_case(b"utf-8") || l.eq_ignore_ascii_case(b"us-ascii")
1396 }),
1397 };
1398 let at = Location::in_part(None, self.bytes_seen());
1399 if !readable {
1400 return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
1401 }
1402 let value = self
1403 .upload
1404 .text
1405 .get(span.value.0..span.value.0.saturating_add(span.value.1))
1406 .unwrap_or(&[]);
1407 if core::str::from_utf8(value).is_err() {
1408 return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
1409 }
1410 if self.upload.slice(span.name) == "_charset_" {
1411 self.declared = Some(value.to_vec());
1412 }
1413 Ok(())
1414 }
1415}
1416
1417pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1436 let store = Store::temp_lazy();
1437 Upload::parse_with(
1438 content_type,
1439 body,
1440 &Limits::DEFAULT,
1441 &Policy::DEFAULT,
1442 &store,
1443 &mut crate::NoInspector,
1444 )
1445}
1446
1447impl FileName<'_> {
1448 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
1472 pub fn sanitized(&self) -> Result<String, FormError> {
1473 let base = FileName::new(self.basename(), self.source());
1474 let text = base.html_unescaped()?;
1475 #[cfg(feature = "unicode")]
1476 let text = unicode_lang::normalize(text.as_ref(), unicode_lang::Form::Nfc);
1477 let text: &str = text.as_ref();
1478 if text.is_ascii() {
1479 return Ok(path_lib::sanitize(text));
1480 }
1481 let visible: String = text
1482 .chars()
1483 .map(|c| if is_default_ignorable(c) { '_' } else { c })
1484 .collect();
1485 Ok(path_lib::sanitize(visible))
1486 }
1487}
1488
1489const fn is_default_ignorable(c: char) -> bool {
1492 matches!(
1493 c,
1494 '\u{00AD}'
1495 | '\u{034F}'
1496 | '\u{061C}'
1497 | '\u{115F}'..='\u{1160}'
1498 | '\u{17B4}'..='\u{17B5}'
1499 | '\u{180B}'..='\u{180F}'
1500 | '\u{200B}'..='\u{200F}'
1501 | '\u{202A}'..='\u{202E}'
1502 | '\u{2060}'..='\u{206F}'
1503 | '\u{3164}'
1504 | '\u{FE00}'..='\u{FE0F}'
1505 | '\u{FEFF}'
1506 | '\u{FFA0}'
1507 | '\u{FFF0}'..='\u{FFF8}'
1508 | '\u{1BCA0}'..='\u{1BCA3}'
1509 | '\u{1D173}'..='\u{1D17A}'
1510 | '\u{E0000}'..='\u{E0FFF}'
1511 )
1512}
1513
1514impl crate::FromField for StoredFile {
1515 type Slot = Option<StoredFile>;
1516
1517 fn accept_file(
1518 slot: &mut Self::Slot,
1519 file: crate::FileValue<'_>,
1520 ) -> Result<(), crate::ValueError> {
1521 let stored = match file {
1522 crate::FileValue::Stored(f) => f,
1523 crate::FileValue::Buffered(part) => StoredFile::detached(
1524 part.name(),
1525 part.filename(),
1526 part.content_type().map(|c| c.raw()),
1527 part.bytes(),
1528 ),
1529 };
1530 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
1531 return Ok(());
1533 }
1534 if slot.is_some() {
1535 return Err(crate::ValueError::Duplicate);
1536 }
1537 *slot = Some(stored);
1538 Ok(())
1539 }
1540
1541 fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
1542 slot.ok_or(crate::ValueError::Missing)
1543 }
1544}