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