diff options
author | Philipp Korber <p.korber@1aim.com> | 2018-11-16 15:46:43 +0100 |
---|---|---|
committer | Philipp Korber <p.korber@1aim.com> | 2018-11-16 15:46:43 +0100 |
commit | 652d6f0ffeee7302a2cb51059bef75d8b0bb50be (patch) | |
tree | c3851592642938172f280f7428d43e08b0fe2cbe /headers/src | |
parent | 0947fe8996149fe20a6d47a793f9555790eb2eae (diff) |
refactor: merged sources of mail-headers,mail-internals,mail-core, mail
Originally it was palaned to do a merge with `--allow-unrelated-history`
but this can not be doesn as `mail-core` has a "invalid" history which
has a merge conflict **with itself**. So even rewinding the history on
a empty repo is not possible.
Instead the code was directly coppied over losing history.
But the history is still available in the different
`history-backup-*` branches. It is just that the past history
is decoupled from the current history.
Diffstat (limited to 'headers/src')
34 files changed, 6556 insertions, 0 deletions
diff --git a/headers/src/convert.rs b/headers/src/convert.rs new file mode 100644 index 0000000..1153965 --- /dev/null +++ b/headers/src/convert.rs @@ -0,0 +1,37 @@ +//TODO potentially move HeaderTryFrom to `mail-headers` +use error::ComponentCreationError; + +//TODO replace with std TryFrom once it is stable +// (either a hard replace, or a soft replace which implements HeaderTryFrom if TryFrom exist) +/// Workaround for `TryFrom`,`TryInto` not being stable. +pub trait HeaderTryFrom<T>: Sized { + fn try_from(val: T) -> Result<Self, ComponentCreationError>; +} + +/// Workaround for `TryFrom`,`TryInto` not being stable. +pub trait HeaderTryInto<T>: Sized { + fn try_into(self) -> Result<T, ComponentCreationError>; +} + +impl<F, T> HeaderTryInto<T> for F where T: HeaderTryFrom<F> { + fn try_into(self) -> Result<T, ComponentCreationError> { + T::try_from(self) + } +} + + +impl<T> HeaderTryFrom<T> for T { + fn try_from(val: T) -> Result<Self, ComponentCreationError> { + Ok( val ) + } +} + +// It is not possible to auto-implement HeaderTryFrom for From/Into as +// this will make new HeaderTryFrom implementations outside of this care +// nearly impossible making the trait partially useless +// +//impl<T, F> HeaderTryFrom<F> for T where F: Into<T> { +// fn try_from(val: F) -> Result<T, Error> { +// Ok( val.into() ) +// } +//}
\ No newline at end of file diff --git a/headers/src/data/inner_item.rs b/headers/src/data/inner_item.rs new file mode 100644 index 0000000..147a959 --- /dev/null +++ b/headers/src/data/inner_item.rs @@ -0,0 +1,251 @@ +use std::ops::Deref; +use std::sync::Arc; +use std::borrow::ToOwned; + +use owning_ref::OwningRef; +use soft_ascii_string::{SoftAsciiString, SoftAsciiStr}; + +#[cfg(feature="serde")] +use serde::{Serialize, Deserialize, Serializer, Deserializer, de::Error as __Error}; + + +/// InnerAscii is string data container which can contain either a +/// owned `SoftAsciiString` or a `SoftAsciiStr` reference into a shared +/// string buffer. +#[derive(Debug, Clone, Hash, Eq)] +pub enum InnerAscii { + Owned(SoftAsciiString), + //by using String+SoftAsciiStr we can eliminate unessesary copies + Shared(OwningRef<Arc<String>, SoftAsciiStr>) +} + +impl InnerAscii { + + /// converts this container into on which uses underlying shared data + /// + /// if the data is already shared nothing is done. + /// If not the owned data is converted into the underlying string buffer + /// and `OwningRef` is used to enable the shared reference + /// + /// Note that the underlying buffer is no an `SoftAsciiString` but a + /// `String` (from which we happend to know that it fulfills the "is + /// us-ascii" soft constraint). This allows us to have an `InnerAscii` + /// share data with a possible non us-ascii string buffer as long as + /// the part accessable through the `SoftAsciiStr` is ascii. Or at last + /// should be ascii as it's a soft constraint. + pub fn into_shared(self) -> Self { + match self { + InnerAscii::Owned(value) => { + let buffer: Arc<String> = Arc::new(value.into()); + let orf = OwningRef::new(buffer).map(|data: &String| { + // we got it from a SoftAsciiString so no check here + SoftAsciiStr::from_unchecked(&**data) + }); + InnerAscii::Shared(orf) + } + v => v + } + } +} + +/// InnerUtf8 is string data container which can contain either a +/// owned `String` or a `str` reference into a shared +/// string buffer. +#[derive(Debug, Clone, Hash, Eq)] +pub enum InnerUtf8 { + Owned(String), + //by using String+SoftAsciiStr we can eliminate unessesary copies + Shared(OwningRef<Arc<String>, str>) +} + +impl InnerUtf8 { + + /// converts this container into on which uses underlying shared data + /// + /// if the data is already shared nothing is done. + /// If not the owned data is converted into the underlying string buffer + /// and `OwningRef` is used to enable the shared reference + pub fn into_shared(self) -> Self { + match self { + InnerUtf8::Owned(value) => { + let buffer = Arc::new(value); + let orf = OwningRef::new(buffer) + .map(|rced| &**rced); + InnerUtf8::Shared(orf) + } + v => v + } + } +} + + +macro_rules! inner_impl { + ($name:ident, $owned_form:ty, $borrowed_form:ty) => ( + impl $name { + pub fn new<S: Into<$owned_form>>( data: S ) -> Self { + $name::Owned( data.into() ) + } + } + impl From<$owned_form> for $name { + fn from( data: $owned_form ) -> Self { + Self::new( data ) + } + } + + impl Into<$owned_form> for $name { + fn into(self) -> $owned_form { + match self { + $name::Owned( owned ) => owned, + $name::Shared( shared ) => { + let as_ref: &$borrowed_form = &*shared; + as_ref.to_owned() + } + } + } + } + + impl Deref for $name { + type Target = $borrowed_form; + + fn deref( &self ) -> &$borrowed_form{ + match *self { + $name::Owned( ref string ) => &*string, + $name::Shared( ref owning_ref ) => &*owning_ref + } + } + } + + #[cfg(feature="serde")] + impl Serialize for $name { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where S: Serializer + { + let borrowed: &$borrowed_form = &*self; + let as_ref: &str = borrowed.as_ref(); + serializer.serialize_str( as_ref ) + } + } + + impl PartialEq for $name { + fn eq(&self, other: &$name) -> bool { + let me: &$borrowed_form = &*self; + let other: &$borrowed_form = &*other; + me == other + } + } + + impl AsRef<str> for $name { + fn as_ref(&self) -> &str { + self.as_str() + } + } + ); +} + +inner_impl!{ InnerAscii, SoftAsciiString, SoftAsciiStr } +inner_impl!{ InnerUtf8, String, str } +//inner_impl!{ InnerOtherItem, OtherString, OtherStr } + +impl InnerAscii { + pub fn as_str( &self ) -> &str { + match *self { + InnerAscii::Owned( ref owned ) => owned.as_str(), + InnerAscii::Shared( ref shared ) => shared.as_str() + } + } +} + +#[cfg(feature="serde")] +impl<'de> Deserialize<'de> for InnerAscii { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where D: Deserializer<'de> + { + let content = String::deserialize(deserializer) + .map_err(|err| D::Error::custom(err))?; + let content = SoftAsciiString::from_string(content) + .map_err(|err| D::Error::custom(err))?; + Ok(InnerAscii::from(content)) + } +} + +impl InnerUtf8 { + pub fn as_str( &self ) -> &str { + match *self { + InnerUtf8::Owned( ref owned ) => owned.as_str(), + InnerUtf8::Shared( ref shared ) => &**shared + } + } +} + +#[cfg(feature="serde")] +impl<'de> Deserialize<'de> for InnerUtf8 { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where D: Deserializer<'de> + { + let content = String::deserialize(deserializer) + .map_err(|err| D::Error::custom(err))?; + Ok(InnerUtf8::from(content)) + } +} + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn inner_ascii_item_eq() { + let a = InnerAscii::Owned( SoftAsciiString::from_string( "same" ).unwrap() ); + let b = InnerAscii::Shared( + OwningRef::new(Arc::new("same".to_owned())) + .map(|v| SoftAsciiStr::from_unchecked(&**v)) + ); + assert_eq!( a, b ); + } + + #[test] + fn inner_ascii_item_neq() { + let a = InnerAscii::Owned( SoftAsciiString::from_string( "same" ).unwrap() ); + let b = InnerAscii::Shared( + OwningRef::new(Arc::new("not same".to_owned())) + .map(|v| SoftAsciiStr::from_unchecked(&**v)) + ); + assert_ne!( a, b ); + } + + #[test] + fn inner_utf8_item_eq() { + let a = InnerUtf8::Owned( String::from( "same" ) ); + let b = InnerUtf8::Shared( + OwningRef::new( + Arc::new( String::from( "same" ) ) ) + .map(|v| &**v) + ); + assert_eq!( a, b ); + } + + #[test] + fn inner_utf8_item_neq() { + let a = InnerUtf8::Owned( String::from( "same" ) ); + let b = InnerUtf8::Shared( + OwningRef::new( + Arc::new( String::from( "not same" ) ) ) + .map(|v| &**v) + ); + assert_ne!( a, b ); + } + + #[test] + fn has_as_str() { + use std::borrow::ToOwned; + + assert_eq!( + "hy", + InnerAscii::Owned( SoftAsciiStr::from_unchecked("hy").to_owned() ).as_str() + ); + assert_eq!( + "hy", + InnerUtf8::Owned( "hy".into() ).as_str() + ); + } +}
\ No newline at end of file diff --git a/headers/src/data/input.rs b/headers/src/data/input.rs new file mode 100644 index 0000000..ad43d10 --- /dev/null +++ b/headers/src/data/input.rs @@ -0,0 +1,131 @@ +use std::result::{ Result as StdResult }; +use std::fmt::{self, Display}; + +use soft_ascii_string::SoftAsciiString; + +use ::HeaderTryFrom; +use ::error::ComponentCreationError; + +use super::inner_item::{ InnerUtf8, InnerAscii }; + +/// a Input is similar to Item a container data container used in different +/// context's with different restrictions, but different to an Item it +/// might contain characters which require encoding (e.g. encoded words) +/// to represent them +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct Input( pub InnerUtf8 ); + + +impl Input { + + pub fn into_shared( self ) -> Self { + Input( self.0.into_shared() ) + } + + + pub fn into_ascii_item( self ) -> StdResult<InnerAscii, Input> { + match self { + Input( InnerUtf8::Owned( string ) ) => { + match SoftAsciiString::from_string(string) { + Ok(asciied) => Ok(InnerAscii::Owned(asciied)), + Err(err) => Err(Input(InnerUtf8::Owned(err.into_source()))) + } + } + Input( InnerUtf8::Shared( shared ) ) => { + if shared.is_ascii() { + Ok(InnerAscii::Owned(SoftAsciiString::from_unchecked(&*shared))) + } else { + Err(Input(InnerUtf8::Shared(shared))) + } + } + } + } + + pub fn into_ascii_item_unchecked( self ) -> InnerAscii { + match self { + Input( InnerUtf8::Owned( string ) ) => + InnerAscii::Owned( SoftAsciiString::from_unchecked( string ) ), + Input( InnerUtf8::Shared( shared ) ) => + InnerAscii::Owned( + SoftAsciiString::from_unchecked(&*shared) ) + } + } + + pub fn into_utf8_item( self ) -> InnerUtf8 { + self.0 + } +} + +impl<'a> From<&'a str> for Input { + fn from( s: &'a str ) -> Self { + Input( InnerUtf8::Owned( s.into() ) ) + } +} + +impl From<String> for Input { + fn from( s: String ) -> Self { + Input( InnerUtf8::Owned( s ) ) + } +} + +impl<'a> HeaderTryFrom<&'a str> for Input +{ + fn try_from(val: &'a str) -> Result<Self, ComponentCreationError> { + Ok(val.into()) + } +} +impl HeaderTryFrom<String> for Input +{ + fn try_from(val: String) -> Result<Self, ComponentCreationError> { + Ok(val.into()) + } +} + +impl Into<String> for Input { + fn into(self) -> String { + self.0.into() + } +} + +impl Display for Input { + fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { + fter.write_str(self.as_str()) + } +} + +deref0!( +mut Input => InnerUtf8 ); + + + +#[cfg(test)] +mod test { + use std::sync::Arc; + use owning_ref::OwningRef; + + use super::*; + + #[test] + fn input_eq() { + let a = Input( InnerUtf8::Owned( "same".into() ) ); + let b = Input( InnerUtf8::Shared( + OwningRef::new( + Arc::new( String::from( "same" ) ) ) + .map(|v| &**v) + ) ); + assert_eq!( a, b ); + } + + #[test] + fn input_neq() { + let a = Input( InnerUtf8::Owned( "not same".into() ) ); + let b = Input( InnerUtf8::Shared( + OwningRef::new( + Arc::new( String::from( "not at all same" ) ) ) + .map(|v| &**v) + ) ); + assert_ne!( a, b ); + } + + + +}
\ No newline at end of file diff --git a/headers/src/data/mod.rs b/headers/src/data/mod.rs new file mode 100644 index 0000000..4d22f1b --- /dev/null +++ b/headers/src/data/mod.rs @@ -0,0 +1,23 @@ +//! A number of little helper types, which contain text. +//! +//! They provide mainly following functionality: +//! +//! 1. remember if the data is Ascii/Utf8 +//! - this might be extended at some point +//! to contain non ascii data +//! 2. make sure the types are cheap to clone, by +//! sharing the text internally. +//! - this is mainly helpful when parsing a mail +//! +//! Both main points are for features which I decided to +//! to not yet implement, as such **there is a chance +//! that this module will be removed int the future**. +//! +mod inner_item; +pub use self::inner_item::*; + +mod input; +pub use self::input::*; + +mod simple_item; +pub use self::simple_item::*;
\ No newline at end of file diff --git a/headers/src/data/simple_item.rs b/headers/src/data/simple_item.rs new file mode 100644 index 0000000..e0cd09f --- /dev/null +++ b/headers/src/data/simple_item.rs @@ -0,0 +1,113 @@ +use std::ops::Deref; + +use soft_ascii_string::{ SoftAsciiStr, SoftAsciiString}; + +use super::input::Input; +use super::inner_item::{ InnerAscii, InnerUtf8 }; + +#[cfg(feature="serde")] +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +#[cfg_attr(feature="serde", derive(Serialize, Deserialize))] +pub enum SimpleItem { + /// specifies that the Item is valid Ascii, nothing more + Ascii( InnerAscii ), + /// specifies that the Item is valid Utf8, nothing more + Utf8( InnerUtf8 ) +} + +impl SimpleItem { + + pub fn as_str( &self ) -> &str { + use self::SimpleItem::*; + match *self { + Ascii( ref value ) => value.as_str(), + Utf8( ref value ) => value.as_str() + } + } + + pub fn is_ascii( &self ) -> bool { + use self::SimpleItem::*; + match *self { + Ascii( .. ) => true, + Utf8( .. ) => false + } + } + + pub fn from_utf8_input( s: Input ) -> Self { + SimpleItem::Utf8( s.0 ) + } + + pub fn from_utf8( s: String ) -> Self { + SimpleItem::Utf8( InnerUtf8::Owned( s ) ) + } + + +} + +impl Deref for SimpleItem { + type Target = str; + + fn deref( &self ) -> &str { + use self::SimpleItem::*; + match *self { + Ascii( ref astr ) => astr.as_str(), + Utf8( ref utf8 ) => &**utf8 + } + } +} + + +impl Into<String> for SimpleItem { + fn into(self) -> String { + use self::SimpleItem::*; + match self { + Ascii( aitem ) => { + let astring: SoftAsciiString = aitem.into(); + astring.into() + }, + Utf8( string ) => string.into() + } + } +} + +impl<'a> From<&'a str> for SimpleItem { + fn from( string: &'a str ) -> Self { + Self::from( String::from( string ) ) + } +} + +impl From<String> for SimpleItem { + fn from( string: String ) -> Self { + match SoftAsciiString::from_string( string ) { + Ok( astring ) => SimpleItem::Ascii( InnerAscii::Owned( astring ) ), + Err( err ) => SimpleItem::Utf8( InnerUtf8::Owned( err.into_source() ) ) + } + } +} + +impl From<SoftAsciiString> for SimpleItem { + fn from( astring: SoftAsciiString ) -> Self { + SimpleItem::Ascii( InnerAscii::Owned( astring ) ) + } +} + +impl From<Input> for SimpleItem { + fn from(input: Input) -> Self { + match input { + Input( InnerUtf8::Owned( string ) ) => match SoftAsciiString::from_string( string ) { + Ok( ascii ) => SimpleItem::Ascii( InnerAscii::Owned( ascii ) ), + Err( err ) => SimpleItem::Utf8( InnerUtf8::Owned( err.into_source() ) ) + }, + Input( InnerUtf8::Shared( shared ) ) => { + if shared.is_ascii() { + let a_shared = shared.map(|s| SoftAsciiStr::from_unchecked(s)); + SimpleItem::Ascii(InnerAscii::Shared(a_shared)) + } else { + SimpleItem::Utf8(InnerUtf8::Shared(shared)) + } + } + } + } +} diff --git a/headers/src/error.rs b/headers/src/error.rs new file mode 100644 index 0000000..beb92cb --- /dev/null +++ b/headers/src/error.rs @@ -0,0 +1,205 @@ +//! module contains the (new) errors emitted by this crate +use std::fmt::{self, Display}; + +use failure::{Fail, Context, Error as FError, Backtrace}; + +use ::name::HeaderName; + +/// This error can occur if different implementations for the +/// same header (e.g. `Subject`) where used in the same `HeaderMap`. +#[derive(Debug, Fail)] +#[fail(display = "cast error caused by mixing different header implementations for {}", header_name)] +pub struct HeaderTypeError { + header_name: HeaderName, + backtrace: Backtrace +} + +impl HeaderTypeError { + pub fn new(name: HeaderName) -> Self { + HeaderTypeError { + header_name: name, + backtrace: Backtrace::new() + } + } + + pub fn new_with_backtrace(name: HeaderName, backtrace: Backtrace) -> Self { + HeaderTypeError { + header_name: name, + backtrace + } + } +} + +/// A validator specified in a header definition failed. +/// +/// Common validators are e.g. to make sure that if a +/// From header has multiple mailboxes that there is +/// a Sender header etc. +#[derive(Debug, Fail)] +pub enum HeaderValidationError { + #[fail(display = "{}", _0)] + BuildIn(Context<BuildInValidationError>), + #[fail(display = "{}", _0)] + Custom(FError) +} + +impl From<BuildInValidationError> for HeaderValidationError { + fn from(err: BuildInValidationError) -> Self { + HeaderValidationError::BuildIn(Context::new(err)) + } +} + +impl From<Context<BuildInValidationError>> for HeaderValidationError { + fn from(err: Context<BuildInValidationError>) -> Self { + HeaderValidationError::BuildIn(err) + } +} + +/// The build-in error variants (error kinds) which can be returned +/// when running a header map validator. +#[derive(Copy, Clone, Debug, Fail, PartialEq, Eq, Hash)] +pub enum BuildInValidationError { + + /// This error is returned by `use_contextual_validators` if there is a "max one" inconsistency. + /// + /// I.e. if multiple implementations of the same header are used in the same map but + /// the implementations do not agree on wether or not the header can appear at most one + /// time in a header section. + #[fail(display = "{} header field contained both \"multi\" and \"max one\" header impl", header_name)] + MaxOneInconsistency { header_name: &'static str }, + + #[fail(display = "{} header field can appear at most one time in a header map", header_name)] + MoreThenOne { header_name: &'static str }, + + #[fail(display = "From field contained multiple addresses but no Sender field was set")] + MultiMailboxFromWithoutSender, + + #[fail(display = "each resent block must have a resent-date field")] + ResentDateFieldMissing, + + #[fail(display = "Resent-From field in resent block without a Resent-Sender field")] + MultiMailboxResentFromWithoutResentSender +} + +macro_rules! header_validation_bail { + (kind: $($tt:tt)*) => ({ + let build_in = $crate::error::BuildInValidationError::$($tt)*; + return Err(HeaderValidationError::BuildIn(::failure::Context::new(build_in))); + }); +} + + +/// Helper type which is either a `Backtrace` or an full `failure::Error`. +/// +/// This can be used to either just contain a backtrace into an custom +/// error or to chain it in front of another error without adding another +/// backtrace, depending on the creating context. +#[derive(Debug)] +pub enum ChainTail { + Backtrace(Backtrace), + Error(FError) +} + +impl ChainTail { + + fn backtrace(&self) -> &Backtrace { + match *self { + ChainTail::Backtrace(ref trace) => trace, + ChainTail::Error(ref error) => error.backtrace() + } + } + + fn as_fail(&self) -> Option<&Fail> { + match *self { + ChainTail::Backtrace(_) => None, + ChainTail::Error(ref error) => Some(error.as_fail()) + } + } +} + +/// Creating a (header field) component from the given data failed +/// +/// A good example converting a string to a mailbox by parsing it, +/// or more concretely failing to do so because it's not a valid +/// mail address. +#[derive(Debug)] +pub struct ComponentCreationError { + component: &'static str, + backtrace: ChainTail, + str_context: Option<String> +} + +impl ComponentCreationError { + + /// create a new `ComponentCreationError` based on a different error and the name of the component + /// + /// The name is normally the type name, for example `Email`, `Mailbox` etc. + pub fn from_parent<P>(parent: P, component: &'static str) -> Self + where P: Into<FError> + { + ComponentCreationError { + component, + backtrace: ChainTail::Error(parent.into()), + str_context: None + } + } + + /// creates a new `ComponentCreationError` based on the components name + /// + /// The name is normally the type name, for example `Email`, `Mailbox` etc. + pub fn new(component: &'static str) -> Self { + ComponentCreationError { + component, + backtrace: ChainTail::Backtrace(Backtrace::new()), + str_context: None + } + } + + /// creates a new `ComponentCreationError` based on the components name with a str_context + /// + /// The name is normally the type name, for example `Email`, `Mailbox` etc. + /// + /// The `str_context` is a snipped of text which can help a human to identify the + /// invalid parts, e.g. for parsing a email it could be the invalid email address. + pub fn new_with_str<I>(component: &'static str, str_context: I) -> Self + where I: Into<String> + { + ComponentCreationError { + component, + backtrace: ChainTail::Backtrace(Backtrace::new()), + str_context: Some(str_context.into()) + } + } + + pub fn str_context(&self) -> Option<&str> { + self.str_context.as_ref().map(|s|&**s) + } + + pub fn set_str_context<I>(&mut self, ctx: I) + where I: Into<String> + { + self.str_context = Some(ctx.into()); + } + + pub fn with_str_context<I>(mut self, ctx: I) -> Self + where I: Into<String> + { + self.set_str_context(ctx); + self + } +} + +impl Fail for ComponentCreationError { + fn cause(&self) -> Option<&Fail> { + self.backtrace.as_fail() + } + fn backtrace(&self) -> Option<&Backtrace> { + Some(self.backtrace.backtrace()) + } +} + +impl Display for ComponentCreationError { + fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { + write!(fter, "creating component {} failed", self.component) + } +} diff --git a/headers/src/header.rs b/headers/src/header.rs new file mode 100644 index 0000000..ae63632 --- /dev/null +++ b/headers/src/header.rs @@ -0,0 +1,253 @@ +use std::any::TypeId; +use std::ops::{Deref, DerefMut}; +use std::fmt::{self, Debug}; + +use internals::{ + error::EncodingError, + encoder::{ + EncodableInHeader, + EncodingWriter, + } +}; + + +use ::error::ComponentCreationError; +use ::convert::HeaderTryInto; +use ::name::{HeaderName, HasHeaderName}; +//NOTE: this is a circular dependency between Header/HeaderMap +// but putting up e.g. a GenericHeaderMap trait/interface is +// not worth the work at all +use ::map::HeaderMapValidator; |