mail_parser/parsers/fields/
list.rs1use std::borrow::Cow;
8
9use crate::{parsers::MessageStream, HeaderValue};
10
11struct ListParser<'x> {
12 token_start: usize,
13 token_end: usize,
14 is_token_start: bool,
15 tokens: Vec<Cow<'x, str>>,
16 list: Vec<Cow<'x, str>>,
17}
18
19impl<'x> ListParser<'x> {
20 fn add_token(&mut self, stream: &MessageStream<'x>, add_space: bool) {
21 if self.token_start > 0 {
22 if !self.tokens.is_empty() {
23 self.tokens.push(" ".into());
24 }
25 self.tokens.push(String::from_utf8_lossy(
26 &stream.data[self.token_start - 1..self.token_end],
27 ));
28
29 if add_space {
30 self.tokens.push(" ".into());
31 }
32
33 self.token_start = 0;
34 self.is_token_start = true;
35 }
36 }
37
38 fn add_tokens_to_list(&mut self) {
39 if !self.tokens.is_empty() {
40 self.list.push(if self.tokens.len() == 1 {
41 self.tokens.pop().unwrap()
42 } else {
43 let value = self.tokens.concat();
44 self.tokens.clear();
45 value.into()
46 });
47 }
48 }
49}
50
51impl<'x> MessageStream<'x> {
52 pub fn parse_comma_separared(&mut self) -> HeaderValue<'x> {
53 let mut parser = ListParser {
54 token_start: 0,
55 token_end: 0,
56 is_token_start: true,
57 tokens: Vec::new(),
58 list: Vec::new(),
59 };
60
61 while let Some(ch) = self.next() {
62 match ch {
63 b'\n' => {
64 parser.add_token(self, false);
65 if !self.try_next_is_space() {
66 parser.add_tokens_to_list();
67
68 return match parser.list.len() {
69 1 => HeaderValue::Text(parser.list.pop().unwrap()),
70 0 => HeaderValue::Empty,
71 _ => HeaderValue::TextList(parser.list),
72 };
73 } else {
74 continue;
75 }
76 }
77 b' ' | b'\t' => {
78 if !parser.is_token_start {
79 parser.is_token_start = true;
80 }
81 continue;
82 }
83 b'=' if parser.is_token_start && self.peek_char(b'?') => {
84 self.checkpoint();
85 if let Some(token) = self.decode_rfc2047() {
86 parser.add_token(self, true);
87 parser.tokens.push(token.into());
88 continue;
89 }
90 self.restore();
91 }
92 b',' => {
93 parser.add_token(self, false);
94 parser.add_tokens_to_list();
95 continue;
96 }
97 b'\r' => continue,
98 _ => (),
99 }
100
101 if parser.is_token_start {
102 parser.is_token_start = false;
103 }
104
105 if parser.token_start == 0 {
106 parser.token_start = self.offset();
107 }
108
109 parser.token_end = self.offset();
110 }
111
112 HeaderValue::Empty
113 }
114}
115#[cfg(test)]
116mod tests {
117 use crate::parsers::{fields::load_tests, MessageStream};
118
119 #[test]
120 fn parse_comma_separated_text() {
121 for test in load_tests::<Vec<String>>("list.json") {
122 assert_eq!(
123 MessageStream::new(test.header.as_bytes())
124 .parse_comma_separared()
125 .unwrap_text_list(),
126 test.expected,
127 "failed for {:?}",
128 test.header
129 );
130 }
131 }
132}