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