use crate::chars::{is_token, param, trim_ows};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentDisposition<'a> {
disposition_type: &'a [u8],
name: Option<ParamValue<'a>>,
filename: Option<ParamValue<'a>>,
filename_star: Option<&'a [u8]>,
}
impl<'a> ContentDisposition<'a> {
#[must_use]
pub const fn disposition_type(&self) -> &'a [u8] {
self.disposition_type
}
#[must_use]
pub fn is_form_data(&self) -> bool {
self.disposition_type.eq_ignore_ascii_case(b"form-data")
}
#[must_use]
pub const fn name(&self) -> Option<ParamValue<'a>> {
self.name
}
#[must_use]
pub const fn filename(&self) -> Option<ParamValue<'a>> {
self.filename
}
#[must_use]
pub const fn filename_star(&self) -> Option<&'a [u8]> {
self.filename_star
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParamValue<'a> {
raw: &'a [u8],
}
impl<'a> ParamValue<'a> {
#[must_use]
pub const fn raw(&self) -> &'a [u8] {
self.raw
}
#[must_use]
pub const fn bytes(&self) -> Decoded<'a> {
Decoded { rest: self.raw }
}
#[must_use]
pub fn decoded_len(&self) -> usize {
self.bytes().count()
}
pub fn decode_into<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> {
let mut n = 0usize;
let mut slots = out.iter_mut();
for b in self.bytes() {
*slots.next()? = b;
n = n.saturating_add(1);
}
out.get(..n)
}
#[must_use]
pub fn eq_bytes(&self, other: &[u8]) -> bool {
self.bytes().eq(other.iter().copied())
}
}
impl PartialEq<[u8]> for ParamValue<'_> {
fn eq(&self, other: &[u8]) -> bool {
ParamValue::eq_bytes(self, other)
}
}
impl PartialEq<&[u8]> for ParamValue<'_> {
fn eq(&self, other: &&[u8]) -> bool {
ParamValue::eq_bytes(self, other)
}
}
impl PartialEq<str> for ParamValue<'_> {
fn eq(&self, other: &str) -> bool {
ParamValue::eq_bytes(self, other.as_bytes())
}
}
impl PartialEq<&str> for ParamValue<'_> {
fn eq(&self, other: &&str) -> bool {
ParamValue::eq_bytes(self, other.as_bytes())
}
}
#[derive(Debug, Clone)]
pub struct Decoded<'a> {
rest: &'a [u8],
}
impl Iterator for Decoded<'_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
let (&b, rest) = self.rest.split_first()?;
if b == b'\\' {
if let Some((&escaped, after)) = rest.split_first() {
self.rest = after;
return Some(escaped);
}
} else if b == b'%' {
let decoded = match rest.get(..2) {
Some(b"22") => Some(b'"'),
Some(b"0D" | b"0d") => Some(b'\r'),
Some(b"0A" | b"0a") => Some(b'\n'),
_ => None,
};
if let Some(d) = decoded {
self.rest = rest.get(2..).unwrap_or(&[]);
return Some(d);
}
}
self.rest = rest;
Some(b)
}
}
pub fn content_disposition(value: &[u8]) -> Result<ContentDisposition<'_>, Error> {
let value = trim_ows(value);
let end = value.iter().position(|&b| b == b';').unwrap_or(value.len());
let (dtype, mut rest) = value.split_at_checked(end).ok_or(Error::Disposition)?;
let dtype = trim_ows(dtype);
if !is_token(dtype) {
return Err(Error::Disposition);
}
let mut cd = ContentDisposition {
disposition_type: dtype,
name: None,
filename: None,
filename_star: None,
};
while let Some(after_semi) = rest.strip_prefix(b";") {
let Some((attr, raw, _, tail)) = param(after_semi)? else {
break;
};
if attr.eq_ignore_ascii_case(b"name") {
cd.name.get_or_insert(ParamValue { raw });
} else if attr.eq_ignore_ascii_case(b"filename") {
cd.filename.get_or_insert(ParamValue { raw });
} else if attr.eq_ignore_ascii_case(b"filename*") {
cd.filename_star.get_or_insert(raw);
}
rest = tail;
}
Ok(cd)
}