Skip to main content

http_type/body/
impl.rs

1use super::*;
2
3/// Implementation for `MultipartFormData`.
4impl MultipartFormData {
5    /// Reads the `boundary` parameter out of a `multipart/form-data` content type.
6    ///
7    /// The parameter is matched case-insensitively and accepts both a quoted and
8    /// a bare value, so the clients that emit `boundary="abc"` and the clients
9    /// that emit `boundary=abc` are both accepted. A parameter that carries no
10    /// `=` is skipped rather than aborting the scan.
11    ///
12    /// # Arguments
13    ///
14    /// - `C` - The content type header value, which requires the `AsRef<str>` bound.
15    ///
16    /// # Returns
17    ///
18    /// - `Option<MultipartFormDataBoundary>` - The declared boundary, or `None` when absent.
19    pub fn get_boundary<C>(content_type: C) -> Option<MultipartFormDataBoundary>
20    where
21        C: AsRef<str>,
22    {
23        for parameter in content_type.as_ref().split(SEMICOLON).skip(1) {
24            let Some((key, value)) = parameter.trim().split_once(EQUAL) else {
25                continue;
26            };
27            if !key
28                .trim()
29                .eq_ignore_ascii_case(MULTIPART_BOUNDARY_PARAMETER)
30            {
31                continue;
32            }
33            let value: &str = Self::unquote(value.trim());
34            if !value.is_empty() {
35                return Some(value.to_string());
36            }
37        }
38        None
39    }
40
41    /// Checks whether a content type declares a `multipart/form-data` body.
42    ///
43    /// # Arguments
44    ///
45    /// - `C` - The content type header value, which requires the `AsRef<str>` bound.
46    ///
47    /// # Returns
48    ///
49    /// - `bool` - Whether the content type is `multipart/form-data`.
50    pub fn is_form_data<C>(content_type: C) -> bool
51    where
52        C: AsRef<str>,
53    {
54        content_type
55            .as_ref()
56            .split(SEMICOLON)
57            .next()
58            .unwrap_or(EMPTY_STR)
59            .trim()
60            .eq_ignore_ascii_case(MULTIPART_FORM_DATA)
61    }
62
63    /// Strips one pair of surrounding quotes from a header parameter value.
64    ///
65    /// # Arguments
66    ///
67    /// - `&str` - The raw parameter value.
68    ///
69    /// # Returns
70    ///
71    /// - `&str` - The value without its surrounding quotes.
72    fn unquote(value: &str) -> &str {
73        value
74            .strip_prefix(MULTIPART_PARAMETER_QUOTE)
75            .and_then(|value: &str| value.strip_suffix(MULTIPART_PARAMETER_QUOTE))
76            .unwrap_or(value)
77    }
78
79    /// Finds the first occurrence of a needle inside a haystack.
80    ///
81    /// # Arguments
82    ///
83    /// - `&[u8]` - The bytes to search in.
84    /// - `&[u8]` - The bytes to search for.
85    ///
86    /// # Returns
87    ///
88    /// - `Option<usize>` - The offset of the first occurrence, or `None` when absent.
89    fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
90        if needle.is_empty() || needle.len() > haystack.len() {
91            return None;
92        }
93        haystack
94            .windows(needle.len())
95            .position(|window: &[u8]| window == needle)
96    }
97
98    /// Builds the byte delimiter that marks a boundary line in the body.
99    ///
100    /// # Arguments
101    ///
102    /// - `&str` - The declared boundary, without its leading dash pair.
103    ///
104    /// # Returns
105    ///
106    /// - `Vec<u8>` - The delimiter bytes, dash pair included.
107    fn build_delimiter(boundary: &str) -> Vec<u8> {
108        let mut delimiter: Vec<u8> = Vec::with_capacity(boundary.len() + 4);
109        delimiter.extend_from_slice(MULTIPART_DELIMITER_DASH.as_bytes());
110        delimiter.extend_from_slice(boundary.as_bytes());
111        delimiter
112    }
113
114    /// Checks whether the bytes after a delimiter are a closing dash pair.
115    ///
116    /// The closing boundary writes a second dash pair right after the boundary
117    /// token, optionally followed by the line break that ends the body.
118    ///
119    /// # Arguments
120    ///
121    /// - `&[u8]` - The bytes that follow the delimiter.
122    ///
123    /// # Returns
124    ///
125    /// - `bool` - Whether the delimiter is closed by a dash pair.
126    fn is_closing_delimiter(remainder: &[u8]) -> bool {
127        let Some(after_dash) = remainder.strip_prefix(MULTIPART_DELIMITER_DASH.as_bytes()) else {
128            return false;
129        };
130        after_dash.is_empty()
131            || after_dash.starts_with(HTTP_BR.as_bytes())
132            || after_dash.starts_with(MULTIPART_LINE_FEED)
133    }
134
135    /// Checks whether the bytes after a delimiter close a boundary line.
136    ///
137    /// A boundary line ends at a line break or at the end of the body, so the
138    /// dash pair of the closing boundary is matched here rather than being part
139    /// of the delimiter itself.
140    ///
141    /// # Arguments
142    ///
143    /// - `&[u8]` - The bytes that follow the delimiter.
144    ///
145    /// # Returns
146    ///
147    /// - `bool` - Whether the delimiter is followed by a boundary line ending.
148    fn is_boundary_tail(remainder: &[u8]) -> bool {
149        remainder.starts_with(HTTP_BR.as_bytes())
150            || remainder.starts_with(MULTIPART_LINE_FEED)
151            || Self::is_closing_delimiter(remainder)
152    }
153
154    /// Finds every byte offset at which the boundary delimiter starts.
155    ///
156    /// # Arguments
157    ///
158    /// - `&[u8]` - The raw request body.
159    /// - `&[u8]` - The boundary delimiter, dash pair included.
160    ///
161    /// # Returns
162    ///
163    /// - `Vec<usize>` - The ascending offsets of every delimiter occurrence.
164    fn find_delimiter_positions(body: &[u8], delimiter: &[u8]) -> Vec<usize> {
165        let mut positions: Vec<usize> = Vec::new();
166        let mut offset: usize = 0;
167        while offset < body.len() {
168            let Some(found) = Self::find_subslice(&body[offset..], delimiter) else {
169                break;
170            };
171            let position: usize = offset + found;
172            let after: usize = position + delimiter.len();
173            if after == body.len() || Self::is_boundary_tail(&body[after..]) {
174                positions.push(position);
175            }
176            offset = position + 1;
177        }
178        positions
179    }
180
181    /// Removes the line breaks that belong to a boundary line from a segment.
182    ///
183    /// The break that follows the opening delimiter is stripped from the front,
184    /// and the break that precedes the closing delimiter is stripped from the
185    /// back: per the multipart grammar both belong to the boundary line, not to
186    /// the part content.
187    ///
188    /// # Arguments
189    ///
190    /// - `&[u8]` - The raw segment between two boundary lines.
191    ///
192    /// # Returns
193    ///
194    /// - `&[u8]` - The segment without its surrounding boundary line breaks.
195    fn trim_boundary_breaks(segment: &[u8]) -> &[u8] {
196        let segment: &[u8] = segment.strip_prefix(HTTP_BR.as_bytes()).unwrap_or(segment);
197        if let Some(trimmed) = segment.strip_suffix(HTTP_BR.as_bytes()) {
198            return trimmed;
199        }
200        segment.strip_suffix(MULTIPART_LINE_FEED).unwrap_or(segment)
201    }
202
203    /// Splits a body into the raw segments carried between boundary lines.
204    ///
205    /// Both `CRLF` and bare `LF` line breaks are accepted, and the breaks that
206    /// belong to a boundary line are not part of a segment. The final boundary
207    /// must be the closing one: a body whose last boundary opens a part that is
208    /// never closed is truncated, and reporting it as `None` keeps a truncated
209    /// upload from passing as a complete one.
210    ///
211    /// # Arguments
212    ///
213    /// - `&'a [u8]` - The raw request body.
214    /// - `&str` - The declared boundary, without its leading dash pair.
215    ///
216    /// # Returns
217    ///
218    /// - `Option<MultipartFormDataSegments<'a>>` - The body segments, or `None` when malformed.
219    fn split_segments<'a>(body: &'a [u8], boundary: &str) -> Option<MultipartFormDataSegments<'a>> {
220        let delimiter: Vec<u8> = Self::build_delimiter(boundary);
221        let positions: Vec<usize> = Self::find_delimiter_positions(body, &delimiter);
222        let last_position: usize = positions.last().copied()?;
223        let last_end: usize = last_position + delimiter.len();
224        if !Self::is_closing_delimiter(&body[last_end..]) {
225            return None;
226        }
227        let mut segments: MultipartFormDataSegments = Vec::with_capacity(positions.len());
228        for index in 0..positions.len().saturating_sub(1) {
229            let start: usize = positions[index] + delimiter.len();
230            let end: usize = positions[index + 1];
231            segments.push(Self::trim_boundary_breaks(&body[start..end]));
232        }
233        Some(segments)
234    }
235
236    /// Splits a part into its header block and its content.
237    ///
238    /// # Arguments
239    ///
240    /// - `&[u8]` - The raw segment of a single part.
241    ///
242    /// # Returns
243    ///
244    /// - `(&[u8], &[u8])` - The header block and the content of the part.
245    fn split_part(segment: &[u8]) -> (&[u8], &[u8]) {
246        let fallback: &[u8] = &segment[segment.len()..];
247        if let Some(position) = Self::find_subslice(segment, HTTP_DOUBLE_BR.as_bytes()) {
248            let data_start: usize = position + HTTP_DOUBLE_BR.len();
249            return (&segment[..position], &segment[data_start..]);
250        }
251        if let Some(position) = Self::find_subslice(segment, MULTIPART_HEADER_SEPARATOR) {
252            let data_start: usize = position + MULTIPART_HEADER_SEPARATOR_LEN;
253            return (&segment[..position], &segment[data_start..]);
254        }
255        (segment, fallback)
256    }
257
258    /// Decodes the header block of a single part.
259    ///
260    /// # Arguments
261    ///
262    /// - `&[u8]` - The header block of the part, terminated by a blank line.
263    ///
264    /// # Returns
265    ///
266    /// - `Option<BodyPartHeaders>` - The decoded headers, or `None` when not valid text.
267    fn parse_part_headers(header_block: &[u8]) -> Option<BodyPartHeaders> {
268        let header_text: &str = from_utf8(header_block).ok()?;
269        let mut headers: BodyPartHeaders = hash_map_xx_hash3_64();
270        for line in header_text.split(HTTP_BR) {
271            let line: &str = line.trim_end_matches('\n').trim();
272            if line.is_empty() {
273                continue;
274            }
275            let (key, value) = line.split_once(COLON)?;
276            let key: String = key.trim().to_ascii_lowercase();
277            if key.is_empty() {
278                continue;
279            }
280            headers.insert(key, value.trim().to_string());
281        }
282        Some(headers)
283    }
284
285    /// Reads the `name` and `filename` parameters of a part content disposition.
286    ///
287    /// # Arguments
288    ///
289    /// - `&str` - The part `Content-Disposition` header value.
290    ///
291    /// # Returns
292    ///
293    /// - `(BodyPartName, BodyPartFileName)` - The declared field name and file name.
294    fn parse_part_disposition(disposition: &str) -> (BodyPartName, BodyPartFileName) {
295        let mut name: BodyPartName = String::new();
296        let mut file_name: BodyPartFileName = String::new();
297        for parameter in disposition.split(SEMICOLON).skip(1) {
298            let Some((key, value)) = parameter.trim().split_once(EQUAL) else {
299                continue;
300            };
301            let value: &str = Self::unquote(value.trim());
302            match key.trim().to_ascii_lowercase().as_str() {
303                MULTIPART_NAME_PARAMETER => name = value.to_string(),
304                FILENAME => file_name = value.to_string(),
305                _ => {}
306            }
307        }
308        (name, file_name)
309    }
310
311    /// Decodes every part of a `multipart/form-data` body.
312    ///
313    /// A body that carries no declared boundary, a body whose final boundary is
314    /// not the closing one, and a part that declares no `name` all yield `None`.
315    ///
316    /// # Arguments
317    ///
318    /// - `B` - The raw request body, which requires the `AsRef<[u8]>` bound.
319    /// - `&str` - The declared boundary, without its leading dash pair.
320    ///
321    /// # Returns
322    ///
323    /// - `Option<MultipartFormDataParts>` - The decoded parts, or `None` when malformed.
324    pub fn parse<B>(body: B, boundary: &str) -> Option<MultipartFormDataParts>
325    where
326        B: AsRef<[u8]>,
327    {
328        let segments: MultipartFormDataSegments = Self::split_segments(body.as_ref(), boundary)?;
329        let mut parts: MultipartFormDataParts = Vec::with_capacity(segments.len());
330        for segment in segments {
331            if segment.is_empty() {
332                continue;
333            }
334            let (header_block, data) = Self::split_part(segment);
335            let headers: BodyPartHeaders = Self::parse_part_headers(header_block)?;
336            let disposition: String = headers
337                .get(MULTIPART_CONTENT_DISPOSITION)
338                .cloned()
339                .unwrap_or_default();
340            let (name, file_name) = Self::parse_part_disposition(&disposition);
341            if name.is_empty() || parts.len() >= MULTIPART_MAX_PARTS {
342                return None;
343            }
344            let content_type: BodyPartContentType =
345                headers.get(CONTENT_TYPE).cloned().unwrap_or_default();
346            parts.push(BodyPart::new(
347                name,
348                file_name,
349                content_type,
350                headers,
351                data.to_vec(),
352            ));
353        }
354        Some(parts)
355    }
356
357    /// Tries to get a part by its form field name.
358    ///
359    /// # Arguments
360    ///
361    /// - `&[BodyPart]` - The decoded parts of the body.
362    /// - `K` - The form field name, which requires the `AsRef<str>` bound.
363    ///
364    /// # Returns
365    ///
366    /// - `Option<&BodyPart>` - The first part carrying that name, or `None`.
367    pub fn try_get_part<K>(parts: &[BodyPart], name: K) -> Option<&BodyPart>
368    where
369        K: AsRef<str>,
370    {
371        let name: &str = name.as_ref();
372        parts
373            .iter()
374            .find(|part: &&BodyPart| part.get_name() == name)
375    }
376
377    /// Tries to get a plain text field by its form field name.
378    ///
379    /// A part that uploads a file is skipped, so a file field never silently
380    /// decodes as text.
381    ///
382    /// # Arguments
383    ///
384    /// - `&[BodyPart]` - The decoded parts of the body.
385    /// - `K` - The form field name, which requires the `AsRef<str>` bound.
386    ///
387    /// # Returns
388    ///
389    /// - `Option<BodyFormValue>` - The field value, or `None` when absent or a file.
390    pub fn try_get_part_text<K>(parts: &[BodyPart], name: K) -> Option<BodyFormValue>
391    where
392        K: AsRef<str>,
393    {
394        let part: &BodyPart = Self::try_get_part(parts, name)?;
395        if !part.get_file_name().is_empty() {
396            return None;
397        }
398        Some(String::from_utf8_lossy(part.get_data()).into_owned())
399    }
400
401    /// Decodes an `application/x-www-form-urlencoded` body into its fields.
402    ///
403    /// # Arguments
404    ///
405    /// - `B` - The raw request body, which requires the `AsRef<[u8]>` bound.
406    ///
407    /// # Returns
408    ///
409    /// - `BodyForm` - The decoded form fields.
410    pub fn parse_url_encoded<B>(body: B) -> BodyForm
411    where
412        B: AsRef<[u8]>,
413    {
414        let decoded: String = String::from_utf8_lossy(body.as_ref()).into_owned();
415        let mut form: BodyForm = hash_map_xx_hash3_64();
416        for pair in decoded.split(AND) {
417            if pair.is_empty() {
418                continue;
419            }
420            let (key, value) = pair.split_once(EQUAL).unwrap_or((pair, EMPTY_STR));
421            let key: BodyFormKey = Self::decode_form_component(key);
422            if key.is_empty() {
423                continue;
424            }
425            form.insert(key, Self::decode_form_component(value));
426        }
427        form
428    }
429
430    /// Decodes one percent-encoded form component.
431    ///
432    /// `+` is decoded as a space, `%XX` as the matching byte, and an invalid
433    /// escape is kept verbatim instead of dropping the component.
434    ///
435    /// # Arguments
436    ///
437    /// - `&str` - The raw component of the form body.
438    ///
439    /// # Returns
440    ///
441    /// - `String` - The decoded component.
442    fn decode_form_component(component: &str) -> String {
443        let bytes: Vec<u8> = component.bytes().collect();
444        let mut decoded: Vec<u8> = Vec::with_capacity(bytes.len());
445        let mut index: usize = 0;
446        while index < bytes.len() {
447            let byte: u8 = bytes[index];
448            match byte {
449                URL_ENCODED_PLUS => {
450                    decoded.push(URL_ENCODED_SPACE);
451                    index += 1;
452                }
453                byte if byte == URL_ENCODED_PERCENT && index + 2 < bytes.len() => {
454                    match Self::parse_hex_pair(&bytes[index + 1], &bytes[index + 2]) {
455                        Some(value) => {
456                            decoded.push(value);
457                            index += 3;
458                        }
459                        None => {
460                            decoded.push(byte);
461                            index += 1;
462                        }
463                    }
464                }
465                _ => {
466                    decoded.push(byte);
467                    index += 1;
468                }
469            }
470        }
471        String::from_utf8_lossy(&decoded).into_owned()
472    }
473
474    /// Reads the two hexadecimal digits of a percent escape.
475    ///
476    /// # Arguments
477    ///
478    /// - `&u8` - The high digit of the escape.
479    /// - `&u8` - The low digit of the escape.
480    ///
481    /// # Returns
482    ///
483    /// - `Option<u8>` - The decoded byte, or `None` when a digit is not hexadecimal.
484    fn parse_hex_pair(high: &u8, low: &u8) -> Option<u8> {
485        let high_digit: u8 = Self::hex_digit(high)?;
486        let low_digit: u8 = Self::hex_digit(low)?;
487        Some(high_digit * HEX_RADIX + low_digit)
488    }
489
490    /// Converts one hexadecimal digit into its numeric value.
491    ///
492    /// # Arguments
493    ///
494    /// - `&u8` - The ASCII byte of the digit.
495    ///
496    /// # Returns
497    ///
498    /// - `Option<u8>` - The numeric value, or `None` when the byte is not hexadecimal.
499    fn hex_digit(byte: &u8) -> Option<u8> {
500        match byte {
501            HEX_DIGIT_ZERO..=HEX_DIGIT_NINE => Some(byte - HEX_DIGIT_ZERO),
502            HEX_DIGIT_LOWER_A..=HEX_DIGIT_LOWER_F => {
503                Some(byte - HEX_DIGIT_LOWER_A + HEX_LOWER_OFFSET)
504            }
505            HEX_DIGIT_UPPER_A..=HEX_DIGIT_UPPER_F => {
506                Some(byte - HEX_DIGIT_UPPER_A + HEX_LOWER_OFFSET)
507            }
508            _ => None,
509        }
510    }
511}
512
513impl BodyPart {
514    /// Tries to get the form field name of this part.
515    ///
516    /// # Returns
517    ///
518    /// - `Option<&BodyPartName>` - The declared field name, or `None` when
519    ///   the part declares none.
520    pub fn try_get_name(&self) -> Option<&BodyPartName> {
521        (!self.get_name().is_empty()).then_some(self.get_name())
522    }
523
524    /// Tries to get the uploaded file name of this part.
525    ///
526    /// # Returns
527    ///
528    /// - `Option<&BodyPartFileName>` - The declared file name, or `None`
529    ///   when this part is a plain field rather than a file upload.
530    pub fn try_get_file_name(&self) -> Option<&BodyPartFileName> {
531        (!self.get_file_name().is_empty()).then_some(self.get_file_name())
532    }
533
534    /// Tries to get the media type declared by this part.
535    ///
536    /// # Returns
537    ///
538    /// - `Option<&BodyPartContentType>` - The declared media type, or
539    ///   `None` when the part declares none.
540    pub fn try_get_content_type(&self) -> Option<&BodyPartContentType> {
541        (!self.get_content_type().is_empty()).then_some(self.get_content_type())
542    }
543
544    /// Tries to get the value of one of this part's headers.
545    ///
546    /// # Arguments
547    ///
548    /// - `K` - The header name, which must implement `AsRef<str>`.
549    ///
550    /// # Returns
551    ///
552    /// - `Option<&BodyPartHeaderValue>` - The header value, or `None`
553    ///   when this part carries no such header.
554    pub fn try_get_header<K>(&self, key: K) -> Option<&BodyPartHeaderValue>
555    where
556        K: AsRef<str>,
557    {
558        self.get_headers().get(key.as_ref())
559    }
560
561    /// Tries to get this part's raw content.
562    ///
563    /// # Returns
564    ///
565    /// - `Option<&[u8]>` - The raw bytes, or `None` when the part is empty.
566    pub fn try_get_data(&self) -> Option<&[u8]> {
567        (!self.get_data().is_empty()).then(|| self.get_data().as_slice())
568    }
569
570    /// Tries to get this part's raw content as UTF-8 text.
571    ///
572    /// Invalid UTF-8 is replaced rather than rejected, so a binary upload
573    /// read as text yields its readable bytes instead of `None`.
574    ///
575    /// # Returns
576    ///
577    /// - `Option<BodyFormValue>` - The decoded text, or `None` when the
578    ///   part is empty.
579    pub fn try_get_text(&self) -> Option<BodyFormValue> {
580        self.try_get_data()
581            .map(|data: &[u8]| String::from_utf8_lossy(data).into_owned())
582    }
583}