rust_web_server/header/content_disposition/
mod.rs1use 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}