rust_web_server/header/content_disposition/
mod.rs

1use crate::header::Header;
2use crate::symbol::{SYMBOL};
3
4#[cfg(test)]
5mod tests;
6
7pub struct ContentDisposition {
8    pub disposition_type: String,
9    pub field_name: Option<String>,
10    pub file_name: Option<String>
11}
12
13pub struct DispositionType {
14    pub inline: &'static str,
15    pub attachment: &'static str,
16    pub form_data: &'static str
17}
18
19impl ContentDisposition {
20    pub fn as_string(&self) -> Result<String, String> {
21        let mut formatted = "".to_string();
22        let is_inline = self.disposition_type.to_string() == DISPOSITION_TYPE.inline.to_string();
23        if is_inline {
24            let is_file_name_specified = self.file_name.is_some();
25            if is_file_name_specified {
26                let message = "For Content-Disposition of type inline 'filename' property is redundant";
27                return Err(message.to_string());
28            }
29            let is_field_name_specified = self.field_name.is_some();
30            if is_field_name_specified {
31                let message = "For Content-Disposition of type inline 'name' property is redundant";
32                return Err(message.to_string());
33            }
34
35            formatted = format!("{}: {}", Header::_CONTENT_DISPOSITION.to_string(), self.disposition_type);
36        }
37
38        let is_attachment = self.disposition_type.to_string() == DISPOSITION_TYPE.attachment.to_string();
39        if is_attachment {
40            let is_field_name_specified = self.field_name.is_some();
41            if is_field_name_specified {
42                let message = "For Content-Disposition of type attachment 'name' property is redundant";
43                return Err(message.to_string());
44            }
45
46
47            let is_file_name_specified = self.file_name.is_some();
48            if is_file_name_specified {
49                let file_name = self.file_name.clone().unwrap();
50                formatted = format!("{}: {}; filename=\"{}\"", Header::_CONTENT_DISPOSITION.to_string(), self.disposition_type, file_name);
51            } else {
52                formatted = format!("{}: {}", Header::_CONTENT_DISPOSITION.to_string(), self.disposition_type);
53            }
54        }
55
56        let is_form_data = self.disposition_type.to_string() == DISPOSITION_TYPE.form_data.to_string();
57        if is_form_data {
58            let is_file_name_specified = self.file_name.is_some();
59            let is_field_name_specified = self.field_name.is_some();
60
61            if !is_field_name_specified {
62                let message = "Content-Dispositon header with a type of multipart/form-data is required to have 'name' property";
63                return Err(message.to_string())
64            }
65
66            if is_file_name_specified {
67                let file_name = self.file_name.clone().unwrap();
68                let field_name = self.field_name.clone().unwrap();
69
70                formatted = format!("{}: {}; name=\"{}\"; filename=\"{}\"", Header::_CONTENT_DISPOSITION.to_string(), self.disposition_type, field_name, file_name);
71            } else {
72                let field_name = self.field_name.clone().unwrap();
73
74                formatted = format!("{}: {}; name=\"{}\"", Header::_CONTENT_DISPOSITION.to_string(), self.disposition_type, field_name);
75            }
76        }
77
78
79        Ok(formatted)
80    }
81}
82
83pub const DISPOSITION_TYPE: DispositionType = DispositionType {
84    inline: "inline",
85    attachment: "attachment",
86    form_data: "form-data",
87};
88
89
90impl ContentDisposition {
91    pub fn parse(raw_content_disposition: &str) -> Result<ContentDisposition, String> {
92        let mut parts: Vec<&str> = raw_content_disposition.split(SYMBOL.semicolon).collect();
93        if parts.len() == 0 {
94            parts.push(raw_content_disposition);
95        }
96
97        let disposition_type = parts.get(0).unwrap();
98        if disposition_type.to_string() != DISPOSITION_TYPE.inline.to_string()
99            && disposition_type.to_string() != DISPOSITION_TYPE.attachment.to_string()
100            && disposition_type.to_string() != DISPOSITION_TYPE.form_data.to_string() {
101            let message = format!("Unable to parse Content-Disposition header: {}", raw_content_disposition);
102            return Err(message)
103        }
104
105        let mut filename = None;
106        let mut fieldname = None;
107
108        let boxed_second_element = parts.get(1);
109        if boxed_second_element.is_some() {
110            let second_element = boxed_second_element.unwrap();
111            let boxed_split = second_element.split_once(SYMBOL.equals);
112            if boxed_split.is_none() {
113                let message = format!("Unable to parse second property in the Content-Disposition header: {}", second_element);
114                return Err(message)
115            }
116            let (key, value) = boxed_split.unwrap();
117            let key = key.trim();
118            let is_filename_field = key == "filename";
119            if is_filename_field {
120                filename = Some(value.to_string().replace(SYMBOL.quotation_mark, SYMBOL.empty_string));
121            }
122            let is_name_field = key == "name";
123            if is_name_field  {
124                fieldname = Some(value.to_string().replace(SYMBOL.quotation_mark, SYMBOL.empty_string));
125            }
126        }
127
128        let boxed_third_element = parts.get(2);
129        if boxed_third_element.is_some() {
130            let second_element = boxed_third_element.unwrap();
131            let boxed_split = second_element.split_once(SYMBOL.equals);
132            if boxed_split.is_none() {
133                let message = format!("Unable to parse second property in the Content-Disposition header: {}", second_element);
134                return Err(message)
135            }
136            let (key, value) = boxed_split.unwrap();
137            let key = key.trim();
138            let is_filename_field = key == "filename";
139            if is_filename_field {
140                filename = Some(value.to_string().replace(SYMBOL.quotation_mark, SYMBOL.empty_string));
141            }
142            let is_name_field = key == "name";
143            if is_name_field  {
144                fieldname = Some(value.to_string().replace(SYMBOL.quotation_mark, SYMBOL.empty_string));
145            }
146
147            if !is_filename_field && !is_name_field {
148                let message = format!("Unable to parse property in the Content-Disposition header: {}", raw_content_disposition);
149                return Err(message.to_string())
150            }
151        }
152
153        let content_disposition = ContentDisposition {
154            disposition_type: disposition_type.to_string(),
155            field_name: fieldname,
156            file_name: filename,
157        };
158
159        if disposition_type.to_string() == DISPOSITION_TYPE.form_data.to_string()
160            && content_disposition.field_name == None {
161            let message = format!("Field name is not set for Content-Disposition: {}", raw_content_disposition);
162            return Err(message)
163        }
164
165        Ok(content_disposition)
166    }
167}