mail_headers/header_components/
word.rs

1use quoted_string;
2
3use internals::grammar::is_atext;
4use internals::grammar::encoded_word::EncodedWordContext;
5use internals::error::{EncodingError, EncodingErrorKind};
6use internals::encoder::{EncodingWriter, EncodableInHeader};
7use internals::bind::encoded_word::{EncodedWordEncoding, WriterWrapper};
8use internals::bind::quoted_string::{MailQsSpec, InternationalizedMailQsSpec};
9use ::{HeaderTryFrom, HeaderTryInto};
10use ::error::ComponentCreationError;
11use ::data::Input;
12
13
14use super::CFWS;
15
16
17/// A ward as in the mail grammar (RFC 5322).
18///
19/// **Warning: This is likely to change in the future before the 1.0 release**.
20#[derive( Debug, Clone, Eq, PartialEq, Hash )]
21pub struct Word {
22    pub left_padding: Option<CFWS>,
23    pub input: Input,
24    pub right_padding: Option<CFWS>
25}
26
27impl<T> HeaderTryFrom<T> for Word
28    where T: HeaderTryInto<Input>
29{
30
31    fn try_from( input: T ) -> Result<Self, ComponentCreationError> {
32        //TODO there should be a better way, I think I take the grammar to literal here
33        // could not any WSP be a potential FWSP, do we really need this kind of fine gained
34        // control, it feels kind of useless??
35        let input = input.try_into()?;
36        //FEATURE_TODO(fail_fast): check if input contains a CTL char,
37        //  which is/>>should<< always be an error (through in the standard you could but should
38        //  not have them in encoded words)
39        Ok( Word { left_padding: None, input, right_padding: None } )
40    }
41}
42
43impl Word {
44
45    pub fn pad_left( &mut self, padding: CFWS) {
46        self.left_padding = Some( padding )
47    }
48
49    pub fn pad_right( &mut self, padding: CFWS) {
50        self.right_padding = Some( padding )
51    }
52
53}
54
55
56/// As word has to be differently encoded, depending on the context it
57/// appears in it cannot implement EncodableInHeader, instead we have
58/// a function which can be used by type containing it which (should)
59/// implement EncodableInHeader
60///
61/// If `ecw_ctx` is `None` the word can not be encoded as a "encoded-word",
62/// if it is `Some(context)` the `context` represents in which context the
63/// word does appear, which changes some properties of the encoded word.
64///
65/// NOTE: != encoded-word, through it might create an encoded-word
66pub fn do_encode_word<'a,'b: 'a>(
67    word: &'a Word,
68    handle: &'a mut EncodingWriter<'b>,
69    ecw_ctx: Option<EncodedWordContext>,
70) -> Result<(), EncodingError> {
71
72    if let Some( pad ) = word.left_padding.as_ref() {
73        pad.encode( handle )?;
74    }
75
76    let input: &str = &*word.input;
77    let mail_type = handle.mail_type();
78    handle.write_if(input, |input| {
79        (!input.contains("=?"))
80            && input.chars().all( |ch| is_atext( ch, mail_type ) )
81
82    }).handle_condition_failure(|handle| {
83        if let Some( _ecw_ctx ) = ecw_ctx {
84            //FIXME actually use the EncodedWordContext
85            let encoding = EncodedWordEncoding::QuotedPrintable;
86            let mut writer = WriterWrapper::new(
87                encoding,
88                handle
89            );
90            encoding.encode(input, &mut writer);
91            Ok(())
92        } else {
93            let mail_type = handle.mail_type();
94            let res =
95                if mail_type.is_internationalized() {
96                    //spec needed is mime internationlized
97                    quoted_string::quote::<InternationalizedMailQsSpec>(input)
98                } else {
99                    //spec is mime
100                    quoted_string::quote::<MailQsSpec>(input)
101                };
102            let quoted = res.map_err(|_err| {
103                EncodingError
104                    ::from(EncodingErrorKind::Malformed)
105                    .with_str_context(input)
106            })?;
107            handle.write_str_unchecked(&*quoted)
108        }
109    })?;
110
111    if let Some( pad ) = word.right_padding.as_ref() {
112        pad.encode( handle )?;
113    }
114    Ok( () )
115}
116
117
118#[cfg(test)]
119mod test {
120    use std::mem;
121
122    use internals::MailType;
123    use internals::encoder::EncodingBuffer;
124    use internals::encoder::TraceToken::*;
125    use internals::encoder::simplify_trace_tokens;
126
127    use super::*;
128    use super::super::FWS;
129
130
131    ec_test!{encode_pseudo_encoded_words, {
132        let word = Word::try_from( "=?" )?;
133        enc_closure!(move |handle: &mut EncodingWriter| {
134            do_encode_word( &word, handle, Some( EncodedWordContext::Text ) )
135        })
136    } => ascii => [
137        Text "=?utf8?Q?=3D=3F?="
138    ]}
139
140    ec_test!{encode_word, {
141        let word = Word::try_from( "a↑b" )?;
142        enc_closure!(move |handle: &mut EncodingWriter| {
143            do_encode_word( &word, handle, Some( EncodedWordContext::Text ) )
144        })
145    } => ascii => [
146        Text "=?utf8?Q?a=E2=86=91b?="
147    ]}
148
149
150    #[test]
151    fn encode_fails() {
152        let mut encoder = EncodingBuffer::new(MailType::Ascii);
153        let mut handle = encoder.writer();
154        let word = Word::try_from( "a↑b" ).unwrap();
155        assert_err!(do_encode_word( &word, &mut handle, None ));
156        handle.undo_header();
157    }
158
159
160    ec_test!{quoted_fallback, {
161        let word = Word::try_from( "a\"b" )?;
162        enc_closure!(move |handle: &mut EncodingWriter| {
163            do_encode_word( &word, handle, None )
164        })
165    } => ascii => [
166        Text r#""a\"b""#
167    ]}
168
169
170    #[test]
171    fn encode_word_padding() {
172        let words = &[
173            ( Word {
174                left_padding: None,
175                input: "abc".into(),
176                right_padding: None,
177            }, vec![
178                Text("abc".into())
179            ] ),
180            ( Word {
181                left_padding: Some( CFWS::SingleFws( FWS) ),
182                input: "abc".into(),
183                right_padding: None,
184            }, vec![
185                MarkFWS,
186                Text(" abc".into())
187            ] ),
188            ( Word {
189                left_padding: Some( CFWS::SingleFws( FWS ) ),
190                input: "abc".into(),
191                right_padding: Some( CFWS::SingleFws( FWS ) ),
192            }, vec![
193                MarkFWS,
194                Text(" abc".into()),
195                MarkFWS,
196                Text(" ".into())
197            ] ),
198            ( Word {
199                left_padding: None,
200                input: "abc".into(),
201                right_padding: Some( CFWS::SingleFws( FWS ) ),
202            }, vec![
203                Text("abc".into()),
204                MarkFWS,
205                Text(" ".into())
206            ] )
207        ];
208
209        for &( ref word, ref expection) in words.iter() {
210            let mut encoder = EncodingBuffer::new(MailType::Ascii);
211            {
212                let mut handle = encoder.writer();
213                do_encode_word( word, &mut handle, None ).unwrap();
214                mem::forget(handle);
215            }
216            assert_eq!(
217                &simplify_trace_tokens(encoder.trace.into_iter()),
218                expection
219            );
220        }
221    }
222
223
224}