mail_headers_ng/header_components/
disposition.rs1use std::borrow::Cow;
2#[cfg(feature="serde")]
3use std::fmt;
4
5use failure::Fail;
6use soft_ascii_string::SoftAsciiStr;
7use media_type::push_params_to_buffer;
8use media_type::spec::{MimeSpec, Ascii, Modern, Internationalized};
9
10#[cfg(feature="serde")]
11use serde::{
12 Serialize, Serializer,
13 Deserialize, Deserializer,
14};
15
16use internals::error::{EncodingError, EncodingErrorKind};
17use internals::encoder::{EncodableInHeader, EncodingWriter};
18use ::HeaderTryFrom;
19use ::error::ComponentCreationError;
20
21use super::FileMeta;
22
23#[derive(Debug, Clone, Eq, PartialEq, Hash)]
25#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
26pub struct Disposition {
27 kind: DispositionKind,
28 file_meta: DispositionParameters
29}
30
31#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
32#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
33struct DispositionParameters(FileMeta);
34
35#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
37pub enum DispositionKind {
38 Inline,
43 Attachment
45}
46
47impl Disposition {
48
49 pub fn inline() -> Self {
51 Disposition::new( DispositionKind::Inline, FileMeta::default() )
52 }
53
54 pub fn attachment() -> Self {
56 Disposition::new( DispositionKind::Attachment, FileMeta::default() )
57 }
58
59 pub fn new( kind: DispositionKind, file_meta: FileMeta ) -> Self {
61 Disposition { kind, file_meta: DispositionParameters( file_meta ) }
62 }
63
64 pub fn kind( &self ) -> DispositionKind {
66 self.kind
67 }
68
69 pub fn file_meta( &self ) -> &FileMeta {
71 &self.file_meta
72 }
73
74 pub fn file_meta_mut( &mut self ) -> &mut FileMeta {
76 &mut self.file_meta
77 }
78
79}
80
81#[cfg(feature="serde")]
82impl Serialize for DispositionKind {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where S: Serializer
85 {
86 match self {
87 &DispositionKind::Inline =>
88 serializer.serialize_str("inline"),
89 &DispositionKind::Attachment =>
90 serializer.serialize_str("attachment")
91 }
92 }
93}
94
95#[cfg(feature="serde")]
96impl<'de> Deserialize<'de> for DispositionKind {
97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98 where D: Deserializer<'de>
99 {
100 struct Visitor;
101 impl<'de> ::serde::de::Visitor<'de> for Visitor {
102 type Value = DispositionKind;
103
104 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
105 formatter.write_str("\"inline\" or \"attachment\"")
106 }
107
108 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
109 where E: ::serde::de::Error,
110 {
111 if value.eq_ignore_ascii_case("inline") {
112 Ok(DispositionKind::Inline)
113 } else if value.eq_ignore_ascii_case("attachment") {
114 Ok(DispositionKind::Attachment)
115 } else {
116 Err(E::custom(format!(
117 "unknown disposition: {:?}", value
118 )))
119 }
120 }
121 }
122
123 deserializer.deserialize_str(Visitor)
124 }
125}
126
127impl<'a> HeaderTryFrom<&'a str> for Disposition {
132 fn try_from(text: &'a str) -> Result<Self, ComponentCreationError> {
133 if text.eq_ignore_ascii_case("Inline") {
134 Ok(Disposition::inline())
135 } else if text.eq_ignore_ascii_case("Attachment") {
136 Ok(Disposition::attachment())
137 } else {
138 let mut err = ComponentCreationError::new("Disposition");
139 err.set_str_context(text);
140 return Err(err);
141 }
142 }
143}
144
145
146impl EncodableInHeader for DispositionParameters {
150
151 fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
152 let mut params = Vec::<(&str, Cow<str>)>::new();
153 if let Some(filename) = self.file_name.as_ref() {
154 params.push(("filename", Cow::Borrowed(filename)));
155 }
156 if let Some(creation_date) = self.creation_date.as_ref() {
157 params.push(("creation-date", Cow::Owned(creation_date.to_rfc2822())));
158 }
159 if let Some(date) = self.modification_date.as_ref() {
160 params.push(("modification-date", Cow::Owned(date.to_rfc2822())));
161 }
162 if let Some(date) = self.read_date.as_ref() {
163 params.push(("read-date", Cow::Owned(date.to_rfc2822())));
164 }
165 if let Some(size) = self.size.as_ref() {
166 params.push(("size", Cow::Owned(size.to_string())));
167 }
168
169 let mut buff = String::new();
172 let res =
173 if handle.mail_type().is_internationalized() {
174 push_params_to_buffer::<MimeSpec<Internationalized, Modern>, _, _, _>(
175 &mut buff, params
176 )
177 } else {
178 push_params_to_buffer::<MimeSpec<Ascii, Modern>, _, _, _>(
179 &mut buff, params
180 )
181 };
182
183 match res {
184 Err(err) => {
185 Err(err.context(EncodingErrorKind::Malformed).into())
186 },
187 Ok(_) => {
188 handle.write_str_unchecked(&*buff)?;
189 Ok(())
190 }
191 }
192 }
193
194 fn boxed_clone(&self) -> Box<EncodableInHeader> {
195 Box::new(self.clone())
196 }
197}
198
199
200impl EncodableInHeader for Disposition {
201
202 fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
203 use self::DispositionKind::*;
204 match self.kind {
205 Inline => {
206 handle.write_str(SoftAsciiStr::from_unchecked("inline"))?;
207 },
208 Attachment => {
209 handle.write_str(SoftAsciiStr::from_unchecked("attachment"))?;
210 }
211 }
212 self.file_meta.encode( handle )?;
213 Ok( () )
214 }
215
216 fn boxed_clone(&self) -> Box<EncodableInHeader> {
217 Box::new(self.clone())
218 }
219}
220
221
222deref0!{+mut DispositionParameters => FileMeta }
223
224#[cfg(test)]
225mod test {
226 use chrono;
227 use std::default::Default;
228
229 use super::*;
230
231 pub fn test_time( modif: u32 ) -> chrono::DateTime<chrono::Utc> {
232 use chrono::prelude::*;
233 Utc.ymd( 2013, 8, 6 ).and_hms( 7, 11, modif )
234 }
235
236 ec_test!{ no_params_inline, {
237 Disposition::inline()
238 } => ascii => [
239 Text "inline"
240 ]}
241
242 ec_test!{ no_params_attachment, {
243 Disposition::attachment()
244 } => ascii => [
245 Text "attachment"
246 ]}
247
248 ec_test!{ attachment_encode_file_name, {
249 Disposition::new( DispositionKind::Attachment, FileMeta {
250 file_name: Some("this is nice".to_owned()),
251 ..Default::default()
252 })
253 } => ascii => [
254 Text "attachment; filename=\"this is nice\""
255 ]}
256
257 ec_test!{ attachment_all_params, {
258 Disposition::new( DispositionKind::Attachment, FileMeta {
259 file_name: Some( "random.png".to_owned() ),
260 creation_date: Some( test_time( 1 ) ),
261 modification_date: Some( test_time( 2 ) ),
262 read_date: Some( test_time( 3 ) ),
263 size: Some( 4096 )
264 })
265 } => ascii => [
266 Text concat!( "attachment",
267 "; filename=random.png",
268 "; creation-date=\"Tue, 6 Aug 2013 07:11:01 +0000\"",
269 "; modification-date=\"Tue, 6 Aug 2013 07:11:02 +0000\"",
270 "; read-date=\"Tue, 6 Aug 2013 07:11:03 +0000\"",
271 "; size=4096" ),
272 ]}
273
274 ec_test!{ inline_file_name_param, {
275 Disposition::new(DispositionKind::Inline, FileMeta {
276 file_name: Some("logo.png".to_owned()),
277 ..Default::default()
278 })
279 } => ascii => [
280 Text "inline; filename=logo.png"
281 ]}
282 #[test]
285 fn test_from_str() {
286 assert_ok!( Disposition::try_from( "Inline" ) );
287 assert_ok!( Disposition::try_from( "InLine" ) );
288 assert_ok!( Disposition::try_from( "Attachment" ) );
289
290 assert_err!( Disposition::try_from( "In line") );
291 }
292
293 #[cfg(feature="serde")]
294 fn assert_serialize<S: ::serde::Serialize>() {}
295 #[cfg(feature="serde")]
296 fn assert_deserialize<S: ::serde::Serialize>() {}
297
298 #[cfg(feature="serde")]
299 #[test]
300 fn disposition_serialization() {
301 assert_serialize::<Disposition>();
302 assert_serialize::<DispositionKind>();
303 assert_serialize::<DispositionParameters>();
304 assert_deserialize::<Disposition>();
305 assert_deserialize::<DispositionKind>();
306 assert_deserialize::<DispositionParameters>();
307 }
308}