Skip to main content

smtp_proto/request/
parser.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;
8use std::char;
9use std::slice::Iter;
10
11use crate::*;
12
13use super::*;
14
15const MAX_ADDRESS_LEN: usize = 256;
16const MAX_DOMAIN_LEN: usize = 255;
17
18impl<'a> Request<Cow<'a, str>> {
19    pub fn parse(bytes: &mut Iter<'a, u8>) -> Result<Request<Cow<'a, str>>, Error> {
20        let mut parser = Rfc5321Parser::new(bytes);
21        let command = parser.hashed_value()?;
22        if !parser.stop_char.is_ascii_whitespace() {
23            parser.seek_lf()?;
24            return Err(Error::UnknownCommand);
25        }
26
27        match command {
28            RCPT => {
29                if !(parser.stop_char == LF
30                    || parser.hashed_value()? != TO
31                    || parser.stop_char != b':' && parser.next_char()? != b':')
32                    && parser.next_char()? == b'<'
33                {
34                    if let Some(to) = parser.address()? {
35                        if parser.stop_char == b'>' {
36                            return Ok(Request::Rcpt {
37                                to: parser.rcpt_to_parameters(to)?,
38                            });
39                        }
40                    } else {
41                        parser.seek_lf()?;
42                        return Err(Error::InvalidRecipientAddress);
43                    }
44                }
45                parser.seek_lf()?;
46                Err(Error::SyntaxError {
47                    syntax: "RCPT TO:<forward-path> [parameters]",
48                })
49            }
50            MAIL => {
51                if !(parser.stop_char == LF
52                    || parser.hashed_value()? != FROM
53                    || parser.stop_char != b':' && parser.next_char()? != b':')
54                    && parser.next_char()? == b'<'
55                {
56                    if let Some(from) = parser.address()? {
57                        if parser.stop_char == b'>' {
58                            return Ok(Request::Mail {
59                                from: parser.mail_from_parameters(from)?,
60                            });
61                        }
62                    } else {
63                        parser.seek_lf()?;
64                        return Err(Error::InvalidSenderAddress);
65                    }
66                }
67
68                parser.seek_lf()?;
69                Err(Error::SyntaxError {
70                    syntax: "MAIL FROM:<reverse-path> [parameters]",
71                })
72            }
73            DATA => {
74                parser.seek_lf()?;
75                Ok(Request::Data)
76            }
77            EHLO => {
78                if parser.stop_char != LF {
79                    let host = parser.text()?;
80                    parser.seek_lf()?;
81                    if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
82                        return Ok(Request::Ehlo { host });
83                    }
84                }
85                Err(Error::SyntaxError {
86                    syntax: "EHLO domain",
87                })
88            }
89            BDAT => {
90                if parser.stop_char != LF {
91                    let chunk_size = parser.size()?;
92                    if chunk_size != usize::MAX && parser.stop_char.is_ascii_whitespace() {
93                        if parser.stop_char != LF {
94                            match parser.hashed_value()? {
95                                LAST => {
96                                    parser.seek_lf()?;
97                                    return Ok(Request::Bdat {
98                                        chunk_size,
99                                        is_last: true,
100                                    });
101                                }
102                                0 => {
103                                    parser.seek_lf()?;
104                                    return Ok(Request::Bdat {
105                                        chunk_size,
106                                        is_last: false,
107                                    });
108                                }
109                                _ => (),
110                            }
111                        } else {
112                            return Ok(Request::Bdat {
113                                chunk_size,
114                                is_last: false,
115                            });
116                        }
117                    }
118                }
119                parser.seek_lf()?;
120                Err(Error::SyntaxError {
121                    syntax: "BDAT chunk-size [LAST]",
122                })
123            }
124            AUTH => {
125                if parser.stop_char != LF
126                    && let Some(mechanism) = parser.mechanism()?
127                {
128                    let initial_response = if parser.stop_char != LF {
129                        parser.text()?
130                    } else {
131                        Cow::Borrowed("")
132                    };
133                    parser.seek_lf()?;
134                    return Ok(Request::Auth {
135                        mechanism,
136                        initial_response,
137                    });
138                }
139                Err(Error::SyntaxError {
140                    syntax: "AUTH mechanism [initial-response]",
141                })
142            }
143            EXPN => {
144                if parser.stop_char != LF {
145                    let value = parser.string()?;
146                    parser.seek_lf()?;
147                    if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
148                        return Ok(Request::Expn { value });
149                    }
150                }
151                Err(Error::SyntaxError {
152                    syntax: "EXPN string",
153                })
154            }
155            NOOP => {
156                if parser.stop_char != LF {
157                    let value = parser.string()?;
158                    parser.seek_lf()?;
159                    Ok(Request::Noop { value })
160                } else {
161                    Ok(Request::Noop {
162                        value: Cow::Borrowed(""),
163                    })
164                }
165            }
166            QUIT => {
167                parser.seek_lf()?;
168                Ok(Request::Quit)
169            }
170            LHLO => {
171                if parser.stop_char != LF {
172                    let host = parser.text()?;
173                    parser.seek_lf()?;
174                    if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
175                        return Ok(Request::Lhlo { host });
176                    }
177                }
178                Err(Error::SyntaxError {
179                    syntax: "LHLO domain",
180                })
181            }
182            RSET => {
183                parser.seek_lf()?;
184                Ok(Request::Rset)
185            }
186            VRFY => {
187                if parser.stop_char != LF {
188                    let value = parser.string()?;
189                    parser.seek_lf()?;
190                    if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
191                        return Ok(Request::Vrfy { value });
192                    }
193                }
194                Err(Error::SyntaxError {
195                    syntax: "VRFY string",
196                })
197            }
198            HELP => {
199                if parser.stop_char != LF {
200                    let value = parser.string()?;
201                    parser.seek_lf()?;
202                    Ok(Request::Help { value })
203                } else {
204                    Ok(Request::Help {
205                        value: Cow::Borrowed(""),
206                    })
207                }
208            }
209            STARTTLS => {
210                parser.seek_lf()?;
211                Ok(Request::StartTls)
212            }
213            ETRN => {
214                if parser.stop_char != LF {
215                    let name = parser.string()?;
216                    parser.seek_lf()?;
217                    if !name.is_empty() {
218                        return Ok(Request::Etrn { name });
219                    }
220                }
221                Err(Error::SyntaxError {
222                    syntax: "ETRN name",
223                })
224            }
225            ATRN => {
226                if parser.stop_char != LF {
227                    let mut domains = Vec::new();
228                    loop {
229                        let domain = parser.seek_char(b',')?;
230                        if !domain.is_empty() {
231                            domains.push(domain);
232                        }
233                        if parser.stop_char != b',' {
234                            parser.seek_lf()?;
235                            if !domains.is_empty() {
236                                return Ok(Request::Atrn { domains });
237                            } else {
238                                break;
239                            }
240                        }
241                    }
242                }
243                Err(Error::SyntaxError {
244                    syntax: "ATRN domain[,domain]",
245                })
246            }
247            BURL => {
248                if parser.stop_char != LF {
249                    let uri = parser.text()?;
250                    if !uri.is_empty() && parser.stop_char.is_ascii_whitespace() {
251                        if parser.stop_char != LF {
252                            match parser.hashed_value()? {
253                                LAST => {
254                                    parser.seek_lf()?;
255                                    return Ok(Request::Burl { uri, is_last: true });
256                                }
257                                0 => {
258                                    parser.seek_lf()?;
259                                    return Ok(Request::Burl {
260                                        uri,
261                                        is_last: false,
262                                    });
263                                }
264                                _ => (),
265                            }
266                        } else {
267                            return Ok(Request::Burl {
268                                uri,
269                                is_last: false,
270                            });
271                        }
272                    }
273                }
274                parser.seek_lf()?;
275                Err(Error::SyntaxError {
276                    syntax: "BURL absolute-uri [LAST]",
277                })
278            }
279            HELO => {
280                if parser.stop_char != LF {
281                    let host = parser.text()?;
282                    parser.seek_lf()?;
283                    if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
284                        return Ok(Request::Helo { host });
285                    }
286                }
287                Err(Error::SyntaxError {
288                    syntax: "HELO domain",
289                })
290            }
291            _ => {
292                parser.seek_lf()?;
293                Err(Error::UnknownCommand)
294            }
295        }
296    }
297}
298
299pub struct Rfc5321Parser<'x, 'y> {
300    bytes: &'x mut Iter<'y, u8>,
301    pub stop_char: u8,
302    pub bytes_left: usize,
303}
304
305impl<'x, 'y> Rfc5321Parser<'x, 'y> {
306    pub fn new(bytes: &'x mut Iter<'y, u8>) -> Self {
307        let (bytes_left, _) = bytes.size_hint();
308        Rfc5321Parser {
309            bytes,
310            bytes_left,
311            stop_char: 0,
312        }
313    }
314
315    #[allow(clippy::while_let_on_iterator)]
316    pub fn hashed_value(&mut self) -> Result<u64, Error> {
317        let mut value: u64 = 0;
318        let mut shift = 0;
319
320        while let Some(&ch) = self.bytes.next() {
321            match ch {
322                b'A'..=b'Z' | b'0'..=b'9' | b'-' if shift < 64 => {
323                    value |= (ch as u64) << shift;
324                    shift += 8;
325                }
326                b'a'..=b'z' if shift < 64 => {
327                    value |= ((ch - b'a' + b'A') as u64) << shift;
328                    shift += 8;
329                }
330                b'\r' => (),
331                b' ' => {
332                    if value != 0 {
333                        self.stop_char = ch;
334                        return Ok(value);
335                    }
336                }
337                _ => {
338                    self.stop_char = ch;
339                    return Ok(value);
340                }
341            }
342        }
343
344        Err(Error::NeedsMoreData {
345            bytes_left: self.bytes_left,
346        })
347    }
348
349    #[allow(clippy::while_let_on_iterator)]
350    pub fn hashed_value_long(&mut self) -> Result<u128, Error> {
351        let mut value: u128 = 0;
352        let mut shift = 0;
353
354        while let Some(&ch) = self.bytes.next() {
355            match ch {
356                b'A'..=b'Z' | b'0'..=b'9' | b'-' if shift < 128 => {
357                    value |= (ch as u128) << shift;
358                    shift += 8;
359                }
360                b'a'..=b'z' if shift < 128 => {
361                    value |= ((ch - b'a' + b'A') as u128) << shift;
362                    shift += 8;
363                }
364                b' ' => {
365                    if value != 0 {
366                        self.stop_char = b' ';
367                        return Ok(value);
368                    }
369                }
370                b'\r' => (),
371                _ => {
372                    self.stop_char = ch;
373                    return Ok(value);
374                }
375            }
376        }
377
378        Err(Error::NeedsMoreData {
379            bytes_left: self.bytes_left,
380        })
381    }
382
383    pub fn address(&mut self) -> Result<Option<Cow<'y, str>>, Error> {
384        let mut value = self.start_zero_copy();
385        let mut last_ch = 0;
386        let mut in_quote = false;
387        let mut at_count = 0;
388        let mut lp_len = 0;
389
390        while let Some(&ch) = self.bytes.next() {
391            match ch {
392                b'0'..=b'9'
393                | b'a'..=b'z'
394                | b'A'..=b'Z'
395                | b'!'
396                | b'#'
397                | b'$'
398                | b'%'
399                | b'&'
400                | b'\''
401                | b'*'
402                | b'+'
403                | b'-'
404                | b'/'
405                | b'='
406                | b'?'
407                | b'^'
408                | b'_'
409                | b'`'
410                | b'{'
411                | b'|'
412                | b'}'
413                | b'~'
414                | 0x7f..=u8::MAX => {}
415                b'.' if !in_quote => {
416                    if last_ch == b'.' || last_ch == b'@' || self.len_excluding_current(&value) == 0
417                    {
418                        self.stop_char = ch;
419                        return Ok(None);
420                    }
421                }
422                b'@' if !in_quote => {
423                    at_count += 1;
424                    lp_len = self.len_excluding_current(&value);
425                }
426                b'>' if !in_quote => {
427                    self.stop_char = ch;
428                    self.flush_excluding_current(&mut value);
429                    let value = value.data;
430
431                    return Ok(match value.len() {
432                        1..=MAX_ADDRESS_LEN if at_count == 1 && lp_len > 0 => Some(value),
433                        10 if at_count == 0 && value.eq_ignore_ascii_case("postmaster") => {
434                            Some("".into())
435                        }
436                        0 => Some("".into()),
437                        _ => None,
438                    });
439                }
440                b'\r' => self.flush_excluding_current(&mut value),
441                b':' if !in_quote && self.first_excluding_current(&value) == Some(b'@') => {
442                    // Remove source route
443                    value = self.start_zero_copy();
444                    at_count = 0;
445                    lp_len = 0;
446                }
447                // Note that if there is an @ at the start, we require `:` a to be in the string
448                // later, since otherwise `at_count == 1 && lp_len > 0` cannot be satisfied. So it
449                // doesn't matter what we do here (other than not error).
450                b',' if !in_quote && self.first_excluding_current(&value) == Some(b'@') => (),
451                b' ' if !in_quote => {
452                    self.flush_excluding_current(&mut value);
453                    if !value.data.is_empty() {
454                        self.stop_char = b' ';
455                        let value = value.data;
456
457                        let is_valid = value.is_empty()
458                            || value.len() <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0;
459
460                        return Ok(is_valid.then_some(value));
461                    }
462                }
463                b'\n' => {
464                    self.stop_char = b'\n';
465                    self.flush_excluding_current(&mut value);
466                    let value = value.data;
467
468                    let is_valid = value.is_empty()
469                        || value.len() <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0;
470
471                    return Ok(is_valid.then_some(value));
472                }
473                b'\"' if !in_quote || last_ch != b'\\' => {
474                    in_quote = !in_quote;
475                    self.flush_excluding_current(&mut value);
476                }
477                b'\\' if in_quote => {
478                    if last_ch != b'\\' {
479                        self.flush_excluding_current(&mut value);
480                    } else {
481                        // Escaped backslash
482                        last_ch = 0;
483                        continue;
484                    }
485                }
486                _ if in_quote => {}
487                _ => {
488                    self.stop_char = ch;
489                    return Ok(None);
490                }
491            }
492
493            last_ch = ch;
494        }
495
496        Err(Error::NeedsMoreData {
497            bytes_left: self.bytes_left,
498        })
499    }
500
501    pub fn string(&mut self) -> Result<Cow<'y, str>, Error> {
502        let mut in_quote = false;
503        let mut value = self.start_zero_copy();
504        let mut last_ch = 0;
505
506        while let Some(&ch) = self.bytes.next() {
507            match ch {
508                b' ' if !in_quote => {
509                    self.flush_excluding_current(&mut value);
510                    if !value.data.is_empty() {
511                        self.stop_char = b' ';
512                        return Ok(value.data);
513                    }
514                }
515                b'\n' => {
516                    self.flush_excluding_current(&mut value);
517                    self.stop_char = b'\n';
518                    return Ok(value.data);
519                }
520                b'\"' if !in_quote || last_ch != b'\\' => {
521                    in_quote = !in_quote;
522                    self.flush_excluding_current(&mut value);
523                }
524                b'\\' if in_quote => {
525                    if last_ch != b'\\' {
526                        self.flush_excluding_current(&mut value);
527                    } else {
528                        last_ch = 0;
529                        continue;
530                    }
531                }
532                b'\r' => self.flush_excluding_current(&mut value),
533                _ => {}
534            }
535
536            last_ch = ch;
537        }
538
539        Err(Error::NeedsMoreData {
540            bytes_left: self.bytes_left,
541        })
542    }
543
544    #[allow(clippy::while_let_on_iterator)]
545    pub fn text(&mut self) -> Result<Cow<'y, str>, Error> {
546        let mut value = self.start_zero_copy();
547        while let Some(&ch) = self.bytes.next() {
548            match ch {
549                b'\n' | b' ' => {
550                    self.flush_excluding_current(&mut value);
551                    self.stop_char = ch;
552                    return Ok(value.data);
553                }
554                b'\r' => self.flush_excluding_current(&mut value),
555                _ => {}
556            }
557        }
558
559        Err(Error::NeedsMoreData {
560            bytes_left: self.bytes_left,
561        })
562    }
563
564    #[allow(clippy::while_let_on_iterator)]
565    pub fn xtext(&mut self) -> Result<Cow<'y, str>, Error> {
566        let mut value = self.start_zero_copy();
567        while let Some(&ch) = self.bytes.next() {
568            match ch {
569                b'\n' | b' ' => {
570                    self.flush_excluding_current(&mut value);
571                    self.stop_char = ch;
572                    return Ok(value.data);
573                }
574                b'+' => {
575                    self.flush_excluding_current(&mut value);
576
577                    let mut hex1 = None;
578
579                    while let Some(&ch) = self.bytes.next() {
580                        if let Some(digit) = char::from(ch).to_digit(16) {
581                            if let Some(hex1) = hex1 {
582                                let data = value.data.to_mut();
583                                data.push(char::from(((hex1 as u8) << 4) | digit as u8));
584                                break;
585                            } else {
586                                hex1 = Some(digit);
587                            }
588                        } else if ch == LF {
589                            self.stop_char = b'\n';
590                            return Ok(value.data);
591                        } else {
592                            break;
593                        }
594                    }
595
596                    self.drop_extra(&mut value);
597                }
598                b'\r' => self.flush_excluding_current(&mut value),
599                _ => {}
600            }
601        }
602
603        Err(Error::NeedsMoreData {
604            bytes_left: self.bytes_left,
605        })
606    }
607
608    #[allow(clippy::while_let_on_iterator)]
609    pub fn seek_char(&mut self, stop_char: u8) -> Result<Cow<'y, str>, Error> {
610        let mut value = self.start_zero_copy();
611        while let Some(&ch) = self.bytes.next() {
612            match ch {
613                b'\n' => {
614                    self.flush_excluding_current(&mut value);
615                    self.stop_char = b'\n';
616                    return Ok(value.data);
617                }
618                b' ' => {
619                    self.flush_excluding_current(&mut value);
620                    if !value.data.is_empty() {
621                        self.stop_char = b' ';
622                        return Ok(value.data);
623                    }
624                }
625                b'\r' => self.flush_excluding_current(&mut value),
626                _ if ch == stop_char => {
627                    self.flush_excluding_current(&mut value);
628                    self.stop_char = ch;
629                    return Ok(value.data);
630                }
631                _ => {}
632            }
633        }
634
635        Err(Error::NeedsMoreData {
636            bytes_left: self.bytes_left,
637        })
638    }
639
640    #[inline(always)]
641    pub fn seek_lf(&mut self) -> Result<(), Error> {
642        if self.stop_char != LF {
643            for &ch in &mut self.bytes {
644                if ch == LF {
645                    return Ok(());
646                }
647            }
648            Err(Error::NeedsMoreData {
649                bytes_left: self.bytes_left,
650            })
651        } else {
652            Ok(())
653        }
654    }
655
656    #[inline(always)]
657    pub fn next_char(&mut self) -> Result<u8, Error> {
658        for &ch in &mut self.bytes {
659            match ch {
660                b' ' | b'\r' => (),
661                _ => {
662                    self.stop_char = ch;
663                    return Ok(ch);
664                }
665            }
666        }
667        Err(Error::NeedsMoreData {
668            bytes_left: self.bytes_left,
669        })
670    }
671
672    #[inline(always)]
673    pub fn read_char(&mut self) -> Result<u8, Error> {
674        self.bytes.next().copied().ok_or(Error::NeedsMoreData {
675            bytes_left: self.bytes_left,
676        })
677    }
678
679    pub fn size(&mut self) -> Result<usize, Error> {
680        let mut value = usize::MAX;
681        for &ch in &mut self.bytes {
682            match ch {
683                b'0'..=b'9' => {
684                    value = if value != usize::MAX {
685                        value
686                            .saturating_mul(10)
687                            .saturating_add((ch - b'0') as usize)
688                    } else {
689                        (ch - b'0') as usize
690                    };
691                }
692                b'\r' => (),
693                b' ' => {
694                    if value != usize::MAX {
695                        self.stop_char = b' ';
696                        return Ok(value);
697                    }
698                }
699                _ => {
700                    self.stop_char = ch;
701                    return Ok(value);
702                }
703            }
704        }
705        Err(Error::NeedsMoreData {
706            bytes_left: self.bytes_left,
707        })
708    }
709
710    pub fn integer(&mut self) -> Result<i64, Error> {
711        let mut value = i64::MAX;
712        let mut multiplier = 1;
713
714        for &ch in &mut self.bytes {
715            match ch {
716                b'0'..=b'9' => {
717                    value = if value != i64::MAX {
718                        value.saturating_mul(10).saturating_add((ch - b'0') as i64)
719                    } else {
720                        (ch - b'0') as i64
721                    };
722                }
723                b' ' => {
724                    if value != i64::MAX {
725                        self.stop_char = b' ';
726                        return Ok(value * multiplier);
727                    }
728                }
729                b'-' if value == i64::MAX => {
730                    multiplier = -1;
731                }
732                b'+' if value == i64::MAX => (),
733                b'\r' => (),
734                _ => {
735                    self.stop_char = ch;
736                    return Ok(if value != i64::MAX {
737                        value * multiplier
738                    } else {
739                        i64::MAX
740                    });
741                }
742            }
743        }
744        Err(Error::NeedsMoreData {
745            bytes_left: self.bytes_left,
746        })
747    }
748
749    pub fn timestamp(&mut self) -> Result<i64, Error> {
750        let mut dt = [0u32; 8];
751        let mut zone_multiplier = 1;
752        let mut pos = 0;
753
754        for &ch in &mut self.bytes {
755            match ch {
756                b'0'..=b'9' if pos < 8 => {
757                    dt[pos] = dt[pos]
758                        .saturating_mul(10)
759                        .saturating_add((ch - b'0') as u32);
760                }
761                b'-' if pos <= 1 || pos == 5 => {
762                    pos += 1;
763                }
764                b'+' if pos == 5 => {
765                    zone_multiplier = -1;
766                    pos += 1;
767                }
768                b'T' if pos == 2 => {
769                    pos += 1;
770                }
771                b':' if pos == 3 || pos == 4 || pos == 6 => {
772                    pos += 1;
773                }
774                b'Z' if pos == 5 => {
775                    pos = 8;
776                }
777                _ => {
778                    self.stop_char = ch;
779                    return Ok(if pos >= 7 {
780                        // Ported from https://github.com/protocolbuffers/upb/blob/22182e6e/upb/json_decode.c#L982-L992
781                        let month = dt[1];
782                        let year_base = 4800; /* Before min year, multiple of 400. */
783                        let m_adj = month.wrapping_sub(3); /* March-based month. */
784                        let carry = i64::from(m_adj > month);
785                        let adjust = if carry > 0 { 12 } else { 0 };
786                        let y_adj = dt[0] as i64 + year_base - carry;
787                        let month_days =
788                            ((m_adj.wrapping_add(adjust)).wrapping_mul(62719) + 769) / 2048;
789                        let leap_days = y_adj / 4 - y_adj / 100 + y_adj / 400;
790                        (y_adj * 365 + leap_days + month_days as i64 + (dt[2] as i64 - 1) - 2472632)
791                            * 86400
792                            + dt[3] as i64 * 3600
793                            + dt[4] as i64 * 60
794                            + dt[5] as i64
795                            + ((dt[6] as i64 * 3600 + dt[7] as i64 * 60) * zone_multiplier)
796                    } else {
797                        i64::MAX
798                    });
799                }
800            }
801        }
802
803        Err(Error::NeedsMoreData {
804            bytes_left: self.bytes_left,
805        })
806    }
807
808    pub fn mail_from_parameters(
809        &mut self,
810        address: Cow<'y, str>,
811    ) -> Result<MailFrom<Cow<'y, str>>, Error> {
812        let mut params = MailFrom {
813            address,
814            flags: 0,
815            size: 0,
816            trans_id: None,
817            by: 0,
818            env_id: None,
819            solicit: None,
820            mtrk: None,
821            auth: None,
822            hold_for: 0,
823            hold_until: 0,
824            mt_priority: 0,
825        };
826        while self.stop_char != LF {
827            let key = self.hashed_value_long()?;
828            match key {
829                SMTPUTF8 if self.stop_char.is_ascii_whitespace() => {
830                    params.flags |= MAIL_SMTPUTF8;
831                }
832                BODY if self.stop_char == b'=' => {
833                    params.flags |= match self.hashed_value_long()? {
834                        EIGHBITMIME if self.stop_char.is_ascii_whitespace() => MAIL_BODY_8BITMIME,
835                        BINARYMIME if self.stop_char.is_ascii_whitespace() => MAIL_BODY_BINARYMIME,
836                        SEVENBIT if self.stop_char.is_ascii_whitespace() => MAIL_BODY_7BIT,
837                        _ => {
838                            self.seek_lf()?;
839                            return Err(Error::InvalidParameter { param: "BODY" });
840                        }
841                    }
842                }
843                SIZE if self.stop_char == b'=' => {
844                    let size = self.size()?;
845                    if size != usize::MAX && self.stop_char.is_ascii_whitespace() {
846                        params.size = size;
847                    } else {
848                        self.seek_lf()?;
849                        return Err(Error::InvalidParameter { param: "SIZE" });
850                    }
851                }
852                BY if self.stop_char == b'=' => {
853                    let time = self.integer()?;
854                    if time != i64::MAX && self.stop_char == b';' {
855                        params.flags |= match self.hashed_value()? {
856                            N if self.stop_char.is_ascii_whitespace() => MAIL_BY_NOTIFY,
857                            NT if self.stop_char.is_ascii_whitespace() => {
858                                MAIL_BY_NOTIFY | MAIL_BY_TRACE
859                            }
860                            R if self.stop_char.is_ascii_whitespace() => MAIL_BY_RETURN,
861                            RT if self.stop_char.is_ascii_whitespace() => {
862                                MAIL_BY_RETURN | MAIL_BY_TRACE
863                            }
864                            _ => {
865                                self.seek_lf()?;
866                                return Err(Error::InvalidParameter { param: "BY" });
867                            }
868                        };
869                        params.by = time;
870                    } else {
871                        self.seek_lf()?;
872                        return Err(Error::InvalidParameter { param: "BY" });
873                    }
874                }
875                HOLDUNTIL if self.stop_char == b'=' => {
876                    let hold = self.size()?;
877                    if hold != usize::MAX && self.stop_char.is_ascii_whitespace() {
878                        params.hold_until = hold as u64;
879                    } else {
880                        self.seek_lf()?;
881                        return Err(Error::InvalidParameter { param: "HOLDUNTIL" });
882                    }
883                }
884                HOLDFOR if self.stop_char == b'=' => {
885                    let hold = self.size()?;
886                    if hold != usize::MAX && self.stop_char.is_ascii_whitespace() {
887                        params.hold_for = hold as u64;
888                    } else {
889                        self.seek_lf()?;
890                        return Err(Error::InvalidParameter { param: "HOLDFOR" });
891                    }
892                }
893                RET if self.stop_char == b'=' => {
894                    params.flags |= match self.hashed_value()? {
895                        FULL if self.stop_char.is_ascii_whitespace() => MAIL_RET_FULL,
896                        HDRS if self.stop_char.is_ascii_whitespace() => MAIL_RET_HDRS,
897                        _ => {
898                            self.seek_lf()?;
899                            return Err(Error::InvalidParameter { param: "RET" });
900                        }
901                    };
902                }
903                ENVID if self.stop_char == b'=' => {
904                    let env_id = self.xtext()?;
905                    if self.stop_char.is_ascii_whitespace() && (1..=100).contains(&env_id.len()) {
906                        params.env_id = env_id.into();
907                    } else {
908                        self.seek_lf()?;
909                        return Err(Error::InvalidParameter { param: "ENVID" });
910                    }
911                }
912                REQUIRETLS if self.stop_char.is_ascii_whitespace() => {
913                    params.flags |= MAIL_REQUIRETLS;
914                }
915                SOLICIT if self.stop_char == b'=' => {
916                    let solicit = self.text()?;
917                    if !solicit.is_empty() && self.stop_char.is_ascii_whitespace() {
918                        params.solicit = solicit.into();
919                    } else {
920                        self.seek_lf()?;
921                        return Err(Error::InvalidParameter { param: "SOLICIT" });
922                    }
923                }
924                TRANSID if self.stop_char == b'=' => {
925                    if self.next_char()? == b'<' {
926                        let transid = self.seek_char(b'>')?;
927                        if self.stop_char == b'>' && !transid.is_empty() {
928                            params.trans_id = transid.into();
929                            self.stop_char = SP;
930                            continue;
931                        }
932                    }
933                    self.seek_lf()?;
934                    return Err(Error::InvalidParameter { param: "TRANSID" });
935                }
936                MTRK if self.stop_char == b'=' => {
937                    let certifier = self.seek_char(b':')?;
938                    let timeout = if self.stop_char == b':' {
939                        self.size()?
940                    } else {
941                        0
942                    };
943
944                    if !certifier.is_empty()
945                        && self.stop_char.is_ascii_whitespace()
946                        && timeout != usize::MAX
947                    {
948                        params.mtrk = Mtrk {
949                            certifier,
950                            timeout: timeout as u64,
951                        }
952                        .into();
953                    } else {
954                        self.seek_lf()?;
955                        return Err(Error::InvalidParameter { param: "MTRK" });
956                    }
957                }
958                AUTH_ if self.stop_char == b'=' => {
959                    let mailbox = self.xtext()?;
960                    if (1..=MAX_ADDRESS_LEN).contains(&mailbox.len())
961                        && self.stop_char.is_ascii_whitespace()
962                    {
963                        params.auth = mailbox.into();
964                    } else {
965                        self.seek_lf()?;
966                        return Err(Error::InvalidParameter { param: "AUTH" });
967                    }
968                }
969                MT_PRIORITY if self.stop_char == b'=' => {
970                    let priority = self.integer()?;
971                    if priority != i64::MAX && self.stop_char.is_ascii_whitespace() {
972                        params.mt_priority = priority;
973                    } else {
974                        self.seek_lf()?;
975                        return Err(Error::InvalidParameter {
976                            param: "MT-PRIORITY",
977                        });
978                    }
979                }
980                CONPERM if self.stop_char.is_ascii_whitespace() => {
981                    params.flags |= MAIL_CONPERM;
982                }
983                0 => {
984                    // hashed_value_long() returned 0, meaning no alphanumeric
985                    // characters were found before hitting a non-keyword char.
986                    // If stop_char is whitespace/LF this is just extra spacing
987                    // between parameters (harmless). Otherwise it's garbage
988                    // like ">", "!", etc. that should be rejected.
989                    if !self.stop_char.is_ascii_whitespace() {
990                        self.seek_lf()?;
991                        return Err(Error::SyntaxError {
992                            syntax: "MAIL FROM:<reverse-path> [parameters]",
993                        });
994                    }
995                }
996                unknown => {
997                    let mut param = Vec::with_capacity(16);
998                    for ch in unknown.to_le_bytes() {
999                        if ch != 0 {
1000                            param.push(ch.to_ascii_uppercase());
1001                        }
1002                    }
1003                    if !self.stop_char.is_ascii_whitespace() {
1004                        param.push(self.stop_char.to_ascii_uppercase());
1005                        for &ch in &mut self.bytes {
1006                            if !ch.is_ascii_whitespace() {
1007                                param.push(ch.to_ascii_uppercase());
1008                            } else {
1009                                self.stop_char = ch;
1010                                break;
1011                            }
1012                        }
1013                    }
1014
1015                    self.seek_lf()?;
1016                    return Err(Error::UnsupportedParameter {
1017                        param: param.into_string(),
1018                    });
1019                }
1020            }
1021        }
1022
1023        Ok(params)
1024    }
1025
1026    pub fn rcpt_to_parameters(
1027        &mut self,
1028        address: Cow<'y, str>,
1029    ) -> Result<RcptTo<Cow<'y, str>>, Error> {
1030        let mut params = RcptTo {
1031            address,
1032            orcpt: None,
1033            rrvs: 0,
1034            flags: 0,
1035        };
1036        while self.stop_char != LF {
1037            let key = self.hashed_value_long()?;
1038            match key {
1039                NOTIFY if self.stop_char == b'=' => loop {
1040                    match self.hashed_value_long()? {
1041                        NEVER
1042                            if (params.flags
1043                                & (RCPT_NOTIFY_NEVER
1044                                    | RCPT_NOTIFY_SUCCESS
1045                                    | RCPT_NOTIFY_FAILURE
1046                                    | RCPT_NOTIFY_DELAY))
1047                                == 0 =>
1048                        {
1049                            params.flags |= RCPT_NOTIFY_NEVER;
1050                        }
1051                        SUCCESS => {
1052                            params.flags |= RCPT_NOTIFY_SUCCESS;
1053                        }
1054                        FAILURE => {
1055                            params.flags |= RCPT_NOTIFY_FAILURE;
1056                        }
1057                        DELAY => {
1058                            params.flags |= RCPT_NOTIFY_DELAY;
1059                        }
1060                        _ => {
1061                            self.seek_lf()?;
1062                            return Err(Error::InvalidParameter { param: "NOTIFY" });
1063                        }
1064                    }
1065                    if self.stop_char.is_ascii_whitespace() {
1066                        break;
1067                    } else if self.stop_char != b',' {
1068                        self.seek_lf()?;
1069                        return Err(Error::InvalidParameter { param: "NOTIFY" });
1070                    }
1071                },
1072                ORCPT if self.stop_char == b'=' => {
1073                    let v = self.hashed_value()?;
1074                    if v != RFC822 || self.stop_char != b';' {
1075                        self.seek_lf()?;
1076                        return Err(Error::InvalidParameter { param: "ORCPT" });
1077                    }
1078                    let addr = self.xtext()?;
1079                    if self.stop_char.is_ascii_whitespace()
1080                        && (1..=MAX_ADDRESS_LEN).contains(&addr.len())
1081                    {
1082                        params.orcpt = addr.into();
1083                    } else {
1084                        self.seek_lf()?;
1085                        return Err(Error::InvalidParameter { param: "ORCPT" });
1086                    }
1087                }
1088                RRVS if self.stop_char == b'=' => {
1089                    let time = self.timestamp()?;
1090                    if time != i64::MAX && self.stop_char.is_ascii_whitespace()
1091                        || self.stop_char == b';'
1092                    {
1093                        let is_reject = self.stop_char != b';'
1094                            || match self.hashed_value()? {
1095                                C if self.stop_char.is_ascii_whitespace() => false,
1096                                R if self.stop_char.is_ascii_whitespace() => true,
1097                                _ => {
1098                                    self.seek_lf()?;
1099                                    return Err(Error::InvalidParameter { param: "RRVS" });
1100                                }
1101                            };
1102                        params.rrvs = time;
1103                        params.flags |= if is_reject {
1104                            RCPT_RRVS_REJECT
1105                        } else {
1106                            RCPT_RRVS_CONTINUE
1107                        };
1108                    } else {
1109                        self.seek_lf()?;
1110                        return Err(Error::InvalidParameter { param: "RRVS" });
1111                    }
1112                }
1113                CONNEG if self.stop_char.is_ascii_whitespace() => {
1114                    params.flags |= RCPT_CONNEG;
1115                }
1116                0 => {
1117                    if !self.stop_char.is_ascii_whitespace() {
1118                        self.seek_lf()?;
1119                        return Err(Error::SyntaxError {
1120                            syntax: "RCPT TO:<forward-path> [parameters]",
1121                        });
1122                    }
1123                }
1124                unknown => {
1125                    let mut param = Vec::with_capacity(16);
1126                    for ch in unknown.to_le_bytes() {
1127                        if ch != 0 {
1128                            param.push(ch.to_ascii_uppercase());
1129                        }
1130                    }
1131                    if !self.stop_char.is_ascii_whitespace() {
1132                        param.push(self.stop_char.to_ascii_uppercase());
1133                        for &ch in &mut self.bytes {
1134                            if !ch.is_ascii_whitespace() {
1135                                param.push(ch.to_ascii_uppercase());
1136                            } else {
1137                                self.stop_char = ch;
1138                                break;
1139                            }
1140                        }
1141                    }
1142
1143                    self.seek_lf()?;
1144                    return Err(Error::UnsupportedParameter {
1145                        param: param.into_string(),
1146                    });
1147                }
1148            }
1149        }
1150
1151        Ok(params)
1152    }
1153
1154    pub fn mechanism(&mut self) -> Result<Option<u64>, Error> {
1155        let mut trailing_chars = [0u8; 8];
1156        let mut pos = 0;
1157        let mechanism = self.hashed_value_long()?;
1158        if !self.stop_char.is_ascii_whitespace() {
1159            trailing_chars[0] = self.stop_char;
1160            pos += 1;
1161            for &ch in &mut self.bytes {
1162                if !ch.is_ascii_whitespace() {
1163                    if let Some(tch) = trailing_chars.get_mut(pos) {
1164                        *tch = ch.to_ascii_uppercase();
1165                    }
1166                    pos += 1;
1167                } else {
1168                    self.stop_char = ch;
1169                    break;
1170                }
1171            }
1172            if !self.stop_char.is_ascii_whitespace() {
1173                return Err(Error::NeedsMoreData {
1174                    bytes_left: self.bytes_left,
1175                });
1176            } else if pos > 8 {
1177                return Ok(0.into());
1178            }
1179        }
1180        Ok(match (mechanism, &trailing_chars[..pos]) {
1181            (_9798_M_DSA_SHA1, b"") => AUTH_9798_M_DSA_SHA1.into(),
1182            (_9798_M_ECDSA_SHA, b"1") => AUTH_9798_M_ECDSA_SHA1.into(),
1183            (_9798_M_RSA_SHA1_, b"ENC") => AUTH_9798_M_RSA_SHA1_ENC.into(),
1184            (_9798_U_DSA_SHA1, b"") => AUTH_9798_U_DSA_SHA1.into(),
1185            (_9798_U_ECDSA_SHA, b"1") => AUTH_9798_U_ECDSA_SHA1.into(),
1186            (_9798_U_RSA_SHA1_, b"ENC") => AUTH_9798_U_RSA_SHA1_ENC.into(),
1187            (ANONYMOUS, b"") => AUTH_ANONYMOUS.into(),
1188            (CRAM_MD5, b"") => AUTH_CRAM_MD5.into(),
1189            (DIGEST_MD5, b"") => AUTH_DIGEST_MD5.into(),
1190            (EAP_AES128, b"") => AUTH_EAP_AES128.into(),
1191            (EAP_AES128_PLUS, b"") => AUTH_EAP_AES128_PLUS.into(),
1192            (ECDH_X25519_CHAL, b"LENGE") => AUTH_ECDH_X25519_CHALLENGE.into(),
1193            (ECDSA_NIST256P_C, b"HALLENGE") => AUTH_ECDSA_NIST256P_CHALLENGE.into(),
1194            (EXTERNAL, b"") => AUTH_EXTERNAL.into(),
1195            (GS2_KRB5, b"") => AUTH_GS2_KRB5.into(),
1196            (GS2_KRB5_PLUS, b"") => AUTH_GS2_KRB5_PLUS.into(),
1197            (GSS_SPNEGO, b"") => AUTH_GSS_SPNEGO.into(),
1198            (GSSAPI, b"") => AUTH_GSSAPI.into(),
1199            (KERBEROS_V4, b"") => AUTH_KERBEROS_V4.into(),
1200            (KERBEROS_V5, b"") => AUTH_KERBEROS_V5.into(),
1201            (LOGIN, b"") => AUTH_LOGIN.into(),
1202            (NMAS_SAMBA_AUTH, b"") => AUTH_NMAS_SAMBA_AUTH.into(),
1203            (NMAS_AUTHEN, b"") => AUTH_NMAS_AUTHEN.into(),
1204            (NMAS_LOGIN, b"") => AUTH_NMAS_LOGIN.into(),
1205            (NTLM, b"") => AUTH_NTLM.into(),
1206            (OAUTH10A, b"") => AUTH_OAUTH10A.into(),
1207            (OAUTHBEARER, b"") => AUTH_OAUTHBEARER.into(),
1208            (OPENID20, b"") => AUTH_OPENID20.into(),
1209            (OTP, b"") => AUTH_OTP.into(),
1210            (PLAIN, b"") => AUTH_PLAIN.into(),
1211            (SAML20, b"") => AUTH_SAML20.into(),
1212            (SCRAM_SHA_1, b"") => AUTH_SCRAM_SHA_1.into(),
1213            (SCRAM_SHA_1_PLUS, b"") => AUTH_SCRAM_SHA_1_PLUS.into(),
1214            (SCRAM_SHA_256, b"") => AUTH_SCRAM_SHA_256.into(),
1215            (SCRAM_SHA_256_PL, b"US") => AUTH_SCRAM_SHA_256_PLUS.into(),
1216            (SECURID, b"") => AUTH_SECURID.into(),
1217            (SKEY, b"") => AUTH_SKEY.into(),
1218            (SPNEGO, b"") => AUTH_SPNEGO.into(),
1219            (SPNEGO_PLUS, b"") => AUTH_SPNEGO_PLUS.into(),
1220            (SXOVER_PLUS, b"") => AUTH_SXOVER_PLUS.into(),
1221            (XOAUTH, b"") => AUTH_XOAUTH.into(),
1222            (XOAUTH2, b"") => AUTH_XOAUTH2.into(),
1223            (0, b"") => None,
1224            _ => 0.into(),
1225        })
1226    }
1227
1228    /// Construct a new `MaybeZeroCopy`, beginning a potentially zero-copy read from the input.
1229    #[inline(always)]
1230    fn start_zero_copy(&self) -> MaybeZeroCopy<'y> {
1231        MaybeZeroCopy {
1232            remaining: self.bytes.as_slice(),
1233            data: Cow::Borrowed(""),
1234        }
1235    }
1236
1237    /// Get the slice starting at the last time `MaybeZeroCopy` was flushed and ending one byte
1238    /// before where `Self` currently is. This is what will be pushed to the `MaybeZeroCopy` on
1239    /// flush.
1240    fn extra_before_current(&self, out: &MaybeZeroCopy<'y>) -> &'y [u8] {
1241        // Calculate the number of bytes we have advanced our iterator from the start of the
1242        // `MaybeZeroCopy`. Subtract one to remove the current byte.
1243        let new_len = self.bytes.as_slice().as_ptr().addr() - out.remaining.as_ptr().addr() - 1;
1244        &out.remaining[..new_len]
1245    }
1246
1247    /// Get the length of a `MaybeZeroCopy`.
1248    fn len_excluding_current(&self, out: &MaybeZeroCopy<'y>) -> usize {
1249        out.data.len() + self.extra_before_current(out).len()
1250    }
1251
1252    /// Get the first byte of a `MaybeZeroCopy`.
1253    fn first_excluding_current(&self, out: &MaybeZeroCopy<'y>) -> Option<u8> {
1254        out.data
1255            .bytes()
1256            .next()
1257            .or_else(|| self.extra_before_current(out).first().copied())
1258    }
1259
1260    /// Drop data up to the current cursor from the `MaybeZeroCopy`.
1261    #[inline(always)]
1262    fn drop_extra(&self, out: &mut MaybeZeroCopy<'y>) {
1263        out.remaining = self.bytes.as_slice();
1264    }
1265
1266    /// Extend a `MaybeZeroCopy` with new bytes.
1267    fn flush_excluding_current(&self, out: &mut MaybeZeroCopy<'y>) {
1268        let extra = self.extra_before_current(out);
1269        self.drop_extra(out);
1270
1271        if out.data.is_empty() {
1272            out.data = String::from_utf8_lossy(extra);
1273        } else {
1274            if let Cow::Borrowed(s) = out.data {
1275                let mut buf = String::with_capacity(32);
1276                buf.push_str(s);
1277                out.data = Cow::Owned(buf);
1278            }
1279
1280            // The same algorithm as `String::from_utf8_lossy`, but appending to an existing
1281            // buffer.
1282            let out = out.data.to_mut();
1283            for chunk in extra.utf8_chunks() {
1284                out.push_str(chunk.valid());
1285                if !chunk.invalid().is_empty() {
1286                    out.push(char::REPLACEMENT_CHARACTER);
1287                }
1288            }
1289        }
1290    }
1291}
1292
1293/// A state machine enabling parsing to be potentially zero-copy.
1294struct MaybeZeroCopy<'y> {
1295    /// Slice from where we start parsing to the end of the input.
1296    remaining: &'y [u8],
1297    /// The data itself.
1298    data: Cow<'y, str>,
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303    use std::borrow::Cow;
1304
1305    use crate::*;
1306
1307    #[test]
1308    fn parse_request() {
1309        for item in [
1310            // HELO et al.
1311            (
1312                "EHLO bar.com",
1313                Ok(Request::Ehlo {
1314                    host: Cow::Borrowed("bar.com"),
1315                }),
1316            ),
1317            (
1318                "EHLO",
1319                Err(Error::SyntaxError {
1320                    syntax: "EHLO domain",
1321                }),
1322            ),
1323            (
1324                "HELO bar.com",
1325                Ok(Request::Helo {
1326                    host: "bar.com".into(),
1327                }),
1328            ),
1329            (
1330                "HELO",
1331                Err(Error::SyntaxError {
1332                    syntax: "HELO domain",
1333                }),
1334            ),
1335            (
1336                "LHLO bar.com",
1337                Ok(Request::Lhlo {
1338                    host: "bar.com".into(),
1339                }),
1340            ),
1341            (
1342                "LHLO",
1343                Err(Error::SyntaxError {
1344                    syntax: "LHLO domain",
1345                }),
1346            ),
1347            // VRFY
1348            (
1349                "VRFY Hello",
1350                Ok(Request::Vrfy {
1351                    value: "Hello".into(),
1352                }),
1353            ),
1354            (
1355                "VRFY \"Hello\\\" Wo\\\\rld\"",
1356                Ok(Request::Vrfy {
1357                    value: "Hello\" Wo\\rld".into(),
1358                }),
1359            ),
1360            (
1361                "VRFY \"\"",
1362                Err(Error::SyntaxError {
1363                    syntax: "VRFY string",
1364                }),
1365            ),
1366            (
1367                "VRFY",
1368                Err(Error::SyntaxError {
1369                    syntax: "VRFY string",
1370                }),
1371            ),
1372            // EXPN
1373            (
1374                "EXPN Hello",
1375                Ok(Request::Expn {
1376                    value: "Hello".into(),
1377                }),
1378            ),
1379            (
1380                "EXPN \"Hello\\\" Wo\\\\rld\"",
1381                Ok(Request::Expn {
1382                    value: "Hello\" Wo\\rld".into(),
1383                }),
1384            ),
1385            (
1386                "EXPN \"\"",
1387                Err(Error::SyntaxError {
1388                    syntax: "EXPN string",
1389                }),
1390            ),
1391            (
1392                "EXPN",
1393                Err(Error::SyntaxError {
1394                    syntax: "EXPN string",
1395                }),
1396            ),
1397            // NOOP
1398            ("NOOP", Ok(Request::Noop { value: "".into() })),
1399            (
1400                "NOOP Hello",
1401                Ok(Request::Noop {
1402                    value: "Hello".into(),
1403                }),
1404            ),
1405            // HELP
1406            ("HELP", Ok(Request::Help { value: "".into() })),
1407            (
1408                "HELP Hello",
1409                Ok(Request::Help {
1410                    value: "Hello".into(),
1411                }),
1412            ),
1413            // No param commands
1414            ("DATA", Ok(Request::Data)),
1415            ("QUIT", Ok(Request::Quit)),
1416            ("RSET", Ok(Request::Rset)),
1417            ("STARTTLS", Ok(Request::StartTls)),
1418            // BDAT
1419            (
1420                "BDAT 0",
1421                Ok(Request::Bdat {
1422                    chunk_size: 0,
1423                    is_last: false,
1424                }),
1425            ),
1426            (
1427                "BDAT 123456",
1428                Ok(Request::Bdat {
1429                    chunk_size: 123456,
1430                    is_last: false,
1431                }),
1432            ),
1433            (
1434                "BDAT 123456 LAST",
1435                Ok(Request::Bdat {
1436                    chunk_size: 123456,
1437                    is_last: true,
1438                }),
1439            ),
1440            (
1441                "BDAT",
1442                Err(Error::SyntaxError {
1443                    syntax: "BDAT chunk-size [LAST]",
1444                }),
1445            ),
1446            (
1447                "BDAT 123LAST",
1448                Err(Error::SyntaxError {
1449                    syntax: "BDAT chunk-size [LAST]",
1450                }),
1451            ),
1452            (
1453                "BDAT 123x LAST",
1454                Err(Error::SyntaxError {
1455                    syntax: "BDAT chunk-size [LAST]",
1456                }),
1457            ),
1458            (
1459                "BDAT LAST",
1460                Err(Error::SyntaxError {
1461                    syntax: "BDAT chunk-size [LAST]",
1462                }),
1463            ),
1464            // AUTH
1465            (
1466                "AUTH GSSAPI",
1467                Ok(Request::Auth {
1468                    mechanism: AUTH_GSSAPI,
1469                    initial_response: "".into(),
1470                }),
1471            ),
1472            (
1473                "AUTH ECDSA-NIST256P-CHALLENGE =",
1474                Ok(Request::Auth {
1475                    mechanism: AUTH_ECDSA_NIST256P_CHALLENGE,
1476                    initial_response: "=".into(),
1477                }),
1478            ),
1479            (
1480                "AUTH SCRAM-SHA-256-PLUS base64_goes_here",
1481                Ok(Request::Auth {
1482                    mechanism: AUTH_SCRAM_SHA_256_PLUS,
1483                    initial_response: "base64_goes_here".into(),
1484                }),
1485            ),
1486            (
1487                "AUTH ECDSA-NIST256P-CHALLENGE100 abcde",
1488                Ok(Request::Auth {
1489                    mechanism: 0,
1490                    initial_response: "abcde".into(),
1491                }),
1492            ),
1493            (
1494                "AUTH",
1495                Err(Error::SyntaxError {
1496                    syntax: "AUTH mechanism [initial-response]",
1497                }),
1498            ),
1499            // ETRN
1500            (
1501                "ETRN Hello",
1502                Ok(Request::Etrn {
1503                    name: "Hello".into(),
1504                }),
1505            ),
1506            (
1507                "ETRN \"Hello\\\" Wo\\\\rld\"",
1508                Ok(Request::Etrn {
1509                    name: "Hello\" Wo\\rld".into(),
1510                }),
1511            ),
1512            (
1513                "ETRN \"\"",
1514                Err(Error::SyntaxError {
1515                    syntax: "ETRN name",
1516                }),
1517            ),
1518            (
1519                "ETRN",
1520                Err(Error::SyntaxError {
1521                    syntax: "ETRN name",
1522                }),
1523            ),
1524            // ATRN
1525            (
1526                "ATRN example.org",
1527                Ok(Request::Atrn {
1528                    domains: vec!["example.org".into()],
1529                }),
1530            ),
1531            (
1532                "ATRN example.org,example.com,example.net",
1533                Ok(Request::Atrn {
1534                    domains: vec![
1535                        "example.org".into(),
1536                        "example.com".into(),
1537                        "example.net".into(),
1538                    ],
1539                }),
1540            ),
1541            (
1542                "ATRN example.org, example.com, example.net",
1543                Ok(Request::Atrn {
1544                    domains: vec![
1545                        "example.org".into(),
1546                        "example.com".into(),
1547                        "example.net".into(),
1548                    ],
1549                }),
1550            ),
1551            (
1552                "ATRN",
1553                Err(Error::SyntaxError {
1554                    syntax: "ATRN domain[,domain]",
1555                }),
1556            ),
1557            // BURL
1558            (
1559                concat!(
1560                    "BURL imap://harry@gryffindor.example.com/outbox",
1561                    ";uidvalidity=1078863300/;uid=25;urlauth=submit+harry",
1562                    ":internal:91354a473744909de610943775f92038 LAST"
1563                ),
1564                Ok(Request::Burl {
1565                    uri: concat!(
1566                        "imap://harry@gryffindor.example.com/outbox",
1567                        ";uidvalidity=1078863300/;uid=25;urlauth=submit+harry",
1568                        ":internal:91354a473744909de610943775f92038"
1569                    )
1570                    .into(),
1571                    is_last: true,
1572                }),
1573            ),
1574            (
1575                "BURL imap:://test.example.org",
1576                Ok(Request::Burl {
1577                    uri: "imap:://test.example.org".into(),
1578                    is_last: false,
1579                }),
1580            ),
1581            (
1582                "BURL",
1583                Err(Error::SyntaxError {
1584                    syntax: "BURL absolute-uri [LAST]",
1585                }),
1586            ),
1587            // MAIL FROM
1588            (
1589                "MAIL FROM:<JQP@bar.com>",
1590                Ok(Request::Mail {
1591                    from: "JQP@bar.com".into(),
1592                }),
1593            ),
1594            (
1595                "MAIL FROM:<@a,@b:user@d>",
1596                Ok(Request::Mail {
1597                    from: "user@d".into(),
1598                }),
1599            ),
1600            (
1601                "MAIL FROM:<\"@a,@b:<user>\"@d>",
1602                Ok(Request::Mail {
1603                    from: "@a,@b:<user>@d".into(),
1604                }),
1605            ),
1606            (
1607                "MAIL FROM: <\" hi there! \"@d>",
1608                Ok(Request::Mail {
1609                    from: " hi there! @d".into(),
1610                }),
1611            ),
1612            ("MAIL  FROM : <>", Ok(Request::Mail { from: "".into() })),
1613            (
1614                "MAIL  FROM : <postmaster>",
1615                Ok(Request::Mail { from: "".into() }),
1616            ),
1617            ("MAIL  FROM : < >", Ok(Request::Mail { from: "".into() })),
1618            (
1619                "MAIL FROM:<hi.there@valid.org>",
1620                Ok(Request::Mail {
1621                    from: "hi.there@valid.org".into(),
1622                }),
1623            ),
1624            ("MAIL FROM:<@invalid>", Err(Error::InvalidSenderAddress)),
1625            (
1626                "MAIL FROM:<hi@@invalid.org>",
1627                Err(Error::InvalidSenderAddress),
1628            ),
1629            (
1630                "MAIL FROM:<hi..there@invalid.org>",
1631                Err(Error::InvalidSenderAddress),
1632            ),
1633            (
1634                "MAIL FROM:<hi.there@invalid..org>",
1635                Err(Error::InvalidSenderAddress),
1636            ),
1637            (
1638                "MAIL FROM:<hi.there@.invalid.org>",
1639                Err(Error::InvalidSenderAddress),
1640            ),
1641            (
1642                "MAIL FROM:<.hi.there@invalid.org>",
1643                Err(Error::InvalidSenderAddress),
1644            ),
1645            ("MAIL FROM:<@>", Err(Error::InvalidSenderAddress)),
1646            ("MAIL FROM:<.@.>", Err(Error::InvalidSenderAddress)),
1647            (
1648                "RCPT TO:<孫子@áéíóú.org>",
1649                Ok(Request::Rcpt {
1650                    to: "孫子@áéíóú.org".into(),
1651                }),
1652            ),
1653            // RCPT TO
1654            (
1655                "RCPT TO:<Jones@XYZ.COM>",
1656                Ok(Request::Rcpt {
1657                    to: "Jones@XYZ.COM".into(),
1658                }),
1659            ),
1660            ("RCPT TO:<>", Ok(Request::Rcpt { to: "".into() })),
1661            // Invalid commands
1662            ("", Err(Error::UnknownCommand)),
1663            ("X-SPECIAL", Err(Error::UnknownCommand)),
1664            ("DATA_", Err(Error::UnknownCommand)),
1665            // Invalid parameters
1666            (
1667                "MAIL FROM:<> HELLO=WORLD",
1668                Err(Error::UnsupportedParameter {
1669                    param: "HELLO=WORLD".to_string(),
1670                }),
1671            ),
1672            (
1673                "MAIL FROM:<> VERY_LONG_AND_INVALID=PARAM",
1674                Err(Error::UnsupportedParameter {
1675                    param: "VERY_LONG_AND_INVALID=PARAM".to_string(),
1676                }),
1677            ),
1678            (
1679                "MAIL FROM:<> SMTPUTF8=YES",
1680                Err(Error::UnsupportedParameter {
1681                    param: "SMTPUTF8=YES".to_string(),
1682                }),
1683            ),
1684            (
1685                "MAIL FROM:<> SMTPUTF8=YES",
1686                Err(Error::UnsupportedParameter {
1687                    param: "SMTPUTF8=YES".to_string(),
1688                }),
1689            ),
1690            // Garbage after address path (non-alphanumeric chars that don't
1691            // form valid parameter keywords should be rejected, not silently
1692            // consumed)
1693            (
1694                "MAIL FROM:<>>>>>",
1695                Err(Error::SyntaxError {
1696                    syntax: "MAIL FROM:<reverse-path> [parameters]",
1697                }),
1698            ),
1699            (
1700                "MAIL FROM:<a@b.com>>>",
1701                Err(Error::SyntaxError {
1702                    syntax: "MAIL FROM:<reverse-path> [parameters]",
1703                }),
1704            ),
1705            (
1706                "RCPT TO:<>>>>>",
1707                Err(Error::SyntaxError {
1708                    syntax: "RCPT TO:<forward-path> [parameters]",
1709                }),
1710            ),
1711            // Parameters
1712            (
1713                "MAIL FROM:<> SMTPUTF8",
1714                Ok(Request::Mail {
1715                    from: MailFrom {
1716                        address: "".into(),
1717                        flags: MAIL_SMTPUTF8,
1718                        ..Default::default()
1719                    },
1720                }),
1721            ),
1722            (
1723                "MAIL FROM:<> SMTPUTF8 REQUIRETLS CONPERM",
1724                Ok(Request::Mail {
1725                    from: MailFrom {
1726                        address: "".into(),
1727                        flags: MAIL_SMTPUTF8 | MAIL_REQUIRETLS | MAIL_CONPERM,
1728                        ..Default::default()
1729                    },
1730                }),
1731            ),
1732            (
1733                "RCPT TO:<> CONNEG",
1734                Ok(Request::Rcpt {
1735                    to: RcptTo {
1736                        address: "".into(),
1737                        flags: RCPT_CONNEG,
1738                        ..Default::default()
1739                    },
1740                }),
1741            ),
1742            (
1743                "MAIL FROM:<> BODY=BINARYMIME BODY=7BIT BODY=8BITMIME",
1744                Ok(Request::Mail {
1745                    from: MailFrom {
1746                        address: "".into(),
1747                        flags: MAIL_BODY_7BIT | MAIL_BODY_8BITMIME | MAIL_BODY_BINARYMIME,
1748                        ..Default::default()
1749                    },
1750                }),
1751            ),
1752            (
1753                "MAIL FROM:<> BODY=OTHER",
1754                Err(Error::InvalidParameter { param: "BODY" }),
1755            ),
1756            (
1757                "MAIL FROM:<> SIZE=500000",
1758                Ok(Request::Mail {
1759                    from: MailFrom {
1760                        address: "".into(),
1761                        size: 500000,
1762                        ..Default::default()
1763                    },
1764                }),
1765            ),
1766            (
1767                "MAIL FROM:<> SIZE=ABC",
1768                Err(Error::InvalidParameter { param: "SIZE" }),
1769            ),
1770            (
1771                "MAIL FROM:<> SIZE=-100",
1772                Err(Error::InvalidParameter { param: "SIZE" }),
1773            ),
1774            (
1775                "MAIL FROM:<> SIZE=",
1776                Err(Error::InvalidParameter { param: "SIZE" }),
1777            ),
1778            (
1779                "MAIL FROM:<> BY=120;R",
1780                Ok(Request::Mail {
1781                    from: MailFrom {
1782                        address: "".into(),
1783                        by: 120,
1784                        flags: MAIL_BY_RETURN,
1785                        ..Default::default()
1786                    },
1787                }),
1788            ),
1789            (
1790                "MAIL FROM:<> BY=0;N",
1791                Ok(Request::Mail {
1792                    from: MailFrom {
1793                        address: "".into(),
1794                        by: 0,
1795                        flags: MAIL_BY_NOTIFY,
1796                        ..Default::default()
1797                    },
1798                }),
1799            ),
1800            (
1801                "MAIL FROM:<> BY=-10;RT",
1802                Ok(Request::Mail {
1803                    from: MailFrom {
1804                        address: "".into(),
1805                        by: -10,
1806                        flags: MAIL_BY_RETURN | MAIL_BY_TRACE,
1807                        ..Default::default()
1808                    },
1809                }),
1810            ),
1811            (
1812                "MAIL FROM:<> BY=+22;NT",
1813                Ok(Request::Mail {
1814                    from: MailFrom {
1815                        address: "".into(),
1816                        by: 22,
1817                        flags: MAIL_BY_NOTIFY | MAIL_BY_TRACE,
1818                        ..Default::default()
1819                    },
1820                }),
1821            ),
1822            (
1823                "MAIL FROM:<> BY=120",
1824                Err(Error::InvalidParameter { param: "BY" }),
1825            ),
1826            (
1827                "MAIL FROM:<> BY=120;T",
1828                Err(Error::InvalidParameter { param: "BY" }),
1829            ),
1830            (
1831                "MAIL FROM:<> BY=120;",
1832                Err(Error::InvalidParameter { param: "BY" }),
1833            ),
1834            (
1835                "MAIL FROM:<> BY=120;0",
1836                Err(Error::InvalidParameter { param: "BY" }),
1837            ),
1838            (
1839                "MAIL FROM:<> BY=120;;",
1840                Err(Error::InvalidParameter { param: "BY" }),
1841            ),
1842            (
1843                "MAIL FROM:<> BY=;",
1844                Err(Error::InvalidParameter { param: "BY" }),
1845            ),
1846            (
1847                "MAIL FROM:<> BY=;R",
1848                Err(Error::InvalidParameter { param: "BY" }),
1849            ),
1850            (
1851                "MAIL FROM:<> BY=",
1852                Err(Error::InvalidParameter { param: "BY" }),
1853            ),
1854            (
1855                "MAIL FROM:<> HOLDUNTIL=12345 HOLDFOR=67890",
1856                Ok(Request::Mail {
1857                    from: MailFrom {
1858                        address: "".into(),
1859                        hold_for: 67890,
1860                        hold_until: 12345,
1861                        ..Default::default()
1862                    },
1863                }),
1864            ),
1865            (
1866                "MAIL FROM:<> HOLDUNTIL=0ABC",
1867                Err(Error::InvalidParameter { param: "HOLDUNTIL" }),
1868            ),
1869            (
1870                "MAIL FROM:<> HOLDUNTIL=",
1871                Err(Error::InvalidParameter { param: "HOLDUNTIL" }),
1872            ),
1873            (
1874                "MAIL FROM:<> HOLDFOR=XYZ",
1875                Err(Error::InvalidParameter { param: "HOLDFOR" }),
1876            ),
1877            (
1878                "MAIL FROM:<> HOLDFOR=",
1879                Err(Error::InvalidParameter { param: "HOLDFOR" }),
1880            ),
1881            (
1882                "RCPT TO:<> NOTIFY=FAILURE",
1883                Ok(Request::Rcpt {
1884                    to: RcptTo {
1885                        address: "".into(),
1886                        flags: RCPT_NOTIFY_FAILURE,
1887                        ..Default::default()
1888                    },
1889                }),
1890            ),
1891            (
1892                "RCPT TO:<> NOTIFY=FAILURE,DELAY",
1893                Ok(Request::Rcpt {
1894                    to: RcptTo {
1895                        address: "".into(),
1896                        flags: RCPT_NOTIFY_FAILURE | RCPT_NOTIFY_DELAY,
1897                        ..Default::default()
1898                    },
1899                }),
1900            ),
1901            (
1902                "RCPT TO:<> NOTIFY=SUCCESS,FAILURE,DELAY",
1903                Ok(Request::Rcpt {
1904                    to: RcptTo {
1905                        address: "".into(),
1906                        flags: RCPT_NOTIFY_FAILURE | RCPT_NOTIFY_DELAY | RCPT_NOTIFY_SUCCESS,
1907                        ..Default::default()
1908                    },
1909                }),
1910            ),
1911            (
1912                "RCPT TO:<> NOTIFY=NEVER",
1913                Ok(Request::Rcpt {
1914                    to: RcptTo {
1915                        address: "".into(),
1916                        flags: RCPT_NOTIFY_NEVER,
1917                        ..Default::default()
1918                    },
1919                }),
1920            ),
1921            (
1922                "RCPT TO:<> NOTIFY=",
1923                Err(Error::InvalidParameter { param: "NOTIFY" }),
1924            ),
1925            (
1926                "RCPT TO:<> NOTIFY=FAILURE,NEVER",
1927                Err(Error::InvalidParameter { param: "NOTIFY" }),
1928            ),
1929            (
1930                "RCPT TO:<> NOTIFY=CHIMICHANGA",
1931                Err(Error::InvalidParameter { param: "NOTIFY" }),
1932            ),
1933            (
1934                "RCPT TO:<> ORCPT=rfc822;Bob@Example.COM",
1935                Ok(Request::Rcpt {
1936                    to: RcptTo {
1937                        address: "".into(),
1938                        orcpt: Some("Bob@Example.COM".into()),
1939                        ..Default::default()
1940                    },
1941                }),
1942            ),
1943            (
1944                concat!("RCPT TO:<> ", "ORCPT=rfc822;George+20@Tax-+20ME+20.GOV"),
1945                Ok(Request::Rcpt {
1946                    to: RcptTo {
1947                        address: "".into(),
1948                        orcpt: Some("George @Tax- ME .GOV".into()),
1949                        ..Default::default()
1950                    },
1951                }),
1952            ),
1953            (
1954                "RCPT TO:<> ORCPT=",
1955                Err(Error::InvalidParameter { param: "ORCPT" }),
1956            ),
1957            (
1958                "RCPT TO:<> ORCPT=;hello@domain.org",
1959                Err(Error::InvalidParameter { param: "ORCPT" }),
1960            ),
1961            (
1962                "RCPT TO:<> ORCPT=rfc822;",
1963                Err(Error::InvalidParameter { param: "ORCPT" }),
1964            ),
1965            (
1966                "RCPT TO:<> ORCPT=;",
1967                Err(Error::InvalidParameter { param: "ORCPT" }),
1968            ),
1969            (
1970                "MAIL FROM:<> RET=HDRS RET=FULL",
1971                Ok(Request::Mail {
1972                    from: MailFrom {
1973                        address: "".into(),
1974                        flags: MAIL_RET_FULL | MAIL_RET_HDRS,
1975                        ..Default::default()
1976                    },
1977                }),
1978            ),
1979            (
1980                "MAIL FROM:<> RET=",
1981                Err(Error::InvalidParameter { param: "RET" }),
1982            ),
1983            (
1984                "MAIL FROM:<> RET=ENCHILADA",
1985                Err(Error::InvalidParameter { param: "RET" }),
1986            ),
1987            (
1988                "MAIL FROM:<> ENVID=QQ314159",
1989                Ok(Request::Mail {
1990                    from: MailFrom {
1991                        address: "".into(),
1992                        env_id: Some("QQ314159".into()),
1993                        ..Default::default()
1994                    },
1995                }),
1996            ),
1997            (
1998                "MAIL FROM:<> ENVID=hi+20there",
1999                Ok(Request::Mail {
2000                    from: MailFrom {
2001                        address: "".into(),
2002                        env_id: Some("hi there".into()),
2003                        ..Default::default()
2004                    },
2005                }),
2006            ),
2007            (
2008                "MAIL FROM:<> ENVID=",
2009                Err(Error::InvalidParameter { param: "ENVID" }),
2010            ),
2011            (
2012                "MAIL FROM:<> SOLICIT=org.example:ADV:ADLT",
2013                Ok(Request::Mail {
2014                    from: MailFrom {
2015                        address: "".into(),
2016                        solicit: Some("org.example:ADV:ADLT".into()),
2017                        ..Default::default()
2018                    },
2019                }),
2020            ),
2021            (
2022                concat!(
2023                    "MAIL FROM:<> ",
2024                    " SOLICIT=net.example:ADV,org.example:ADV:ADLT"
2025                ),
2026                Ok(Request::Mail {
2027                    from: MailFrom {
2028                        address: "".into(),
2029                        solicit: Some("net.example:ADV,org.example:ADV:ADLT".into()),
2030                        ..Default::default()
2031                    },
2032                }),
2033            ),
2034            (
2035                "MAIL FROM:<> SOLICIT=",
2036                Err(Error::InvalidParameter { param: "SOLICIT" }),
2037            ),
2038            (
2039                "MAIL FROM:<> TRANSID=<12345@claremont.edu>",
2040                Ok(Request::Mail {
2041                    from: MailFrom {
2042                        address: "".into(),
2043                        trans_id: Some("12345@claremont.edu".into()),
2044                        ..Default::default()
2045                    },
2046                }),
2047            ),
2048            (
2049                "MAIL FROM:<> TRANSID=",
2050                Err(Error::InvalidParameter { param: "TRANSID" }),
2051            ),
2052            (
2053                "MAIL FROM:<> MTRK=my-ceritifier",
2054                Ok(Request::Mail {
2055                    from: MailFrom {
2056                        address: "".into(),
2057                        mtrk: Mtrk {
2058                            certifier: "my-ceritifier".into(),
2059                            timeout: 0,
2060                        }
2061                        .into(),
2062                        ..Default::default()
2063                    },
2064                }),
2065            ),
2066            (
2067                "MAIL FROM:<> MTRK=other-certifier:1234",
2068                Ok(Request::Mail {
2069                    from: MailFrom {
2070                        address: "".into(),
2071                        mtrk: Mtrk {
2072                            certifier: "other-certifier".into(),
2073                            timeout: 1234,
2074                        }
2075                        .into(),
2076                        ..Default::default()
2077                    },
2078                }),
2079            ),
2080            (
2081                "MAIL FROM:<> MTRK=",
2082                Err(Error::InvalidParameter { param: "MTRK" }),
2083            ),
2084            (
2085                "MAIL FROM:<> MTRK=:",
2086                Err(Error::InvalidParameter { param: "MTRK" }),
2087            ),
2088            (
2089                "MAIL FROM:<> MTRK=:998",
2090                Err(Error::InvalidParameter { param: "MTRK" }),
2091            ),
2092            (
2093                "MAIL FROM:<> MTRK=abc:",
2094                Err(Error::InvalidParameter { param: "MTRK" }),
2095            ),
2096            (
2097                "MAIL FROM:<> MTRK=abc:abc",
2098                Err(Error::InvalidParameter { param: "MTRK" }),
2099            ),
2100            (
2101                "MAIL FROM:<> AUTH=<>",
2102                Ok(Request::Mail {
2103                    from: MailFrom {
2104                        address: "".into(),
2105                        auth: Some("<>".into()),
2106                        ..Default::default()
2107                    },
2108                }),
2109            ),
2110            (
2111                "MAIL FROM:<> AUTH=e+3Dmc2@example.com",
2112                Ok(Request::Mail {
2113                    from: MailFrom {
2114                        address: "".into(),
2115                        auth: Some("e=mc2@example.com".into()),
2116                        ..Default::default()
2117                    },
2118                }),
2119            ),
2120            (
2121                "MAIL FROM:<> AUTH=",
2122                Err(Error::InvalidParameter { param: "AUTH" }),
2123            ),
2124            (
2125                "MAIL FROM:<> MT-PRIORITY=3",
2126                Ok(Request::Mail {
2127                    from: MailFrom {
2128                        address: "".into(),
2129                        mt_priority: 3,
2130                        ..Default::default()
2131                    },
2132                }),
2133            ),
2134            (
2135                "MAIL FROM:<> MT-PRIORITY=-6",
2136                Ok(Request::Mail {
2137                    from: MailFrom {
2138                        address: "".into(),
2139                        mt_priority: -6,
2140                        ..Default::default()
2141                    },
2142                }),
2143            ),
2144            (
2145                "MAIL FROM:<> MT-PRIORITY=",
2146                Err(Error::InvalidParameter {
2147                    param: "MT-PRIORITY",
2148                }),
2149            ),
2150            (
2151                "MAIL FROM:<> MT-PRIORITY=ab",
2152                Err(Error::InvalidParameter {
2153                    param: "MT-PRIORITY",
2154                }),
2155            ),
2156            (
2157                "MAIL FROM:<> MT-PRIORITY=-",
2158                Err(Error::InvalidParameter {
2159                    param: "MT-PRIORITY",
2160                }),
2161            ),
2162            (
2163                "RCPT TO:<> RRVS=2014-04-03T23:01:00Z",
2164                Ok(Request::Rcpt {
2165                    to: RcptTo {
2166                        address: "".into(),
2167                        rrvs: 1396566060,
2168                        flags: RCPT_RRVS_REJECT,
2169                        ..Default::default()
2170                    },
2171                }),
2172            ),
2173            (
2174                "RCPT TO:<> RRVS=1997-11-24T14:22:01-08:00;C",
2175                Ok(Request::Rcpt {
2176                    to: RcptTo {
2177                        address: "".into(),
2178                        rrvs: 880410121,
2179                        flags: RCPT_RRVS_CONTINUE,
2180                        ..Default::default()
2181                    },
2182                }),
2183            ),
2184            (
2185                "RCPT TO:<> RRVS=2003-07-01T10:52:37+02:00;R",
2186                Ok(Request::Rcpt {
2187                    to: RcptTo {
2188                        address: "".into(),
2189                        rrvs: 1057049557,
2190                        flags: RCPT_RRVS_REJECT,
2191                        ..Default::default()
2192                    },
2193                }),
2194            ),
2195            (
2196                "RCPT TO:<> RRVS=",
2197                Err(Error::InvalidParameter { param: "RRVS" }),
2198            ),
2199            (
2200                "RCPT TO:<> RRVS=2022-01-02",
2201                Err(Error::InvalidParameter { param: "RRVS" }),
2202            ),
2203            (
2204                "RCPT TO:<> RRVS=2022-01-02T01:01:01",
2205                Err(Error::InvalidParameter { param: "RRVS" }),
2206            ),
2207            (
2208                "RCPT TO:<> RRVS=2022-01-02T01:01:01ZZ",
2209                Err(Error::InvalidParameter { param: "RRVS" }),
2210            ),
2211            (
2212                "RCPT TO:<> RRVS=ABC",
2213                Err(Error::InvalidParameter { param: "RRVS" }),
2214            ),
2215            (
2216                r#"MAIL FROM:<foo@bar"aa\\" hello world".com>"#,
2217                Err(Error::SyntaxError {
2218                    syntax: "MAIL FROM:<reverse-path> [parameters]",
2219                }),
2220            ),
2221            (
2222                r#"MAIL FROM:<foo@bar"aa\" hello world".com>"#,
2223                Ok(Request::Mail {
2224                    from: r#"foo@baraa" hello world.com"#.into(),
2225                }),
2226            ),
2227        ] {
2228            let (request, parsed_request): (&str, Result<Request<Cow<'_, str>>, Error>) = item;
2229
2230            for extra in ["\n", "\r\n", " \n", " \r\n"] {
2231                let request = format!("{request}{extra}");
2232                assert_eq!(
2233                    parsed_request,
2234                    Request::parse(&mut request.as_bytes().iter()),
2235                    "failed for {request:?}"
2236                );
2237            }
2238        }
2239    }
2240
2241    #[test]
2242    fn request_name() {
2243        assert_eq!(Request::<String>::Quit.name(), "QUIT");
2244        assert_eq!(Request::<String>::Data.name(), "DATA");
2245        assert_eq!(Request::<String>::Rset.name(), "RSET");
2246        assert_eq!(Request::<String>::StartTls.name(), "STARTTLS");
2247        assert_eq!(
2248            Request::Ehlo::<Cow<'_, str>> {
2249                host: "test.com".into()
2250            }
2251            .name(),
2252            "EHLO"
2253        );
2254        assert_eq!(
2255            Request::Helo::<Cow<'_, str>> {
2256                host: "test.com".into()
2257            }
2258            .name(),
2259            "HELO"
2260        );
2261        assert_eq!(
2262            Request::Mail {
2263                from: MailFrom::<Cow<'_, str>>::default()
2264            }
2265            .name(),
2266            "MAIL"
2267        );
2268        assert_eq!(
2269            Request::Rcpt {
2270                to: RcptTo::<Cow<'_, str>>::default()
2271            }
2272            .name(),
2273            "RCPT"
2274        );
2275    }
2276
2277    impl<'a> From<&'a str> for MailFrom<Cow<'a, str>> {
2278        fn from(value: &'a str) -> Self {
2279            Self {
2280                address: value.into(),
2281                ..Default::default()
2282            }
2283        }
2284    }
2285
2286    impl<'a> From<&'a str> for RcptTo<Cow<'a, str>> {
2287        fn from(value: &'a str) -> Self {
2288            Self {
2289                address: value.into(),
2290                ..Default::default()
2291            }
2292        }
2293    }
2294}