Skip to main content

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        let mut maybe_uppercase = false;
169        for &ch in self {
170            match ch {
171                b'A'..=b'Z' if to_lower => {
172                    tag.push(ch + 32);
173                }
174                b'\t' | b'\n' | b'\x0C' | b'\r' | b' ' => (),
175                b';' => {
176                    break;
177                }
178                0x7f.. => {
179                    maybe_uppercase = true;
180                    tag.push(ch);
181                }
182                _ => {
183                    tag.push(ch);
184                }
185            }
186        }
187        if to_lower && maybe_uppercase {
188            String::from_utf8_lossy(&tag).to_lowercase()
189        } else {
190            String::from_utf8(tag)
191                .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
192        }
193    }
194
195    #[inline(always)]
196    #[allow(clippy::while_let_on_iterator)]
197    fn text_qp(&mut self, mut tag: Vec<u8>, to_lower: bool, stop_comma: bool) -> String {
198        'outer: while let Some(&ch) = self.next() {
199            if ch == b';' || (stop_comma && ch == b',') {
200                break;
201            } else if ch == b'=' {
202                let mut hex1 = 0;
203
204                while let Some(&ch) = self.next() {
205                    if ch.is_ascii_hexdigit() {
206                        if hex1 != 0 {
207                            if let Some(ch) = quoted_printable_decode_char(hex1, ch) {
208                                tag.push(ch);
209                            }
210                            break;
211                        } else {
212                            hex1 = ch;
213                        }
214                    } else if ch == b';' {
215                        break 'outer;
216                    } else if !ch.is_ascii_whitespace() {
217                        break;
218                    }
219                }
220            } else if !ch.is_ascii_whitespace() {
221                tag.push(ch);
222            }
223        }
224        if to_lower {
225            String::from_utf8_lossy(&tag).to_lowercase()
226        } else {
227            String::from_utf8(tag)
228                .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
229        }
230    }
231
232    #[inline(always)]
233    #[allow(clippy::while_let_on_iterator)]
234    fn headers_qp<T: ItemParser>(&mut self) -> Vec<T> {
235        let mut tags = Vec::new();
236        let mut tag = Vec::with_capacity(20);
237
238        'outer: while let Some(&ch) = self.next() {
239            if ch == b';' {
240                break;
241            } else if ch == b'|' {
242                if !tag.is_empty() {
243                    if let Some(tag) = T::parse(&tag) {
244                        tags.push(tag);
245                    }
246
247                    tag.clear();
248                }
249            } else if ch == b'=' {
250                let mut hex1 = 0;
251
252                while let Some(&ch) = self.next() {
253                    if ch.is_ascii_hexdigit() {
254                        if hex1 != 0 {
255                            if let Some(ch) = quoted_printable_decode_char(hex1, ch) {
256                                tag.push(ch);
257                            }
258                            break;
259                        } else {
260                            hex1 = ch;
261                        }
262                    } else if ch == b'|' {
263                        if !tag.is_empty() {
264                            if let Some(tag) = T::parse(&tag) {
265                                tags.push(tag);
266                            }
267                            tag.clear();
268                        }
269                        break;
270                    } else if ch == b';' {
271                        break 'outer;
272                    } else if !ch.is_ascii_whitespace() {
273                        break;
274                    }
275                }
276            } else if !ch.is_ascii_whitespace() {
277                tag.push(ch);
278            }
279        }
280
281        if !tag.is_empty()
282            && let Some(tag) = T::parse(&tag)
283        {
284            tags.push(tag);
285        }
286
287        tags
288    }
289
290    #[inline(always)]
291    fn number(&mut self) -> Option<u64> {
292        let mut num: u64 = 0;
293        let mut has_digits = false;
294
295        for &ch in self {
296            if ch == b';' {
297                break;
298            } else if ch.is_ascii_digit() {
299                num = (num.saturating_mul(10)).saturating_add((ch - b'0') as u64);
300                has_digits = true;
301            } else if !ch.is_ascii_whitespace() {
302                return None;
303            }
304        }
305
306        if has_digits { num.into() } else { None }
307    }
308
309    #[inline(always)]
310    fn ignore(&mut self) {
311        for &ch in self {
312            if ch == b';' {
313                break;
314            }
315        }
316    }
317
318    #[inline(always)]
319    fn seek_tag_end(&mut self) -> bool {
320        for &ch in self {
321            if ch == b';' {
322                return true;
323            } else if !ch.is_ascii_whitespace() {
324                return false;
325            }
326        }
327        true
328    }
329
330    #[inline(always)]
331    fn next_skip_whitespaces(&mut self) -> Option<u8> {
332        for &ch in self {
333            if !ch.is_ascii_whitespace() {
334                return ch.into();
335            }
336        }
337        None
338    }
339
340    fn items<T: ItemParser>(&mut self) -> Vec<T> {
341        let mut buf = Vec::with_capacity(10);
342        let mut items = Vec::new();
343        for &ch in self {
344            if ch == b':' {
345                if !buf.is_empty() {
346                    if let Some(item) = T::parse(&buf) {
347                        items.push(item);
348                    }
349                    buf.clear();
350                }
351            } else if ch == b';' {
352                break;
353            } else if !ch.is_ascii_whitespace() {
354                buf.push(ch);
355            }
356        }
357        if !buf.is_empty()
358            && let Some(item) = T::parse(&buf)
359        {
360            items.push(item);
361        }
362        items
363    }
364
365    fn flags<T: ItemParser + Into<u64>>(&mut self) -> u64 {
366        let mut buf = Vec::with_capacity(10);
367        let mut flags = 0;
368        for &ch in self {
369            if ch == b':' {
370                if !buf.is_empty() {
371                    if let Some(item) = T::parse(&buf) {
372                        flags |= item.into();
373                    }
374                    buf.clear();
375                }
376            } else if ch == b';' {
377                break;
378            } else if !ch.is_ascii_whitespace() {
379                buf.push(ch);
380            }
381        }
382        if !buf.is_empty()
383            && let Some(item) = T::parse(&buf)
384        {
385            flags |= item.into();
386        }
387        flags
388    }
389}
390
391impl ItemParser for Vec<u8> {
392    fn parse(bytes: &[u8]) -> Option<Self> {
393        Some(bytes.to_vec())
394    }
395}
396
397impl ItemParser for Box<[u8]> {
398    fn parse(bytes: &[u8]) -> Option<Self> {
399        Some(bytes.into())
400    }
401}
402
403impl ItemParser for Box<str> {
404    fn parse(bytes: &[u8]) -> Option<Self> {
405        Some(std::str::from_utf8(bytes).ok()?.into())
406    }
407}
408
409impl ItemParser for String {
410    fn parse(bytes: &[u8]) -> Option<Self> {
411        Some(String::from_utf8_lossy(bytes).into_owned())
412    }
413}
414
415impl ItemParser for Cow<'_, str> {
416    fn parse(bytes: &[u8]) -> Option<Self> {
417        Some(
418            std::str::from_utf8(bytes)
419                .unwrap_or_default()
420                .to_string()
421                .into(),
422        )
423    }
424}