mod file;
mod macros;
mod secret;
mod value;
pub use file::FileData;
pub use secret::Secret;
use crate::error::{ExtractError, FormError};
use crate::hook::{Inspector, NoInspector};
use crate::kind::FormKind;
use crate::limits::{Limits, Policy};
use crate::multipart::Part;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ValueError {
Missing,
Duplicate,
Invalid(&'static str),
ExpectedFile,
ExpectedText,
}
#[non_exhaustive]
pub enum FileValue<'a> {
Buffered(&'a Part<'a>),
#[cfg(feature = "fs")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
Stored(crate::store::StoredFile),
}
impl core::fmt::Debug for FileValue<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FileValue::Buffered(p) => f.debug_tuple("Buffered").field(p).finish(),
#[cfg(feature = "fs")]
FileValue::Stored(s) => f.debug_tuple("Stored").field(s).finish(),
}
}
}
pub trait FromField: Sized {
type Slot: Default;
fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
let _ = (slot, value);
Err(ValueError::ExpectedFile)
}
fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
let _ = (slot, file);
Err(ValueError::ExpectedText)
}
fn finish(slot: Self::Slot) -> Result<Self, ValueError>;
}
pub trait FormBuilder {
type Output;
fn text(&mut self, name: &str, value: &str) -> Result<(), FormError>;
fn file(&mut self, name: &str, file: FileValue<'_>) -> Result<(), FormError>;
fn finish(self) -> Result<Self::Output, FormError>;
fn file_field(&self, name: &str) -> Option<bool> {
let _ = name;
Some(true)
}
fn finish_all(self, errors: &mut alloc::vec::Vec<FormError>) -> Option<Self::Output>
where
Self: Sized,
{
match self.finish() {
Ok(v) => Some(v),
Err(e) => {
errors.push(e);
None
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FormErrors(alloc::vec::Vec<FormError>);
impl FormErrors {
pub fn iter(&self) -> impl Iterator<Item = &FormError> {
self.0.iter()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn first(&self) -> Option<&FormError> {
self.0.first()
}
#[must_use]
pub fn field(&self, name: &str) -> Option<ExtractError> {
self.0.iter().find_map(|e| match e {
FormError::Extract {
reason,
field: Some(f),
} if *f == name => Some(*reason),
_ => None,
})
}
#[must_use]
pub fn unknown(&self) -> usize {
self.0
.iter()
.filter(|e| {
matches!(
e,
FormError::Extract {
reason: ExtractError::Unknown,
..
}
)
})
.count()
}
#[must_use]
pub fn status_hint(&self) -> u16 {
self.first().map_or(422, FormError::status_hint)
}
}
impl From<FormError> for FormErrors {
fn from(e: FormError) -> FormErrors {
FormErrors(alloc::vec![e])
}
}
impl core::fmt::Display for FormErrors {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.first() {
Some(e) if self.0.len() > 1 => {
write!(f, "{e} (and {} more)", self.0.len().saturating_sub(1))
}
Some(e) => write!(f, "{e}"),
None => f.write_str("no errors"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FormErrors {}
struct Collect<'e, B> {
inner: B,
errors: &'e mut alloc::vec::Vec<FormError>,
}
impl<B: FormBuilder> FormBuilder for Collect<'_, B> {
type Output = B::Output;
fn text(&mut self, name: &str, value: &str) -> Result<(), FormError> {
match self.inner.text(name, value) {
Err(e @ FormError::Extract { .. }) => {
self.errors.push(e);
Ok(())
}
other => other,
}
}
fn file(&mut self, name: &str, file: FileValue<'_>) -> Result<(), FormError> {
match self.inner.file(name, file) {
Err(e @ FormError::Extract { .. }) => {
self.errors.push(e);
Ok(())
}
other => other,
}
}
fn finish(self) -> Result<B::Output, FormError> {
self.inner.finish()
}
}
const STACK: usize = 512;
fn feed_urlencoded<B: FormBuilder, I: Inspector>(
builder: &mut B,
input: &[u8],
limits: &Limits,
policy: &Policy,
inspector: &mut I,
) -> Result<(), FormError> {
let mut name_stack = [0u8; STACK];
let mut value_stack = [0u8; STACK];
let (mut name_hi, mut value_hi) = (0usize, 0usize);
let mut heap = alloc::vec::Vec::new();
let whole = core::str::from_utf8(input).ok();
let fed = (|| {
for pair in crate::urlencoded::raw::raw_pairs(input, limits) {
let pair = pair?;
if let (Some(name), Some(value)) = (pair.name_text(whole), pair.value_text(whole)) {
let at = crate::error::Location::at(pair.offset());
match inspector.field(name, value.as_bytes()) {
crate::Verdict::Refuse(refusal) => {
return Err(FormError::Refused { refusal, at });
}
crate::Verdict::Skip => {}
_ => builder.text(name, value)?,
}
continue;
}
let name_len = pair.name_raw().len();
let value_len = pair.value_raw().len();
let (name_buf, value_buf): (&mut [u8], &mut [u8]) =
if name_len <= STACK && value_len <= STACK {
name_hi = name_hi.max(name_len);
value_hi = value_hi.max(value_len);
(&mut name_stack, &mut value_stack)
} else {
let needed = name_len.saturating_add(value_len);
crate::wipe::wipe(&mut heap);
heap.clear();
if needed > heap.capacity() {
let mut old =
core::mem::replace(&mut heap, alloc::vec::Vec::with_capacity(needed));
crate::wipe::wipe_vec(&mut old);
}
heap.resize(needed, 0);
heap.split_at_mut(name_len)
};
let name = pair.decode_name(name_buf, policy.percent)?;
let value = pair.decode_value(value_buf, policy.percent)?;
let at = crate::error::Location::at(pair.offset());
match inspector.field(name, value.as_bytes()) {
crate::Verdict::Refuse(refusal) => return Err(FormError::Refused { refusal, at }),
crate::Verdict::Skip => {}
_ => builder.text(name, value)?,
}
}
Ok(())
})();
crate::wipe::wipe(name_stack.get_mut(..name_hi).unwrap_or(&mut []));
crate::wipe::wipe(value_stack.get_mut(..value_hi).unwrap_or(&mut []));
crate::wipe::wipe_vec(&mut heap);
fed?;
if let crate::Verdict::Refuse(refusal) = inspector.end() {
return Err(FormError::Refused {
refusal,
at: crate::error::Location::at(input.len() as u64),
});
}
Ok(())
}
fn feed_multipart<B: FormBuilder>(
builder: &mut B,
mp: &crate::Multipart<'_>,
) -> Result<(), FormError> {
for part in mp.parts() {
if part.is_file() {
builder.file(part.name(), FileValue::Buffered(part))?;
} else {
builder.text(part.name(), part.text()?)?;
}
}
Ok(())
}
pub trait FromForm: Sized {
type Builder: FormBuilder<Output = Self> + Default;
fn from_urlencoded(input: &[u8]) -> Result<Self, FormError> {
Self::from_urlencoded_with(input, &Limits::DEFAULT, &Policy::DEFAULT)
}
fn from_query(query: &[u8]) -> Result<Self, FormError> {
Self::from_urlencoded(query.strip_prefix(b"?").unwrap_or(query))
}
fn from_multipart(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
let mp = crate::parse_multipart(content_type, body)?;
let mut b = Self::Builder::default();
feed_multipart(&mut b, &mp)?;
b.finish()
}
fn from_body(content_type: &[u8], body: &[u8]) -> Result<Self, FormError> {
Self::from_body_with(content_type, body, &Limits::DEFAULT, &Policy::DEFAULT)
}
fn from_urlencoded_with(
input: &[u8],
limits: &Limits,
policy: &Policy,
) -> Result<Self, FormError> {
limits.validate()?;
let mut b = Self::Builder::default();
feed_urlencoded(&mut b, input, limits, policy, &mut NoInspector)?;
b.finish()
}
fn from_body_with(
content_type: &[u8],
body: &[u8],
limits: &Limits,
policy: &Policy,
) -> Result<Self, FormError> {
Self::from_body_inspected(content_type, body, limits, policy, &mut NoInspector)
}
fn from_body_inspected<I: Inspector>(
content_type: &[u8],
body: &[u8],
limits: &Limits,
policy: &Policy,
inspector: &mut I,
) -> Result<Self, FormError> {
let result = (|| {
let mut b = Self::Builder::default();
match FormKind::detect(content_type)? {
FormKind::Urlencoded => {
limits.validate()?;
feed_urlencoded(&mut b, body, limits, policy, inspector)?;
}
FormKind::Multipart(_) => {
let mp = crate::multipart::parse_multipart_inspected_raw(
content_type,
body,
limits,
policy,
inspector,
)?;
feed_multipart(&mut b, &mp)?;
}
}
b.finish()
})();
if let Err(e) = &result {
inspector.failed(e);
}
result
}
fn from_body_all(content_type: &[u8], body: &[u8]) -> Result<Self, FormErrors> {
Self::from_body_all_with(content_type, body, &Limits::DEFAULT, &Policy::DEFAULT)
}
fn from_body_all_with(
content_type: &[u8],
body: &[u8],
limits: &Limits,
policy: &Policy,
) -> Result<Self, FormErrors> {
let mut errors = alloc::vec::Vec::new();
let mut b = Collect {
inner: Self::Builder::default(),
errors: &mut errors,
};
match FormKind::detect(content_type)? {
FormKind::Urlencoded => {
limits.validate()?;
feed_urlencoded(&mut b, body, limits, policy, &mut NoInspector)?;
}
FormKind::Multipart(_) => {
let mp = crate::parse_multipart_with(content_type, body, limits, policy)?;
feed_multipart(&mut b, &mp)?;
}
}
let Collect { inner, errors: _ } = b;
let value = inner.finish_all(&mut errors);
match value {
Some(v) if errors.is_empty() => Ok(v),
_ => Err(FormErrors(errors)),
}
}
#[cfg(feature = "fs")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
fn from_upload_mut(upload: &mut crate::store::Upload) -> Result<Self, FormError> {
let mut b = Self::Builder::default();
for (name, value) in upload.iter() {
b.text(name, value)?;
}
let files = upload.files_mut();
let mut i = 0usize;
while let Some(file) = files.get(i) {
match b.file_field(file.name()) {
Some(true) => {
let file = files.remove(i);
let name = alloc::string::String::from(file.name());
b.file(&name, FileValue::Stored(file))?;
}
Some(false) => i = i.wrapping_add(1),
None => return Err(__unknown()),
}
}
b.finish()
}
#[cfg(feature = "fs")]
#[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
fn from_upload(upload: crate::store::Upload) -> Result<Self, FormError> {
let (fields, files) = upload.into_parts();
let mut b = Self::Builder::default();
for (name, value) in fields.iter() {
b.text(name, value)?;
}
for file in files {
let name = alloc::string::String::from(file.name());
b.file(&name, FileValue::Stored(file))?;
}
b.finish()
}
}
#[doc(hidden)]
#[must_use]
pub fn __value_error(e: ValueError, field: &'static str) -> FormError {
let reason = match e {
ValueError::Missing => ExtractError::Missing,
ValueError::Duplicate => ExtractError::Duplicate,
ValueError::Invalid(why) => ExtractError::Invalid(why),
_ => ExtractError::WrongKind,
};
FormError::Extract {
reason,
field: Some(field),
}
}
#[doc(hidden)]
pub fn __push_field_error(
errors: &mut alloc::vec::Vec<FormError>,
e: ValueError,
field: &'static str,
) {
let seen = errors
.iter()
.any(|x| matches!(x, FormError::Extract { field: Some(f), .. } if *f == field));
if !(seen && e == ValueError::Missing) {
errors.push(__value_error(e, field));
}
}
#[doc(hidden)]
#[must_use]
pub const fn __unknown() -> FormError {
FormError::Extract {
reason: ExtractError::Unknown,
field: None,
}
}