diff options
Diffstat (limited to 'headers/src/header_components/email.rs')
-rw-r--r-- | headers/src/header_components/email.rs | 210 |
1 files changed, 91 insertions, 119 deletions
diff --git a/headers/src/header_components/email.rs b/headers/src/header_components/email.rs index 5b9ad4b..75d655b 100644 --- a/headers/src/header_components/email.rs +++ b/headers/src/header_components/email.rs @@ -1,47 +1,40 @@ -use std::ops::Deref; use std::borrow::Cow; +use std::ops::Deref; use std::str::FromStr; use failure::Fail; -use soft_ascii_string::{SoftAsciiStr, SoftAsciiString, SoftAsciiChar}; +use soft_ascii_string::{SoftAsciiChar, SoftAsciiStr, SoftAsciiString}; -use media_type::spec::{MimeSpec, Ascii, Internationalized, Modern}; +use media_type::spec::{Ascii, Internationalized, MimeSpec, Modern}; use quoted_string::quote_if_needed; -use internals::error::{EncodingError, EncodingErrorKind}; -use internals::grammar::{ - is_ascii, - is_atext, - is_dtext, - is_ws, -}; -use internals::MailType; -use internals::encoder::{EncodingWriter, EncodableInHeader}; use internals::bind::idna; use internals::bind::quoted_string::UnquotedDotAtomTextValidator; +use internals::encoder::{EncodableInHeader, EncodingWriter}; +use internals::error::{EncodingError, EncodingErrorKind}; +use internals::grammar::{is_ascii, is_atext, is_dtext, is_ws}; +use internals::MailType; -use ::{HeaderTryFrom, HeaderTryInto}; -use ::data::{Input, SimpleItem, InnerUtf8 }; -use ::error::ComponentCreationError; +use data::{InnerUtf8, Input, SimpleItem}; +use error::ComponentCreationError; +use {HeaderTryFrom, HeaderTryInto}; /// an email of the form `local-part@domain` /// corresponds to RFC5322 addr-spec, so `<`, `>` padding is _not_ /// part of this Email type (but of the Mailbox type instead) -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct Email { pub local_part: LocalPart, - pub domain: Domain + pub domain: Domain, } +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct LocalPart(Input); -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct LocalPart( Input ); - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct Domain( SimpleItem ); +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Domain(SimpleItem); impl Email { - pub fn check_if_internationalized(&self) -> bool { self.local_part.check_if_internationalized() } @@ -49,59 +42,55 @@ impl Email { pub fn new<T: HeaderTryInto<Input>>(email: T) -> Result<Self, ComponentCreationError> { let email = email.try_into()?.into_shared(); match email { - Input( InnerUtf8::Owned( .. ) ) => unreachable!(), - Input( InnerUtf8::Shared( shared ) ) => { + Input(InnerUtf8::Owned(..)) => unreachable!(), + Input(InnerUtf8::Shared(shared)) => { //1. ownify Input //2. get 2 sub shares split befor/after @ - let index = shared.find( "@" ) - .ok_or_else(|| { - ComponentCreationError::new_with_str("Email", shared.to_string()) - })?; + let index = shared.find("@").ok_or_else(|| { + ComponentCreationError::new_with_str("Email", shared.to_string()) + })?; - let left = shared.clone().map( |all| &all[..index] ); - let local_part = LocalPart::try_from( Input( InnerUtf8::Shared( left ) ) )?; + let left = shared.clone().map(|all| &all[..index]); + let local_part = LocalPart::try_from(Input(InnerUtf8::Shared(left)))?; //index+1 is ok as '@'.utf8_len() == 1 - let right = shared.map( |all| &all[index+1..] ); - let domain = Domain::try_from( Input( InnerUtf8::Shared( right ) ) )?; - Ok( Email { local_part, domain } ) + let right = shared.map(|all| &all[index + 1..]); + let domain = Domain::try_from(Input(InnerUtf8::Shared(right)))?; + Ok(Email { local_part, domain }) } } } } impl LocalPart { - pub fn check_if_internationalized(&self) -> bool { self.0.as_str().bytes().any(|b| b > 0x7f) } } impl<'a> HeaderTryFrom<&'a str> for Email { - fn try_from( email: &str ) -> Result<Self, ComponentCreationError> { + fn try_from(email: &str) -> Result<Self, ComponentCreationError> { Email::new(email) } } impl HeaderTryFrom<String> for Email { - fn try_from( email: String ) -> Result<Self, ComponentCreationError> { + fn try_from(email: String) -> Result<Self, ComponentCreationError> { Email::new(email) } } impl HeaderTryFrom<Input> for Email { - fn try_from( email: Input ) -> Result<Self, ComponentCreationError> { + fn try_from(email: Input) -> Result<Self, ComponentCreationError> { Email::new(email) } } - -impl EncodableInHeader for Email { - +impl EncodableInHeader for Email { fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> { - self.local_part.encode( handle )?; - handle.write_char( SoftAsciiChar::from_unchecked('@') )?; - self.domain.encode( handle )?; - Ok( () ) + self.local_part.encode(handle)?; + handle.write_char(SoftAsciiChar::from_unchecked('@'))?; + self.domain.encode(handle)?; + Ok(()) } fn boxed_clone(&self) -> Box<EncodableInHeader> { @@ -110,40 +99,36 @@ impl EncodableInHeader for Email { } impl<T> HeaderTryFrom<T> for LocalPart - where T: HeaderTryInto<Input> +where + T: HeaderTryInto<Input>, { - - fn try_from( input: T ) -> Result<Self, ComponentCreationError> { - Ok( LocalPart( input.try_into()? ) ) + fn try_from(input: T) -> Result<Self, ComponentCreationError> { + Ok(LocalPart(input.try_into()?)) } - } impl EncodableInHeader for LocalPart { - fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> { let input: &str = &*self.0; let mail_type = handle.mail_type(); let mut validator = UnquotedDotAtomTextValidator::new(mail_type); - let res = - if mail_type.is_internationalized() { - quote_if_needed::<MimeSpec<Internationalized, Modern>, _>(input, &mut validator) - } else { - quote_if_needed::<MimeSpec<Ascii, Modern>, _>(input, &mut validator) - }.map_err(|err| EncodingError - ::from(err.context(EncodingErrorKind::Malformed)) - .with_str_context(input) - )?; - + let res = if mail_type.is_internationalized() { + quote_if_needed::<MimeSpec<Internationalized, Modern>, _>(input, &mut validator) + } else { + quote_if_needed::<MimeSpec<Ascii, Modern>, _>(input, &mut validator) + } + .map_err(|err| { + EncodingError::from(err.context(EncodingErrorKind::Malformed)).with_str_context(input) + })?; handle.mark_fws_pos(); // if mail_type == Ascii quote_if_needed already made sure it's ascii // it also made sure it is valid as it is either `dot-atom-text` or `quoted-string` handle.write_str_unchecked(&*res)?; handle.mark_fws_pos(); - Ok( () ) + Ok(()) } fn boxed_clone(&self) -> Box<EncodableInHeader> { @@ -159,22 +144,18 @@ impl Deref for LocalPart { } } - - impl<T> HeaderTryFrom<T> for Domain - where T: HeaderTryInto<Input> +where + T: HeaderTryInto<Input>, { fn try_from(input: T) -> Result<Self, ComponentCreationError> { let input = input.try_into()?; - let item = - match Domain::check_domain(input.as_str())? { - MailType::Ascii | MailType::Mime8BitEnabled => { - SimpleItem::Ascii(input.into_ascii_item_unchecked()) - }, - MailType::Internationalized => { - SimpleItem::from_utf8_input(input) - } - }; + let item = match Domain::check_domain(input.as_str())? { + MailType::Ascii | MailType::Mime8BitEnabled => { + SimpleItem::Ascii(input.into_ascii_item_unchecked()) + } + MailType::Internationalized => SimpleItem::from_utf8_input(input), + }; Ok(Domain(item)) } @@ -190,14 +171,12 @@ impl FromStr for Domain { } impl Domain { - /// creates a domain from a string without checking for validity pub fn from_unchecked(string: String) -> Self { - let item = - match SoftAsciiString::from_string(string) { - Ok(ascii) => ascii.into(), - Err(err) => err.into_source().into() - }; + let item = match SoftAsciiString::from_string(string) { + Ok(ascii) => ascii.into(), + Err(err) => err.into_source().into(), + }; Domain(item) } @@ -205,7 +184,7 @@ impl Domain { //CONSTRAINT: // the function is only allowed to return MailType::Ascii // if the domain is actually ascii - fn check_domain( domain: &str ) -> Result<MailType, ComponentCreationError> { + fn check_domain(domain: &str) -> Result<MailType, ComponentCreationError> { if domain.starts_with("[") && domain.ends_with("]") { //TODO improved support for domain literals, e.g. internationalized ones? CRLF? etc. for ch in domain.chars() { @@ -220,10 +199,12 @@ impl Domain { let mut ascii = true; let mut dot_alowed = false; for char in domain.chars() { - if ascii { ascii = is_ascii( char ) } + if ascii { + ascii = is_ascii(char) + } if char == '.' && dot_alowed { dot_alowed = false; - } else if !is_atext( char, MailType::Internationalized ) { + } else if !is_atext(char, MailType::Internationalized) { let mut err = ComponentCreationError::new("Domain"); err.set_str_context(domain); return Err(err); @@ -246,39 +227,35 @@ impl Domain { pub fn into_ascii_string(self) -> Result<SoftAsciiString, EncodingError> { match self.0 { SimpleItem::Ascii(ascii) => Ok(ascii.into()), - SimpleItem::Utf8(utf8) => idna::puny_code_domain(utf8) + SimpleItem::Utf8(utf8) => idna::puny_code_domain(utf8), } } pub fn to_ascii_string(&self) -> Result<Cow<SoftAsciiStr>, EncodingError> { Ok(match self.0 { - SimpleItem::Ascii(ref ascii) => { - Cow::Borrowed(ascii) - }, - SimpleItem::Utf8(ref utf8) => { - Cow::Owned(idna::puny_code_domain(utf8)?) - } + SimpleItem::Ascii(ref ascii) => Cow::Borrowed(ascii), + SimpleItem::Utf8(ref utf8) => Cow::Owned(idna::puny_code_domain(utf8)?), }) } } -impl EncodableInHeader for Domain { - +impl EncodableInHeader for Domain { fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> { handle.mark_fws_pos(); match self.0 { - SimpleItem::Ascii( ref ascii ) => { - handle.write_str( ascii )?; - }, - SimpleItem::Utf8( ref utf8 ) => { - handle.write_if_utf8(utf8) + SimpleItem::Ascii(ref ascii) => { + handle.write_str(ascii)?; + } + SimpleItem::Utf8(ref utf8) => { + handle + .write_if_utf8(utf8) .handle_condition_failure(|handle| { - handle.write_str( &*idna::puny_code_domain( utf8 )? ) + handle.write_str(&*idna::puny_code_domain(utf8)?) })?; } } handle.mark_fws_pos(); - Ok( () ) + Ok(()) } fn boxed_clone(&self) -> Box<EncodableInHeader> { @@ -294,26 +271,24 @@ impl Deref for Domain { } } - - #[cfg(test)] mod test { - use internals::encoder::EncodingBuffer; use super::*; + use internals::encoder::EncodingBuffer; #[test] fn email_try_from() { - let email = Email::try_from( "abc@de.fg" ).unwrap(); + let email = Email::try_from("abc@de.fg").unwrap(); assert_eq!( Email { - local_part: LocalPart::try_from( "abc" ).unwrap(), - domain: Domain::try_from( "de.fg" ).unwrap() + local_part: LocalPart::try_from("abc").unwrap(), + domain: Domain::try_from("de.fg").unwrap() }, email ) } - ec_test!{ local_part_simple, { + ec_test! { local_part_simple, { LocalPart::try_from( "hans" )? } => ascii => [ MarkFWS, @@ -322,7 +297,7 @@ mod test { ]} //fails tries to write utf8 - ec_test!{ local_part_quoted, { + ec_test! { local_part_quoted, { LocalPart::try_from( "ha ns" )? } => ascii => [ MarkFWS, @@ -330,8 +305,7 @@ mod test { MarkFWS ]} - - ec_test!{ local_part_utf8, { + ec_test! { local_part_utf8, { LocalPart::try_from( "Jörn" )? } => utf8 => [ MarkFWS, @@ -341,14 +315,14 @@ mod test { #[test] fn local_part_utf8_on_ascii() { - let mut encoder = EncodingBuffer::new( MailType::Ascii ); + let mut encoder = EncodingBuffer::new(MailType::Ascii); let mut handle = encoder.writer(); - let local = LocalPart::try_from( "Jörn" ).unwrap(); - assert_err!(local.encode( &mut handle )); + let local = LocalPart::try_from("Jörn").unwrap(); + assert_err!(local.encode(&mut handle)); handle.undo_header(); } - ec_test!{ domain, { + ec_test! { domain, { Domain::try_from( "bad.at.domain" )? } => ascii => [ MarkFWS, @@ -356,7 +330,7 @@ mod test { MarkFWS ]} - ec_test!{ domain_international, { + ec_test! { domain_international, { Domain::try_from( "dömain" )? } => utf8 => [ MarkFWS, @@ -364,8 +338,7 @@ mod test { MarkFWS ]} - - ec_test!{ domain_encoded, { + ec_test! { domain_encoded, { Domain::try_from( "dat.ü.dü" )? } => ascii => [ MarkFWS, @@ -373,8 +346,7 @@ mod test { MarkFWS ]} - - ec_test!{ email_simple, { + ec_test! { email_simple, { Email::try_from( "simple@and.ascii" )? } => ascii => [ MarkFWS, @@ -420,4 +392,4 @@ mod test { let res: Result<Domain, _> = "...".parse(); assert!(res.is_err()); } -}
\ No newline at end of file +} |