mail_headers/header_components/
message_id.rs

1use std::fmt::{self, Display};
2use nom::IResult;
3
4use soft_ascii_string::{SoftAsciiChar, SoftAsciiStr, SoftAsciiString};
5use vec1::Vec1;
6#[cfg(feature="serde")]
7use serde::{
8    Serialize, Serializer,
9    Deserialize, Deserializer,
10    de::Error
11};
12
13use internals::error::EncodingError;
14use internals::encoder::{EncodingWriter, EncodableInHeader};
15use ::{HeaderTryFrom, HeaderTryInto};
16use ::error::ComponentCreationError;
17use ::data::{ Input, SimpleItem };
18
19/// # Implementation Details
20///
21/// This is used for both message-id/content-id, but
22/// depending on usage and support for obsolete parts there
23/// are two "kind" of id's one which allows  FWS(/CFWS) in
24/// some places and one which doesn't. This implementation
25/// only supports the later one.
26#[derive(Debug, Clone, Hash, Eq, PartialEq)]
27pub struct MessageId {
28    message_id: SimpleItem
29}
30
31
32impl MessageId {
33
34    /// creates a message id from a string without checking for validity
35    ///
36    /// The string is expected to have the format `<left_part> "@" <right_part>`,
37    /// i.e. it should not include the `"<"`, `">"` surrounding message id's in
38    /// more or less all places they are used.
39    pub fn from_unchecked(string: String) -> Self {
40        let item =
41            match SoftAsciiString::from_string(string) {
42                Ok(ascii) => ascii.into(),
43                Err(err) => err.into_source().into()
44            };
45
46        MessageId { message_id: item }
47    }
48
49    pub fn new(left_part: &SoftAsciiStr, right_part: &SoftAsciiStr)
50        -> Result<Self, ComponentCreationError>
51    {
52        use self::{parser_parts as parser};
53
54        match parser::id_left(left_part.as_str()) {
55            IResult::Done( "", _part ) => {},
56            _other => {
57                return Err(ComponentCreationError::new_with_str(
58                    "MessageId", format!("{}@{}", left_part, right_part)));
59            }
60        }
61
62        match parser::id_right(right_part.as_str()) {
63            IResult::Done( "", _part ) => {},
64            _other => {
65                return Err(ComponentCreationError::new_with_str(
66                    "MessageId", format!("{}@{}", left_part, right_part)));
67            }
68        }
69
70        let id = SoftAsciiString::from_unchecked(
71            format!("{}@{}", left_part, right_part));
72        let item = SimpleItem::Ascii(id.into());
73        Ok(MessageId { message_id: item })
74    }
75
76    //FIXME make into AsRef<str> for MessageId
77    pub fn as_str( &self ) -> &str {
78        self.message_id.as_str()
79    }
80}
81
82#[cfg(feature="serde")]
83impl Serialize for MessageId {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85        where S: Serializer
86    {
87        serializer.serialize_str(self.as_str())
88    }
89}
90
91#[cfg(feature="serde")]
92impl<'de> Deserialize<'de> for MessageId {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94        where D: Deserializer<'de>
95    {
96        let as_string = String::deserialize(deserializer)?;
97        let as_ascii = SoftAsciiStr::from_str(&as_string)
98            .map_err(|err| D::Error::custom(format!("message id is not ascii: {}", err)))?;
99
100        let split_point =
101            if as_ascii.as_str().ends_with("]") {
102                as_ascii.as_str()
103                    .bytes()
104                    .rposition(|bch| bch == b'[')
105                    .and_then(|pos| pos.checked_sub(1))
106                    .ok_or_else(|| D::Error::custom("invalid message id format"))?
107            } else {
108                as_ascii.as_str()
109                    .bytes()
110                    .rposition(|bch| bch == b'@')
111                    .ok_or_else(|| D::Error::custom("invalid message id format"))?
112            };
113
114        let left_part = &as_ascii[..split_point];
115        let right_part = &as_ascii[split_point+1..];
116        MessageId::new(left_part, right_part)
117            .map_err(|err| D::Error::custom(format!("invalid message id format: {}", err)))
118    }
119}
120
121impl Display for MessageId {
122    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
123        fter.write_str(self.as_str())
124    }
125}
126
127impl<T> HeaderTryFrom<T> for MessageId
128    where T: HeaderTryInto<Input>
129{
130    fn try_from( input: T ) ->  Result<Self, ComponentCreationError> {
131        use self::parser_parts::parse_message_id;
132
133        let input = input.try_into()?;
134
135        match parse_message_id(input.as_str()) {
136            IResult::Done( "", _msg_id ) => {},
137            _other => {
138                return Err(ComponentCreationError::new_with_str("MessageId", input.as_str()));
139            }
140        }
141
142
143        Ok( MessageId { message_id: input.into() } )
144    }
145}
146
147impl EncodableInHeader for  MessageId {
148
149    fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
150        handle.mark_fws_pos();
151        handle.write_char( SoftAsciiChar::from_unchecked('<') )?;
152        match self.message_id {
153            SimpleItem::Ascii( ref ascii ) => handle.write_str( ascii )?,
154            SimpleItem::Utf8( ref utf8 ) => handle.write_utf8( utf8 )?
155        }
156        handle.write_char( SoftAsciiChar::from_unchecked('>') )?;
157        handle.mark_fws_pos();
158        Ok( () )
159    }
160
161    fn boxed_clone(&self) -> Box<EncodableInHeader> {
162        Box::new(self.clone())
163    }
164}
165
166#[derive(Debug, Clone)]
167#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
168pub struct MessageIdList( pub Vec1<MessageId> );
169
170deref0!{ +mut MessageIdList => Vec1<MessageId> }
171
172impl EncodableInHeader for  MessageIdList {
173
174    fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
175        for msg_id in self.iter() {
176            msg_id.encode( handle )?;
177        }
178        Ok( () )
179    }
180
181    fn boxed_clone(&self) -> Box<EncodableInHeader> {
182        Box::new(self.clone())
183    }
184}
185
186
187mod parser_parts {
188    use nom::IResult;
189    use internals::grammar::{is_atext, is_dtext};
190    use internals::MailType;
191
192    pub fn parse_message_id( input: &str) -> IResult<&str, (&str, &str)> {
193        do_parse!( input,
194            l: id_left >>
195            char!( '@' ) >>
196            r: id_right >>
197            (l, r)
198        )
199    }
200
201    pub fn id_left(input: &str) -> IResult<&str, &str> {
202        dot_atom_text(input)
203    }
204
205    pub fn id_right(input: &str) -> IResult<&str, &str> {
206        alt!(
207            input,
208            no_fold_literal |
209            dot_atom_text
210        )
211    }
212
213    fn no_fold_literal( input: &str ) -> IResult<&str, &str> {
214        recognize!( input,
215            tuple!(
216                char!( '[' ),
217                take_while!( call!( is_dtext, MailType::Internationalized ) ),
218                char!( ']' )
219            )
220        )
221    }
222
223    fn dot_atom_text(input: &str) -> IResult<&str, &str> {
224        recognize!( input, tuple!(
225            take_while1!( call!( is_atext, MailType::Internationalized ) ),
226            many0!(tuple!(
227                char!( '.' ),
228                take_while1!( call!( is_atext, MailType::Internationalized ) )
229            ))
230        ) )
231    }
232
233    #[cfg(test)]
234    mod test {
235        use nom;
236        use super::*;
237
238        #[test]
239        fn rec_dot_atom_text_no_dot() {
240            match dot_atom_text( "abc" ) {
241                IResult::Done( "", "abc" ) => {},
242                other  => panic!("excepted Done(\"\",\"abc\") got {:?}", other )
243            }
244        }
245
246        #[test]
247        fn rec_dot_atom_text_dots() {
248            match dot_atom_text( "abc.def.ghi" ) {
249                IResult::Done( "", "abc.def.ghi" ) => {},
250                other  => panic!("excepted Done(\"\",\"abc.def.ghi\") got {:?}", other )
251            }
252        }
253
254        #[test]
255        fn rec_dot_atom_text_no_end_dot() {
256            let test_str = "abc.";
257            let need_size = test_str.len() + 1;
258            match dot_atom_text( test_str ) {
259                IResult::Incomplete( nom::Needed::Size( ns ) ) if ns == need_size => {}
260                other  => panic!("excepted Incomplete(Complete) got {:?}", other )
261            }
262        }
263
264        #[test]
265        fn rec_dot_atom_text_no_douple_dot() {
266            match dot_atom_text( "abc..de" ) {
267                IResult::Done( "..de", "abc" ) => {},
268                other  => panic!( "excepted Done(\"..de\",\"abc\") got {:?}", other )
269            }
270        }
271
272        #[test]
273        fn rec_dot_atom_text_no_start_dot() {
274            match dot_atom_text( ".abc" ) {
275                IResult::Error( .. ) => {},
276                other => panic!( "expected error got {:?}", other )
277            }
278        }
279
280
281
282        #[test]
283        fn no_empty() {
284            match dot_atom_text( "" ) {
285                IResult::Incomplete( nom::Needed::Size( 1 ) ) => {},
286                other => panic!( "excepted Incomplete(Size(1)) got {:?}", other )
287            }
288        }
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use internals::MailType;
295    use internals::encoder::EncodingBuffer;
296    use super::*;
297
298    ec_test!{ new, {
299        MessageId::new(
300            SoftAsciiStr::from_unchecked("just.me"),
301            SoftAsciiStr::from_unchecked("[127.0.0.1]")
302        )?
303    } => ascii => [
304        MarkFWS,
305        Text "<just.me@[127.0.0.1]>",
306        MarkFWS
307    ]}
308
309    ec_test!{ simple, {
310        MessageId::try_from( "affen@haus" )?
311    } => ascii => [
312        MarkFWS,
313        // there are two "context" one which allows FWS inside (defined = email)
314        // and one which doesn't for simplicity we use the later every where
315        Text "<affen@haus>",
316        MarkFWS
317    ]}
318
319    ec_test!{ utf8, {
320        MessageId::try_from( "↓@↑.utf8")?
321    } => utf8 => [
322        MarkFWS,
323        Text "<↓@↑.utf8>",
324        MarkFWS
325    ]}
326
327    #[test]
328    fn utf8_fails() {
329        let mut encoder = EncodingBuffer::new(MailType::Ascii);
330        let mut handle = encoder.writer();
331        let mid = MessageId::try_from( "abc@øpunny.code" ).unwrap();
332        assert_err!(mid.encode( &mut handle ));
333        handle.undo_header();
334    }
335
336    ec_test!{ multipls, {
337        let fst = MessageId::try_from( "affen@haus" )?;
338        let snd = MessageId::try_from( "obst@salat" )?;
339        MessageIdList( vec1! [
340            fst,
341            snd
342        ])
343    } => ascii => [
344        MarkFWS,
345        Text "<affen@haus>",
346        MarkFWS,
347        MarkFWS,
348        Text "<obst@salat>",
349        MarkFWS,
350    ]}
351}
352
353