mail_headers_ng/
headers.rs

1
2
3use ::header_components;
4use self::validators::{
5    from as validator_from,
6    resent_any as validator_resent_any
7};
8
9
10def_headers! {
11    test_name: validate_header_names,
12    scope: header_components,
13    /// (rfc5322)
14    Date,         unchecked { "Date"          },  DateTime,       maxOne,   None,
15    /// (rfc5322)
16    _From,        unchecked { "From"          },  MailboxList,    maxOne,   validator_from,
17    /// (rfc5322)
18    Sender,       unchecked { "Sender"        },  Mailbox,        maxOne,   None,
19    /// (rfc5322)
20    ReplyTo,      unchecked { "Reply-To"      },  MailboxList,    maxOne,   None,
21    /// (rfc5322)
22    _To,          unchecked { "To"            },  MailboxList,    maxOne,   None,
23    /// (rfc5322)
24    Cc,           unchecked { "Cc"            },  MailboxList,    maxOne,   None,
25    /// (rfc5322)
26    Bcc,          unchecked { "Bcc"           },  MailboxList,    maxOne,   None,
27    /// (rfc5322)
28    MessageId,    unchecked { "Message-Id"    },  MessageId,      maxOne,   None,
29    /// (rfc5322)
30    InReplyTo,    unchecked { "In-Reply-To"   },  MessageIdList,  maxOne,   None,
31    /// (rfc5322)
32    References,   unchecked { "References"    },  MessageIdList,  maxOne,   None,
33    /// (rfc5322)
34    Subject,      unchecked { "Subject"       },  Unstructured,   maxOne,   None,
35    /// (rfc5322)
36    Comments,     unchecked { "Comments"      },  Unstructured,   multi,    None,
37    /// (rfc5322)
38    Keywords,     unchecked { "Keywords"      },  PhraseList,     multi,    None,
39    /// (rfc5322)
40    ResentDate,   unchecked { "Resent-Date"   },  DateTime,       multi,    validator_resent_any,
41    /// (rfc5322)
42    ResentFrom,   unchecked { "Resent-From"   },  MailboxList,    multi,    validator_resent_any,
43    /// (rfc5322)
44    ResentSender, unchecked { "Resent-Sender" },  Mailbox,        multi,    validator_resent_any,
45    /// (rfc5322)
46    ResentTo,     unchecked { "Resent-To"     },  MailboxList,    multi,    validator_resent_any,
47    /// (rfc5322)
48    ResentCc,     unchecked { "Resent-Cc"     },  MailboxList,    multi,    validator_resent_any,
49    /// (rfc5322)
50    ResentBcc,    unchecked { "Resent-Bcc"    },  OptMailboxList, multi,    validator_resent_any,
51    /// (rfc5322)
52    ResentMsgId,  unchecked { "Resent-Msg-Id" },  MessageId,      multi,    validator_resent_any,
53    /// (rfc5322)
54    ReturnPath,   unchecked { "Return-Path"   },  Path,           multi,    None,
55    /// (rfc5322)
56    Received,     unchecked { "Received"      },  ReceivedToken,  multi,    None,
57
58    /// (rfc2045)
59    ContentType,  unchecked { "Content-Type"  }, MediaType,       maxOne,   None,
60
61    /// (rfc2045)
62    ContentId,    unchecked { "Content-Id"    }, ContentId,       maxOne,   None,
63
64    /// The transfer encoding used to (transfer) encode the body (rfc2045)
65    ///
66    /// This should either be:
67    ///
68    /// - `7bit`: Us-ascii only text, default value if header filed is not present
69    /// - `quoted-printable`: Data encoded with quoted-printable encoding).
70    /// - `base64`: Data encoded with base64 encoding.
71    ///
72    /// Through other defined values include:
73    ///
74    /// - `8bit`: Data which is not encoded but still considers lines and line length,
75    ///           i.e. has no more then 998 bytes between two CRLF (or the start/end of data).
76    ///           Bodies of this kind can still be send if the server supports the 8bit
77    ///           mime extension.
78    ///
79    /// - `binary`: Data which is not encoded and can be any kind of arbitrary binary data.
80    ///             To send binary bodies the `CHUNKING` smpt extension (rfc3030) needs to be
81    ///             supported using BDATA instead of DATA to send the content. Note that the
82    ///             extension does not fix the potential but rare problem of accendentall
83    ///             multipart boundary collisions.
84    ///
85    ///
86    /// Nevertheless this encodings are mainly meant to be used for defining the
87    /// domain of data in a system before it is encoded.
88    ContentTransferEncoding, unchecked { "Content-Transfer-Encoding" }, TransferEncoding, maxOne, None,
89
90    /// A description of the content of the body (rfc2045)
91    ///
92    /// This is mainly usefull for multipart body parts, e.g.
93    /// to add an description to a inlined/attached image.
94    ContentDescription,   unchecked { "Content-Description"       }, Unstructured, maxOne, None,
95
96    /// Defines the disposition of a multipart part it is used on (rfc2183)
97    ///
98    /// This is meant to be used as a header for a multipart body part, which
99    /// was created from a resource, mainly a file.
100    ///
101    /// Examples are attachments like images, etc.
102    ///
103    /// Possible Dispositions are:
104    /// - Inline
105    /// - Attachment
106    ///
107    /// Additional it is used to provide following information as parameters:
108    /// - `filename`: the file name associated with the resource this body is based on
109    /// - `creation-date`: when the resource this body is based on was created
110    /// - `modification-date`: when the resource this body is based on was last modified
111    /// - `read-date`: when the resource this body is based on was read (to create the body)
112    /// - `size`: the size this resource should have, note that `Content-Size` is NOT a mail
113    ///           related header but specific to http.
114    ContentDisposition, unchecked { "Content-Disposition"       }, Disposition, maxOne, None
115}
116
117mod validators {
118    use std::collections::HashMap;
119
120    use ::{ HeaderMap, HeaderKind, HeaderName, HeaderObj };
121    use ::error::HeaderValidationError;
122
123    use super::{ _From, ResentFrom, Sender, ResentSender, ResentDate };
124
125
126    pub fn from(map: &HeaderMap) -> Result<(), HeaderValidationError> {
127        // Note: we do not care about the quantity of From bodies,
128        // nor "other" From bodies
129        // (which do not use a MailboxList and we could
130        //  therefore not cast to it,
131        // whatever header put them in has also put in
132        // this bit of validation )
133        let needs_sender =
134            map.get(_From)
135                .filter_map(|res| res.ok())
136                .any(|list| list.len() > 1);
137
138        if needs_sender && !map.contains(Sender) {
139            //this is the wrong bail...
140            header_validation_bail!(kind: MultiMailboxFromWithoutSender);
141        }
142        Ok(())
143    }
144
145    fn validate_resent_block<'a>(
146            block: &HashMap<HeaderName, &'a HeaderObj>
147    ) -> Result<(), HeaderValidationError> {
148        if !block.contains_key(&ResentDate::name()) {
149            //this is the wrong bail...
150            header_validation_bail!(kind: ResentDateFieldMissing);
151        }
152        let needs_sender =
153            //no Resend-From? => no problem
154            block.get(&ResentFrom::name())
155                //can't cast? => not my problem/responsibility
156                .and_then(|tobj| tobj.downcast_ref::<ResentFrom>())
157                .map(|list| list.len() > 1)
158                .unwrap_or(false);
159
160        if needs_sender && !block.contains_key(&ResentSender::name()) {
161            //this is the wrong bail...
162            header_validation_bail!(kind: MultiMailboxResentFromWithoutResentSender)
163        }
164        Ok(())
165    }
166
167    pub fn resent_any(map: &HeaderMap) -> Result<(), HeaderValidationError> {
168        let resents = map
169            .iter()
170            .filter(|&(name, _)| name.as_str().starts_with("Resent-"));
171
172        let mut block = HashMap::new();
173        for (name, content) in resents {
174            if block.contains_key(&name) {
175                validate_resent_block(&block)?;
176                //create new block
177                block = HashMap::new();
178            }
179            block.insert(name, content);
180        }
181        validate_resent_block(&block)
182    }
183}
184
185#[cfg(test)]
186mod test {
187    use ::header_components::DateTime;
188    use ::{HeaderMap, HeaderKind};
189    use ::headers::{
190        _From, ResentFrom, ResentTo, ResentDate,
191        Sender, ResentSender, Subject
192    };
193
194    test!(from_validation_normal {
195        let mut map = HeaderMap::new();
196        map.insert(_From   ::auto_body( [("Mr. Peté", "pete@nixmail.example")] )?);
197        map.insert(Subject ::auto_body( "Ok"                                   )?);
198
199        assert_ok!(map.use_contextual_validators());
200    });
201
202    test!(from_validation_multi_err {
203        let mut map = HeaderMap::new();
204        map.insert(_From::auto_body((
205            ("Mr. Peté", "nixperson@nixmail.nixdomain"),
206            "a@b.c"
207        ))?);
208        map.insert(Subject::auto_body("Ok")?);
209
210        assert_err!(map.use_contextual_validators());
211    });
212
213    test!(from_validation_multi_ok {
214        let mut map = HeaderMap::new();
215        map.insert(_From::auto_body((
216            ("Mr. Peté", "nixperson@nixmail.nixdomain"),
217            "a@b.c"
218        ))?);
219        map.insert(Sender  ::auto_body(  "abx@d.e" )?);
220        map.insert(Subject ::auto_body(  "Ok"      )?);
221
222        assert_ok!(map.use_contextual_validators());
223    });
224
225    test!(resent_no_date_err {
226        let mut map = HeaderMap::new();
227        map.insert(ResentFrom ::auto_body( ["a@b.c"] )?);
228        assert_err!(map.use_contextual_validators());
229    });
230
231    test!(resent_with_date {
232        let mut map = HeaderMap::new();
233        map.insert(ResentFrom ::auto_body( ["a@b.c"]       )?);
234        map.insert(ResentDate ::auto_body( DateTime::now() )?);
235        assert_ok!(map.use_contextual_validators());
236    });
237
238    test!(resent_no_date_err_second_block {
239        let mut map = HeaderMap::new();
240        map.insert(ResentDate ::auto_body( DateTime::now() )?);
241        map.insert(ResentFrom ::auto_body( ["a@b.c"]       )?);
242        map.insert(ResentTo   ::auto_body( ["e@f.d"]       )?);
243        map.insert(ResentFrom ::auto_body( ["ee@ee.e"]     )?);
244
245        assert_err!(map.use_contextual_validators());
246    });
247
248    test!(resent_with_date_second_block {
249        let mut map = HeaderMap::new();
250        map.insert(ResentDate ::auto_body( DateTime::now() )?);
251        map.insert(ResentFrom ::auto_body( ["a@b.c"]       )?);
252        map.insert(ResentTo   ::auto_body( ["e@f.d"]       )?);
253        map.insert(ResentFrom ::auto_body( ["ee@ee.e"]     )?);
254        map.insert(ResentDate ::auto_body( DateTime::now() )?);
255
256        assert_ok!(map.use_contextual_validators());
257    });
258
259    test!(resent_multi_mailbox_from_no_sender {
260
261        let mut map = HeaderMap::new();
262        map.insert(ResentDate ::auto_body( DateTime::now()   )?);
263        map.insert(ResentFrom ::auto_body( ["a@b.c","e@c.d"] )?);
264
265        assert_err!(map.use_contextual_validators());
266    });
267
268    test!(resent_multi_mailbox_from_with_sender {
269        let mut map = HeaderMap::new();
270        map.insert(ResentDate   ::auto_body( DateTime::now()   )?);
271        map.insert(ResentFrom   ::auto_body( ["a@b.c","e@c.d"] )?);
272        map.insert(ResentSender ::auto_body( "a@b.c"           )?);
273        assert_ok!(map.use_contextual_validators());
274    });
275
276}