diff options
Diffstat (limited to 'headers/src/header_components/word.rs')
-rw-r--r-- | headers/src/header_components/word.rs | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/headers/src/header_components/word.rs b/headers/src/header_components/word.rs new file mode 100644 index 0000000..ed8b065 --- /dev/null +++ b/headers/src/header_components/word.rs @@ -0,0 +1,222 @@ +use quoted_string; + +use internals::grammar::is_atext; +use internals::grammar::encoded_word::EncodedWordContext; +use internals::error::{EncodingError, EncodingErrorKind}; +use internals::encoder::{EncodingWriter, EncodableInHeader}; +use internals::bind::encoded_word::{EncodedWordEncoding, WriterWrapper}; +use internals::bind::quoted_string::{MailQsSpec, InternationalizedMailQsSpec}; +use ::{HeaderTryFrom, HeaderTryInto}; +use ::error::ComponentCreationError; +use ::data::Input; + + +use super::CFWS; + + + +#[derive( Debug, Clone, Eq, PartialEq, Hash )] +pub struct Word { + pub left_padding: Option<CFWS>, + pub input: Input, + pub right_padding: Option<CFWS> +} + +impl<T> HeaderTryFrom<T> for Word + where T: HeaderTryInto<Input> +{ + + fn try_from( input: T ) -> Result<Self, ComponentCreationError> { + //TODO there should be a better way, I think I take the grammar to literal here + // could not any WSP be a potential FWSP, do we really need this kind of fine gained + // control, it feels kind of useless?? + let input = input.try_into()?; + //FEATURE_TODO(fail_fast): check if input contains a CTL char, + // which is/>>should<< always be an error (through in the standard you could but should + // not have them in encoded words) + Ok( Word { left_padding: None, input, right_padding: None } ) + } +} + +impl Word { + + pub fn pad_left( &mut self, padding: CFWS) { + self.left_padding = Some( padding ) + } + + pub fn pad_right( &mut self, padding: CFWS) { + self.right_padding = Some( padding ) + } + +} + + +/// As word has to be differently encoded, depending on the context it +/// appears in it cannot implement EncodableInHeader, instead we have +/// a function which can be used by type containing it which (should) +/// implement EncodableInHeader +/// +/// If `ecw_ctx` is `None` the word can not be encoded as a "encoded-word", +/// if it is `Some(context)` the `context` represents in which context the +/// word does appear, which changes some properties of the encoded word. +/// +/// NOTE: != encoded-word, through it might create an encoded-word +pub fn do_encode_word<'a,'b: 'a>( + word: &'a Word, + handle: &'a mut EncodingWriter<'b>, + ecw_ctx: Option<EncodedWordContext>, +) -> Result<(), EncodingError> { + + if let Some( pad ) = word.left_padding.as_ref() { + pad.encode( handle )?; + } + + let input: &str = &*word.input; + let mail_type = handle.mail_type(); + handle.write_if(input, |input| { + (!input.starts_with("=?")) + && input.chars().all( |ch| is_atext( ch, mail_type ) ) + + }).handle_condition_failure(|handle| { + if let Some( _ecw_ctx ) = ecw_ctx { + //FIXME actually use the EncodedWordContext + let encoding = EncodedWordEncoding::QuotedPrintable; + let mut writer = WriterWrapper::new( + encoding, + handle + ); + encoding.encode(input, &mut writer); + Ok(()) + } else { + let mail_type = handle.mail_type(); + let res = + if mail_type.is_internationalized() { + //spec needed is mime internationlized + quoted_string::quote::<InternationalizedMailQsSpec>(input) + } else { + //spec is mime + quoted_string::quote::<MailQsSpec>(input) + }; + let quoted = res.map_err(|_err| { + EncodingError + ::from(EncodingErrorKind::Malformed) + .with_str_context(input) + })?; + handle.write_str_unchecked(&*quoted) + } + })?; + + if let Some( pad ) = word.right_padding.as_ref() { + pad.encode( handle )?; + } + Ok( () ) +} + + +#[cfg(test)] +mod test { + use std::mem; + + use internals::MailType; + use internals::encoder::EncodingBuffer; + use internals::encoder::TraceToken::*; + use internals::encoder::simplify_trace_tokens; + + use super::*; + use super::super::FWS; + + + ec_test!{encode_pseudo_encoded_words, { + let word = Word::try_from( "=?" )?; + enc_closure!(move |handle: &mut EncodingWriter| { + do_encode_word( &word, handle, Some( EncodedWordContext::Text ) ) + }) + } => ascii => [ + Text "=?utf8?Q?=3D=3F?=" + ]} + + ec_test!{encode_word, { + let word = Word::try_from( "a↑b" )?; + enc_closure!(move |handle: &mut EncodingWriter| { + do_encode_word( &word, handle, Some( EncodedWordContext::Text ) ) + }) + } => ascii => [ + Text "=?utf8?Q?a=E2=86=91b?=" + ]} + + + #[test] + fn encode_fails() { + let mut encoder = EncodingBuffer::new(MailType::Ascii); + let mut handle = encoder.writer(); + let word = Word::try_from( "a↑b" ).unwrap(); + assert_err!(do_encode_word( &word, &mut handle, None )); + handle.undo_header(); + } + + + ec_test!{quoted_fallback, { + let word = Word::try_from( "a\"b" )?; + enc_closure!(move |handle: &mut EncodingWriter| { + do_encode_word( &word, handle, None ) + }) + } => ascii => [ + Text r#""a\"b""# + ]} + + + #[test] + fn encode_word_padding() { + let words = &[ + ( Word { + left_padding: None, + input: "abc".into(), + right_padding: None, + }, vec![ + Text("abc".into()) + ] ), + ( Word { + left_padding: Some( CFWS::SingleFws( FWS) ), + input: "abc".into(), + right_padding: None, + }, vec![ + MarkFWS, + Text(" abc".into()) + ] ), + ( Word { + left_padding: Some( CFWS::SingleFws( FWS ) ), + input: "abc".into(), + right_padding: Some( CFWS::SingleFws( FWS ) ), + }, vec![ + MarkFWS, + Text(" abc".into()), + MarkFWS, + Text(" ".into()) + ] ), + ( Word { + left_padding: None, + input: "abc".into(), + right_padding: Some( CFWS::SingleFws( FWS ) ), + }, vec![ + Text("abc".into()), + MarkFWS, + Text(" ".into()) + ] ) + ]; + + for &( ref word, ref expection) in words.iter() { + let mut encoder = EncodingBuffer::new(MailType::Ascii); + { + let mut handle = encoder.writer(); + do_encode_word( word, &mut handle, None ).unwrap(); + mem::forget(handle); + } + assert_eq!( + &simplify_trace_tokens(encoder.trace.into_iter()), + expection + ); + } + } + + +}
\ No newline at end of file |