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}