1#![allow(non_snake_case)]
2
3use std::{borrow::Cow, str::from_utf8};
4
5use abnf_core::streaming::{is_ALPHA, is_DIGIT, DQUOTE};
6use nom::{
7 branch::alt,
8 bytes::streaming::{tag, take_while, take_while1, take_while_m_n},
9 character::streaming::digit1,
10 combinator::{map, map_res, opt, recognize},
11 multi::{many0, separated_list1},
12 sequence::{delimited, tuple},
13 IResult,
14};
15
16use crate::{parse::imf::atom::is_atext, types::AtomOrQuoted, utils::unescape_quoted};
17
18pub mod address;
19pub mod command;
20pub mod imf;
21pub mod response;
22pub mod trace;
23pub mod utils;
24
25pub fn base64(input: &[u8]) -> IResult<&[u8], &str> {
26 let mut parser = map_res(
27 recognize(tuple((
28 take_while(is_base64_char),
29 opt(alt((tag("=="), tag("=")))),
30 ))),
31 from_utf8,
32 );
33
34 let (remaining, base64) = parser(input)?;
35
36 Ok((remaining, base64))
37}
38
39fn is_base64_char(i: u8) -> bool {
40 is_ALPHA(i) || is_DIGIT(i) || i == b'+' || i == b'/'
41}
42
43pub fn number(input: &[u8]) -> IResult<&[u8], u32> {
44 map_res(map_res(digit1, from_utf8), str::parse::<u32>)(input) }
46
47pub fn String(input: &[u8]) -> IResult<&[u8], AtomOrQuoted> {
51 alt((
52 map(Atom, |atom| AtomOrQuoted::Atom(atom.into())),
53 map(Quoted_string, |quoted| AtomOrQuoted::Quoted(quoted.into())),
54 ))(input)
55}
56
57pub fn Atom(input: &[u8]) -> IResult<&[u8], &str> {
59 map_res(take_while1(is_atext), std::str::from_utf8)(input)
60}
61
62pub fn Quoted_string(input: &[u8]) -> IResult<&[u8], Cow<'_, str>> {
64 map(
65 delimited(
66 DQUOTE,
67 map_res(recognize(many0(QcontentSMTP)), std::str::from_utf8),
68 DQUOTE,
69 ),
70 |s| unescape_quoted(s),
71 )(input)
72}
73
74pub fn QcontentSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
76 let parser = alt((take_while_m_n(1, 1, is_qtextSMTP), quoted_pairSMTP));
77
78 let (remaining, parsed) = recognize(parser)(input)?;
79
80 Ok((remaining, parsed))
81}
82
83pub fn is_qtextSMTP(byte: u8) -> bool {
88 matches!(byte, 32..=33 | 35..=91 | 93..=126)
89}
90
91pub fn quoted_pairSMTP(input: &[u8]) -> IResult<&[u8], &[u8]> {
97 fn is_value(byte: u8) -> bool {
103 byte == b'\\' || byte == b'\"'
104 }
105
106 let parser = tuple((tag("\\"), take_while_m_n(1, 1, is_value)));
107
108 let (remaining, parsed) = recognize(parser)(input)?;
109
110 Ok((remaining, parsed))
111}
112
113pub fn Domain(input: &[u8]) -> IResult<&[u8], &str> {
117 let parser = separated_list1(tag(b"."), sub_domain);
118
119 let (remaining, parsed) = map_res(recognize(parser), std::str::from_utf8)(input)?;
120
121 Ok((remaining, parsed))
122}
123
124pub fn sub_domain(input: &[u8]) -> IResult<&[u8], &[u8]> {
126 let parser = tuple((take_while_m_n(1, 1, is_Let_dig), opt(Ldh_str)));
127
128 let (remaining, parsed) = recognize(parser)(input)?;
129
130 Ok((remaining, parsed))
131}
132
133pub fn is_Let_dig(byte: u8) -> bool {
135 is_ALPHA(byte) || is_DIGIT(byte)
136}
137
138pub fn Ldh_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
140 let parser = many0(alt((
141 take_while_m_n(1, 1, is_ALPHA),
142 take_while_m_n(1, 1, is_DIGIT),
143 recognize(tuple((tag(b"-"), take_while_m_n(1, 1, is_Let_dig)))),
144 )));
145
146 let (remaining, parsed) = recognize(parser)(input)?;
147
148 Ok((remaining, parsed))
149}
150
151#[cfg(test)]
154pub mod test {
155 use super::sub_domain;
156
157 #[test]
158 fn test_subdomain() {
159 let (rem, parsed) = sub_domain(b"example???").unwrap();
160 assert_eq!(parsed, b"example");
161 assert_eq!(rem, b"???");
162 }
163}