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