mail_headers_ng/header_components/
unstructured.rs

1use std::ops::{ Deref, DerefMut};
2use std::fmt::{self, Display};
3
4use failure::Fail;
5use soft_ascii_string::SoftAsciiChar;
6
7use internals::grammar::is_vchar;
8use internals::error::{EncodingError, EncodingErrorKind};
9use internals::encoder::{EncodingWriter, EncodableInHeader};
10use internals::bind::encoded_word::{EncodedWordEncoding, WriterWrapper};
11use ::{HeaderTryFrom, HeaderTryInto};
12use ::error::ComponentCreationError;
13use ::data::Input;
14
15use super::utils::text_partition::{partition, Partition};
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Unstructured {
19    //FEATUR_TODO(non_utf8_input): split into parts each possibke having their own encoding
20    text: Input,
21}
22
23impl Display for Unstructured {
24    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
25        fter.write_str(self.as_str())
26    }
27}
28
29impl Deref for Unstructured {
30    type Target = Input;
31
32    fn deref(&self) -> &Self::Target {
33        &self.text
34    }
35}
36
37impl DerefMut for Unstructured {
38    fn deref_mut(&mut self) -> &mut Self::Target {
39        &mut self.text
40    }
41}
42
43impl<T> HeaderTryFrom<T> for Unstructured
44    where T: HeaderTryInto<Input>
45{
46    fn try_from(text: T) -> Result<Self, ComponentCreationError> {
47        let text = text.try_into()?;
48        Ok( Unstructured { text })
49    }
50}
51
52
53
54impl EncodableInHeader for  Unstructured {
55
56    fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
57        let text: &str = &*self.text;
58        if text.len() == 0 {
59            return Ok( () )
60        }
61
62        let partitions = partition(text)
63            .map_err(|err| EncodingError
64                ::from(err.context(EncodingErrorKind::Malformed))
65                .with_str_context(text)
66            )?;
67
68        for block in partitions.into_iter() {
69            match block {
70                Partition::VCHAR( data ) => {
71                    let mail_type = handle.mail_type();
72                    handle.write_if(data, |s|
73                        s.chars().all(|ch| is_vchar(ch, mail_type))
74                    ).handle_condition_failure(|handle| {
75                        let encoding = EncodedWordEncoding::QuotedPrintable;
76                        let mut writer = WriterWrapper::new(
77                            encoding,
78                            handle
79                        );
80                        encoding.encode(data, &mut writer);
81                        Ok(())
82                    })?;
83                },
84                Partition::SPACE( data ) => {
85                    let mut had_fws = false;
86                    for ch in data.chars() {
87                        if ch == '\r' || ch == '\n' {
88                            continue;
89                        } else if !had_fws {
90                            handle.mark_fws_pos();
91                            had_fws = true;
92                        }
93                        handle.write_char( SoftAsciiChar::from_unchecked(ch) )?;
94                    }
95                    if !had_fws {
96                        // currently this can only happen if data only consists of '\r','\n'
97                        // which we strip which in turn would remove the spacing completely
98                        //NOTE: space has to be at last one horizontal-white-space
99                        // (required by the possibility of VCHAR partitions being
100                        //  encoded words)
101                        handle.write_fws();
102                    }
103                }
104            }
105
106        }
107        Ok(())
108    }
109
110    fn boxed_clone(&self) -> Box<EncodableInHeader> {
111        Box::new(self.clone())
112    }
113}
114
115
116#[cfg(test)]
117mod test {
118
119    use super::*;
120
121    ec_test! { simple_encoding, {
122        Unstructured::try_from( "this simple case" )?
123    } => ascii => [
124        Text "this",
125        MarkFWS,
126        Text " simple",
127        MarkFWS,
128        Text " case"
129    ]}
130
131    ec_test!{ simple_utf8,  {
132         Unstructured::try_from( "thüs sümple case" )?
133    } => utf8 => [
134        Text "thüs",
135        MarkFWS,
136        Text " sümple",
137        MarkFWS,
138        Text " case"
139    ]}
140
141    ec_test!{ encoded_words,  {
142         Unstructured::try_from( "↑ ↓ ←→ bA" )?
143    } => ascii => [
144        Text "=?utf8?Q?=E2=86=91?=",
145        MarkFWS,
146        Text " =?utf8?Q?=E2=86=93?=",
147        MarkFWS,
148        Text " =?utf8?Q?=E2=86=90=E2=86=92?=",
149        MarkFWS,
150        Text " bA"
151    ]}
152
153    ec_test!{ eats_cr_lf, {
154        Unstructured::try_from( "a \rb\n c\r\n " )?
155    } => ascii => [
156        Text "a",
157        MarkFWS,
158        Text " b",
159        MarkFWS,
160        Text " c",
161        MarkFWS,
162        Text " "
163    ]}
164
165    ec_test!{ at_last_one_fws, {
166        Unstructured::try_from( "a\rb\nc\r\n" )?
167    } => ascii => [
168        Text "a",
169        MarkFWS,
170        Text " b",
171        MarkFWS,
172        Text " c",
173        MarkFWS,
174        Text " "
175    ]}
176
177    ec_test!{ kinda_keeps_wsp, {
178        Unstructured::try_from("\t\ta  b \t")?
179    } => ascii => [
180        MarkFWS,
181        Text "\t\ta",
182        MarkFWS,
183        Text "  b",
184        MarkFWS,
185        Text " \t"
186    ]}
187
188    ec_test!{ wsp_only_phrase, {
189        Unstructured::try_from( " \t " )?
190    } => ascii => [
191        MarkFWS,
192        Text " \t "
193    ]}
194
195    ec_test!{ long_mixed_input, {
196        Unstructured::try_from("Subject: …. AAAAAAAAAAAAAAAAAAA….. AA…")?
197    } => ascii => [
198        Text "Subject:",
199        MarkFWS,
200        Text " =?utf8?Q?=E2=80=A6=2E?=",
201        MarkFWS,
202        Text " =?utf8?Q?AAAAAAAAAAAAAAAAAAA=E2=80=A6=2E=2E?=",
203        MarkFWS,
204        Text " =?utf8?Q?AA=E2=80=A6?="
205    ]}
206}