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