1mod temp;
9
10use alloc::borrow::ToOwned;
11use alloc::string::String;
12use alloc::sync::Arc;
13use alloc::vec::Vec;
14use core::fmt;
15use core::sync::atomic::{AtomicU64, Ordering};
16use std::fs::File;
17use std::io::{self, Read, Seek, SeekFrom, Write};
18use std::path::{Path, PathBuf};
19use std::sync::OnceLock;
20
21use crate::error::{EncodingError, ExtractError, FormError, Limit, Location};
22use crate::hook::{Inspector, Sink};
23use crate::limits::{Limits, Policy};
24use crate::multipart::driver::{Driver, Progress};
25use crate::multipart::filename::{FileName, FileNameSource};
26use crate::multipart::parser::{Charset, ContentType, MultipartParser, PartHead};
27
28#[non_exhaustive]
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum StoreError {
32 QuotaExceeded,
34 NotADirectory,
36 DestinationExists,
38}
39
40impl StoreError {
41 pub(crate) const fn code(self) -> &'static str {
42 match self {
43 Self::QuotaExceeded => "store.quota_exceeded",
44 Self::NotADirectory => "store.not_a_directory",
45 Self::DestinationExists => "store.destination_exists",
46 }
47 }
48
49 pub(crate) const fn describe(self) -> &'static str {
50 match self {
51 Self::QuotaExceeded => "the shared upload budget is full; retry later",
52 Self::NotADirectory => "not a directory, or a link to one",
53 Self::DestinationExists => "the destination exists; nothing was overwritten",
54 }
55 }
56}
57
58#[non_exhaustive]
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
68pub enum SpillMode {
69 #[default]
71 Anonymous,
72 Named,
74}
75
76#[non_exhaustive]
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub struct StoreStats {
80 pub files_in_memory: u64,
82 pub files_on_disk: u64,
84 pub bytes_in_memory: u64,
86 pub bytes_on_disk: u64,
88 pub files_spilled: u64,
90 pub cleanup_failures: u64,
92}
93
94#[derive(Default)]
95struct Counters {
96 files_in_memory: AtomicU64,
97 files_on_disk: AtomicU64,
98 bytes_in_memory: AtomicU64,
99 bytes_on_disk: AtomicU64,
100 files_spilled: AtomicU64,
101 cleanup_failures: AtomicU64,
102}
103
104fn add(c: &AtomicU64, n: u64) {
105 c.fetch_add(n, Ordering::Relaxed);
106}
107
108fn sub(c: &AtomicU64, n: u64) {
109 let mut v = c.load(Ordering::Relaxed);
111 loop {
112 match c.compare_exchange_weak(v, v.saturating_sub(n), Ordering::Relaxed, Ordering::Relaxed)
113 {
114 Ok(_) => return,
115 Err(now) => v = now,
116 }
117 }
118}
119
120struct Shared {
124 dir: OnceLock<PathBuf>,
125 owns_dir: bool,
126 counters: Counters,
127}
128
129impl Drop for Shared {
130 fn drop(&mut self) {
131 if self.owns_dir {
132 if let Some(dir) = self.dir.get() {
133 let _ = std::fs::remove_dir(dir);
134 }
135 }
136 }
137}
138
139impl Shared {
140 fn dir(&self) -> Result<&Path, FormError> {
141 if let Some(d) = self.dir.get() {
142 return Ok(d);
143 }
144 let created = temp::create_private_dir()?;
145 if let Err(lost) = self.dir.set(created) {
146 let _ = std::fs::remove_dir(lost);
148 }
149 self.dir.get().map(PathBuf::as_path).ok_or(FormError::Io {
150 kind: io::ErrorKind::Other,
151 })
152 }
153
154 fn cleanup_failed(&self) {
155 add(&self.counters.cleanup_failures, 1);
156 }
157}
158
159#[derive(Clone)]
177pub struct Store {
178 shared: Arc<Shared>,
179 memory_only: bool,
180 threshold: u64,
181 max_disk: Option<u64>,
182 mode: SpillMode,
183}
184
185impl fmt::Debug for Store {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.debug_struct("Store")
188 .field("memory_only", &self.memory_only)
189 .field("threshold", &self.threshold)
190 .field("mode", &self.mode)
191 .field("stats", &self.stats())
192 .finish_non_exhaustive()
193 }
194}
195
196const DEFAULT_THRESHOLD: u64 = 64 * 1024;
197
198impl Store {
199 fn with(dir: Option<PathBuf>, owns_dir: bool, memory_only: bool) -> Store {
200 let lock = OnceLock::new();
201 if let Some(d) = dir {
202 let _ = lock.set(d);
203 }
204 Store {
205 shared: Arc::new(Shared {
206 dir: lock,
207 owns_dir,
208 counters: Counters::default(),
209 }),
210 memory_only,
211 threshold: DEFAULT_THRESHOLD,
212 max_disk: None,
213 mode: SpillMode::Anonymous,
214 }
215 }
216
217 pub fn temp() -> Result<Store, FormError> {
227 let dir = temp::create_private_dir()?;
228 Ok(Store::with(Some(dir), true, false))
229 }
230
231 pub(crate) fn temp_lazy() -> Store {
234 Store::with(None, true, false)
235 }
236
237 #[must_use]
240 pub fn memory_only() -> Store {
241 let mut s = Store::with(None, false, true);
242 s.threshold = u64::MAX;
243 s
244 }
245
246 pub fn in_dir(dir: impl AsRef<Path>) -> Result<Store, FormError> {
262 let dir = dir.as_ref();
263 temp::check_dir(dir)?;
264 Ok(Store::with(Some(dir.to_owned()), false, false))
265 }
266
267 #[must_use]
270 pub fn memory_threshold(mut self, bytes: u64) -> Store {
271 if !self.memory_only {
272 self.threshold = bytes;
273 }
274 self
275 }
276
277 #[must_use]
280 pub fn max_disk_bytes(mut self, bytes: u64) -> Store {
281 self.max_disk = Some(bytes);
282 self
283 }
284
285 #[must_use]
287 pub fn mode(mut self, mode: SpillMode) -> Store {
288 self.mode = mode;
289 self
290 }
291
292 #[must_use]
294 pub fn dir(&self) -> Option<&Path> {
295 self.shared.dir.get().map(PathBuf::as_path)
296 }
297
298 #[must_use]
300 pub fn cleanup_failures(&self) -> u64 {
301 self.shared
302 .counters
303 .cleanup_failures
304 .load(Ordering::Relaxed)
305 }
306
307 #[must_use]
309 pub fn stats(&self) -> StoreStats {
310 let c = &self.shared.counters;
311 StoreStats {
312 files_in_memory: c.files_in_memory.load(Ordering::Relaxed),
313 files_on_disk: c.files_on_disk.load(Ordering::Relaxed),
314 bytes_in_memory: c.bytes_in_memory.load(Ordering::Relaxed),
315 bytes_on_disk: c.bytes_on_disk.load(Ordering::Relaxed),
316 files_spilled: c.files_spilled.load(Ordering::Relaxed),
317 cleanup_failures: c.cleanup_failures.load(Ordering::Relaxed),
318 }
319 }
320
321 #[must_use]
323 pub fn sink(&self, limits: &Limits) -> StoreSink {
324 StoreSink {
325 shared: Arc::clone(&self.shared),
326 memory_only: self.memory_only,
327 threshold: self.threshold,
328 mode: self.mode,
329 max_disk: self.max_disk.unwrap_or(limits.max_body_bytes),
330 used_disk: 0,
331 current: None,
332 }
333 }
334}
335
336struct Current {
338 name: String,
339 filename: Option<(Vec<u8>, FileNameSource)>,
340 content_type: Option<Vec<u8>>,
341 memory: Vec<u8>,
342 disk: Option<(File, Option<PathBuf>)>,
343 len: u64,
344 on_disk: u64,
346}
347
348pub struct StoreSink {
350 shared: Arc<Shared>,
351 memory_only: bool,
352 threshold: u64,
353 mode: SpillMode,
354 max_disk: u64,
355 used_disk: u64,
356 current: Option<Current>,
357}
358
359impl fmt::Debug for StoreSink {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 f.debug_struct("StoreSink")
362 .field("used_disk", &self.used_disk)
363 .field("max_disk", &self.max_disk)
364 .finish_non_exhaustive()
365 }
366}
367
368impl StoreSink {
369 pub(crate) const fn used_disk(&self) -> u64 {
371 self.used_disk
372 }
373
374 pub(crate) const fn with_used_disk(mut self, bytes: u64) -> StoreSink {
376 self.used_disk = bytes;
377 self
378 }
379
380 fn disk_write(&mut self, bytes: &[u8]) -> Result<(), FormError> {
381 let used = self.used_disk.saturating_add(bytes.len() as u64);
382 if used > self.max_disk {
383 return Err(FormError::limit(Limit::Disk, Location::NONE));
384 }
385 let Some(cur) = self.current.as_mut() else {
386 return Ok(());
387 };
388 let Some((file, _)) = cur.disk.as_mut() else {
389 return Ok(());
390 };
391 file.write_all(bytes).map_err(|e| temp::io(&e))?;
392 self.used_disk = used;
393 cur.on_disk = cur.on_disk.saturating_add(bytes.len() as u64);
394 add(&self.shared.counters.bytes_on_disk, bytes.len() as u64);
395 Ok(())
396 }
397
398 fn spill(&mut self) -> Result<(), FormError> {
399 let dir = self.shared.dir()?.to_owned();
400 let anonymous = self.mode == SpillMode::Anonymous;
401 let (file, path) = temp::create_file(&dir, anonymous)?;
402 add(&self.shared.counters.files_on_disk, 1);
403 add(&self.shared.counters.files_spilled, 1);
404 let prefix = match self.current.as_mut() {
405 Some(cur) => {
406 cur.disk = Some((file, path));
407 core::mem::take(&mut cur.memory)
408 }
409 None => Vec::new(),
410 };
411 let mut prefix = prefix;
412 let r = self.disk_write(&prefix);
413 crate::wipe::wipe_vec(&mut prefix);
414 r
415 }
416}
417
418impl Sink for StoreSink {
419 type Output = StoredFile;
420
421 fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
422 self.abort();
423 self.current = Some(Current {
424 name: String::from(head.name()),
425 filename: head.filename().map(|f| (f.raw().to_vec(), f.source())),
426 content_type: head.content_type().map(|c| c.raw().to_vec()),
427 memory: Vec::new(),
428 disk: None,
429 len: 0,
430 on_disk: 0,
431 });
432 Ok(())
433 }
434
435 fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
436 let (on_disk, len) = match self.current.as_ref() {
437 Some(c) => (c.disk.is_some(), c.len),
438 None => return Ok(()),
439 };
440 let new_len = len.saturating_add(chunk.len() as u64);
441 if !on_disk && !self.memory_only && new_len > self.threshold {
442 self.spill()?;
443 }
444 let on_disk = self.current.as_ref().is_some_and(|c| c.disk.is_some());
445 if on_disk {
446 self.disk_write(chunk)?;
447 } else if let Some(cur) = self.current.as_mut() {
448 crate::wipe::append_wiping(&mut cur.memory, chunk, 4096, usize::MAX);
449 }
450 if let Some(cur) = self.current.as_mut() {
451 cur.len = new_len;
452 }
453 Ok(())
454 }
455
456 fn close(&mut self) -> Result<StoredFile, FormError> {
457 let Some(mut cur) = self.current.take() else {
458 return Err(FormError::Poisoned);
459 };
460 let place = match cur.disk.take() {
461 Some((file, path)) => Place::Disk {
462 file: Some(file),
463 path,
464 },
465 None => {
466 add(&self.shared.counters.files_in_memory, 1);
467 add(&self.shared.counters.bytes_in_memory, cur.len);
468 Place::Memory(core::mem::take(&mut cur.memory))
469 }
470 };
471 Ok(StoredFile {
472 name: core::mem::take(&mut cur.name),
473 filename: cur.filename.take(),
474 content_type: cur.content_type.take(),
475 len: cur.len,
476 place,
477 shared: Some(Arc::clone(&self.shared)),
478 })
479 }
480
481 fn abort(&mut self) {
482 if let Some(mut cur) = self.current.take() {
483 crate::wipe::wipe_vec(&mut cur.memory);
484 wipe_meta(&mut cur.name, &mut cur.filename, &mut cur.content_type);
485 if let Some((file, path)) = cur.disk.take() {
486 drop(file);
489 sub(&self.shared.counters.files_on_disk, 1);
490 sub(&self.shared.counters.bytes_on_disk, cur.on_disk);
491 if let Some(p) = path {
492 if std::fs::remove_file(&p).is_err() {
493 self.shared.cleanup_failed();
494 }
495 }
496 }
497 }
498 }
499}
500
501impl Drop for StoreSink {
502 fn drop(&mut self) {
503 self.abort();
504 }
505}
506
507enum Place {
508 Memory(Vec<u8>),
509 Disk {
510 file: Option<File>,
511 path: Option<PathBuf>,
512 },
513 Gone,
514}
515
516#[must_use]
536pub struct StoredFile {
537 name: String,
538 filename: Option<(Vec<u8>, FileNameSource)>,
539 content_type: Option<Vec<u8>>,
540 len: u64,
541 place: Place,
542 shared: Option<Arc<Shared>>,
543}
544
545impl fmt::Debug for StoredFile {
546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547 f.debug_struct("StoredFile")
548 .field("name", &self.name)
549 .field("len", &self.len)
550 .field("in_memory", &self.is_in_memory())
551 .finish_non_exhaustive()
552 }
553}
554
555impl StoredFile {
556 pub(crate) fn detached(
558 name: &str,
559 filename: Option<FileName<'_>>,
560 content_type: Option<&[u8]>,
561 bytes: &[u8],
562 ) -> StoredFile {
563 StoredFile {
564 name: String::from(name),
565 filename: filename.map(|f| (f.raw().to_vec(), f.source())),
566 content_type: content_type.map(<[u8]>::to_vec),
567 len: bytes.len() as u64,
568 place: Place::Memory(bytes.to_vec()),
569 shared: None,
570 }
571 }
572
573 #[must_use]
575 pub fn name(&self) -> &str {
576 &self.name
577 }
578
579 #[must_use]
581 pub fn filename(&self) -> Option<FileName<'_>> {
582 self.filename
583 .as_ref()
584 .map(|(raw, source)| FileName::new(raw, *source))
585 }
586
587 #[must_use]
589 pub fn content_type(&self) -> Option<ContentType<'_>> {
590 self.content_type.as_deref().and_then(ContentType::parse)
591 }
592
593 #[must_use]
595 pub const fn len(&self) -> u64 {
596 self.len
597 }
598
599 #[must_use]
601 pub const fn is_empty(&self) -> bool {
602 self.len == 0
603 }
604
605 #[must_use]
607 pub const fn is_in_memory(&self) -> bool {
608 matches!(self.place, Place::Memory(_))
609 }
610
611 #[must_use]
613 pub fn bytes(&self) -> Option<&[u8]> {
614 match &self.place {
615 Place::Memory(b) => Some(b),
616 _ => None,
617 }
618 }
619
620 pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
627 match &self.place {
628 Place::Gone => Err(FormError::Io {
629 kind: io::ErrorKind::NotFound,
630 }),
631 _ => Ok(StoredReader { file: self, pos: 0 }),
632 }
633 }
634
635 pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
642 if self.len > max {
643 return Err(FormError::limit(Limit::FileSize, Location::NONE));
644 }
645 let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
646 self.reader()?
647 .read_to_end(&mut out)
648 .map_err(|e| temp::io(&e))?;
649 Ok(out)
650 }
651
652 #[must_use]
654 pub fn path(&self) -> Option<&Path> {
655 match &self.place {
656 Place::Disk { path, .. } => path.as_deref(),
657 _ => None,
658 }
659 }
660
661 fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
662 match &self.place {
663 Place::Memory(b) => {
664 let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
665 let src = b.get(start..).unwrap_or(&[]);
666 let n = src.len().min(buf.len());
667 if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
668 dst.copy_from_slice(src);
669 }
670 Ok(n)
671 }
672 Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
673 _ => Err(io::Error::from(io::ErrorKind::NotFound)),
674 }
675 }
676
677 pub fn persist(mut self, dest: &Path) -> Result<(), FormError> {
688 let exists = |e: io::Error| {
689 if e.kind() == io::ErrorKind::AlreadyExists {
690 FormError::Store {
691 reason: StoreError::DestinationExists,
692 }
693 } else {
694 temp::io(&e)
695 }
696 };
697 if let Place::Disk {
698 file,
699 path: Some(p),
700 } = &mut self.place
701 {
702 drop(file.take());
704 match std::fs::hard_link(&*p, dest) {
705 Ok(()) => {
706 let p = p.clone();
707 self.place = Place::Gone;
708 self.release();
709 if std::fs::remove_file(&p).is_err() {
710 if let Some(s) = &self.shared {
711 s.cleanup_failed();
712 }
713 }
714 return Ok(());
715 }
716 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => return Err(exists(e)),
717 Err(_) => {
718 let f = File::open(&*p).map_err(|e| temp::io(&e))?;
720 *file = Some(f);
721 }
722 }
723 }
724 let mut out = std::fs::OpenOptions::new()
725 .write(true)
726 .create_new(true)
727 .open(dest)
728 .map_err(exists)?;
729 let mut buf = [0u8; 64 * 1024];
730 let mut pos = 0u64;
731 let copied = loop {
732 let n = match self.read_at(&mut buf, pos) {
733 Ok(0) => break Ok(()),
734 Ok(n) => n,
735 Err(e) => break Err(temp::io(&e)),
736 };
737 if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
738 break Err(temp::io(&e));
739 }
740 pos = pos.saturating_add(n as u64);
741 };
742 crate::wipe::wipe(&mut buf);
743 if copied.is_err() {
744 drop(out);
746 let _ = std::fs::remove_file(dest);
747 }
748 copied
749 }
750
751 fn release(&mut self) {
753 if let Some(s) = &self.shared {
754 match &self.place {
755 Place::Memory(_) => {
756 sub(&s.counters.files_in_memory, 1);
757 sub(&s.counters.bytes_in_memory, self.len);
758 }
759 _ => {
760 sub(&s.counters.files_on_disk, 1);
761 sub(&s.counters.bytes_on_disk, self.len);
762 }
763 }
764 }
765 }
766}
767
768fn wipe_meta(
770 name: &mut String,
771 filename: &mut Option<(Vec<u8>, FileNameSource)>,
772 content_type: &mut Option<Vec<u8>>,
773) {
774 crate::wipe::wipe_string(name);
775 if let Some((mut f, _)) = filename.take() {
776 crate::wipe::wipe_vec(&mut f);
777 }
778 if let Some(mut c) = content_type.take() {
779 crate::wipe::wipe_vec(&mut c);
780 }
781}
782
783impl Drop for StoredFile {
784 fn drop(&mut self) {
785 wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
786 match core::mem::replace(&mut self.place, Place::Gone) {
787 Place::Memory(mut b) => {
788 self.place = Place::Memory(Vec::new());
789 self.release();
790 crate::wipe::wipe_vec(&mut b);
791 }
792 Place::Disk { file, path } => {
793 self.place = Place::Disk {
794 file: None,
795 path: None,
796 };
797 self.release();
798 drop(file);
799 if let Some(p) = path {
800 if std::fs::remove_file(&p).is_err() {
801 if let Some(s) = &self.shared {
802 s.cleanup_failed();
803 }
804 }
805 }
806 }
807 Place::Gone => {}
808 }
809 }
810}
811
812#[derive(Debug)]
814pub struct StoredReader<'f> {
815 file: &'f StoredFile,
816 pos: u64,
817}
818
819impl Read for StoredReader<'_> {
820 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
821 let n = self.file.read_at(buf, self.pos)?;
822 self.pos = self.pos.saturating_add(n as u64);
823 Ok(n)
824 }
825}
826
827impl Seek for StoredReader<'_> {
828 fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
829 let target = match to {
830 SeekFrom::Start(n) => Some(n),
831 SeekFrom::End(d) => self.file.len.checked_add_signed(d),
832 SeekFrom::Current(d) => self.pos.checked_add_signed(d),
833 };
834 match target {
835 Some(t) => {
836 self.pos = t;
837 Ok(t)
838 }
839 None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
840 }
841 }
842}
843
844#[derive(Clone, Copy)]
846struct Span {
847 name: (usize, usize),
848 value: (usize, usize),
849}
850
851#[must_use]
866#[derive(Default)]
867pub struct Upload {
868 text: Vec<u8>,
869 spans: Vec<Span>,
870 files: Vec<StoredFile>,
871 token: Option<String>,
872}
873
874impl fmt::Debug for Upload {
875 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
876 f.debug_struct("Upload")
877 .field("fields", &self.spans.len())
878 .field("text_len", &self.text_len())
879 .field("files", &self.files)
880 .finish_non_exhaustive()
881 }
882}
883
884impl Drop for Upload {
885 fn drop(&mut self) {
886 crate::wipe::wipe_vec(&mut self.text);
887 if let Some(t) = self.token.as_mut() {
888 crate::wipe::wipe_string(t);
889 }
890 }
891}
892
893impl Upload {
894 fn slice(&self, (start, len): (usize, usize)) -> &str {
895 let b = self
896 .text
897 .get(start..start.saturating_add(len))
898 .unwrap_or(&[]);
899 core::str::from_utf8(b).unwrap_or("")
901 }
902
903 fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
904 let n0 = self.text.len();
905 crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 64 * 1024, cap);
906 let v0 = self.text.len();
907 crate::wipe::append_wiping(&mut self.text, value, 64 * 1024, cap);
908 self.spans.push(Span {
909 name: (n0, name.len()),
910 value: (v0, value.len()),
911 });
912 }
913
914 #[must_use]
916 pub fn field(&self, name: &str) -> Option<&str> {
917 self.spans
918 .iter()
919 .find(|s| self.slice(s.name) == name)
920 .map(|s| self.slice(s.value))
921 }
922
923 pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
929 let mut found = None;
930 for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
931 if found.is_some() {
932 return Err(FormError::Extract {
933 reason: ExtractError::Duplicate,
934 field: None,
935 });
936 }
937 found = Some(self.slice(s.value));
938 }
939 Ok(found)
940 }
941
942 pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
944 self.spans
945 .iter()
946 .filter(move |s| self.slice(s.name) == name)
947 .map(move |s| self.slice(s.value))
948 }
949
950 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
952 self.spans
953 .iter()
954 .map(|s| (self.slice(s.name), self.slice(s.value)))
955 }
956
957 #[must_use]
959 pub fn file(&self, name: &str) -> Option<&StoredFile> {
960 self.files.iter().find(|f| f.name() == name)
961 }
962
963 pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
965 self.files.iter().filter(move |f| f.name() == name)
966 }
967
968 pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
970 let i = self.files.iter().position(|f| f.name() == name)?;
971 Some(self.files.remove(i))
972 }
973
974 #[must_use]
976 pub fn into_files(mut self) -> Vec<StoredFile> {
977 core::mem::take(&mut self.files)
978 }
979
980 pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
981 let files = core::mem::take(&mut self.files);
982 (self, files)
983 }
984
985 #[must_use]
987 pub fn captured_token(&self) -> Option<&str> {
988 self.token.as_deref()
989 }
990
991 #[must_use]
993 pub fn text_len(&self) -> u64 {
994 self.spans.iter().map(|s| s.value.1 as u64).sum()
995 }
996
997 #[must_use]
999 pub fn file_count(&self) -> usize {
1000 self.files.len()
1001 }
1002
1003 pub fn parse_with<R: Read, I: Inspector>(
1024 content_type: &[u8],
1025 mut reader: R,
1026 limits: &Limits,
1027 policy: &Policy,
1028 store: &Store,
1029 inspector: &mut I,
1030 ) -> Result<Upload, FormError> {
1031 let mut session = UploadSession::new(content_type, limits, policy, store)?;
1032 let mut buf = crate::wipe::WipeVec(alloc::vec![0u8; 64 * 1024]);
1033 loop {
1034 let n = match reader.read(&mut buf) {
1035 Ok(n) => n,
1036 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1037 Err(e) => {
1038 let err = temp::io(&e);
1039 inspector.failed(&err);
1040 return Err(err);
1041 }
1042 };
1043 if n == 0 {
1044 break;
1045 }
1046 session.push(buf.get(..n).unwrap_or(&[]), inspector)?;
1047 }
1048 drop(buf);
1049 session.finish(inspector)
1050 }
1051}
1052
1053pub struct UploadSession {
1077 driver: Driver<Vec<u8>, StoreSink>,
1078 upload: Upload,
1079 declared: Option<Vec<u8>>,
1080 text_cap: usize,
1081 ended: bool,
1082 failed: bool,
1083}
1084
1085impl fmt::Debug for UploadSession {
1086 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1087 f.debug_struct("UploadSession")
1088 .field("bytes_seen", &self.bytes_seen())
1089 .field("upload", &self.upload)
1090 .finish_non_exhaustive()
1091 }
1092}
1093
1094impl UploadSession {
1095 pub fn new(
1102 content_type: &[u8],
1103 limits: &Limits,
1104 policy: &Policy,
1105 store: &Store,
1106 ) -> Result<Self, FormError> {
1107 let boundary = crate::Boundary::parse(content_type)?;
1108 let parser = MultipartParser::new(boundary, limits, policy)?;
1109 Ok(UploadSession {
1110 driver: Driver::new(parser, store.sink(limits)),
1111 upload: Upload::default(),
1112 declared: None,
1113 text_cap: usize::try_from(limits.max_text_bytes.saturating_mul(2))
1114 .unwrap_or(usize::MAX),
1115 ended: false,
1116 failed: false,
1117 })
1118 }
1119
1120 pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
1127 self.feed(chunk, false, inspector)
1128 }
1129
1130 pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
1137 if self.failed {
1138 return Err(FormError::Poisoned);
1139 }
1140 if !self.ended {
1141 self.feed(&[], true, inspector)?;
1142 }
1143 let mut upload = core::mem::take(&mut self.upload);
1144 upload.token = self.driver.parser().captured_token().map(String::from);
1145 Ok(upload)
1146 }
1147
1148 #[must_use]
1150 pub fn captured_token(&self) -> Option<&str> {
1151 self.driver.parser().captured_token()
1152 }
1153
1154 #[must_use]
1156 pub fn bytes_seen(&self) -> u64 {
1157 self.driver.parser().bytes_seen()
1158 }
1159
1160 fn feed<I: Inspector>(
1161 &mut self,
1162 chunk: &[u8],
1163 eof: bool,
1164 inspector: &mut I,
1165 ) -> Result<(), FormError> {
1166 if self.failed {
1167 return Err(FormError::Poisoned);
1168 }
1169 let r = self.feed_inner(chunk, eof, inspector);
1170 if let Err(e) = &r {
1171 self.failed = true;
1172 if !self.driver.has_failed() {
1175 inspector.failed(e);
1176 }
1177 self.upload.files.clear();
1179 }
1180 r
1181 }
1182
1183 fn feed_inner<I: Inspector>(
1184 &mut self,
1185 chunk: &[u8],
1186 eof: bool,
1187 inspector: &mut I,
1188 ) -> Result<(), FormError> {
1189 let mut pos = 0usize;
1190 loop {
1191 let rest = chunk.get(pos..).unwrap_or(&[]);
1192 let (n, progress) = self.driver.push(rest, eof, inspector)?;
1193 pos = pos.saturating_add(n);
1194 let mut stored_field = false;
1195 match progress {
1196 Progress::Field { name, value } => {
1197 let cap = self.text_cap;
1198 self.upload.push_field(name, value, cap);
1199 stored_field = true;
1200 }
1201 Progress::File(f) => self.upload.files.push(f),
1202 Progress::End => {
1203 self.ended = true;
1204 if pos >= chunk.len() {
1205 return Ok(());
1206 }
1207 }
1208 _ => {
1209 if pos >= chunk.len() {
1210 return Ok(());
1211 }
1212 }
1213 }
1214 if stored_field {
1215 self.check_last_field()?;
1216 }
1217 }
1218 }
1219
1220 fn check_last_field(&mut self) -> Result<(), FormError> {
1224 let Some(span) = self.upload.spans.last().copied() else {
1225 return Ok(());
1226 };
1227 let readable = match self.driver.current_head_charset() {
1228 Charset::Utf8 | Charset::Ascii => true,
1229 Charset::Other => false,
1230 Charset::Unspecified => self.declared.as_deref().is_none_or(|l| {
1231 l.eq_ignore_ascii_case(b"utf-8") || l.eq_ignore_ascii_case(b"us-ascii")
1232 }),
1233 };
1234 let at = Location::in_part(None, self.bytes_seen());
1235 if !readable {
1236 return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
1237 }
1238 let value = self
1239 .upload
1240 .text
1241 .get(span.value.0..span.value.0.saturating_add(span.value.1))
1242 .unwrap_or(&[]);
1243 if core::str::from_utf8(value).is_err() {
1244 return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
1245 }
1246 if self.upload.slice(span.name) == "_charset_" {
1247 self.declared = Some(value.to_vec());
1248 }
1249 Ok(())
1250 }
1251}
1252
1253pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
1272 let store = Store::temp_lazy();
1273 Upload::parse_with(
1274 content_type,
1275 body,
1276 &Limits::DEFAULT,
1277 &Policy::DEFAULT,
1278 &store,
1279 &mut crate::NoInspector,
1280 )
1281}
1282
1283impl FileName<'_> {
1284 #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
1308 pub fn sanitized(&self) -> Result<String, FormError> {
1309 let base = FileName::new(self.basename(), self.source());
1310 let text = base.html_unescaped()?;
1311 let text: &str = text.as_ref();
1312 if text.is_ascii() {
1313 return Ok(path_lib::sanitize(text));
1314 }
1315 let visible: String = text
1316 .chars()
1317 .map(|c| if is_default_ignorable(c) { '_' } else { c })
1318 .collect();
1319 Ok(path_lib::sanitize(visible))
1320 }
1321}
1322
1323const fn is_default_ignorable(c: char) -> bool {
1326 matches!(
1327 c,
1328 '\u{00AD}'
1329 | '\u{034F}'
1330 | '\u{061C}'
1331 | '\u{115F}'..='\u{1160}'
1332 | '\u{17B4}'..='\u{17B5}'
1333 | '\u{180B}'..='\u{180F}'
1334 | '\u{200B}'..='\u{200F}'
1335 | '\u{202A}'..='\u{202E}'
1336 | '\u{2060}'..='\u{206F}'
1337 | '\u{3164}'
1338 | '\u{FE00}'..='\u{FE0F}'
1339 | '\u{FEFF}'
1340 | '\u{FFA0}'
1341 | '\u{FFF0}'..='\u{FFF8}'
1342 | '\u{1BCA0}'..='\u{1BCA3}'
1343 | '\u{1D173}'..='\u{1D17A}'
1344 | '\u{E0000}'..='\u{E0FFF}'
1345 )
1346}
1347
1348impl crate::FromField for StoredFile {
1349 type Slot = Option<StoredFile>;
1350
1351 fn accept_file(
1352 slot: &mut Self::Slot,
1353 file: crate::FileValue<'_>,
1354 ) -> Result<(), crate::ValueError> {
1355 let stored = match file {
1356 crate::FileValue::Stored(f) => f,
1357 crate::FileValue::Buffered(part) => StoredFile::detached(
1358 part.name(),
1359 part.filename(),
1360 part.content_type().map(|c| c.raw()),
1361 part.bytes(),
1362 ),
1363 };
1364 if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
1365 return Ok(());
1367 }
1368 if slot.is_some() {
1369 return Err(crate::ValueError::Duplicate);
1370 }
1371 *slot = Some(stored);
1372 Ok(())
1373 }
1374
1375 fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
1376 slot.ok_or(crate::ValueError::Missing)
1377 }
1378}