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 | |
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')
38 files changed, 6827 insertions, 0 deletions
diff --git a/headers/Cargo.toml b/headers/Cargo.toml new file mode 100644 index 0000000..539ec5e --- /dev/null +++ b/headers/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "mail-headers" +description = "[internal/mail-api] header parts for the mail-api crate (inkl. header map and standard header impl)" +version = "0.6.0-wip" +authors = ["Philipp Korber <philippkorber@gmail.com>"] +documentation = "https://docs.rs/mail-headers" +keywords = [] +categories = [] +license = "MIT OR Apache-2.0" +repository = "https://github.com/1aim/mail-headers" + +[features] +serde-impl = [ "serde", "vec1/serde" ] +traceing = [ "mail-internals/traceing" ] + +[dependencies] +failure = "0.1" +owning_ref = "0.4" +nom = "3.1.0" +soft-ascii-string = "1" +quoted-string = "0.6" +mail-internals = { git="https://github.com/1aim/mail-internal" } +vec1 = "1" +chrono = "0.4" +total-order-multi-map = "0.4.5" +serde = { version="1.0", optional=true, features=["derive"] } + +[dependencies.mime] +git="https://github.com/1aim/mime" +branch="parser_revamp" +features=["expose-param-utils"] +version="0.4.0" + +[dev-dependencies] +serde_test = "1.0.80" diff --git a/headers/README.md b/headers/README.md new file mode 100644 index 0000000..8a49553 --- /dev/null +++ b/headers/README.md @@ -0,0 +1,150 @@ + +# mail-headers + +**Provides header specific functionality for the `mail` crate** + +--- + +This crate provides header specific functionality for the `mail` +crate. This includes: + +- `HeaderName`, `Header` and `HeaderMap` as for the general API +- `HeaderTryFrom`, `HeaderTryInto` as the `TryFrom`/`TryInto` + traits are not stable but we need something similar to their + functionality. +- a number of headers like `_To`,`_From`, `Sender`, `Subject` + and many more (Note that `_To` and `_From` are prefixed with + and `_` to prevent name collisions when importing them, i.e. + importing `_From as From` would shadow `std::convert::From` + which can lead to extremely irritating errors). +- a number of components which are used to represent the + content/field body of an header field e.g. `MailboxList` + or `Email`. They are placed in the `components` module. +- a `headers!` macro for making the creation of an `HeaderMap` + with a number of headers easier. +- a `def_headers!` macro for defining new custom headers + +## Example (HeaderMap) + +A header map is a collection representing a number +of mail headers in an specific order. It can be +created like this: + +```rust +#[macro_use] +extern crate mail_headers; + +// just import all headers +use mail_headers::*; +use mail_headers::error::ComponentCreationError; + +fn create_headers() -> Result<HeaderMap, ComponentCreationError> { + headers!{ + // from and to can have multiple values + // until specialization is stable is array + // is necessary + _From: [("My Fancy Display Name", "theduck@example.com")], + _To: [ "unknown@example.com", ], + Subject: "Who are you?" + } +} + +fn main() { + let headers = create_headers().unwrap(); + assert_eq!(headers.len(), 3); + + if let Some(subject) = headers.get_single(Subject) { + // as long a you don't play around with custom headers AND + // don't mix multiple implementations for the same header + // `.unwrap()` is just fine. + let subject = subject.expect("mixed different Subject header implementations"); + println!("found subject: {}", subject); + } +} +``` + +## Example (custom header) + +If needed users of the `mail` crate can create their own +headers, through this should be done with care. + +Note that the second field (then `unchecked { <name> }`), +expects a specific naming scheme, the auto-generated test +do check if it's violated but if you just run the code and +ignore the failing tests strange error can occure. ( +The scheme is a capitalise the first letter of each +word and write all other letter in lower case, i.e. +`X-Id` is ok but `X-ID` isn't). The reason for this is because +of the way the header does the field lookup. While this +is not nice, for most use cases there is no need to +generate custom headers and in the future this might be +circumvented by auto-generating the name with a proc-derive. + +```rust +#[macro_use] +extern crate mail_headers; + +use mail_headers::components; + +// this will define two headers `XFooEmail` and `XBarMailbox` +// the first will add a header field named `X-Foo-Email` with +// a value which is an `components::Email` and the second will +// add field with a value which is an `components::Mailbox`. +// +// Note that through the way they are defined `XFooEmail` can +// at most appear 1 time in an header map, while `XBarMailbox` +// can appear multiple times. Be aware that this is checked through +// so called validators which needs to be explicitly run, which they +// are if this header map is used to create a mail (running them +// when adding fields would be a mess as you would have to add +// transactions which can add/remove multiple fields at once, and +// implementing auto-generation for some fields which are required if +// some other fields are given in a certain way would be harder too). + +// If in scope both can be used in the `headers!` macro, +// like any other header. +// +def_headers! { + // the name of the auto-generated test + test_name: validate_header_names, + + // the scope from which all components should be imported + // E.g. `DateTime` refers to `components::DateTime`. + scope: components, + + // definitions of the headers or the form + // <type_name>, unchecked { <filed_name> }, <component>, <validator> + XFooEmail, unchecked { "X-Foo-Email" }, Email , maxOne, + XBarMailbox, unchecked { "X-Bar-Mailbox" }, Mailbox, None +} + +fn main() { + let headers = headers! { + XFooEmail: "123@example.com", + XBarMailbox: ("My Funy Name", "notfunny@example.com"), + XBarMailbox: "without.display.name@example.com" + }.unwrap(); +} +``` + + +## Documentation + +Documentation can be [viewed on docs.rs](https://docs.rs/mail-headers). +(once it is published ;) ) + + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/headers/doc/list_of_headers.txt b/headers/doc/list_of_headers.txt new file mode 100644 index 0000000..7cf1666 --- /dev/null +++ b/headers/doc/list_of_headers.txt @@ -0,0 +1,78 @@ +-- FIXME AsciiString => HeaderName +-- NOT HERE Content-Header-Extension |AsciiString | Unstructured|, +-- NOT HERE Other |AsciiString | Unstructured|, + +RFC | Name | Rust-Type | Comment +------|---------------------------|-------------------|---------------------------- +5322 | | | RFC 5322 obsoletes RFC 822 + | Date | DateTime | + | From | MailboxList | + | Sender | Mailbox | + | Reply-To | MailboxList | + | To | MailboxList | + | Cc | MailboxList | + | Bcc | OptMailboxList | + | Message-ID | MessageID | + | In-Reply-To | MessageIDList | + | References | MessageIDList | + | Subject | Unstructured | + | Comments | Unstructured | + | Keywords | PhraseList | + | Resent-Date | DateTime | + | Resent-From | MailboxList | + | Resent-Sender | Mailbox | + | Resent-To | MailboxList | + | Resent-Cc | MailboxList | + | Resent-Bcc | OptMailboxList | + | Resent-Msg-ID | MessageID | + | Return-Path | Path | + | Received | ReceivedToken | +------|---------------------------|-------------------|--------------------------- +2045 | Content-Type | Mime | + | Content-ID | MessageID | + | Content-Transfer-Encoding | TransferEncoding | + | Content-Description | Unstructured | the rfc states it is TEXT, but referes to RFC822 + | | | in RFC5322 there is no longer TEXT, it was replaced + | | | by Unstructured +------|---------------------------|-------------------|--------------------------- +2183 | | | proposed standard (obsoltets rfc 1806) + | Content-Disposition | Disposition | +------|---------------------------|-------------------|--------------------------- + + + +------ "others" ---- +-- e.g. see https://www.cs.tut.fi/~jkorpela/headers.html +--Delivered-To |loop detection| +--User-Agent |client software used by orginator| +--Abuse-Reports-To |inserted by some servers| +--X-Envelop-From |Mailbox| |sender in the envelop copied into the body| +--X-Envelop-To |Mailbox| |again envelop information moved into body| +--X-Remote-Addr |from html| +-- +------Proposed Standard---- +--RFC 1766 +-- Content-Language |LanguageTag| +--RFC 1864 +-- Content-MD5 |Base64| +-- +------Experimental-------- +--RFC 1806 |attachment of inline| +-- Content-Disposition |Dispositions| +--RFC 1327 & 1911 +-- Importance +-- Sensitivity +--RFC 1154 & 1505 +-- Encoding +-- +------Not Standad ------ +--RFC 1036 +-- FollowupTo |??MessageID| +--RFC 1036 |count of lines| +-- Lines |usize| +--RFC ???? +-- Status |U/R/O/D/N| |should NEVER EVER be generate for a mail to send, use by some mail delivery systems INTERNAL ONLY| +-- +-- +------Not Standard Discouraged---- +--ContentLength |usize| |do never generate content length header in a mail you send, it's a HTTP think|
\ No newline at end of file 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, Deseria |