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 pub(crate) const fn code(self) -> &'static str {
47 match self {
48 Self::QuotaExceeded => "store.quota_exceeded",
49 Self::NotADirectory => "store.not_a_directory",
50 Self::DestinationExists => "store.destination_exists",
51 }
52 }
53
54 pub(crate) const fn describe(self) -> &'static str {
55 match self {
56 Self::QuotaExceeded => "the shared upload budget is full; retry later",
57 Self::NotADirectory => "not a directory, or a link to one",
58 Self::DestinationExists => "the destination exists; nothing was overwritten",
59 }
60 }
61}
62
63#[non_exhaustive]
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
73pub enum SpillMode {
74 #[default]
76 Anonymous,
77 Named,
79}
80
81#[non_exhaustive]
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct StoreStats {
85 pub files_in_memory: u64,
87 pub files_on_disk: u64,
89 pub bytes_in_memory: u64,
91 pub bytes_on_disk: u64,
93 pub files_spilled: u64,
95 pub cleanup_failures: u64,
97}
98
99#[derive(Default)]
100struct Counters {
101 files_in_memory: AtomicU64,
102 files_on_disk: AtomicU64,
103 bytes_in_memory: AtomicU64,
104 bytes_on_disk: AtomicU64,
105 files_spilled: AtomicU64,
106 cleanup_failures: AtomicU64,
107}
108
109fn add(c: &AtomicU64, n: u64) {
110 c.fetch_add(n, Ordering::Relaxed);
111}
112
113fn sub(c: &AtomicU64, n: u64) {
114 let mut v = c.load(Ordering::Relaxed);
116 loop {
117 match c.compare_exchange_weak(v, v.saturating_sub(n), Ordering::Relaxed, Ordering::Relaxed)
118 {
119 Ok(_) => return,
120 Err(now) => v = now,
121 }
122 }
123}
124
125struct Shared {
129 dir: OnceLock<PathBuf>,
130 owns_dir: bool,
131 counters: Counters,
132}
133
134impl Drop for Shared {
135 fn drop(&mut self) {
136 if self.owns_dir {
137 if let Some(dir) = self.dir.get() {
138 let _ = std::fs::remove_dir(dir);
139 }
140 }
141 }
142}
143
144impl Shared {
145 fn dir(&self) -> Result<&Path, FormError> {
146 if let Some(d) = self.dir.get() {
147 return Ok(d);
148 }
149 let created = temp::create_private_dir()?;
150 if let Err(lost) = self.dir.set(created) {
151 let _ = std::fs::remove_dir(lost);
153 }
154 self.dir.get().map(PathBuf::as_path).ok_or(FormError::Io {
155 kind: io::ErrorKind::Other,
156 })
157 }
158
159 fn cleanup_failed(&self) {
160 add(&self.counters.cleanup_failures, 1);
161 }
162}
163
164#[derive(Clone)]
182pub struct Store {
183 shared: Arc<Shared>,
184 memory_only: bool,
185 threshold: u64,
186 max_disk: Option<u64>,
187 mode: SpillMode,
188 budget: Option<Arc<dyn SpillBudget>>,
189}
190
191impl fmt::Debug for Store {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.debug_struct("Store")
194 .field("memory_only", &self.memory_only)
195 .field("threshold", &self.threshold)
196 .field("mode", &self.mode)
197 .field("quota", &self.budget.is_some())
198 .field("stats", &self.stats())
199 .finish_non_exhaustive()
200 }
201}
202
203const DEFAULT_THRESHOLD: u64 = 64 * 1024;
204
205impl Store {
206 fn with(dir: Option<PathBuf>, owns_dir: bool, memory_only: bool) -> Store {
207 let lock = OnceLock::new();
208 if let Some(d) = dir {
209 let _ = lock.set(d);
210 }
211 Store {
212 shared: Arc::new(Shared {
213 dir: lock,
214 owns_dir,
215 counters: Counters::default(),
216 }),
217 memory_only,
218 threshold: DEFAULT_THRESHOLD,
219 max_disk: None,
220 mode: SpillMode::Anonymous,
221 budget: None,
222 }
223 }
224
225 pub fn temp() -> Result<Store, FormError> {
235 let dir = temp::create_private_dir()?;
236 Ok(Store::with(Some(dir), true, false))
237 }
238
239 pub(crate) fn temp_lazy() -> Store {
242 Store::with(None, true, false)
243 }
244
245 #[must_use]
248 pub fn memory_only() -> Store {
249 let mut s = Store::with(None, false, true);
250 s.threshold = u64::MAX;
251 s
252 }
253
254 pub fn in_dir(dir: impl AsRef<Path>) -> Result<Store, FormError> {
270 let dir = dir.as_ref();
271 temp::check_dir(dir)?;
272 Ok(Store::with(Some(dir.to_owned()), false, false))
273 }
274
275 #[must_use]
278 pub fn memory_threshold(mut self, bytes: u64) -> Store {
279 if !self.memory_only {
280 self.threshold = bytes;
281 }
282 self
283 }
284
285 #[must_use]
288 pub fn max_disk_bytes(mut self, bytes: u64) -> Store {
289 self.max_disk = Some(bytes);
290 self
291 }
292
293 #[must_use]
295 pub fn mode(mut self, mode: SpillMode) -> Store {
296 self.mode = mode;
297 self
298 }
299
300 #[must_use]
302 pub fn dir(&self) -> Option<&Path> {
303 self.shared.dir.get().map(PathBuf::as_path)
304 }
305
306 #[must_use]
308 pub fn cleanup_failures(&self) -> u64 {
309 self.shared
310 .counters
311 .cleanup_failures
312 .load(Ordering::Relaxed)
313 }
314
315 #[must_use]
317 pub fn stats(&self) -> StoreStats {
318 let c = &self.shared.counters;
319 StoreStats {
320 files_in_memory: c.files_in_memory.load(Ordering::Relaxed),
321 files_on_disk: c.files_on_disk.load(Ordering::Relaxed),
322 bytes_in_memory: c.bytes_in_memory.load(Ordering::Relaxed),
323 bytes_on_disk: c.bytes_on_disk.load(Ordering::Relaxed),
324 files_spilled: c.files_spilled.load(Ordering::Relaxed),
325 cleanup_failures: c.cleanup_failures.load(Ordering::Relaxed),
326 }
327 }
328
329 pub fn quota(mut self, budget: impl SpillBudget) -> Store {
348 self.budget = Some(Arc::new(budget));
349 self
350 }
351
352 #[must_use]
366 pub fn memory_bound(&self, limits: &Limits) -> u64 {
367 let in_memory = if self.memory_only {
368 u64::MAX
369 } else {
370 self.threshold
371 };
372 limits.memory_bound(in_memory)
373 }
374
375 #[must_use]
377 pub fn sink(&self, limits: &Limits) -> StoreSink {
378 StoreSink {
379 budget: self.budget.clone(),
380 shared: Arc::clone(&self.shared),
381 memory_only: self.memory_only,
382 threshold: self.threshold,
383 memory_cap: usize::try_from(self.threshold.min(limits.file())).unwrap_or(usize::MAX),
386 mode: self.mode,
387 max_disk: self.max_disk.unwrap_or(limits.max_body_bytes),
388 used_disk: 0,
389 current: None,
390 }
391 }
392}
393
394struct Current {
396 name: String,
397 filename: Option<(Vec<u8>, FileNameSource)>,
398 content_type: Option<Vec<u8>>,
399 memory: Vec<u8>,
400 disk: Option<(File, Option<PathBuf>)>,
401 len: u64,
402 on_disk: u64,
404}
405
406pub struct StoreSink {
408 budget: Option<Arc<dyn SpillBudget>>,
409 shared: Arc<Shared>,
410 memory_only: bool,
411 threshold: u64,
412 memory_cap: usize,
413 mode: SpillMode,
414 max_disk: u64,
415 used_disk: u64,
416 current: Option<Current>,
417}
418
419impl fmt::Debug for StoreSink {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 f.debug_struct("StoreSink")
422 .field("used_disk", &self.used_disk)
423 .field("max_disk", &self.max_disk)
424 .finish_non_exhaustive()
425 }
426}
427
428impl StoreSink {
429 pub(crate) const fn used_disk(&self) -> u64 {
431 self.used_disk
432 }
433
434 pub(crate) const fn with_used_disk(mut self, bytes: u64) -> StoreSink {
436 self.used_disk = bytes;
437 self
438 }
439
440 fn disk_write(&mut self, bytes: &[u8]) -> Result<(), FormError> {
441 let used = self.used_disk.saturating_add(bytes.len() as u64);
442 if used > self.max_disk {
443 return Err(FormError::limit(Limit::Disk, Location::NONE));
444 }
445 let Some(cur) = self.current.as_mut() else {
446 return Ok(());
447 };
448 let Some((file, _)) = cur.disk.as_mut() else {
449 return Ok(());
450 };
451 let n = bytes.len() as u64;
452 if let Some(budget) = &self.budget {
453 if !budget.reserve(n) {
454 return Err(FormError::Store {
455 reason: StoreError::QuotaExceeded,
456 });
457 }
458 }
459 if let Err(e) = file.write_all(bytes) {
460 if let Some(budget) = &self.budget {
464 budget.release(n);
465 }
466 return Err(temp::io(&e));
467 }
468 self.used_disk = used;
469 cur.on_disk = cur.on_disk.saturating_add(bytes.len() as u64);
470 add(&self.shared.counters.bytes_on_disk, bytes.len() as u64);
471 Ok(())
472 }
473
474 fn spill(&mut self) -> Result<(), FormError> {
475 let dir = self.shared.dir()?.to_owned();
476 let anonymous = self.mode == SpillMode::Anonymous;
477 let (file, path) = temp::create_file(&dir, anonymous)?;
478 add(&self.shared.counters.files_on_disk, 1);
479 add(&self.shared.counters.files_spilled, 1);
480 let prefix = match self.current.as_mut() {
481 Some(cur) => {
482 cur.disk = Some((file, path));
483 core::mem::take(&mut cur.memory)
484 }
485 None => Vec::new(),
486 };
487 let mut prefix = prefix;
488 let r = self.disk_write(&prefix);
489 crate::wipe::wipe_vec(&mut prefix);
490 r
491 }
492}
493
494impl Sink for StoreSink {
495 type Output = StoredFile;
496
497 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
498 self.abort();
499 self.current = Some(Current {
500 name: String::from(head.name()),
501 filename: head.filename().map(|f| (f.raw().to_vec(), f.source())),
502 content_type: head.content_type().map(|c| c.raw().to_vec()),
503 memory: Vec::new(),
504 disk: None,
505 len: 0,
506 on_disk: 0,
507 });
508 Ok(())
509 }
510
511 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
512 let (on_disk, len) = match self.current.as_ref() {
513 Some(c) => (c.disk.is_some(), c.len),
514 None => return Ok(()),
515 };
516 let new_len = len.saturating_add(chunk.len() as u64);
517 if !on_disk && !self.memory_only && new_len > self.threshold {
518 self.spill()?;
519 }
520 let on_disk = self.current.as_ref().is_some_and(|c| c.disk.is_some());
521 if on_disk {
522 self.disk_write(chunk)?;
523 } else if let Some(cur) = self.current.as_mut() {
524 crate::wipe::append_wiping(&mut cur.memory, chunk, 4096, self.memory_cap);
525 }
526 if let Some(cur) = self.current.as_mut() {
527 cur.len = new_len;
528 }
529 Ok(())
530 }
531
532 fn close(&mut self) -> Result<StoredFile, FormError> {
533 let Some(mut cur) = self.current.take() else {
534 return Err(FormError::Poisoned);
535 };
536 let on_disk = cur.disk.is_some();
537 let place = match cur.disk.take() {
538 Some((file, path)) => Place::Disk {
539 file: Some(file),
540 path,
541 },
542 None => {
543 add(&self.shared.counters.files_in_memory, 1);
544 add(&self.shared.counters.bytes_in_memory, cur.len);
545 Place::Memory(core::mem::take(&mut cur.memory))
546 }
547 };
548 Ok(StoredFile {
549 name: core::mem::take(&mut cur.name),
550 filename: cur.filename.take(),
551 content_type: cur.content_type.take(),
552 len: cur.len,
553 place,
554 shared: Some(Arc::clone(&self.shared)),
555 budget: if on_disk { self.budget.clone() } else { None },
556 reserved: cur.on_disk,
557 })
558 }
559
560 fn abort(&mut self) {
561 if let Some(mut cur) = self.current.take() {
562 crate::wipe::wipe_vec(&mut cur.memory);
563 wipe_meta(&mut cur.name, &mut cur.filename, &mut cur.content_type);
564 if let Some((file, path)) = cur.disk.take() {
565 drop(file);
568 sub(&self.shared.counters.files_on_disk, 1);
569 sub(&self.shared.counters.bytes_on_disk, cur.on_disk);
570 if let Some(budget) = &self.budget {
571 budget.release(cur.on_disk);
572 }
573 if let Some(p) = path {
574 if std::fs::remove_file(&p).is_err() {
575 self.shared.cleanup_failed();
576 }
577 }
578 }
579 }
580 }
581}
582
583impl Drop for StoreSink {
584 fn drop(&mut self) {
585 self.abort();
586 }
587}
588
589enum Place {
590 Memory(Vec<u8>),
591 Disk {
592 file: Option<File>,
593 path: Option<PathBuf>,
594 },
595 Gone,
596}
597
598#[must_use]
618pub struct StoredFile {
619 name: String,
620 filename: Option<(Vec<u8>, FileNameSource)>,
621 content_type: Option<Vec<u8>>,
622 len: u64,
623 place: Place,
624 shared: Option<Arc<Shared>>,
625 budget: Option<Arc<dyn SpillBudget>>,
627 reserved: u64,
628}
629
630impl fmt::Debug for StoredFile {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 f.debug_struct("StoredFile")
633 .field("name", &self.name)
634 .field("len", &self.len)
635 .field("in_memory", &self.is_in_memory())
636 .finish_non_exhaustive()
637 }
638}
639
640impl StoredFile {
641 pub(crate) fn detached(
643 name: &str,
644 filename: Option<FileName<'_>>,
645 content_type: Option<&[u8]>,
646 bytes: &[u8],
647 ) -> StoredFile {
648 StoredFile {
649 name: String::from(name),
650 filename: filename.map(|f| (f.raw().to_vec(), f.source())),
651 content_type: content_type.map(<[u8]>::to_vec),
652 len: bytes.len() as u64,
653 place: Place::Memory(bytes.to_vec()),
654 shared: None,
655 budget: None,
656 reserved: 0,
657 }
658 }
659
660 #[must_use]
662 pub fn name(&self) -> &str {
663 &self.name
664 }
665
666 #[must_use]
668 pub fn filename(&self) -> Option<FileName<'_>> {
669 self.filename
670 .as_ref()
671 .map(|(raw, source)| FileName::new(raw, *source))
672 }
673
674 #[must_use]
676 pub fn content_type(&self) -> Option<ContentType<'_>> {
677 self.content_type.as_deref().and_then(ContentType::parse)
678 }
679
680 #[must_use]
682 pub const fn len(&self) -> u64 {
683 self.len
684 }
685
686 #[must_use]
688 pub const fn is_empty(&self) -> bool {
689 self.len == 0
690 }
691
692 #[must_use]
694 pub const fn is_in_memory(&self) -> bool {
695 matches!(self.place, Place::Memory(_))
696 }
697
698 #[must_use]
700 pub fn bytes(&self) -> Option<&[u8]> {
701 match &self.place {
702 Place::Memory(b) => Some(b),
703 _ => None,
704 }
705 }
706
707 pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
714 match &self.place {
715 Place::Gone => Err(FormError::Io {
716 kind: io::ErrorKind::NotFound,
717 }),
718 _ => Ok(StoredReader { file: self, pos: 0 }),
719 }
720 }
721
722 pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
729 if self.len > max {
730 return Err(FormError::limit(Limit::FileSize, Location::NONE));
731 }
732 let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
733 self.reader()?
734 .read_to_end(&mut out)
735 .map_err(|e| temp::io(&e))?;
736 Ok(out)
737 }
738
739 #[must_use]
741 pub fn path(&self) -> Option<&Path> {
742 match &self.place {
743 Place::Disk { path, .. } => path.as_deref(),
744 _ => None,
745 }
746 }
747
748 fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
749 match &self.place {
750 Place::Memory(b) => {
751 let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
752 let src = b.get(start..).unwrap_or(&[]);
753 let n = src.len().min(buf.len());
754 if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
755 dst.copy_from_slice(src);
756 }
757 Ok(n)
758 }
759 Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
760 _ => Err(io::Error::from(io::ErrorKind::NotFound)),
761 }
762 }
763
764 pub fn persist(mut self, dest: &Path) -> Result<(), FormError> {
775 let exists = |e: io::Error| {
776 if e.kind() == io::ErrorKind::AlreadyExists {
777 FormError::Store {
778 reason: StoreError::DestinationExists,
779 }
780 } else {
781 temp::io(&e)
782 }
783 };
784 if let Place::Disk {
785 file,
786 path: Some(p),
787 } = &mut self.place
788 {
789 drop(file.take());
791 match std::fs::hard_link(&*p, dest) {
792 Ok(()) => {
793 let p = p.clone();
794 self.place = Place::Gone;
795 self.release();
796 self.release_budget();
797 if std::fs::remove_file(&p).is_err() {
798 if let Some(s) = &self.shared {
799 s.cleanup_failed();
800 }
801 }
802 return Ok(());
803 }
804 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => return Err(exists(e)),
805 Err(_) => {
806 let f = File::open(&*p).map_err(|e| temp::io(&e))?;
808 *file = Some(f);
809 }
810 }
811 }
812 let mut out = std::fs::OpenOptions::new()
813 .write(true)
814 .create_new(true)
815 .open(dest)
816 .map_err(exists)?;
817 let mut buf = [0u8; 64 * 1024];
818 let mut pos = 0u64;
819 let copied = loop {
820 let n = match self.read_at(&mut buf, pos) {
821 Ok(0) => break Ok(()),
822 Ok(n) => n,
823 Err(e) => break Err(temp::io(&e)),
824 };
825 if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
826 break Err(temp::io(&e));
827 }
828 pos = pos.saturating_add(n as u64);
829 };
830 crate::wipe::wipe(&mut buf);
831 if copied.is_err() {
832 drop(out);
834 let _ = std::fs::remove_file(dest);
835 }
836 copied
837 }
838
839 fn release_budget(&mut self) {
841 if let Some(budget) = self.budget.take() {
842 budget.release(self.reserved);
843 }
844 }
845
846 fn release(&mut self) {
848 if let Some(s) = &self.shared {
849 match &self.place {
850 Place::Memory(_) => {
851 sub(&s.counters.files_in_memory, 1);
852 sub(&s.counters.bytes_in_memory, self.len);
853 }
854 _ => {
855 sub(&s.counters.files_on_disk, 1);
856 sub(&s.counters.bytes_on_disk, self.len);
857 }
858 }
859 }
860 }
861}
862
863fn wipe_meta(
865 name: &mut String,
866 filename: &mut Option<(Vec<u8>, FileNameSource)>,
867 content_type: &mut Option<Vec<u8>>,
868) {
869 crate::wipe::wipe_string(name);
870 if let Some((mut f, _)) = filename.take() {
871 crate::wipe::wipe_vec(&mut f);
872 }
873 if let Some(mut c) = content_type.take() {
874 crate::wipe::wipe_vec(&mut c);
875 }
876}
877
878impl Drop for StoredFile {
879 fn drop(&mut self) {
880 wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
881 match core::mem::replace(&mut self.place, Place::Gone) {
882 Place::Memory(mut b) => {
883 self.place = Place::Memory(Vec::new());
884 self.release();
885 crate::wipe::wipe_vec(&mut b);
886 }
887 Place::Disk { file, path } => {
888 self.place = Place::Disk {
889 file: None,
890 path: None,
891 };
892 self.release();
893 drop(file);
894 let mut freed = true;
895 if let Some(p) = path {
896 if std::fs::remove_file(&p).is_err() {
897 freed = false;
898 if let Some(s) = &self.shared {
899 s.cleanup_failed();
900 }
901 }
902 }
903 if freed {
906 self.release_budget();
907 }
908 }
909 Place::Gone => {}
910 }
911 }
912}
913
914#[derive(Debug)]
916pub struct StoredReader<'f> {
917 file: &'f StoredFile,
918 pos: u64,
919}
920
921impl Read for StoredReader<'_> {
922 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
923 let n = self.file.read_at(buf, self.pos)?;
924 self.pos = self.pos.saturating_add(n as u64);
925 Ok(n)
926 }
927}
928
929impl Seek for StoredReader<'_> {
930 fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
931 let target = match to {
932 SeekFrom::Start(n) => Some(n),
933 SeekFrom::End(d) => self.file.len.checked_add_signed(d),
934 SeekFrom::Current(d) => self.pos.checked_add_signed(d),
935 };
936 match target {
937 Some(t) => {
938 self.pos = t;
939 Ok(t)
940 }
941 None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
942 }
943 }
944}
945
946#[derive(Clone, Copy)]
948struct Span {
949 name: (usize, usize),
950 value: (usize, usize),
951}
952
953#[must_use]
968#[derive(Default)]
969pub struct Upload {
970 text: Vec<u8>,
971 spans: Vec<Span>,
972 files: Vec<StoredFile>,
973 token: Option<String>,
974}
975
976impl fmt::Debug for Upload {
977 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
978 f.debug_struct("Upload")
979 .field("fields", &self.spans.len())
980 .field("text_len", &self.text_len())
981 .field("files", &self.files)
982 .finish_non_exhaustive()
983 }
984}
985
986impl Drop for Upload {
987 fn drop(&mut self) {
988 crate::wipe::wipe_vec(&mut self.text);
989 if let Some(t) = self.token.as_mut() {
990 crate::wipe::wipe_string(t);
991 }
992 }
993}
994
995impl Upload {
996 fn slice(&self, (start, len): (usize, usize)) -> &str {
997 let b = self
998 .text
999 .get(start..start.saturating_add(len))
1000 .unwrap_or(&[]);
1001 core::str::from_utf8(b).unwrap_or("")
1003 }
1004
1005 fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
1006 let n0 = self.text.len();
1007 crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 64 * 1024, cap);
1008 let v0 = self.text.len();
1009 crate::wipe::append_wiping(&mut self.text, value, 64 * 1024, cap);
1010 self.spans.push(Span {
1011 name: (n0, name.len()),
1012 value: (v0, value.len()),
1013 });
1014 }
1015
1016 #[must_use]
1018 pub fn field(&self, name: &str) -> Option<&str> {
1019 self.spans
1020 .iter()
1021 .find(|s| self.slice(s.name) == name)
1022 .map(|s| self.slice(s.value))
1023 }
1024
1025 pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
1031 let mut found = None;
1032 for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
1033 if found.is_some() {
1034 return Err(FormError::Extract {
1035 reason: ExtractError::Duplicate,
1036 field: None,
1037 });
1038 }
1039 found = Some(self.slice(s.value));
1040 }
1041 Ok(found)
1042 }
1043
1044 pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
1046 self.spans
1047 .iter()
1048 .filter(move |s| self.slice(s.name) == name)
1049 .map(move |s| self.slice(s.value))
1050 }
1051
1052 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1054 self.spans
1055 .iter()
1056 .map(|s| (self.slice(s.name), self.slice(s.value)))
1057 }
1058
1059 #[must_use]
1061 pub fn file(&self, name: &str) -> Option<&StoredFile> {
1062 self.files.iter().find(|f| f.name() == name)
1063 }
1064
1065 pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
1067 self.files.iter().filter(move |f| f.name() == name)
1068 }
1069
1070 pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
1072 let i = self.files.iter().position(|f| f.name() == name)?;
1073 Some(self.files.remove(i))
1074 }
1075
1076 #[must_use]
1078 pub fn into_files(mut self) -> Vec<StoredFile> {
1079 core::mem::take(&mut self.files)
1080 }
1081
1082 pub fn from_form(form: crate::Form, policy: &Policy) -> Result<Upload, FormError> {
1107 let mut upload = Upload::default();
1108 let token = policy.token_field.map(|t| t.name());
1109 for (name, value) in form.iter() {
1110 if Some(name) == token {
1111 if upload.token.is_some() {
1112 return Err(FormError::policy(
1113 crate::error::PolicyRefusal::DuplicateToken,
1114 Location::NONE,
1115 ));
1116 }
1117 upload.token = Some(String::from(value));
1118 }
1119 upload.push_field(name, value.as_bytes(), usize::MAX);
1120 }
1121 Ok(upload)
1122 }
1123
1124 pub(crate) fn files_mut(&mut self) -> &mut Vec<StoredFile> {
1126 &mut self.files
1127 }
1128
1129 pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
1130 let files = core::mem::take(&mut self.files);
1131 (self, files)
1132 }
1133
1134 #[must_use]
1136 pub fn captured_token(&self) -> Option<&str> {
1137 self.token.as_deref()
1138 }
1139
1140 #[must_use]
1142 pub fn text_len(&self) -> u64 {
1143 self.spans.iter().map(|s| s.value.1 as u64).sum()
1144 }
1145
1146 #[must_use]
1148 pub fn file_count(&self) -> usize {
1149 self.files.len()
1150 }
1151
1152 pub fn parse_with<R: Read, I: Inspector>(
1173 content_type: &[u8],
1174 mut reader: R,
1175 limits: &Limits,
1176 policy: &Policy,
1177 store: &Store,
1178 inspector: &mut I,
1179 ) -> Result<Upload, FormError> {
1180 let mut session = UploadSession::new(content_type, limits, policy, store)?;
1181 let mut buf = crate::wipe::WipeVec(alloc::vec![0u8; 64 * 1024]);
1182 loop {
1183 let n = match reader.read(&mut buf) {
1184 Ok(n) => n,
1185 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1186 Err(e) => {
1187 let err = temp::io(&e);
1188 inspector.failed(&err);
1189 return Err(err);
1190 }
1191 };
1192 if n == 0 {
1193 break;
1194 }
1195 session.push(buf.get(..n).unwrap_or(&[]), inspector)?;
1196 }
1197 drop(buf);
1198 session.finish(inspector)
1199 }
1200}
1201
1202pub struct UploadSession {
1226 driver: Driver<Vec<u8>, StoreSink>,
1227 upload: Upload,
1228 declared: Option<Vec<u8>>,
1229 text_cap: usize,
1230 ended: bool,
1231 failed: bool,
1232}
1233
1234impl fmt::Debug for UploadSession {
1235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1236 f.debug_struct("UploadSession")
1237 .field("bytes_seen", &self.bytes_seen())
1238 .field("upload", &self.upload)
1239 .finish_non_exhaustive()
1240 }
1241}
1242
1243impl UploadSession {
1244 pub fn new(
1251 content_type: &[u8],
1252 limits: &Limits,
1253 policy: &Policy,
1254 store: &Store,
1255 ) -> Result<Self, FormError> {
1256 let boundary = crate::Boundary::parse(content_type)?;
1257 let parser = MultipartParser::new(boundary, limits, policy)?;
1258 Ok(UploadSession {
1259 driver: Driver::new(parser, store.sink(limits)),
1260 upload: Upload::default(),
1261 declared: None,
1262 text_cap: usize::try_from(limits.text_and_names()).unwrap_or(usize::MAX),
1263 ended: false,
1264 failed: false,
1265 })
1266 }
1267
1268 pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1275 self.feed(chunk, false, inspector)
1276 }
1277
1278 pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1285 if self.failed {
1286 return Err(FormError::Poisoned);
1287 }
1288 if !self.ended {
1289 self.feed(&[], true, inspector)?;
1290 }
1291 let mut upload = core::mem::take(&mut self.upload);
1292 upload.token = self.driver.parser().captured_token().map(String::from);
1293 Ok(upload)
1294 }
1295
1296 #[must_use]
1298 pub fn captured_token(&self) -> Option<&str> {
1299 self.driver.parser().captured_token()
1300 }
1301
1302 #[must_use]
1304 pub fn bytes_seen(&self) -> u64 {
1305 self.driver.parser().bytes_seen()
1306 }
1307
1308 fn feed<I: Inspector>(
1309 &mut self,
1310 chunk: &[u8],
1311 eof: bool,
1312 inspector: &mut I,
1313 ) -> Result<(), FormError> {
1314 if self.failed {
1315 return Err(FormError::Poisoned);
1316 }
1317 let r = self.feed_inner(chunk, eof, inspector);
1318 if let Err(e) = &r {
1319 self.failed = true;
1320 if !self.driver.has_failed() {
1323 inspector.failed(e);
1324 }
1325 self.upload.files.clear();
1327 }
1328 r
1329 }
1330
1331 fn feed_inner<I: Inspector>(
1332 &mut self,
1333 chunk: &[u8],
1334 eof: bool,
1335 inspector: &mut I,
1336 ) -> Result<(), FormError> {
1337 let mut pos = 0usize;
1338 loop {
1339 let rest = chunk.get(pos..).unwrap_or(&[]);
1340 let (n, progress) = self.driver.push(rest, eof, inspector)?;
1341 pos = pos.saturating_add(n);
1342 let mut stored_field = false;
1343 match progress {
1344 Progress::Field { name, value } => {
1345 let cap = self.text_cap;
1346 self.upload.push_field(name, value, cap);
1347 stored_field = true;
1348 }
1349 Progress::File(f) => self.upload.files.push(f),
1350 Progress::End => {
1351 self.ended = true;
1352 if pos >= chunk.len() {
1353 return Ok(());
1354 }
1355 }
1356 _ => {
1357 if pos >= chunk.len() {
1358 return Ok(());
1359 }
1360 }
1361 }
1362 if stored_field {
1363 self.check_last_field()?;
1364 }
1365 }
1366 }
1367
1368 fn check_last_field(&mut self) -> Result<(), FormError> {
1372 let Some(span) = self.upload.spans.last().copied() else {
1373 return Ok(());
1374 };
1375 let readable = match self.driver.current_head_charset() {
1376 Charset::Utf8 | Charset::Ascii => true,
1377 Charset::Other => false,
1378 Charset::Unspecified => self.declared.as_deref().is_none_or(|l| {
1379 l.eq_ignore_ascii_case(b"utf-8") || l.eq_ignore_ascii_case(b"us-ascii")
1380 }),
1381 };
1382 let at = Location::in_part(None, self.bytes_seen());
1383 if !readable {
1384 return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
1385 }
1386 let value = self
1387 .upload
1388 .text
1389 .get(span.value.0..span.value.0.saturating_add(span.value.1))
1390 .unwrap_or(&[]);
1391 if core::str::from_utf8(value).is_err() {
1392 return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
1393 }
1394 if self.upload.slice(span.name) == "_charset_" {
1395 self.declared = Some(value.to_vec());
1396 }
1397 Ok(())
1398 }
1399}
1400
1401pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1420 let store = Store::temp_lazy();
1421 Upload::parse_with(
1422 content_type,
1423 body,
1424 &Limits::DEFAULT,
1425 &Policy::DEFAULT,
1426 &store,
1427 &mut crate::NoInspector,
1428 )
1429}
1430
1431impl FileName<'_> {
1432 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
1456 pub fn sanitized(&self) -> Result<String, FormError> {
1457 let base = FileName::new(self.basename(), self.source());
1458 let text = base.html_unescaped()?;
1459 #[cfg(feature = "unicode")]
1460 let text = unicode_lang::normalize(text.as_ref(), unicode_lang::Form::Nfc);
1461 let text: &str = text.as_ref();
1462 if text.is_ascii() {
1463 return Ok(path_lib::sanitize(text));
1464 }
1465 let visible: String = text
1466 .chars()
1467 .map(|c| if is_default_ignorable(c) { '_' } else { c })
1468 .collect();
1469 Ok(path_lib::sanitize(visible))
1470 }
1471}
1472
1473const fn is_default_ignorable(c: char) -> bool {
1476 matches!(
1477 c,
1478 '\u{00AD}'
1479 | '\u{034F}'
1480 | '\u{061C}'
1481 | '\u{115F}'..='\u{1160}'
1482 | '\u{17B4}'..='\u{17B5}'
1483 | '\u{180B}'..='\u{180F}'
1484 | '\u{200B}'..='\u{200F}'
1485 | '\u{202A}'..='\u{202E}'
1486 | '\u{2060}'..='\u{206F}'
1487 | '\u{3164}'
1488 | '\u{FE00}'..='\u{FE0F}'
1489 | '\u{FEFF}'
1490 | '\u{FFA0}'
1491 | '\u{FFF0}'..='\u{FFF8}'
1492 | '\u{1BCA0}'..='\u{1BCA3}'
1493 | '\u{1D173}'..='\u{1D17A}'
1494 | '\u{E0000}'..='\u{E0FFF}'
1495 )
1496}
1497
1498impl crate::FromField for StoredFile {
1499 type Slot = Option<StoredFile>;
1500
1501 fn accept_file(
1502 slot: &mut Self::Slot,
1503 file: crate::FileValue<'_>,
1504 ) -> Result<(), crate::ValueError> {
1505 let stored = match file {
1506 crate::FileValue::Stored(f) => f,
1507 crate::FileValue::Buffered(part) => StoredFile::detached(
1508 part.name(),
1509 part.filename(),
1510 part.content_type().map(|c| c.raw()),
1511 part.bytes(),
1512 ),
1513 };
1514 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
1515 return Ok(());
1517 }
1518 if slot.is_some() {
1519 return Err(crate::ValueError::Duplicate);
1520 }
1521 *slot = Some(stored);
1522 Ok(())
1523 }
1524
1525 fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
1526 slot.ok_or(crate::ValueError::Missing)
1527 }
1528}