clia_ntex_files/file_header/
content_disposition.rs1use language_tags::LanguageTag;
10use std::fmt;
11
12use crate::standard_header;
13
14use super::error;
15use super::parsing::{self, http_percent_encode, parse_extended_value};
16use super::{Charset, Header, RawLike};
17
18#[derive(Clone, Debug, PartialEq)]
20pub enum DispositionType {
21 Inline,
23 Attachment,
26 Ext(String),
28}
29
30#[derive(Clone, Debug, PartialEq)]
32pub enum DispositionParam {
33 Filename(Charset, Option<LanguageTag>, Vec<u8>),
36 Ext(String, String),
39}
40
41#[derive(Clone, Debug, PartialEq)]
71pub struct ContentDisposition {
72 pub disposition: DispositionType,
74 pub parameters: Vec<DispositionParam>,
76}
77
78impl Header for ContentDisposition {
79 fn header_name() -> &'static str {
80 static NAME: &str = "Content-Disposition";
81 NAME
82 }
83
84 fn parse_header<'a, T>(raw: &'a T) -> error::Result<ContentDisposition>
85 where
86 T: RawLike<'a>,
87 {
88 parsing::from_one_raw_str(raw).and_then(|s: String| {
89 let mut sections = s.split(';');
90 let disposition = match sections.next() {
91 Some(s) => s.trim(),
92 None => return Err(error::Error::Header),
93 };
94
95 let mut cd = ContentDisposition {
96 disposition: if unicase::eq_ascii(disposition, "inline") {
97 DispositionType::Inline
98 } else if unicase::eq_ascii(disposition, "attachment") {
99 DispositionType::Attachment
100 } else {
101 DispositionType::Ext(disposition.to_owned())
102 },
103 parameters: Vec::new(),
104 };
105
106 for section in sections {
107 let mut parts = section.splitn(2, '=');
108
109 let key = if let Some(key) = parts.next() {
110 key.trim()
111 } else {
112 return Err(error::Error::Header);
113 };
114
115 let val = if let Some(val) = parts.next() {
116 val.trim()
117 } else {
118 return Err(error::Error::Header);
119 };
120
121 cd.parameters.push(if unicase::eq_ascii(key, "filename") {
122 DispositionParam::Filename(
123 Charset::Ext("UTF-8".to_owned()),
124 None,
125 val.trim_matches('"').as_bytes().to_owned(),
126 )
127 } else if unicase::eq_ascii(key, "filename*") {
128 let extended_value = parse_extended_value(val)?;
129 DispositionParam::Filename(
130 extended_value.charset,
131 extended_value.language_tag,
132 extended_value.value,
133 )
134 } else {
135 DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned())
136 });
137 }
138
139 Ok(cd)
140 })
141 }
142
143 #[inline]
144 fn fmt_header(&self, f: &mut super::Formatter) -> fmt::Result {
145 f.fmt_line(self)
146 }
147}
148
149impl fmt::Display for ContentDisposition {
150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151 match self.disposition {
152 DispositionType::Inline => write!(f, "inline")?,
153 DispositionType::Attachment => write!(f, "attachment")?,
154 DispositionType::Ext(ref s) => write!(f, "{}", s)?,
155 }
156 for param in &self.parameters {
157 match *param {
158 DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => {
159 let mut use_simple_format: bool = false;
160 if opt_lang.is_none() {
161 if let Charset::Ext(ref ext) = *charset {
162 if unicase::eq_ascii(&**ext, "utf-8") {
163 use_simple_format = true;
164 }
165 }
166 }
167 if use_simple_format {
168 write!(
169 f,
170 "; filename=\"{}\"",
171 match String::from_utf8(bytes.clone()) {
172 Ok(s) => s,
173 Err(_) => return Err(fmt::Error),
174 }
175 )?;
176 } else {
177 write!(f, "; filename*={}'", charset)?;
178 if let Some(ref lang) = *opt_lang {
179 write!(f, "{}", lang)?;
180 };
181 write!(f, "'")?;
182 http_percent_encode(f, bytes)?;
183 }
184 }
185 DispositionParam::Ext(ref k, ref v) => write!(f, "; {}=\"{}\"", k, v)?,
186 }
187 }
188 Ok(())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::{Charset, ContentDisposition, DispositionParam, DispositionType, Header};
195 use crate::file_header::Raw;
196
197 #[test]
198 fn test_parse_header() {
199 let a: Raw = "".into();
200 assert!(ContentDisposition::parse_header(&a).is_err());
201
202 let a: Raw = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into();
203 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
204 let b = ContentDisposition {
205 disposition: DispositionType::Ext("form-data".to_owned()),
206 parameters: vec![
207 DispositionParam::Ext("dummy".to_owned(), "3".to_owned()),
208 DispositionParam::Ext("name".to_owned(), "upload".to_owned()),
209 DispositionParam::Filename(
210 Charset::Ext("UTF-8".to_owned()),
211 None,
212 "sample.png".bytes().collect(),
213 ),
214 ],
215 };
216 assert_eq!(a, b);
217
218 let a: Raw = "attachment; filename=\"image.jpg\"".into();
219 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
220 let b = ContentDisposition {
221 disposition: DispositionType::Attachment,
222 parameters: vec![DispositionParam::Filename(
223 Charset::Ext("UTF-8".to_owned()),
224 None,
225 "image.jpg".bytes().collect(),
226 )],
227 };
228 assert_eq!(a, b);
229
230 let a: Raw = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into();
231 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
232 let b = ContentDisposition {
233 disposition: DispositionType::Attachment,
234 parameters: vec![DispositionParam::Filename(
235 Charset::Ext("UTF-8".to_owned()),
236 None,
237 vec![
238 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r',
239 b'a', b't', b'e', b's',
240 ],
241 )],
242 };
243 assert_eq!(a, b);
244 }
245
246 #[test]
247 fn test_display() {
248 let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates";
249 let a: Raw = as_string.into();
250 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
251 let display_rendered = format!("{}", a);
252 assert_eq!(as_string, display_rendered);
253
254 let a: Raw = "attachment; filename*=UTF-8''black%20and%20white.csv".into();
255 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
256 let display_rendered = format!("{}", a);
257 assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered);
258
259 let a: Raw = "attachment; filename=colourful.csv".into();
260 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
261 let display_rendered = format!("{}", a);
262 assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered);
263 }
264}
265
266standard_header!(ContentDisposition, CONTENT_DISPOSITION);