mail_auth/common/
parse.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use mail_parser::decoders::quoted_printable::quoted_printable_decode_char;
8use std::{borrow::Cow, slice::Iter};
9
10pub(crate) const V: u64 = b'v' as u64;
11pub(crate) const A: u64 = b'a' as u64;
12pub(crate) const B: u64 = b'b' as u64;
13pub(crate) const BH: u64 = (b'b' as u64) | ((b'h' as u64) << 8);
14pub(crate) const C: u64 = b'c' as u64;
15pub(crate) const D: u64 = b'd' as u64;
16pub(crate) const H: u64 = b'h' as u64;
17pub(crate) const I: u64 = b'i' as u64;
18pub(crate) const K: u64 = b'k' as u64;
19pub(crate) const L: u64 = b'l' as u64;
20pub(crate) const N: u64 = b'n' as u64;
21pub(crate) const O: u64 = b'o' as u64;
22pub(crate) const P: u64 = b'p' as u64;
23pub(crate) const R: u64 = b'r' as u64;
24pub(crate) const S: u64 = b's' as u64;
25pub(crate) const T: u64 = b't' as u64;
26pub(crate) const U: u64 = b'u' as u64;
27pub(crate) const X: u64 = b'x' as u64;
28pub(crate) const Y: u64 = b'y' as u64;
29pub(crate) const Z: u64 = b'z' as u64;
30
31pub trait TxtRecordParser: Sized {
32    fn parse(record: &[u8]) -> crate::Result<Self>;
33}
34
35pub(crate) trait TagParser: Sized {
36    fn match_bytes(&mut self, bytes: &[u8]) -> bool;
37    fn key(&mut self) -> Option<u64>;
38    fn value(&mut self) -> u64;
39    fn text(&mut self, to_lower: bool) -> String;
40    fn text_qp(&mut self, base: Vec<u8>, to_lower: bool, stop_comma: bool) -> String;
41    fn headers_qp<T: ItemParser>(&mut self) -> Vec<T>;
42    fn number(&mut self) -> Option<u64>;
43    fn items<T: ItemParser>(&mut self) -> Vec<T>;
44    fn flag_value(&mut self) -> (u64, u8);
45    fn flags<T: ItemParser + Into<u64>>(&mut self) -> u64;
46    fn ignore(&mut self);
47    fn seek_tag_end(&mut self) -> bool;
48    fn next_skip_whitespaces(&mut self) -> Option<u8>;
49}
50
51pub(crate) trait ItemParser: Sized {
52    fn parse(bytes: &[u8]) -> Option<Self>;
53}
54
55impl TagParser for Iter<'_, u8> {
56    #[allow(clippy::while_let_on_iterator)]
57    fn key(&mut self) -> Option<u64> {
58        let mut key: u64 = 0;
59        let mut shift = 0;
60
61        while let Some(&ch) = self.next() {
62            match ch {
63                b'a'..=b'z' if shift < 64 => {
64                    key |= (ch as u64) << shift;
65                    shift += 8;
66                }
67                b' ' | b'\t' | b'\r' | b'\n' => (),
68                b'=' => {
69                    return key.into();
70                }
71                b'A'..=b'Z' if shift < 64 => {
72                    key |= ((ch - b'A' + b'a') as u64) << shift;
73                    shift += 8;
74                }
75                b';' => {
76                    key = 0;
77                }
78                _ => {
79                    key = u64::MAX;
80                    shift = 64;
81                }
82            }
83        }
84
85        None
86    }
87
88    #[allow(clippy::while_let_on_iterator)]
89    fn value(&mut self) -> u64 {
90        let mut value: u64 = 0;
91        let mut shift = 0;
92
93        while let Some(&ch) = self.next() {
94            match ch {
95                b'a'..=b'z' | b'0'..=b'9' if shift < 64 => {
96                    value |= (ch as u64) << shift;
97                    shift += 8;
98                }
99                b' ' | b'\t' | b'\r' | b'\n' => (),
100                b'A'..=b'Z' if shift < 64 => {
101                    value |= ((ch - b'A' + b'a') as u64) << shift;
102                    shift += 8;
103                }
104                b';' => {
105                    break;
106                }
107                _ => {
108                    value = u64::MAX;
109                    shift = 64;
110                }
111            }
112        }
113
114        value
115    }
116
117    #[allow(clippy::while_let_on_iterator)]
118    fn flag_value(&mut self) -> (u64, u8) {
119        let mut value: u64 = 0;
120        let mut shift = 0;
121
122        while let Some(&ch) = self.next() {
123            match ch {
124                b'a'..=b'z' | b'0'..=b'9' if shift < 64 => {
125                    value |= (ch as u64) << shift;
126                    shift += 8;
127                }
128                b' ' | b'\t' | b'\r' | b'\n' => (),
129                b'A'..=b'Z' if shift < 64 => {
130                    value |= ((ch - b'A' + b'a') as u64) << shift;
131                    shift += 8;
132                }
133                b';' | b':' => {
134                    return (value, ch);
135                }
136                _ => {
137                    value = u64::MAX;
138                    shift = 64;
139                }
140            }
141        }
142
143        (value, 0)
144    }
145
146    #[inline(always)]
147    #[allow(clippy::while_let_on_iterator)]
148    fn match_bytes(&mut self, bytes: &[u8]) -> bool {
149        'outer: for byte in bytes {
150            while let Some(&ch) = self.next() {
151                if !ch.is_ascii_whitespace() {
152                    if ch.eq_ignore_ascii_case(byte) {
153                        continue 'outer;
154                    } else {
155                        return false;
156                    }
157                }
158            }
159            return false;
160        }
161
162        true
163    }
164
165    #[inline(always)]
166    fn text(&mut self, to_lower: bool) -> String {
167        let mut tag = Vec::with_capacity(20);
168        for &ch in self {
169            if ch == b';' {
170                break;
171            } else if !ch.is_ascii_whitespace() {
172                tag.push(ch);
173            }
174        }
175        if to_lower {
176            String::from_utf8_lossy(&tag).to_lowercase()
177        } else {
178            String::from_utf8(tag)
179                .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
180        }
181    }
182
183    #[inline(always)]
184    #[allow(clippy::while_let_on_iterator)]
185    fn text_qp(&mut self, mut tag: Vec<u8>, to_lower: bool, stop_comma: bool) -> String {
186        'outer: while let Some(&ch) = self.next() {
187            if ch == b';' || (stop_comma && ch == b',') {
188                break;
189            } else if ch == b'=' {
190                let mut hex1 = 0;
191
192                while let Some(&ch) = self.next() {
193                    if ch.is_ascii_hexdigit() {
194                        if hex1 != 0 {
195                            if let Some(ch) = quoted_printable_decode_char(hex1, ch) {
196                                tag.push(ch);
197                            }
198                            break;
199                        } else {
200                            hex1 = ch;
201                        }
202                    } else if ch == b';' {
203                        break 'outer;
204                    } else if !ch.is_ascii_whitespace() {
205                        break;
206                    }
207                }
208            } else if !ch.is_ascii_whitespace() {
209                tag.push(ch);
210            }
211        }
212        if to_lower {
213            String::from_utf8_lossy(&tag).to_lowercase()
214        } else {
215            String::from_utf8(tag)
216                .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
217        }
218    }
219
220    #[inline(always)]
221    #[allow(clippy::while_let_on_iterator)]
222    fn headers_qp<T: ItemParser>(&mut self) -> Vec<T> {
223        let mut tags = Vec::new();
224        let mut tag = Vec::with_capacity(20);
225
226        'outer: while let Some(&ch) = self.next() {
227            if ch == b';' {
228                break;
229            } else if ch == b'|' {
230                if !tag.is_empty() {
231                    if let Some(tag) = T::parse(&tag) {
232                        tags.push(tag);
233                    }
234
235                    tag.clear();
236                }
237            } else if ch == b'=' {
238                let mut hex1 = 0;
239
240                while let Some(&ch) = self.next() {
241                    if ch.is_ascii_hexdigit() {
242                        if hex1 != 0 {
243                            if let Some(ch) = quoted_printable_decode_char(hex1, ch) {
244                                tag.push(ch);
245                            }
246                            break;
247                        } else {
248                            hex1 = ch;
249                        }
250                    } else if ch == b'|' {
251                        if !tag.is_empty() {
252                            if let Some(tag) = T::parse(&tag) {
253                                tags.push(tag);
254                            }
255                            tag.clear();
256                        }
257                        break;
258                    } else if ch == b';' {
259                        break 'outer;
260                    } else if !ch.is_ascii_whitespace() {
261                        break;
262                    }
263                }
264            } else if !ch.is_ascii_whitespace() {
265                tag.push(ch);
266            }
267        }
268
269        if !tag.is_empty()
270            && let Some(tag) = T::parse(&tag)
271        {
272            tags.push(tag);
273        }
274
275        tags
276    }
277
278    #[inline(always)]
279    fn number(&mut self) -> Option<u64> {
280        let mut num: u64 = 0;
281        let mut has_digits = false;
282
283        for &ch in self {
284            if ch == b';' {
285                break;
286            } else if ch.is_ascii_digit() {
287                num = (num.saturating_mul(10)).saturating_add((ch - b'0') as u64);
288                has_digits = true;
289            } else if !ch.is_ascii_whitespace() {
290                return None;
291            }
292        }
293
294        if has_digits { num.into() } else { None }
295    }
296
297    #[inline(always)]
298    fn ignore(&mut self) {
299        for &ch in self {
300            if ch == b';' {
301                break;
302            }
303        }
304    }
305
306    #[inline(always)]
307    fn seek_tag_end(&mut self) -> bool {
308        for &ch in self {
309            if ch == b';' {
310                return true;
311            } else if !ch.is_ascii_whitespace() {
312                return false;
313            }
314        }
315        true
316    }
317
318    #[inline(always)]
319    fn next_skip_whitespaces(&mut self) -> Option<u8> {
320        for &ch in self {
321            if !ch.is_ascii_whitespace() {
322                return ch.into();
323            }
324        }
325        None
326    }
327
328    fn items<T: ItemParser>(&mut self) -> Vec<T> {
329        let mut buf = Vec::with_capacity(10);
330        let mut items = Vec::new();
331        for &ch in self {
332            if ch == b':' {
333                if !buf.is_empty() {
334                    if let Some(item) = T::parse(&buf) {
335                        items.push(item);
336                    }
337                    buf.clear();
338                }
339            } else if ch == b';' {
340                break;
341            } else if !ch.is_ascii_whitespace() {
342                buf.push(ch);
343            }
344        }
345        if !buf.is_empty()
346            && let Some(item) = T::parse(&buf)
347        {
348            items.push(item);
349        }
350        items
351    }
352
353    fn flags<T: ItemParser + Into<u64>>(&mut self) -> u64 {
354        let mut buf = Vec::with_capacity(10);
355        let mut flags = 0;
356        for &ch in self {
357            if ch == b':' {
358                if !buf.is_empty() {
359                    if let Some(item) = T::parse(&buf) {
360                        flags |= item.into();
361                    }
362                    buf.clear();
363                }
364            } else if ch == b';' {
365                break;
366            } else if !ch.is_ascii_whitespace() {
367                buf.push(ch);
368            }
369        }
370        if !buf.is_empty()
371            && let Some(item) = T::parse(&buf)
372        {
373            flags |= item.into();
374        }
375        flags
376    }
377}
378
379impl ItemParser for Vec<u8> {
380    fn parse(bytes: &[u8]) -> Option<Self> {
381        Some(bytes.to_vec())
382    }
383}
384
385impl ItemParser for String {
386    fn parse(bytes: &[u8]) -> Option<Self> {
387        Some(String::from_utf8_lossy(bytes).into_owned())
388    }
389}
390
391impl ItemParser for Cow<'_, str> {
392    fn parse(bytes: &[u8]) -> Option<Self> {
393        Some(
394            std::str::from_utf8(bytes)
395                .unwrap_or_default()
396                .to_string()
397                .into(),
398        )
399    }
400}