hyper_sync/header/common/
content_disposition.rs1use language_tags::LanguageTag;
10use std::fmt;
11use unicase;
12
13use header::{Header, Raw, 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)]
85pub struct ContentDisposition {
86 pub disposition: DispositionType,
88 pub parameters: Vec<DispositionParam>,
90}
91
92impl Header for ContentDisposition {
93 fn header_name() -> &'static str {
94 static NAME: &'static str = "Content-Disposition";
95 NAME
96 }
97
98 fn parse_header(raw: &Raw) -> ::Result<ContentDisposition> {
99 parsing::from_one_raw_str(raw).and_then(|s: String| {
100 let mut sections = s.split(';');
101 let disposition = match sections.next() {
102 Some(s) => s.trim(),
103 None => return Err(::Error::Header),
104 };
105
106 let mut cd = ContentDisposition {
107 disposition: if unicase::eq_ascii(&*disposition, "inline") {
108 DispositionType::Inline
109 } else if unicase::eq_ascii(&*disposition, "attachment") {
110 DispositionType::Attachment
111 } else {
112 DispositionType::Ext(disposition.to_owned())
113 },
114 parameters: Vec::new(),
115 };
116
117 for section in sections {
118 let mut parts = section.splitn(2, '=');
119
120 let key = if let Some(key) = parts.next() {
121 key.trim()
122 } else {
123 return Err(::Error::Header);
124 };
125
126 let val = if let Some(val) = parts.next() {
127 val.trim()
128 } else {
129 return Err(::Error::Header);
130 };
131
132 cd.parameters.push(
133 if unicase::eq_ascii(&*key, "filename") {
134 DispositionParam::Filename(
135 Charset::Ext("UTF-8".to_owned()), None,
136 val.trim_matches('"').as_bytes().to_owned())
137 } else if unicase::eq_ascii(&*key, "filename*") {
138 let extended_value = try!(parse_extended_value(val));
139 DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value)
140 } else {
141 DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned())
142 }
143 );
144 }
145
146 Ok(cd)
147 })
148 }
149
150 #[inline]
151 fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
152 f.fmt_line(self)
153 }
154}
155
156impl fmt::Display for ContentDisposition {
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 match self.disposition {
159 DispositionType::Inline => try!(write!(f, "inline")),
160 DispositionType::Attachment => try!(write!(f, "attachment")),
161 DispositionType::Ext(ref s) => try!(write!(f, "{}", s)),
162 }
163 for param in &self.parameters {
164 match *param {
165 DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => {
166 let mut use_simple_format: bool = false;
167 if opt_lang.is_none() {
168 if let Charset::Ext(ref ext) = *charset {
169 if unicase::eq_ascii(&**ext, "utf-8") {
170 use_simple_format = true;
171 }
172 }
173 }
174 if use_simple_format {
175 try!(write!(f, "; filename=\"{}\"",
176 match String::from_utf8(bytes.clone()) {
177 Ok(s) => s,
178 Err(_) => return Err(fmt::Error),
179 }));
180 } else {
181 try!(write!(f, "; filename*={}'", charset));
182 if let Some(ref lang) = *opt_lang {
183 try!(write!(f, "{}", lang));
184 };
185 try!(write!(f, "'"));
186 try!(http_percent_encode(f, bytes))
187 }
188 },
189 DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
190 }
191 }
192 Ok(())
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::{ContentDisposition,DispositionType,DispositionParam};
199 use ::header::Header;
200 use ::header::shared::Charset;
201
202 #[test]
203 fn test_parse_header() {
204 assert!(ContentDisposition::parse_header(&"".into()).is_err());
205
206 let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into();
207 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
208 let b = ContentDisposition {
209 disposition: DispositionType::Ext("form-data".to_owned()),
210 parameters: vec![
211 DispositionParam::Ext("dummy".to_owned(), "3".to_owned()),
212 DispositionParam::Ext("name".to_owned(), "upload".to_owned()),
213 DispositionParam::Filename(
214 Charset::Ext("UTF-8".to_owned()),
215 None,
216 "sample.png".bytes().collect()) ]
217 };
218 assert_eq!(a, b);
219
220 let a = "attachment; filename=\"image.jpg\"".into();
221 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
222 let b = ContentDisposition {
223 disposition: DispositionType::Attachment,
224 parameters: vec![
225 DispositionParam::Filename(
226 Charset::Ext("UTF-8".to_owned()),
227 None,
228 "image.jpg".bytes().collect()) ]
229 };
230 assert_eq!(a, b);
231
232 let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into();
233 let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap();
234 let b = ContentDisposition {
235 disposition: DispositionType::Attachment,
236 parameters: vec![
237 DispositionParam::Filename(
238 Charset::Ext("UTF-8".to_owned()),
239 None,
240 vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20,
241 0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ]
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 = 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 = "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 = "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}