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>;
}
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 heap = alloc::vec::Vec::new();
let fed = (|| {
for pair in crate::urlencoded::raw::raw_pairs(input, limits) {
let pair = pair?;
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 {
(&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(&mut name_stack);
crate::wipe::wipe(&mut value_stack);
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
}
#[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)]
#[must_use]
pub const fn __unknown() -> FormError {
FormError::Extract {
reason: ExtractError::Unknown,
field: None,
}
}