mail_headers/header_components/
message_id.rs1use 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#[derive(Debug, Clone, Hash, Eq, PartialEq)]
27pub struct MessageId {
28 message_id: SimpleItem
29}
30
31
32impl MessageId {
33
34 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 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 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