summaryrefslogtreecommitdiffstats
path: root/headers/src/header_components/disposition.rs
diff options
context:
space:
mode:
Diffstat (limited to 'headers/src/header_components/disposition.rs')
-rw-r--r--headers/src/header_components/disposition.rs308
1 files changed, 308 insertions, 0 deletions
diff --git a/headers/src/header_components/disposition.rs b/headers/src/header_components/disposition.rs
new file mode 100644
index 0000000..66c4a25
--- /dev/null
+++ b/headers/src/header_components/disposition.rs
@@ -0,0 +1,308 @@
+use std::borrow::Cow;
+#[cfg(feature="serde")]
+use std::fmt;
+
+use failure::Fail;
+use soft_ascii_string::SoftAsciiStr;
+use mime::push_params_to_buffer;
+use mime::spec::{MimeSpec, Ascii, Modern, Internationalized};
+
+#[cfg(feature="serde")]
+use serde::{
+ Serialize, Serializer,
+ Deserialize, Deserializer,
+};
+
+use internals::error::{EncodingError, EncodingErrorKind};
+use internals::encoder::{EncodableInHeader, EncodingWriter};
+use ::HeaderTryFrom;
+use ::error::ComponentCreationError;
+
+use super::FileMeta;
+
+/// Disposition Component mainly used for the Content-Disposition header (rfc2183)
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+pub struct Disposition {
+ kind: DispositionKind,
+ file_meta: DispositionParameters
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
+#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
+struct DispositionParameters(FileMeta);
+
+/// Represents what kind of disposition is used (Inline/Attachment)
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum DispositionKind {
+ /// Display the body "inline".
+ ///
+ /// This disposition is mainly used to add some additional content
+ /// and then refers to it through its cid (e.g. in a html mail).
+ Inline,
+ /// Display the body as an attachment to of the mail.
+ Attachment
+}
+
+impl Disposition {
+
+ /// Create a inline disposition with default parameters.
+ pub fn inline() -> Self {
+ Disposition::new( DispositionKind::Inline, FileMeta::default() )
+ }
+
+ /// Create a attachment disposition with default parameters.
+ pub fn attachment() -> Self {
+ Disposition::new( DispositionKind::Attachment, FileMeta::default() )
+ }
+
+ /// Create a new disposition with given parameters.
+ pub fn new( kind: DispositionKind, file_meta: FileMeta ) -> Self {
+ Disposition { kind, file_meta: DispositionParameters( file_meta ) }
+ }
+
+ /// Return which kind of disposition this represents.
+ pub fn kind( &self ) -> DispositionKind {
+ self.kind
+ }
+
+ /// Returns the parameters associated with the disposition.
+ pub fn file_meta( &self ) -> &FileMeta {
+ &self.file_meta
+ }
+
+ /// Returns a mutable reference to the parameters associated with the disposition.
+ pub fn file_meta_mut( &mut self ) -> &mut FileMeta {
+ &mut self.file_meta
+ }
+
+}
+
+#[cfg(feature="serde")]
+impl Serialize for DispositionKind {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where S: Serializer
+ {
+ match self {
+ &DispositionKind::Inline =>
+ serializer.serialize_str("inline"),
+ &DispositionKind::Attachment =>
+ serializer.serialize_str("attachment")
+ }
+ }
+}
+
+#[cfg(feature="serde")]
+impl<'de> Deserialize<'de> for DispositionKind {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where D: Deserializer<'de>
+ {
+ struct Visitor;
+ impl<'de> ::serde::de::Visitor<'de> for Visitor {
+ type Value = DispositionKind;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("\"inline\" or \"attachment\"")
+ }
+
+ fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+ where E: ::serde::de::Error,
+ {
+ if value.eq_ignore_ascii_case("inline") {
+ Ok(DispositionKind::Inline)
+ } else if value.eq_ignore_ascii_case("attachment") {
+ Ok(DispositionKind::Attachment)
+ } else {
+ Err(E::custom(format!(
+ "unknown disposition: {:?}", value
+ )))
+ }
+ }
+ }
+
+ deserializer.deserialize_str(Visitor)
+ }
+}
+
+/// This try from is for usability only, it is
+/// generally recommendet to use Disposition::inline()/::attachment()
+/// as it is type safe / compiler time checked, while this one
+/// isn't
+impl<'a> HeaderTryFrom<&'a str> for Disposition {
+ fn try_from(text: &'a str) -> Result<Self, ComponentCreationError> {
+ if text.eq_ignore_ascii_case("Inline") {
+ Ok(Disposition::inline())
+ } else if text.eq_ignore_ascii_case("Attachment") {
+ Ok(Disposition::attachment())
+ } else {
+ let mut err = ComponentCreationError::new("Disposition");
+ err.set_str_context(text);
+ return Err(err);
+ }
+ }
+}
+
+
+//TODO provide a gnneral way for encoding header parameter ...
+// which follow the scheme: <mainvalue> *(";" <key>"="<value> )
+// this are: ContentType and ContentDisposition for now
+impl EncodableInHeader for DispositionParameters {
+
+ fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
+ let mut params = Vec::<(&str, Cow<str>)>::new();
+ if let Some(filename) = self.file_name.as_ref() {
+ params.push(("filename", Cow::Borrowed(filename)));
+ }
+ if let Some(creation_date) = self.creation_date.as_ref() {
+ params.push(("creation-date", Cow::Owned(creation_date.to_rfc2822())));
+ }
+ if let Some(date) = self.modification_date.as_ref() {
+ params.push(("modification-date", Cow::Owned(date.to_rfc2822())));
+ }
+ if let Some(date) = self.read_date.as_ref() {
+ params.push(("read-date", Cow::Owned(date.to_rfc2822())));
+ }
+ if let Some(size) = self.size.as_ref() {
+ params.push(("size", Cow::Owned(size.to_string())));
+ }
+
+ //TODO instead do optCFWS ; spCFWS <name>=<value>
+ // so that soft line brakes can be done
+ let mut buff = String::new();
+ let res =
+ if handle.mail_type().is_internationalized() {
+ push_params_to_buffer::<MimeSpec<Internationalized, Modern>, _, _, _>(
+ &mut buff, params
+ )
+ } else {
+ push_params_to_buffer::<MimeSpec<Ascii, Modern>, _, _, _>(
+ &mut buff, params
+ )
+ };
+
+ match res {
+ Err(err) => {
+ Err(err.context(EncodingErrorKind::Malformed).into())
+ },
+ Ok(_) => {
+ handle.write_str_unchecked(&*buff)?;
+ Ok(())
+ }
+ }
+ }
+
+ fn boxed_clone(&self) -> Box<EncodableInHeader> {
+ Box::new(self.clone())
+ }
+}
+
+
+impl EncodableInHeader for Disposition {
+
+ fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
+ use self::DispositionKind::*;
+ match self.kind {
+ Inline => {
+ handle.write_str(SoftAsciiStr::from_unchecked("inline"))?;
+ },
+ Attachment => {
+ handle.write_str(SoftAsciiStr::from_unchecked("attachment"))?;
+ }
+ }
+ self.file_meta.encode( handle )?;
+ Ok( () )
+ }
+
+ fn boxed_clone(&self) -> Box<EncodableInHeader> {
+ Box::new(self.clone())
+ }
+}
+
+
+deref0!{+mut DispositionParameters => FileMeta }
+
+#[cfg(test)]
+mod test {
+ use chrono;
+ use std::default::Default;
+
+ use super::*;
+
+ pub fn test_time( modif: u32 ) -> chrono::DateTime<chrono::Utc> {
+ use chrono::prelude::*;
+ Utc.ymd( 2013, 8, 6 ).and_hms( 7, 11, modif )
+ }
+
+ ec_test!{ no_params_inline, {
+ Disposition::inline()
+ } => ascii => [
+ Text "inline"
+ ]}
+
+ ec_test!{ no_params_attachment, {
+ Disposition::attachment()
+ } => ascii => [
+ Text "attachment"
+ ]}
+
+ ec_test!{ attachment_encode_file_name, {
+ Disposition::new( DispositionKind::Attachment, FileMeta {
+ file_name: Some("this is nice".to_owned()),
+ ..Default::default()
+ })
+ } => ascii => [
+ Text "attachment; filename=\"this is nice\""
+ ]}
+
+ ec_test!{ attachment_all_params, {
+ Disposition::new( DispositionKind::Attachment, FileMeta {
+ file_name: Some( "random.png".to_owned() ),
+ creation_date: Some( test_time( 1 ) ),
+ modification_date: Some( test_time( 2 ) ),
+ read_date: Some( test_time( 3 ) ),
+ size: Some( 4096 )
+ })
+ } => ascii => [
+ Text concat!( "attachment",
+ "; filename=random.png",
+ "; creation-date=\"Tue, 6 Aug 2013 07:11:01 +0000\"",
+ "; modification-date=\"Tue, 6 Aug 2013 07:11:02 +0000\"",
+ "; read-date=\"Tue, 6 Aug 2013 07:11:03 +0000\"",
+ "; size=4096" ),
+ ]}
+
+ ec_test!{ inline_file_name_param, {
+ Disposition::new(DispositionKind::Inline, FileMeta {
+ file_name: Some("logo.png".to_owned()),
+ ..Default::default()
+ })
+ } => ascii => [
+ Text "inline; filename=logo.png"
+ ]}
+ //TODO: (1 allow FWS or so in parameters) (2 utf8 file names)
+
+ #[test]
+ fn test_from_str() {
+ assert_ok!( Disposition::try_from( "Inline" ) );
+ assert_ok!( Disposition::try_from( "InLine" ) );
+ assert_ok!( Disposition::try_from( "Attachment" ) );
+
+ assert_err!( Disposition::try_from( "In line") );
+ }
+
+ #[cfg(feature="serde")]
+ fn assert_serialize<S: ::serde::Serialize>() {}
+ #[cfg(feature="serde")]
+ fn assert_deserialize<S: ::serde::Serialize>() {}
+
+ #[cfg(feature="serde")]
+ #[test]
+ fn disposition_serialization() {
+ assert_serialize::<Disposition>();
+ assert_serialize::<DispositionKind>();
+ assert_serialize::<DispositionParameters>();
+ assert_deserialize::<Disposition>();
+ assert_deserialize::<DispositionKind>();
+ assert_deserialize::<DispositionParameters>();
+ }
+} \ No newline at end of file