Skip to main content

rust_web_server/body/multipart_form_data/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use std::io;
5use std::io::{BufRead, Cursor};
6use crate::ext::string_ext::StringExt;
7use crate::header::Header;
8use crate::symbol::SYMBOL;
9
10/// Parser for `multipart/form-data` request bodies (file uploads).
11pub struct FormMultipartData;
12
13/// A single part from a multipart body, with its own headers and raw byte body.
14pub struct Part {
15    pub headers: Vec<Header>,
16    pub body: Vec<u8>,
17}
18
19impl Part {
20    pub fn get_header(&self, name: String) -> Option<&Header> {
21        self.header(&name)
22    }
23
24    /// The first header named `name` (case-insensitive), without allocating.
25    pub fn header(&self, name: &str) -> Option<&Header> {
26        self.headers.iter().find(|h| h.name.eq_ignore_ascii_case(name))
27    }
28}
29
30impl FormMultipartData {
31    /// Parses a raw multipart body into a list of [`Part`]s.
32    /// `boundary` is extracted from the `Content-Type` header value after `boundary=`.
33    pub fn parse(data: &[u8], boundary: String) -> Result<Vec<Part>, String> {
34
35        let cursor = io::Cursor::new(data);
36        let bytes_read : i128 = 0;
37        let total_bytes : i128 = data.len() as i128;
38
39        let part_list : Vec<Part> = vec![];
40
41        let boxed_part_list = FormMultipartData::
42            parse_form_part_recursively(
43                cursor,
44                boundary,
45                bytes_read,
46                total_bytes,
47                part_list
48            );
49
50        if boxed_part_list.is_err() {
51            let message  = boxed_part_list.err().unwrap();
52            return Err(message)
53        }
54
55        Ok(boxed_part_list.unwrap())
56    }
57
58    fn parse_form_part_recursively(
59                mut cursor: Cursor<&[u8]>,
60                boundary: String,
61                mut bytes_read: i128,
62                total_bytes: i128,
63                mut part_list: Vec<Part>) -> Result<Vec<Part>, String> {
64        // A loop, not tail recursion (not guaranteed to be eliminated):
65        // one stack frame per part let an upload with thousands of tiny
66        // parts overflow the stack and abort the process.
67        loop {
68            let mut buf = vec![];
69            let mut part = Part { headers: vec![], body: Default::default() };
70
71            // first boundary starts parsable payload
72            if bytes_read == 0 {
73                let boxed_read = cursor.read_until(b'\n', &mut buf);
74                if boxed_read.is_err() {
75                    let message = boxed_read.err().unwrap().to_string();
76                    return Err(message);
77                }
78                let bytes_offset = boxed_read.unwrap();
79                let b : &[u8] = &buf;
80                bytes_read = bytes_read + bytes_offset as i128;
81
82                let boxed_line = String::from_utf8(Vec::from(b));
83                if boxed_line.is_err() {
84                    let error_message = boxed_line.err().unwrap().to_string();
85                    return Err(error_message);
86                }
87                let string = boxed_line.unwrap();
88                let string = StringExt::filter_ascii_control_characters(&string);
89                let string = StringExt::truncate_new_line_carriage_return(&string);
90
91                let _current_string_is_boundary =
92                    string.replace(SYMBOL.hyphen, SYMBOL.empty_string)
93                        .ends_with(&boundary.replace(SYMBOL.hyphen, SYMBOL.empty_string));
94
95                if !_current_string_is_boundary {
96                    let message = format!("Body in multipart/form-data request needs to start with a boundary, actual string: '{}'", string);
97                    return Err(message.to_string())
98                }
99            }
100
101            // headers part. by spec it shall have at least Content-Disposition header or more, following
102            // by empty line. Headers shall be valid utf-8 encoded strings
103            let mut current_string_is_empty = false;
104            while !current_string_is_empty {
105                buf = vec![];
106                let boxed_read = cursor.read_until(b'\n', &mut buf);
107                if boxed_read.is_err() {
108                    let message = boxed_read.err().unwrap().to_string();
109                    return Err(message);
110                }
111                let bytes_offset = boxed_read.unwrap();
112                let b : &[u8] = &buf;
113                bytes_read = bytes_read + bytes_offset as i128;
114
115                let boxed_line = String::from_utf8(Vec::from(b));
116                if boxed_line.is_err() {
117                    let error_message = boxed_line.err().unwrap().to_string();
118                    return Err(error_message);
119                }
120                let string = boxed_line.unwrap();
121
122                let string = StringExt::filter_ascii_control_characters(&string);
123                current_string_is_empty = string.trim().len() == 0;
124
125                let _current_string_is_boundary =
126                    string.replace(SYMBOL.hyphen, SYMBOL.empty_string)
127                        .ends_with(&boundary.replace(SYMBOL.hyphen, SYMBOL.empty_string));
128
129                if _current_string_is_boundary {
130                    let message = "There is at least one missing body part in the multipart/form-data request";
131                    return Err(message.to_string())
132                }
133
134                if bytes_read == total_bytes as i128 {
135                    return Ok(part_list)
136                }
137
138
139                // multipart/form-data part does not have any header specified
140                if current_string_is_empty && part.headers.len() == 0 {
141                    let message = "One of the body parts does not have any header specified. At least Content-Disposition is required";
142                    return Err(message.to_string());
143                }
144
145                if !current_string_is_empty {
146                    let boxed_header = Header::parse_header(&string);
147                    if boxed_header.is_err() {
148                        let message = boxed_header.err().unwrap();
149                        return Err(message)
150                    }
151
152                    let header = boxed_header.unwrap();
153                    part.headers.push(header);
154                }
155            }
156
157
158            // multipart/form-data body part. it just arbitrary bytes. ends by delimiter.
159            let mut _boundary_position = 0;
160            let mut current_string_is_boundary = false;
161            while !current_string_is_boundary {
162                buf = vec![];
163
164                let boxed_read = cursor.read_until(b'\n', &mut buf);
165                if boxed_read.is_err() {
166                    let message = boxed_read.err().unwrap().to_string();
167                    return Err(message);
168                }
169
170                let bytes_offset = boxed_read.unwrap();
171
172                if bytes_offset == 0 { break };
173
174                let b : &[u8] = &buf;
175
176                bytes_read = bytes_read + bytes_offset as i128;
177
178                let escaped_dash_boundary = boundary.replace(SYMBOL.hyphen, SYMBOL.empty_string);
179
180                current_string_is_boundary = false;
181                if b.len() >= escaped_dash_boundary.len() {
182                    let boxed_sequence = FormMultipartData::find_subsequence(b, escaped_dash_boundary.as_bytes());
183                    if boxed_sequence.is_some() {
184                        current_string_is_boundary = true;
185                        _boundary_position = boxed_sequence.unwrap();
186                    }
187                }
188
189                if !current_string_is_boundary {
190                    part.body.append(&mut buf.clone());
191                }
192
193            }
194
195            if !current_string_is_boundary && bytes_read == total_bytes as i128 {
196                let message = "No end boundary present in the multipart/form-data request body";
197                return Err(message.to_string());
198            }
199
200            // body for specific part may end with a new line or carriage return and a new line
201            // in both cases new line carriage return delimiter is not part of the body
202            let body_length = part.body.len();
203            if body_length > 2 { // check if body itself is present
204                let is_new_line_carriage_return_ending =
205                    *part.body.get(body_length-2).unwrap() == b'\r'
206                        && *part.body.get(body_length-1).unwrap() == b'\n';
207
208                let is_new_line_ending =
209                    *part.body.get(body_length-2).unwrap() != b'\r'
210                        && *part.body.get(body_length-1).unwrap() == b'\n';
211
212                if is_new_line_carriage_return_ending {
213                    part.body.remove(body_length - 1); // removing \n
214                    part.body.remove(body_length - 2); // removing \r
215                }
216
217                if is_new_line_ending {
218                    part.body.remove(body_length - 1); // removing \n
219                }
220            }
221
222
223
224            part_list.push(part);
225
226
227            if bytes_read == total_bytes as i128 {
228                return Ok(part_list)
229            }
230
231        }
232    }
233
234    pub fn extract_boundary(content_type: &str) -> Result<String, String> {
235        let boxed_split = content_type.split_once("boundary=");
236        if boxed_split.is_none() {
237            let message = "No boundary found in Content-Type header";
238            return Err(message.to_string())
239        }
240
241
242        let (_, boundary) = boxed_split.unwrap();
243        Ok(boundary.to_string())
244    }
245
246    fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
247        haystack.windows(needle.len()).position(|window| window == needle)
248    }
249
250    pub fn generate_part(part: Part) -> Result<Vec<u8>, String> {
251        if part.headers.len() == 0 {
252            let message = "One of the body parts does not have any header specified. At least Content-Disposition is required";
253            return Err(message.to_string())
254        }
255
256        let mut formatted_header_list : String = "".to_string();
257        for header in part.headers.into_iter() {
258            let formatted = format!("{}{}", header.as_string(), SYMBOL.new_line_carriage_return.to_string());
259            formatted_header_list = [formatted_header_list, formatted].join(SYMBOL.empty_string);
260        }
261
262        let header_body_delimiter = SYMBOL.new_line_carriage_return.to_string();
263
264        let body = part.body;
265
266        let part = [
267            formatted_header_list.as_bytes().to_vec(),
268            header_body_delimiter.as_bytes().to_vec(),
269            body
270        ].join(SYMBOL.empty_string.as_bytes());
271
272        Ok(part)
273    }
274
275    pub fn generate(part_list: Vec<Part>, boundary: &str) -> Result<Vec<u8>, String> {
276        if part_list.len() == 0 {
277            let message = "List of the multipart/form-data request body parts is empty";
278            return Err(message.to_string());
279        }
280
281        let mut bytes = vec![];
282        bytes.push(boundary.as_bytes().to_vec());
283
284        for part in part_list.into_iter() {
285            let boxed_part_as_bytes = FormMultipartData::generate_part(part);
286            if boxed_part_as_bytes.is_err() {
287                let message = boxed_part_as_bytes.err().unwrap();
288                return Err(message);
289            }
290            let part_as_bytes = boxed_part_as_bytes.unwrap();
291            bytes.push(part_as_bytes);
292            bytes.push(boundary.as_bytes().to_vec());
293        }
294
295        let result = bytes.join(SYMBOL.new_line_carriage_return.as_bytes());
296
297        Ok(result)
298    }
299}