nostd_multipart/
disposition.rs1use crate::chars::{is_token, param, trim_ows};
4use crate::Error;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ContentDisposition<'a> {
9 disposition_type: &'a [u8],
10 name: Option<ParamValue<'a>>,
11 filename: Option<ParamValue<'a>>,
12 filename_star: Option<&'a [u8]>,
13}
14
15impl<'a> ContentDisposition<'a> {
16 #[must_use]
18 pub const fn disposition_type(&self) -> &'a [u8] {
19 self.disposition_type
20 }
21
22 #[must_use]
24 pub fn is_form_data(&self) -> bool {
25 self.disposition_type.eq_ignore_ascii_case(b"form-data")
26 }
27
28 #[must_use]
30 pub const fn name(&self) -> Option<ParamValue<'a>> {
31 self.name
32 }
33
34 #[must_use]
36 pub const fn filename(&self) -> Option<ParamValue<'a>> {
37 self.filename
38 }
39
40 #[must_use]
43 pub const fn filename_star(&self) -> Option<&'a [u8]> {
44 self.filename_star
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct ParamValue<'a> {
53 raw: &'a [u8],
54}
55
56impl<'a> ParamValue<'a> {
57 #[must_use]
59 pub const fn raw(&self) -> &'a [u8] {
60 self.raw
61 }
62
63 #[must_use]
65 pub const fn bytes(&self) -> Decoded<'a> {
66 Decoded { rest: self.raw }
67 }
68
69 #[must_use]
71 pub fn decoded_len(&self) -> usize {
72 self.bytes().count()
73 }
74
75 pub fn decode_into<'o>(&self, out: &'o mut [u8]) -> Option<&'o [u8]> {
77 let mut n = 0usize;
78 let mut slots = out.iter_mut();
79 for b in self.bytes() {
80 *slots.next()? = b;
81 n = n.saturating_add(1);
82 }
83 out.get(..n)
84 }
85
86 #[must_use]
88 pub fn eq_bytes(&self, other: &[u8]) -> bool {
89 self.bytes().eq(other.iter().copied())
90 }
91}
92
93impl PartialEq<[u8]> for ParamValue<'_> {
94 fn eq(&self, other: &[u8]) -> bool {
95 ParamValue::eq_bytes(self, other)
96 }
97}
98
99impl PartialEq<&[u8]> for ParamValue<'_> {
100 fn eq(&self, other: &&[u8]) -> bool {
101 ParamValue::eq_bytes(self, other)
102 }
103}
104
105impl PartialEq<str> for ParamValue<'_> {
106 fn eq(&self, other: &str) -> bool {
107 ParamValue::eq_bytes(self, other.as_bytes())
108 }
109}
110
111impl PartialEq<&str> for ParamValue<'_> {
112 fn eq(&self, other: &&str) -> bool {
113 ParamValue::eq_bytes(self, other.as_bytes())
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct Decoded<'a> {
120 rest: &'a [u8],
121}
122
123impl Iterator for Decoded<'_> {
124 type Item = u8;
125
126 fn next(&mut self) -> Option<u8> {
127 let (&b, rest) = self.rest.split_first()?;
128 if b == b'\\' {
129 if let Some((&escaped, after)) = rest.split_first() {
130 self.rest = after;
131 return Some(escaped);
132 }
133 } else if b == b'%' {
134 let decoded = match rest.get(..2) {
135 Some(b"22") => Some(b'"'),
136 Some(b"0D" | b"0d") => Some(b'\r'),
137 Some(b"0A" | b"0a") => Some(b'\n'),
138 _ => None,
139 };
140 if let Some(d) = decoded {
141 self.rest = rest.get(2..).unwrap_or(&[]);
142 return Some(d);
143 }
144 }
145 self.rest = rest;
146 Some(b)
147 }
148}
149
150pub fn content_disposition(value: &[u8]) -> Result<ContentDisposition<'_>, Error> {
161 let value = trim_ows(value);
162 let end = value.iter().position(|&b| b == b';').unwrap_or(value.len());
163 let (dtype, mut rest) = value.split_at_checked(end).ok_or(Error::Disposition)?;
164 let dtype = trim_ows(dtype);
165 if !is_token(dtype) {
166 return Err(Error::Disposition);
167 }
168 let mut cd = ContentDisposition {
169 disposition_type: dtype,
170 name: None,
171 filename: None,
172 filename_star: None,
173 };
174 while let Some(after_semi) = rest.strip_prefix(b";") {
175 let Some((attr, raw, _, tail)) = param(after_semi)? else {
176 break;
177 };
178 if attr.eq_ignore_ascii_case(b"name") {
179 cd.name.get_or_insert(ParamValue { raw });
180 } else if attr.eq_ignore_ascii_case(b"filename") {
181 cd.filename.get_or_insert(ParamValue { raw });
182 } else if attr.eq_ignore_ascii_case(b"filename*") {
183 cd.filename_star.get_or_insert(raw);
184 }
185 rest = tail;
186 }
187 Ok(cd)
188}