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            && let Some(tag) = T::parse(&tag)
272        {
273            tags.push(tag);
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 { num.into() } else { None }
296    }
297
298    #[inline(always)]
299    fn ignore(&mut self) {
300        for &ch in self {
301            if ch == b';' {
302                break;
303            }
304        }
305    }
306
307    #[inline(always)]
308    fn seek_tag_end(&mut self) -> bool {
309        for &ch in self {
310            if ch == b';' {
311                return true;
312            } else if !ch.is_ascii_whitespace() {
313                return false;
314            }
315        }
316        true
317    }
318
319    #[inline(always)]
320    fn next_skip_whitespaces(&mut self) -> Option<u8> {
321        for &ch in self {
322            if !ch.is_ascii_whitespace() {
323                return ch.into();
324            }
325        }
326        None
327    }
328
329    fn items<T: ItemParser>(&mut self) -> Vec<T> {
330        let mut buf = Vec::with_capacity(10);
331        let mut items = Vec::new();
332        for &ch in self {
333            if ch == b':' {
334                if !buf.is_empty() {
335                    if let Some(item) = T::parse(&buf) {
336                        items.push(item);
337                    }
338                    buf.clear();
339                }
340            } else if ch == b';' {
341                break;
342            } else if !ch.is_ascii_whitespace() {
343                buf.push(ch);
344            }
345        }
346        if !buf.is_empty()
347            && let Some(item) = T::parse(&buf)
348        {
349            items.push(item);
350        }
351        items
352    }
353
354    fn flags<T: ItemParser + Into<u64>>(&mut self) -> u64 {
355        let mut buf = Vec::with_capacity(10);
356        let mut flags = 0;
357        for &ch in self {
358            if ch == b':' {
359                if !buf.is_empty() {
360                    if let Some(item) = T::parse(&buf) {
361                        flags |= item.into();
362                    }
363                    buf.clear();
364                }
365            } else if ch == b';' {
366                break;
367            } else if !ch.is_ascii_whitespace() {
368                buf.push(ch);
369            }
370        }
371        if !buf.is_empty()
372            && let Some(item) = T::parse(&buf)
373        {
374            flags |= item.into();
375        }
376        flags
377    }
378}
379
380impl ItemParser for Vec<u8> {
381    fn parse(bytes: &[u8]) -> Option<Self> {
382        Some(bytes.to_vec())
383    }
384}
385
386impl ItemParser for String {
387    fn parse(bytes: &[u8]) -> Option<Self> {
388        Some(String::from_utf8_lossy(bytes).into_owned())
389    }
390}
391
392impl ItemParser for Cow<'_, str> {
393    fn parse(bytes: &[u8]) -> Option<Self> {
394        Some(
395            std::str::from_utf8(bytes)
396                .unwrap_or_default()
397                .to_string()
398                .into(),
399        )
400    }
401}