mail_auth/mta_sts/
parse.rs1use super::{MtaSts, ReportUri, TlsRpt};
8use crate::common::parse::{TagParser, TxtRecordParser, V};
9
10const ID: u64 = (b'i' as u64) | ((b'd' as u64) << 8);
11const RUA: u64 = (b'r' as u64) | ((b'u' as u64) << 8) | ((b'a' as u64) << 16);
12
13const MAILTO: u64 = (b'm' as u64)
14 | ((b'a' as u64) << 8)
15 | ((b'i' as u64) << 16)
16 | ((b'l' as u64) << 24)
17 | ((b't' as u64) << 32)
18 | ((b'o' as u64) << 40);
19const HTTPS: u64 = (b'h' as u64)
20 | ((b't' as u64) << 8)
21 | ((b't' as u64) << 16)
22 | ((b'p' as u64) << 24)
23 | ((b's' as u64) << 32);
24
25impl TxtRecordParser for MtaSts {
26 #[allow(clippy::while_let_on_iterator)]
27 fn parse(record: &[u8]) -> crate::Result<Self> {
28 let mut record = record.iter();
29 let mut id = None;
30 let mut has_version = false;
31
32 while let Some(key) = record.key() {
33 match key {
34 V => {
35 if !record.match_bytes(b"STSv1") || !record.seek_tag_end() {
36 return Err(crate::Error::InvalidRecordType);
37 }
38 has_version = true;
39 }
40 ID => {
41 id = record.text(false).into();
42 }
43 _ => {
44 record.ignore();
45 }
46 }
47 }
48
49 if let Some(id) = id
50 && has_version
51 {
52 return Ok(MtaSts { id });
53 }
54 Err(crate::Error::InvalidRecordType)
55 }
56}
57
58impl TxtRecordParser for TlsRpt {
59 #[allow(clippy::while_let_on_iterator)]
60 fn parse(record: &[u8]) -> crate::Result<Self> {
61 let mut record = record.iter();
62
63 if record.key().unwrap_or(0) != V
64 || !record.match_bytes(b"TLSRPTv1")
65 || !record.seek_tag_end()
66 {
67 return Err(crate::Error::InvalidRecordType);
68 }
69
70 let mut rua = Vec::new();
71
72 while let Some(key) = record.key() {
73 match key {
74 RUA => loop {
75 match record.flag_value() {
76 (MAILTO, b':') => {
77 let mail_to = record.text_qp(Vec::with_capacity(20), false, true);
78 if !mail_to.is_empty() {
79 rua.push(ReportUri::Mail(mail_to));
80 }
81 }
82 (HTTPS, b':') => {
83 let mut url = Vec::with_capacity(20);
84 url.extend_from_slice(b"https:");
85 let url = record.text_qp(url, false, true);
86 if !url.is_empty() {
87 rua.push(ReportUri::Http(url));
88 }
89 }
90 _ => {
91 record.ignore();
92 break;
93 }
94 }
95 },
96 _ => {
97 record.ignore();
98 }
99 }
100 }
101
102 if !rua.is_empty() {
103 Ok(TlsRpt { rua })
104 } else {
105 Err(crate::Error::InvalidRecordType)
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use crate::{
113 common::parse::TxtRecordParser,
114 mta_sts::{MtaSts, ReportUri, TlsRpt},
115 };
116
117 #[test]
118 fn mta_sts_record_parse() {
119 for (mta_sts, expected_mta_sts) in [
120 (
121 "v=STSv1; id=20160831085700Z;",
122 MtaSts {
123 id: "20160831085700Z".to_string(),
124 },
125 ),
126 (
127 "v=STSv1; id=20190429T010101",
128 MtaSts {
129 id: "20190429T010101".to_string(),
130 },
131 ),
132 ] {
133 assert_eq!(MtaSts::parse(mta_sts.as_bytes()).unwrap(), expected_mta_sts);
134 }
135 }
136
137 #[test]
138 fn tlsrpt_parse() {
139 for (tls_rpt, expected_tls_rpt) in [
140 (
141 "v=TLSRPTv1;rua=mailto:reports@example.com",
142 TlsRpt {
143 rua: vec![ReportUri::Mail("reports@example.com".to_string())],
144 },
145 ),
146 (
147 "v=TLSRPTv1; rua=https://reporting.example.com/v1/tlsrpt",
148 TlsRpt {
149 rua: vec![ReportUri::Http(
150 "https://reporting.example.com/v1/tlsrpt".to_string(),
151 )],
152 },
153 ),
154 (
155 "v=TLSRPTv1; rua=mailto:tlsrpt@mydomain.com,https://tlsrpt.mydomain.com/v1",
156 TlsRpt {
157 rua: vec![
158 ReportUri::Mail("tlsrpt@mydomain.com".to_string()),
159 ReportUri::Http("https://tlsrpt.mydomain.com/v1".to_string()),
160 ],
161 },
162 ),
163 ] {
164 assert_eq!(TlsRpt::parse(tls_rpt.as_bytes()).unwrap(), expected_tls_rpt);
165 }
166 }
167}