mail_parser/parsers/fields/
address.rs1use crate::{Addr, Address, Group, HeaderValue, parsers::MessageStream};
8use std::borrow::Cow;
9
10#[derive(PartialEq, Clone, Copy, Debug)]
11enum AddressState {
12 Address,
13 Name,
14 Quote,
15 Comment,
16}
17
18pub struct AddressParser<'x> {
19 token_start: usize,
20 token_end: usize,
21
22 is_token_email: bool,
23 is_token_start: bool,
24 is_escaped: bool,
25 last_is_encoded: bool,
26
27 name_tokens: Vec<Cow<'x, str>>,
28 mail_tokens: Vec<Cow<'x, str>>,
29 comment_tokens: Vec<Cow<'x, str>>,
30
31 state: AddressState,
32 state_stack: Vec<AddressState>,
33
34 addresses: Vec<Addr<'x>>,
35 group_name: Option<Cow<'x, str>>,
36 group_comment: Option<Cow<'x, str>>,
37 result: Vec<Group<'x>>,
38}
39
40impl<'x> AddressParser<'x> {
41 pub fn add_token(&mut self, stream: &MessageStream<'x>) {
42 if self.token_start > 0 {
43 let token = String::from_utf8_lossy(&stream.data[self.token_start - 1..self.token_end]);
44 let mut add_space = false;
45 let list = match self.state {
46 AddressState::Address => &mut self.mail_tokens,
47 AddressState::Name => {
48 if self.is_token_email {
49 &mut self.mail_tokens
50 } else {
51 add_space = true;
52 &mut self.name_tokens
53 }
54 }
55 AddressState::Quote => &mut self.name_tokens,
56 AddressState::Comment => {
57 add_space = true;
58 &mut self.comment_tokens
59 }
60 };
61
62 if add_space && !list.is_empty() {
63 list.push(" ".into());
64 }
65
66 list.push(token);
67
68 self.token_start = 0;
69 self.is_token_email = false;
70 self.is_token_start = true;
71 self.is_escaped = false;
72 self.last_is_encoded = false;
73 }
74 }
75
76 pub fn add_rfc2047(&mut self, token: String) {
77 let add_space = !self.last_is_encoded && self.state != AddressState::Quote;
78 let list = if self.state != AddressState::Comment {
79 &mut self.name_tokens
80 } else {
81 &mut self.comment_tokens
82 };
83
84 if add_space && !list.is_empty() {
85 list.push(" ".into());
86 }
87
88 list.push(token.into());
89 self.last_is_encoded = true;
90 }
91
92 pub fn add_address(&mut self) {
93 let has_mail = !self.mail_tokens.is_empty();
94 let has_name = !self.name_tokens.is_empty();
95 let has_comment = !self.comment_tokens.is_empty();
96
97 self.addresses.push(if has_mail && has_name && has_comment {
98 Addr {
99 name: Some(
100 format!(
101 "{} ({})",
102 concat_tokens(&mut self.name_tokens),
103 concat_tokens(&mut self.comment_tokens)
104 )
105 .into(),
106 ),
107 address: concat_tokens(&mut self.mail_tokens).into(),
108 }
109 } else if has_name && has_mail {
110 Addr {
111 name: concat_tokens(&mut self.name_tokens).into(),
112 address: concat_tokens(&mut self.mail_tokens).into(),
113 }
114 } else if has_mail && has_comment {
115 Addr {
116 name: concat_tokens(&mut self.comment_tokens).into(),
117 address: concat_tokens(&mut self.mail_tokens).into(),
118 }
119 } else if has_mail {
120 Addr {
121 name: None,
122 address: concat_tokens(&mut self.mail_tokens).into(),
123 }
124 } else if has_name && has_comment {
125 let name = concat_tokens(&mut self.name_tokens);
126 let comment = concat_tokens(&mut self.comment_tokens);
127
128 if !name.chars().any(char::is_whitespace) {
129 Addr {
130 name: comment.into(),
131 address: name.into(),
132 }
133 } else {
134 Addr {
135 name: Some(format!("{name} ({comment})").into()),
136 address: None,
137 }
138 }
139 } else if has_name {
140 Addr {
141 name: concat_tokens(&mut self.name_tokens).into(),
142 address: None,
143 }
144 } else if has_comment {
145 Addr {
146 name: concat_tokens(&mut self.comment_tokens).into(),
147 address: None,
148 }
149 } else {
150 return;
151 });
152 }
153
154 pub fn add_group_details(&mut self) {
155 if !self.name_tokens.is_empty() {
156 self.group_name = concat_tokens(&mut self.name_tokens).into();
157 }
158
159 if !self.comment_tokens.is_empty() {
160 self.group_comment = concat_tokens(&mut self.comment_tokens).into();
161 }
162
163 if !self.mail_tokens.is_empty() {
164 if let Some(group_name) = &self.group_name {
165 self.group_name =
166 Some(format!("{} {}", group_name, concat_tokens(&mut self.mail_tokens)).into());
167 } else {
168 self.group_name = concat_tokens(&mut self.mail_tokens).into();
169 }
170 }
171 }
172
173 pub fn add_group(&mut self) {
174 let has_name = self.group_name.is_some();
175 let has_comment = self.group_comment.is_some();
176 let has_addresses = !self.addresses.is_empty();
177
178 self.result
179 .push(if has_name && has_addresses && has_comment {
180 Group {
181 name: Some(
182 format!(
183 "{} ({})",
184 self.group_name.take().unwrap(),
185 self.group_comment.take().unwrap()
186 )
187 .into(),
188 ),
189 addresses: std::mem::take(&mut self.addresses),
190 }
191 } else if has_addresses && has_name {
192 Group {
193 name: self.group_name.take(),
194 addresses: std::mem::take(&mut self.addresses),
195 }
196 } else if has_addresses {
197 Group {
198 name: self.group_comment.take(),
199 addresses: std::mem::take(&mut self.addresses),
200 }
201 } else if has_name {
202 Group {
203 name: self.group_name.take(),
204 addresses: Vec::new(),
205 }
206 } else {
207 return;
208 });
209 }
210}
211
212impl<'x> MessageStream<'x> {
213 pub fn parse_address(&mut self) -> HeaderValue<'x> {
214 let mut parser = AddressParser {
215 token_start: 0,
216 token_end: 0,
217
218 is_token_email: false,
219 is_token_start: true,
220 is_escaped: false,
221 last_is_encoded: true,
222
223 name_tokens: Vec::with_capacity(3),
224 mail_tokens: Vec::with_capacity(3),
225 comment_tokens: Vec::with_capacity(3),
226
227 state: AddressState::Name,
228 state_stack: Vec::with_capacity(5),
229
230 addresses: Vec::new(),
231 group_name: None,
232 group_comment: None,
233 result: Vec::new(),
234 };
235
236 while let Some(ch) = self.next() {
237 match ch {
238 b'\n' => {
239 parser.add_token(self);
240 if parser.state == AddressState::Quote {
241 if self.peek_next_is_space() {
242 continue;
243 } else {
244 break;
245 }
246 }
247 if self.try_next_is_space() {
248 if !parser.is_token_start {
249 parser.is_token_start = true;
250 }
251 continue;
252 } else {
253 break;
254 }
255 }
256 b'\\' if parser.state != AddressState::Name && !parser.is_escaped => {
257 if parser.token_start > 0 {
258 if parser.state == AddressState::Quote {
259 parser.token_end = self.offset() - 1;
260 }
261 parser.add_token(self);
262 }
263 parser.is_escaped = true;
264 continue;
265 }
266 b',' if parser.state == AddressState::Name => {
267 parser.add_token(self);
268 parser.add_address();
269 continue;
270 }
271 b'<' if parser.state == AddressState::Name => {
272 parser.is_token_email = false;
273 parser.add_token(self);
274 parser.state_stack.push(AddressState::Name);
275 parser.state = AddressState::Address;
276 continue;
277 }
278 b'>' if parser.state == AddressState::Address => {
279 parser.add_token(self);
280 parser.state = parser.state_stack.pop().unwrap();
281 continue;
282 }
283 b'"' if !parser.is_escaped => match parser.state {
284 AddressState::Name => {
285 parser.state_stack.push(AddressState::Name);
286 parser.state = AddressState::Quote;
287 parser.add_token(self);
288 continue;
289 }
290 AddressState::Quote => {
291 parser.add_token(self);
292 parser.state = parser.state_stack.pop().unwrap();
293 continue;
294 }
295 _ => (),
296 },
297 b'@' if parser.state == AddressState::Name => {
298 parser.is_token_email = true;
299 }
300 b'=' if parser.is_token_start && !parser.is_escaped && self.peek_char(b'?') => {
301 self.checkpoint();
302 if let Some(token) = self.decode_rfc2047() {
303 parser.add_token(self);
304 parser.add_rfc2047(token);
305 continue;
306 }
307 self.restore();
308 }
309 b' ' | b'\t' => {
310 if !parser.is_token_start {
311 parser.is_token_start = true;
312 }
313 if parser.is_escaped {
314 parser.is_escaped = false;
315 }
316 if parser.state == AddressState::Quote {
317 if parser.token_start == 0 {
318 parser.token_start = self.offset();
319 parser.token_end = parser.token_start;
320 } else {
321 parser.token_end = self.offset();
322 }
323 }
324 continue;
325 }
326 b'\r' => continue,
327 b'(' if parser.state != AddressState::Quote && !parser.is_escaped => {
328 parser.state_stack.push(parser.state);
329 if parser.state != AddressState::Comment {
330 parser.add_token(self);
331 parser.state = AddressState::Comment;
332 parser.last_is_encoded = false;
333 continue;
334 }
335 }
336 b')' if parser.state == AddressState::Comment && !parser.is_escaped => {
337 let new_state = parser.state_stack.pop().unwrap();
338 if parser.state != new_state {
339 parser.add_token(self);
340 parser.state = new_state;
341 parser.last_is_encoded = false;
342 continue;
343 }
344 }
345 b':' if parser.state == AddressState::Name && !parser.is_escaped => {
346 parser.add_group();
347 parser.add_token(self);
348 parser.add_group_details();
349 continue;
350 }
351 b';' if parser.state == AddressState::Name => {
352 parser.add_token(self);
353 parser.add_address();
354 parser.add_group();
355 continue;
356 }
357 _ => (),
358 }
359
360 if parser.is_escaped {
361 parser.is_escaped = false;
362 }
363
364 if parser.is_token_start {
365 parser.is_token_start = false;
366 }
367
368 if parser.token_start == 0 {
369 parser.token_start = self.offset();
370 parser.token_end = parser.token_start;
371 } else {
372 parser.token_end = self.offset();
373 }
374 }
375
376 parser.add_address();
377
378 if parser.group_name.is_some() || !parser.result.is_empty() {
379 parser.add_group();
380 HeaderValue::Address(Address::Group(parser.result))
381 } else if !parser.addresses.is_empty() {
382 HeaderValue::Address(Address::List(parser.addresses))
383 } else {
384 HeaderValue::Empty
385 }
386 }
387}
388
389fn concat_tokens<'x>(tokens: &mut Vec<Cow<'x, str>>) -> Cow<'x, str> {
390 if tokens.len() == 1 {
391 tokens.pop().unwrap()
392 } else {
393 let result = tokens.concat();
394 tokens.clear();
395 result.into()
396 }
397}
398
399pub fn parse_address_local_part(addr: &str) -> Option<&str> {
400 let addr = addr.as_bytes();
401 let mut iter = addr.iter().enumerate();
402 while let Some((pos, &ch)) = iter.next() {
403 if ch == b'@' {
404 return if pos > 0 && iter.next().is_some() {
405 std::str::from_utf8(addr.get(..pos)?).ok()
406 } else {
407 None
408 };
409 } else if !ch.is_ascii() {
410 return None;
411 }
412 }
413
414 None
415}
416
417pub fn parse_address_domain(addr: &str) -> Option<&str> {
418 let addr = addr.as_bytes();
419 for (pos, &ch) in addr.iter().enumerate() {
420 if ch == b'@' {
421 return if pos > 0 && pos + 1 < addr.len() {
422 std::str::from_utf8(addr.get(pos + 1..)?).ok()
423 } else {
424 None
425 };
426 } else if !ch.is_ascii() {
427 return None;
428 }
429 }
430
431 None
432}
433
434pub fn parse_address_user_part(addr: &str) -> Option<&str> {
435 let addr = addr.as_bytes();
436
437 let mut iter = addr.iter().enumerate();
438 while let Some((pos, &ch)) = iter.next() {
439 if ch == b'+' {
440 if pos > 0 {
441 while let Some((_, &ch)) = iter.next() {
442 if ch == b'@' && iter.next().is_some() {
443 return std::str::from_utf8(addr.get(..pos)?).ok();
444 }
445 }
446 }
447 return None;
448 } else if ch == b'@' {
449 return if pos > 0 && iter.next().is_some() {
450 std::str::from_utf8(addr.get(..pos)?).ok()
451 } else {
452 None
453 };
454 } else if !ch.is_ascii() {
455 return None;
456 }
457 }
458
459 None
460}
461
462pub fn parse_address_detail_part(addr: &str) -> Option<&str> {
463 let addr = addr.as_bytes();
464 let mut plus_pos = usize::MAX;
465
466 let mut iter = addr.iter().enumerate();
467 while let Some((pos, &ch)) = iter.next() {
468 if ch == b'+' {
469 plus_pos = pos + 1;
470 } else if ch == b'@' {
471 if plus_pos != usize::MAX && iter.next().is_some() {
472 return std::str::from_utf8(addr.get(plus_pos..pos)?).ok();
473 } else {
474 return None;
475 }
476 } else if !ch.is_ascii() {
477 return None;
478 }
479 }
480
481 None
482}
483
484#[cfg(test)]
485mod tests {
486 use crate::parsers::{MessageStream, fields::load_tests};
487
488 #[test]
489 fn parse_addresses() {
490 for test in load_tests("address.json") {
491 assert_eq!(
492 MessageStream::new(test.header.as_bytes())
493 .parse_address()
494 .unwrap_address(),
495 test.expected,
496 "failed for {:?}",
497 test.header
498 );
499 }
500 }
501}