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 hi = 0usize;
819 let mut pos = 0u64;
820 let copied = loop {
821 let n = match self.read_at(&mut buf, pos) {
822 Ok(0) => break Ok(()),
823 Ok(n) => n,
824 Err(e) => break Err(temp::io(&e)),
825 };
826 hi = hi.max(n);
827 if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
828 break Err(temp::io(&e));
829 }
830 pos = pos.saturating_add(n as u64);
831 };
832 crate::wipe::wipe(buf.get_mut(..hi).unwrap_or(&mut []));
833 if copied.is_err() {
834 drop(out);
836 let _ = std::fs::remove_file(dest);
837 }
838 copied
839 }
840
841 fn release_budget(&mut self) {
843 if let Some(budget) = self.budget.take() {
844 budget.release(self.reserved);
845 }
846 }
847
848 fn release(&mut self) {
850 if let Some(s) = &self.shared {
851 match &self.place {
852 Place::Memory(_) => {
853 sub(&s.counters.files_in_memory, 1);
854 sub(&s.counters.bytes_in_memory, self.len);
855 }
856 _ => {
857 sub(&s.counters.files_on_disk, 1);
858 sub(&s.counters.bytes_on_disk, self.len);
859 }
860 }
861 }
862 }
863}
864
865fn wipe_meta(
867 name: &mut String,
868 filename: &mut Option<(Vec<u8>, FileNameSource)>,
869 content_type: &mut Option<Vec<u8>>,
870) {
871 crate::wipe::wipe_string(name);
872 if let Some((mut f, _)) = filename.take() {
873 crate::wipe::wipe_vec(&mut f);
874 }
875 if let Some(mut c) = content_type.take() {
876 crate::wipe::wipe_vec(&mut c);
877 }
878}
879
880impl Drop for StoredFile {
881 fn drop(&mut self) {
882 wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
883 match core::mem::replace(&mut self.place, Place::Gone) {
884 Place::Memory(mut b) => {
885 self.place = Place::Memory(Vec::new());
886 self.release();
887 crate::wipe::wipe_vec(&mut b);
888 }
889 Place::Disk { file, path } => {
890 self.place = Place::Disk {
891 file: None,
892 path: None,
893 };
894 self.release();
895 drop(file);
896 let mut freed = true;
897 if let Some(p) = path {
898 if std::fs::remove_file(&p).is_err() {
899 freed = false;
900 if let Some(s) = &self.shared {
901 s.cleanup_failed();
902 }
903 }
904 }
905 if freed {
908 self.release_budget();
909 }
910 }
911 Place::Gone => {}
912 }
913 }
914}
915
916#[derive(Debug)]
918pub struct StoredReader<'f> {
919 file: &'f StoredFile,
920 pos: u64,
921}
922
923impl Read for StoredReader<'_> {
924 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
925 let n = self.file.read_at(buf, self.pos)?;
926 self.pos = self.pos.saturating_add(n as u64);
927 Ok(n)
928 }
929}
930
931impl Seek for StoredReader<'_> {
932 fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
933 let target = match to {
934 SeekFrom::Start(n) => Some(n),
935 SeekFrom::End(d) => self.file.len.checked_add_signed(d),
936 SeekFrom::Current(d) => self.pos.checked_add_signed(d),
937 };
938 match target {
939 Some(t) => {
940 self.pos = t;
941 Ok(t)
942 }
943 None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
944 }
945 }
946}
947
948#[derive(Clone, Copy)]
950struct Span {
951 name: (usize, usize),
952 value: (usize, usize),
953}
954
955#[must_use]
970#[derive(Default)]
971pub struct Upload {
972 text: Vec<u8>,
973 spans: Vec<Span>,
974 files: Vec<StoredFile>,
975 token: Option<String>,
976}
977
978impl fmt::Debug for Upload {
979 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
980 f.debug_struct("Upload")
981 .field("fields", &self.spans.len())
982 .field("text_len", &self.text_len())
983 .field("files", &self.files)
984 .finish_non_exhaustive()
985 }
986}
987
988impl Drop for Upload {
989 fn drop(&mut self) {
990 crate::wipe::wipe_vec(&mut self.text);
991 if let Some(t) = self.token.as_mut() {
992 crate::wipe::wipe_string(t);
993 }
994 }
995}
996
997impl Upload {
998 fn slice(&self, (start, len): (usize, usize)) -> &str {
999 let b = self
1000 .text
1001 .get(start..start.saturating_add(len))
1002 .unwrap_or(&[]);
1003 core::str::from_utf8(b).unwrap_or("")
1005 }
1006
1007 fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
1008 let n0 = self.text.len();
1009 crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 1024, cap);
1010 let v0 = self.text.len();
1011 crate::wipe::append_wiping(&mut self.text, value, 1024, cap);
1012 self.spans.push(Span {
1013 name: (n0, name.len()),
1014 value: (v0, value.len()),
1015 });
1016 }
1017
1018 #[must_use]
1020 pub fn field(&self, name: &str) -> Option<&str> {
1021 self.spans
1022 .iter()
1023 .find(|s| self.slice(s.name) == name)
1024 .map(|s| self.slice(s.value))
1025 }
1026
1027 pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
1033 let mut found = None;
1034 for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
1035 if found.is_some() {
1036 return Err(FormError::Extract {
1037 reason: ExtractError::Duplicate,
1038 field: None,
1039 });
1040 }
1041 found = Some(self.slice(s.value));
1042 }
1043 Ok(found)
1044 }
1045
1046 pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
1048 self.spans
1049 .iter()
1050 .filter(move |s| self.slice(s.name) == name)
1051 .map(move |s| self.slice(s.value))
1052 }
1053
1054 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
1056 self.spans
1057 .iter()
1058 .map(|s| (self.slice(s.name), self.slice(s.value)))
1059 }
1060
1061 #[must_use]
1063 pub fn file(&self, name: &str) -> Option<&StoredFile> {
1064 self.files.iter().find(|f| f.name() == name)
1065 }
1066
1067 pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
1069 self.files.iter().filter(move |f| f.name() == name)
1070 }
1071
1072 pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
1074 let i = self.files.iter().position(|f| f.name() == name)?;
1075 Some(self.files.remove(i))
1076 }
1077
1078 #[must_use]
1080 pub fn into_files(mut self) -> Vec<StoredFile> {
1081 core::mem::take(&mut self.files)
1082 }
1083
1084 pub fn from_form(form: crate::Form, policy: &Policy) -> Result<Upload, FormError> {
1109 let mut upload = Upload::default();
1110 let token = policy.token_field.map(|t| t.name());
1111 for (name, value) in form.iter() {
1112 if Some(name) == token {
1113 if upload.token.is_some() {
1114 return Err(FormError::policy(
1115 crate::error::PolicyRefusal::DuplicateToken,
1116 Location::NONE,
1117 ));
1118 }
1119 upload.token = Some(String::from(value));
1120 }
1121 upload.push_field(name, value.as_bytes(), usize::MAX);
1122 }
1123 Ok(upload)
1124 }
1125
1126 pub(crate) fn files_mut(&mut self) -> &mut Vec<StoredFile> {
1128 &mut self.files
1129 }
1130
1131 pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
1132 let files = core::mem::take(&mut self.files);
1133 (self, files)
1134 }
1135
1136 #[must_use]
1138 pub fn captured_token(&self) -> Option<&str> {
1139 self.token.as_deref()
1140 }
1141
1142 #[must_use]
1144 pub fn text_len(&self) -> u64 {
1145 self.spans.iter().map(|s| s.value.1 as u64).sum()
1146 }
1147
1148 #[must_use]
1150 pub fn file_count(&self) -> usize {
1151 self.files.len()
1152 }
1153
1154 pub fn parse_with<R: Read, I: Inspector>(
1175 content_type: &[u8],
1176 mut reader: R,
1177 limits: &Limits,
1178 policy: &Policy,
1179 store: &Store,
1180 inspector: &mut I,
1181 ) -> Result<Upload, FormError> {
1182 let mut session = UploadSession::new(content_type, limits, policy, store)?;
1183 let mut buf = crate::wipe::ReadBuf::new(64 * 1024);
1184 loop {
1185 let n = match reader.read(&mut buf.buf) {
1186 Ok(n) => n,
1187 Err(e) if e.kind() == io::ErrorKind::Interrupted => {
1188 buf.failed();
1189 continue;
1190 }
1191 Err(e) => {
1192 buf.failed();
1193 let err = temp::io(&e);
1194 inspector.failed(&err);
1195 return Err(err);
1196 }
1197 };
1198 if n == 0 {
1199 break;
1200 }
1201 buf.filled(n);
1202 session.push(buf.buf.get(..n).unwrap_or(&[]), inspector)?;
1203 }
1204 drop(buf);
1205 session.finish(inspector)
1206 }
1207}
1208
1209pub struct UploadSession {
1233 driver: Driver<Vec<u8>, StoreSink>,
1234 upload: Upload,
1235 declared: Option<Vec<u8>>,
1236 text_cap: usize,
1237 ended: bool,
1238 failed: bool,
1239}
1240
1241impl fmt::Debug for UploadSession {
1242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1243 f.debug_struct("UploadSession")
1244 .field("bytes_seen", &self.bytes_seen())
1245 .field("upload", &self.upload)
1246 .finish_non_exhaustive()
1247 }
1248}
1249
1250impl UploadSession {
1251 pub fn new(
1258 content_type: &[u8],
1259 limits: &Limits,
1260 policy: &Policy,
1261 store: &Store,
1262 ) -> Result<Self, FormError> {
1263 let boundary = crate::Boundary::parse(content_type)?;
1264 let parser = MultipartParser::new(boundary, limits, policy)?;
1265 Ok(UploadSession {
1266 driver: Driver::new(parser, store.sink(limits)),
1267 upload: Upload::default(),
1268 declared: None,
1269 text_cap: usize::try_from(limits.text_and_names()).unwrap_or(usize::MAX),
1270 ended: false,
1271 failed: false,
1272 })
1273 }
1274
1275 pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1282 self.feed(chunk, false, inspector)
1283 }
1284
1285 pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1292 if self.failed {
1293 return Err(FormError::Poisoned);
1294 }
1295 if !self.ended {
1296 self.feed(&[], true, inspector)?;
1297 }
1298 let mut upload = core::mem::take(&mut self.upload);
1299 upload.token = self.driver.parser().captured_token().map(String::from);
1300 Ok(upload)
1301 }
1302
1303 #[must_use]
1305 pub fn captured_token(&self) -> Option<&str> {
1306 self.driver.parser().captured_token()
1307 }
1308
1309 #[must_use]
1311 pub fn bytes_seen(&self) -> u64 {
1312 self.driver.parser().bytes_seen()
1313 }
1314
1315 fn feed<I: Inspector>(
1316 &mut self,
1317 chunk: &[u8],
1318 eof: bool,
1319 inspector: &mut I,
1320 ) -> Result<(), FormError> {
1321 if self.failed {
1322 return Err(FormError::Poisoned);
1323 }
1324 let r = self.feed_inner(chunk, eof, inspector);
1325 if let Err(e) = &r {
1326 self.failed = true;
1327 if !self.driver.has_failed() {
1330 inspector.failed(e);
1331 }
1332 self.upload.files.clear();
1334 }
1335 r
1336 }
1337
1338 fn feed_inner<I: Inspector>(
1339 &mut self,
1340 chunk: &[u8],
1341 eof: bool,
1342 inspector: &mut I,
1343 ) -> Result<(), FormError> {
1344 let mut pos = 0usize;
1345 loop {
1346 let rest = chunk.get(pos..).unwrap_or(&[]);
1347 let (n, progress) = self.driver.push(rest, eof, inspector)?;
1348 pos = pos.saturating_add(n);
1349 let mut stored_field = false;
1350 match progress {
1351 Progress::Field { name, value } => {
1352 let cap = self.text_cap;
1353 self.upload.push_field(name, value, cap);
1354 stored_field = true;
1355 }
1356 Progress::File(f) => self.upload.files.push(f),
1357 Progress::End => {
1358 self.ended = true;
1359 if pos >= chunk.len() {
1360 return Ok(());
1361 }
1362 }
1363 _ => {
1364 if pos >= chunk.len() {
1365 return Ok(());
1366 }
1367 }
1368 }
1369 if stored_field {
1370 self.check_last_field()?;
1371 }
1372 }
1373 }
1374
1375 fn check_last_field(&mut self) -> Result<(), FormError> {
1379 let Some(span) = self.upload.spans.last().copied() else {
1380 return Ok(());
1381 };
1382 let readable = match self.driver.current_head_charset() {
1383 Charset::Utf8 | Charset::Ascii => true,
1384 Charset::Other => false,
1385 Charset::Unspecified => self.declared.as_deref().is_none_or(|l| {
1386 l.eq_ignore_ascii_case(b"utf-8") || l.eq_ignore_ascii_case(b"us-ascii")
1387 }),
1388 };
1389 let at = Location::in_part(None, self.bytes_seen());
1390 if !readable {
1391 return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
1392 }
1393 let value = self
1394 .upload
1395 .text
1396 .get(span.value.0..span.value.0.saturating_add(span.value.1))
1397 .unwrap_or(&[]);
1398 if core::str::from_utf8(value).is_err() {
1399 return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
1400 }
1401 if self.upload.slice(span.name) == "_charset_" {
1402 self.declared = Some(value.to_vec());
1403 }
1404 Ok(())
1405 }
1406}
1407
1408pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1427 let store = Store::temp_lazy();
1428 Upload::parse_with(
1429 content_type,
1430 body,
1431 &Limits::DEFAULT,
1432 &Policy::DEFAULT,
1433 &store,
1434 &mut crate::NoInspector,
1435 )
1436}
1437
1438impl FileName<'_> {
1439 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
1463 pub fn sanitized(&self) -> Result<String, FormError> {
1464 let base = FileName::new(self.basename(), self.source());
1465 let text = base.html_unescaped()?;
1466 #[cfg(feature = "unicode")]
1467 let text = unicode_lang::normalize(text.as_ref(), unicode_lang::Form::Nfc);
1468 let text: &str = text.as_ref();
1469 if text.is_ascii() {
1470 return Ok(path_lib::sanitize(text));
1471 }
1472 let visible: String = text
1473 .chars()
1474 .map(|c| if is_default_ignorable(c) { '_' } else { c })
1475 .collect();
1476 Ok(path_lib::sanitize(visible))
1477 }
1478}
1479
1480const fn is_default_ignorable(c: char) -> bool {
1483 matches!(
1484 c,
1485 '\u{00AD}'
1486 | '\u{034F}'
1487 | '\u{061C}'
1488 | '\u{115F}'..='\u{1160}'
1489 | '\u{17B4}'..='\u{17B5}'
1490 | '\u{180B}'..='\u{180F}'
1491 | '\u{200B}'..='\u{200F}'
1492 | '\u{202A}'..='\u{202E}'
1493 | '\u{2060}'..='\u{206F}'
1494 | '\u{3164}'
1495 | '\u{FE00}'..='\u{FE0F}'
1496 | '\u{FEFF}'
1497 | '\u{FFA0}'
1498 | '\u{FFF0}'..='\u{FFF8}'
1499 | '\u{1BCA0}'..='\u{1BCA3}'
1500 | '\u{1D173}'..='\u{1D17A}'
1501 | '\u{E0000}'..='\u{E0FFF}'
1502 )
1503}
1504
1505impl crate::FromField for StoredFile {
1506 type Slot = Option<StoredFile>;
1507
1508 fn accept_file(
1509 slot: &mut Self::Slot,
1510 file: crate::FileValue<'_>,
1511 ) -> Result<(), crate::ValueError> {
1512 let stored = match file {
1513 crate::FileValue::Stored(f) => f,
1514 crate::FileValue::Buffered(part) => StoredFile::detached(
1515 part.name(),
1516 part.filename(),
1517 part.content_type().map(|c| c.raw()),
1518 part.bytes(),
1519 ),
1520 };
1521 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
1522 return Ok(());
1524 }
1525 if slot.is_some() {
1526 return Err(crate::ValueError::Duplicate);
1527 }
1528 *slot = Some(stored);
1529 Ok(())
1530 }
1531
1532 fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
1533 slot.ok_or(crate::ValueError::Missing)
1534 }
1535}