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