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 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}