mod temp;
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use core::sync::atomic::{AtomicU64, Ordering};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use crate::error::{EncodingError, ExtractError, FormError, Limit, Location};
use crate::hook::{Inspector, Sink};
use crate::limits::{Limits, Policy};
use crate::multipart::driver::{Driver, Progress};
use crate::multipart::filename::{FileName, FileNameSource};
use crate::multipart::parser::{Charset, ContentType, MultipartParser, PartHead};
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StoreError {
QuotaExceeded,
NotADirectory,
DestinationExists,
}
impl StoreError {
pub(crate) const fn code(self) -> &'static str {
match self {
Self::QuotaExceeded => "store.quota_exceeded",
Self::NotADirectory => "store.not_a_directory",
Self::DestinationExists => "store.destination_exists",
}
}
pub(crate) const fn describe(self) -> &'static str {
match self {
Self::QuotaExceeded => "the shared upload budget is full; retry later",
Self::NotADirectory => "not a directory, or a link to one",
Self::DestinationExists => "the destination exists; nothing was overwritten",
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum SpillMode {
#[default]
Anonymous,
Named,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StoreStats {
pub files_in_memory: u64,
pub files_on_disk: u64,
pub bytes_in_memory: u64,
pub bytes_on_disk: u64,
pub files_spilled: u64,
pub cleanup_failures: u64,
}
#[derive(Default)]
struct Counters {
files_in_memory: AtomicU64,
files_on_disk: AtomicU64,
bytes_in_memory: AtomicU64,
bytes_on_disk: AtomicU64,
files_spilled: AtomicU64,
cleanup_failures: AtomicU64,
}
fn add(c: &AtomicU64, n: u64) {
c.fetch_add(n, Ordering::Relaxed);
}
fn sub(c: &AtomicU64, n: u64) {
let mut v = c.load(Ordering::Relaxed);
loop {
match c.compare_exchange_weak(v, v.saturating_sub(n), Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) => return,
Err(now) => v = now,
}
}
}
struct Shared {
dir: OnceLock<PathBuf>,
owns_dir: bool,
counters: Counters,
}
impl Drop for Shared {
fn drop(&mut self) {
if self.owns_dir {
if let Some(dir) = self.dir.get() {
let _ = std::fs::remove_dir(dir);
}
}
}
}
impl Shared {
fn dir(&self) -> Result<&Path, FormError> {
if let Some(d) = self.dir.get() {
return Ok(d);
}
let created = temp::create_private_dir()?;
if let Err(lost) = self.dir.set(created) {
let _ = std::fs::remove_dir(lost);
}
self.dir.get().map(PathBuf::as_path).ok_or(FormError::Io {
kind: io::ErrorKind::Other,
})
}
fn cleanup_failed(&self) {
add(&self.counters.cleanup_failures, 1);
}
}
#[derive(Clone)]
pub struct Store {
shared: Arc<Shared>,
memory_only: bool,
threshold: u64,
max_disk: Option<u64>,
mode: SpillMode,
}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Store")
.field("memory_only", &self.memory_only)
.field("threshold", &self.threshold)
.field("mode", &self.mode)
.field("stats", &self.stats())
.finish_non_exhaustive()
}
}
const DEFAULT_THRESHOLD: u64 = 64 * 1024;
impl Store {
fn with(dir: Option<PathBuf>, owns_dir: bool, memory_only: bool) -> Store {
let lock = OnceLock::new();
if let Some(d) = dir {
let _ = lock.set(d);
}
Store {
shared: Arc::new(Shared {
dir: lock,
owns_dir,
counters: Counters::default(),
}),
memory_only,
threshold: DEFAULT_THRESHOLD,
max_disk: None,
mode: SpillMode::Anonymous,
}
}
pub fn temp() -> Result<Store, FormError> {
let dir = temp::create_private_dir()?;
Ok(Store::with(Some(dir), true, false))
}
pub(crate) fn temp_lazy() -> Store {
Store::with(None, true, false)
}
#[must_use]
pub fn memory_only() -> Store {
let mut s = Store::with(None, false, true);
s.threshold = u64::MAX;
s
}
pub fn in_dir(dir: impl AsRef<Path>) -> Result<Store, FormError> {
let dir = dir.as_ref();
temp::check_dir(dir)?;
Ok(Store::with(Some(dir.to_owned()), false, false))
}
#[must_use]
pub fn memory_threshold(mut self, bytes: u64) -> Store {
if !self.memory_only {
self.threshold = bytes;
}
self
}
#[must_use]
pub fn max_disk_bytes(mut self, bytes: u64) -> Store {
self.max_disk = Some(bytes);
self
}
#[must_use]
pub fn mode(mut self, mode: SpillMode) -> Store {
self.mode = mode;
self
}
#[must_use]
pub fn dir(&self) -> Option<&Path> {
self.shared.dir.get().map(PathBuf::as_path)
}
#[must_use]
pub fn cleanup_failures(&self) -> u64 {
self.shared
.counters
.cleanup_failures
.load(Ordering::Relaxed)
}
#[must_use]
pub fn stats(&self) -> StoreStats {
let c = &self.shared.counters;
StoreStats {
files_in_memory: c.files_in_memory.load(Ordering::Relaxed),
files_on_disk: c.files_on_disk.load(Ordering::Relaxed),
bytes_in_memory: c.bytes_in_memory.load(Ordering::Relaxed),
bytes_on_disk: c.bytes_on_disk.load(Ordering::Relaxed),
files_spilled: c.files_spilled.load(Ordering::Relaxed),
cleanup_failures: c.cleanup_failures.load(Ordering::Relaxed),
}
}
#[must_use]
pub fn sink(&self, limits: &Limits) -> StoreSink {
StoreSink {
shared: Arc::clone(&self.shared),
memory_only: self.memory_only,
threshold: self.threshold,
mode: self.mode,
max_disk: self.max_disk.unwrap_or(limits.max_body_bytes),
used_disk: 0,
current: None,
}
}
}
struct Current {
name: String,
filename: Option<(Vec<u8>, FileNameSource)>,
content_type: Option<Vec<u8>>,
memory: Vec<u8>,
disk: Option<(File, Option<PathBuf>)>,
len: u64,
on_disk: u64,
}
pub struct StoreSink {
shared: Arc<Shared>,
memory_only: bool,
threshold: u64,
mode: SpillMode,
max_disk: u64,
used_disk: u64,
current: Option<Current>,
}
impl fmt::Debug for StoreSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StoreSink")
.field("used_disk", &self.used_disk)
.field("max_disk", &self.max_disk)
.finish_non_exhaustive()
}
}
impl StoreSink {
pub(crate) const fn used_disk(&self) -> u64 {
self.used_disk
}
pub(crate) const fn with_used_disk(mut self, bytes: u64) -> StoreSink {
self.used_disk = bytes;
self
}
fn disk_write(&mut self, bytes: &[u8]) -> Result<(), FormError> {
let used = self.used_disk.saturating_add(bytes.len() as u64);
if used > self.max_disk {
return Err(FormError::limit(Limit::Disk, Location::NONE));
}
let Some(cur) = self.current.as_mut() else {
return Ok(());
};
let Some((file, _)) = cur.disk.as_mut() else {
return Ok(());
};
file.write_all(bytes).map_err(|e| temp::io(&e))?;
self.used_disk = used;
cur.on_disk = cur.on_disk.saturating_add(bytes.len() as u64);
add(&self.shared.counters.bytes_on_disk, bytes.len() as u64);
Ok(())
}
fn spill(&mut self) -> Result<(), FormError> {
let dir = self.shared.dir()?.to_owned();
let anonymous = self.mode == SpillMode::Anonymous;
let (file, path) = temp::create_file(&dir, anonymous)?;
add(&self.shared.counters.files_on_disk, 1);
add(&self.shared.counters.files_spilled, 1);
let prefix = match self.current.as_mut() {
Some(cur) => {
cur.disk = Some((file, path));
core::mem::take(&mut cur.memory)
}
None => Vec::new(),
};
let mut prefix = prefix;
let r = self.disk_write(&prefix);
crate::wipe::wipe_vec(&mut prefix);
r
}
}
impl Sink for StoreSink {
type Output = StoredFile;
fn open(&mut self, head: &PartHead<'_>) -> Result<(), FormError> {
self.abort();
self.current = Some(Current {
name: String::from(head.name()),
filename: head.filename().map(|f| (f.raw().to_vec(), f.source())),
content_type: head.content_type().map(|c| c.raw().to_vec()),
memory: Vec::new(),
disk: None,
len: 0,
on_disk: 0,
});
Ok(())
}
fn write(&mut self, chunk: &[u8]) -> Result<(), FormError> {
let (on_disk, len) = match self.current.as_ref() {
Some(c) => (c.disk.is_some(), c.len),
None => return Ok(()),
};
let new_len = len.saturating_add(chunk.len() as u64);
if !on_disk && !self.memory_only && new_len > self.threshold {
self.spill()?;
}
let on_disk = self.current.as_ref().is_some_and(|c| c.disk.is_some());
if on_disk {
self.disk_write(chunk)?;
} else if let Some(cur) = self.current.as_mut() {
crate::wipe::append_wiping(&mut cur.memory, chunk, 4096, usize::MAX);
}
if let Some(cur) = self.current.as_mut() {
cur.len = new_len;
}
Ok(())
}
fn close(&mut self) -> Result<StoredFile, FormError> {
let Some(mut cur) = self.current.take() else {
return Err(FormError::Poisoned);
};
let place = match cur.disk.take() {
Some((file, path)) => Place::Disk {
file: Some(file),
path,
},
None => {
add(&self.shared.counters.files_in_memory, 1);
add(&self.shared.counters.bytes_in_memory, cur.len);
Place::Memory(core::mem::take(&mut cur.memory))
}
};
Ok(StoredFile {
name: core::mem::take(&mut cur.name),
filename: cur.filename.take(),
content_type: cur.content_type.take(),
len: cur.len,
place,
shared: Some(Arc::clone(&self.shared)),
})
}
fn abort(&mut self) {
if let Some(mut cur) = self.current.take() {
crate::wipe::wipe_vec(&mut cur.memory);
wipe_meta(&mut cur.name, &mut cur.filename, &mut cur.content_type);
if let Some((file, path)) = cur.disk.take() {
drop(file);
sub(&self.shared.counters.files_on_disk, 1);
sub(&self.shared.counters.bytes_on_disk, cur.on_disk);
if let Some(p) = path {
if std::fs::remove_file(&p).is_err() {
self.shared.cleanup_failed();
}
}
}
}
}
}
impl Drop for StoreSink {
fn drop(&mut self) {
self.abort();
}
}
enum Place {
Memory(Vec<u8>),
Disk {
file: Option<File>,
path: Option<PathBuf>,
},
Gone,
}
#[must_use]
pub struct StoredFile {
name: String,
filename: Option<(Vec<u8>, FileNameSource)>,
content_type: Option<Vec<u8>>,
len: u64,
place: Place,
shared: Option<Arc<Shared>>,
}
impl fmt::Debug for StoredFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StoredFile")
.field("name", &self.name)
.field("len", &self.len)
.field("in_memory", &self.is_in_memory())
.finish_non_exhaustive()
}
}
impl StoredFile {
pub(crate) fn detached(
name: &str,
filename: Option<FileName<'_>>,
content_type: Option<&[u8]>,
bytes: &[u8],
) -> StoredFile {
StoredFile {
name: String::from(name),
filename: filename.map(|f| (f.raw().to_vec(), f.source())),
content_type: content_type.map(<[u8]>::to_vec),
len: bytes.len() as u64,
place: Place::Memory(bytes.to_vec()),
shared: None,
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn filename(&self) -> Option<FileName<'_>> {
self.filename
.as_ref()
.map(|(raw, source)| FileName::new(raw, *source))
}
#[must_use]
pub fn content_type(&self) -> Option<ContentType<'_>> {
self.content_type.as_deref().and_then(ContentType::parse)
}
#[must_use]
pub const fn len(&self) -> u64 {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub const fn is_in_memory(&self) -> bool {
matches!(self.place, Place::Memory(_))
}
#[must_use]
pub fn bytes(&self) -> Option<&[u8]> {
match &self.place {
Place::Memory(b) => Some(b),
_ => None,
}
}
pub fn reader(&self) -> Result<StoredReader<'_>, FormError> {
match &self.place {
Place::Gone => Err(FormError::Io {
kind: io::ErrorKind::NotFound,
}),
_ => Ok(StoredReader { file: self, pos: 0 }),
}
}
pub fn read_to_vec(&self, max: u64) -> Result<Vec<u8>, FormError> {
if self.len > max {
return Err(FormError::limit(Limit::FileSize, Location::NONE));
}
let mut out = Vec::with_capacity(usize::try_from(self.len).unwrap_or(0));
self.reader()?
.read_to_end(&mut out)
.map_err(|e| temp::io(&e))?;
Ok(out)
}
#[must_use]
pub fn path(&self) -> Option<&Path> {
match &self.place {
Place::Disk { path, .. } => path.as_deref(),
_ => None,
}
}
fn read_at(&self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
match &self.place {
Place::Memory(b) => {
let start = usize::try_from(pos).unwrap_or(usize::MAX).min(b.len());
let src = b.get(start..).unwrap_or(&[]);
let n = src.len().min(buf.len());
if let (Some(dst), Some(src)) = (buf.get_mut(..n), src.get(..n)) {
dst.copy_from_slice(src);
}
Ok(n)
}
Place::Disk { file: Some(f), .. } => temp::read_at(f, buf, pos),
_ => Err(io::Error::from(io::ErrorKind::NotFound)),
}
}
pub fn persist(mut self, dest: &Path) -> Result<(), FormError> {
let exists = |e: io::Error| {
if e.kind() == io::ErrorKind::AlreadyExists {
FormError::Store {
reason: StoreError::DestinationExists,
}
} else {
temp::io(&e)
}
};
if let Place::Disk {
file,
path: Some(p),
} = &mut self.place
{
drop(file.take());
match std::fs::hard_link(&*p, dest) {
Ok(()) => {
let p = p.clone();
self.place = Place::Gone;
self.release();
if std::fs::remove_file(&p).is_err() {
if let Some(s) = &self.shared {
s.cleanup_failed();
}
}
return Ok(());
}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => return Err(exists(e)),
Err(_) => {
let f = File::open(&*p).map_err(|e| temp::io(&e))?;
*file = Some(f);
}
}
}
let mut out = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(dest)
.map_err(exists)?;
let mut buf = [0u8; 64 * 1024];
let mut pos = 0u64;
let copied = loop {
let n = match self.read_at(&mut buf, pos) {
Ok(0) => break Ok(()),
Ok(n) => n,
Err(e) => break Err(temp::io(&e)),
};
if let Err(e) = out.write_all(buf.get(..n).unwrap_or(&[])) {
break Err(temp::io(&e));
}
pos = pos.saturating_add(n as u64);
};
crate::wipe::wipe(&mut buf);
if copied.is_err() {
drop(out);
let _ = std::fs::remove_file(dest);
}
copied
}
fn release(&mut self) {
if let Some(s) = &self.shared {
match &self.place {
Place::Memory(_) => {
sub(&s.counters.files_in_memory, 1);
sub(&s.counters.bytes_in_memory, self.len);
}
_ => {
sub(&s.counters.files_on_disk, 1);
sub(&s.counters.bytes_on_disk, self.len);
}
}
}
}
}
fn wipe_meta(
name: &mut String,
filename: &mut Option<(Vec<u8>, FileNameSource)>,
content_type: &mut Option<Vec<u8>>,
) {
crate::wipe::wipe_string(name);
if let Some((mut f, _)) = filename.take() {
crate::wipe::wipe_vec(&mut f);
}
if let Some(mut c) = content_type.take() {
crate::wipe::wipe_vec(&mut c);
}
}
impl Drop for StoredFile {
fn drop(&mut self) {
wipe_meta(&mut self.name, &mut self.filename, &mut self.content_type);
match core::mem::replace(&mut self.place, Place::Gone) {
Place::Memory(mut b) => {
self.place = Place::Memory(Vec::new());
self.release();
crate::wipe::wipe_vec(&mut b);
}
Place::Disk { file, path } => {
self.place = Place::Disk {
file: None,
path: None,
};
self.release();
drop(file);
if let Some(p) = path {
if std::fs::remove_file(&p).is_err() {
if let Some(s) = &self.shared {
s.cleanup_failed();
}
}
}
}
Place::Gone => {}
}
}
}
#[derive(Debug)]
pub struct StoredReader<'f> {
file: &'f StoredFile,
pos: u64,
}
impl Read for StoredReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.file.read_at(buf, self.pos)?;
self.pos = self.pos.saturating_add(n as u64);
Ok(n)
}
}
impl Seek for StoredReader<'_> {
fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
let target = match to {
SeekFrom::Start(n) => Some(n),
SeekFrom::End(d) => self.file.len.checked_add_signed(d),
SeekFrom::Current(d) => self.pos.checked_add_signed(d),
};
match target {
Some(t) => {
self.pos = t;
Ok(t)
}
None => Err(io::Error::from(io::ErrorKind::InvalidInput)),
}
}
}
#[derive(Clone, Copy)]
struct Span {
name: (usize, usize),
value: (usize, usize),
}
#[must_use]
#[derive(Default)]
pub struct Upload {
text: Vec<u8>,
spans: Vec<Span>,
files: Vec<StoredFile>,
token: Option<String>,
}
impl fmt::Debug for Upload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Upload")
.field("fields", &self.spans.len())
.field("text_len", &self.text_len())
.field("files", &self.files)
.finish_non_exhaustive()
}
}
impl Drop for Upload {
fn drop(&mut self) {
crate::wipe::wipe_vec(&mut self.text);
if let Some(t) = self.token.as_mut() {
crate::wipe::wipe_string(t);
}
}
}
impl Upload {
fn slice(&self, (start, len): (usize, usize)) -> &str {
let b = self
.text
.get(start..start.saturating_add(len))
.unwrap_or(&[]);
core::str::from_utf8(b).unwrap_or("")
}
fn push_field(&mut self, name: &str, value: &[u8], cap: usize) {
let n0 = self.text.len();
crate::wipe::append_wiping(&mut self.text, name.as_bytes(), 64 * 1024, cap);
let v0 = self.text.len();
crate::wipe::append_wiping(&mut self.text, value, 64 * 1024, cap);
self.spans.push(Span {
name: (n0, name.len()),
value: (v0, value.len()),
});
}
#[must_use]
pub fn field(&self, name: &str) -> Option<&str> {
self.spans
.iter()
.find(|s| self.slice(s.name) == name)
.map(|s| self.slice(s.value))
}
pub fn field_one(&self, name: &str) -> Result<Option<&str>, FormError> {
let mut found = None;
for s in self.spans.iter().filter(|s| self.slice(s.name) == name) {
if found.is_some() {
return Err(FormError::Extract {
reason: ExtractError::Duplicate,
field: None,
});
}
found = Some(self.slice(s.value));
}
Ok(found)
}
pub fn fields<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u str> + 'u {
self.spans
.iter()
.filter(move |s| self.slice(s.name) == name)
.map(move |s| self.slice(s.value))
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.spans
.iter()
.map(|s| (self.slice(s.name), self.slice(s.value)))
}
#[must_use]
pub fn file(&self, name: &str) -> Option<&StoredFile> {
self.files.iter().find(|f| f.name() == name)
}
pub fn files<'u>(&'u self, name: &'u str) -> impl Iterator<Item = &'u StoredFile> + 'u {
self.files.iter().filter(move |f| f.name() == name)
}
pub fn take_file(&mut self, name: &str) -> Option<StoredFile> {
let i = self.files.iter().position(|f| f.name() == name)?;
Some(self.files.remove(i))
}
#[must_use]
pub fn into_files(mut self) -> Vec<StoredFile> {
core::mem::take(&mut self.files)
}
pub(crate) fn into_parts(mut self) -> (Upload, Vec<StoredFile>) {
let files = core::mem::take(&mut self.files);
(self, files)
}
#[must_use]
pub fn captured_token(&self) -> Option<&str> {
self.token.as_deref()
}
#[must_use]
pub fn text_len(&self) -> u64 {
self.spans.iter().map(|s| s.value.1 as u64).sum()
}
#[must_use]
pub fn file_count(&self) -> usize {
self.files.len()
}
pub fn parse_with<R: Read, I: Inspector>(
content_type: &[u8],
mut reader: R,
limits: &Limits,
policy: &Policy,
store: &Store,
inspector: &mut I,
) -> Result<Upload, FormError> {
let mut session = UploadSession::new(content_type, limits, policy, store)?;
let mut buf = crate::wipe::WipeVec(alloc::vec![0u8; 64 * 1024]);
loop {
let n = match reader.read(&mut buf) {
Ok(n) => n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => {
let err = temp::io(&e);
inspector.failed(&err);
return Err(err);
}
};
if n == 0 {
break;
}
session.push(buf.get(..n).unwrap_or(&[]), inspector)?;
}
drop(buf);
session.finish(inspector)
}
}
pub struct UploadSession {
driver: Driver<Vec<u8>, StoreSink>,
upload: Upload,
declared: Option<Vec<u8>>,
text_cap: usize,
ended: bool,
failed: bool,
}
impl fmt::Debug for UploadSession {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UploadSession")
.field("bytes_seen", &self.bytes_seen())
.field("upload", &self.upload)
.finish_non_exhaustive()
}
}
impl UploadSession {
pub fn new(
content_type: &[u8],
limits: &Limits,
policy: &Policy,
store: &Store,
) -> Result<Self, FormError> {
let boundary = crate::Boundary::parse(content_type)?;
let parser = MultipartParser::new(boundary, limits, policy)?;
Ok(UploadSession {
driver: Driver::new(parser, store.sink(limits)),
upload: Upload::default(),
declared: None,
text_cap: usize::try_from(limits.max_text_bytes.saturating_mul(2))
.unwrap_or(usize::MAX),
ended: false,
failed: false,
})
}
pub fn push<I: Inspector>(&mut self, chunk: &[u8], inspector: &mut I) -> Result<(), FormError> {
self.feed(chunk, false, inspector)
}
pub fn finish<I: Inspector>(mut self, inspector: &mut I) -> Result<Upload, FormError> {
if self.failed {
return Err(FormError::Poisoned);
}
if !self.ended {
self.feed(&[], true, inspector)?;
}
let mut upload = core::mem::take(&mut self.upload);
upload.token = self.driver.parser().captured_token().map(String::from);
Ok(upload)
}
#[must_use]
pub fn captured_token(&self) -> Option<&str> {
self.driver.parser().captured_token()
}
#[must_use]
pub fn bytes_seen(&self) -> u64 {
self.driver.parser().bytes_seen()
}
fn feed<I: Inspector>(
&mut self,
chunk: &[u8],
eof: bool,
inspector: &mut I,
) -> Result<(), FormError> {
if self.failed {
return Err(FormError::Poisoned);
}
let r = self.feed_inner(chunk, eof, inspector);
if let Err(e) = &r {
self.failed = true;
if !self.driver.has_failed() {
inspector.failed(e);
}
self.upload.files.clear();
}
r
}
fn feed_inner<I: Inspector>(
&mut self,
chunk: &[u8],
eof: bool,
inspector: &mut I,
) -> Result<(), FormError> {
let mut pos = 0usize;
loop {
let rest = chunk.get(pos..).unwrap_or(&[]);
let (n, progress) = self.driver.push(rest, eof, inspector)?;
pos = pos.saturating_add(n);
let mut stored_field = false;
match progress {
Progress::Field { name, value } => {
let cap = self.text_cap;
self.upload.push_field(name, value, cap);
stored_field = true;
}
Progress::File(f) => self.upload.files.push(f),
Progress::End => {
self.ended = true;
if pos >= chunk.len() {
return Ok(());
}
}
_ => {
if pos >= chunk.len() {
return Ok(());
}
}
}
if stored_field {
self.check_last_field()?;
}
}
}
fn check_last_field(&mut self) -> Result<(), FormError> {
let Some(span) = self.upload.spans.last().copied() else {
return Ok(());
};
let readable = match self.driver.current_head_charset() {
Charset::Utf8 | Charset::Ascii => true,
Charset::Other => false,
Charset::Unspecified => self.declared.as_deref().is_none_or(|l| {
l.eq_ignore_ascii_case(b"utf-8") || l.eq_ignore_ascii_case(b"us-ascii")
}),
};
let at = Location::in_part(None, self.bytes_seen());
if !readable {
return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
}
let value = self
.upload
.text
.get(span.value.0..span.value.0.saturating_add(span.value.1))
.unwrap_or(&[]);
if core::str::from_utf8(value).is_err() {
return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
}
if self.upload.slice(span.name) == "_charset_" {
self.declared = Some(value.to_vec());
}
Ok(())
}
}
pub fn read_multipart<R: Read>(content_type: &[u8], body: R) -> Result<Upload, FormError> {
let store = Store::temp_lazy();
Upload::parse_with(
content_type,
body,
&Limits::DEFAULT,
&Policy::DEFAULT,
&store,
&mut crate::NoInspector,
)
}
impl FileName<'_> {
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
pub fn sanitized(&self) -> Result<String, FormError> {
let base = FileName::new(self.basename(), self.source());
let text = base.html_unescaped()?;
let text: &str = text.as_ref();
if text.is_ascii() {
return Ok(path_lib::sanitize(text));
}
let visible: String = text
.chars()
.map(|c| if is_default_ignorable(c) { '_' } else { c })
.collect();
Ok(path_lib::sanitize(visible))
}
}
const fn is_default_ignorable(c: char) -> bool {
matches!(
c,
'\u{00AD}'
| '\u{034F}'
| '\u{061C}'
| '\u{115F}'..='\u{1160}'
| '\u{17B4}'..='\u{17B5}'
| '\u{180B}'..='\u{180F}'
| '\u{200B}'..='\u{200F}'
| '\u{202A}'..='\u{202E}'
| '\u{2060}'..='\u{206F}'
| '\u{3164}'
| '\u{FE00}'..='\u{FE0F}'
| '\u{FEFF}'
| '\u{FFA0}'
| '\u{FFF0}'..='\u{FFF8}'
| '\u{1BCA0}'..='\u{1BCA3}'
| '\u{1D173}'..='\u{1D17A}'
| '\u{E0000}'..='\u{E0FFF}'
)
}
impl crate::FromField for StoredFile {
type Slot = Option<StoredFile>;
fn accept_file(
slot: &mut Self::Slot,
file: crate::FileValue<'_>,
) -> Result<(), crate::ValueError> {
let stored = match file {
crate::FileValue::Stored(f) => f,
crate::FileValue::Buffered(part) => StoredFile::detached(
part.name(),
part.filename(),
part.content_type().map(|c| c.raw()),
part.bytes(),
),
};
if stored.is_empty() && stored.filename().is_some_and(|n| n.is_empty()) {
return Ok(());
}
if slot.is_some() {
return Err(crate::ValueError::Duplicate);
}
*slot = Some(stored);
Ok(())
}
fn finish(slot: Self::Slot) -> Result<Self, crate::ValueError> {
slot.ok_or(crate::ValueError::Missing)
}
}