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