mail_auth/common/
parse.rs1use 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}