Commit
Author: Mauro D [mauro@stalw.art]
Hash: c06c39477594936503efebcf28bc1a9c1d3778eb
Timestamp: Sat, 14 Jan 2023 17:21:29 +0000 (1 year ago)

+120 -41 +/-6 browse
Base32 reader implementation.
1diff --git a/src/common/base32.rs b/src/common/base32.rs
2index f73cacb..a53b2c9 100644
3--- a/src/common/base32.rs
4+++ b/src/common/base32.rs
5 @@ -8,9 +8,26 @@
6 * except according to those terms.
7 */
8
9+ use std::slice::Iter;
10+
11 use super::headers::Writer;
12
13 pub(crate) static BASE32_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
14+ pub static BASE32_INVERSE: [u8; 256] = [
15+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
16+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
17+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 255, 255,
18+ 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
19+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
20+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255,
21+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
22+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
23+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
24+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
25+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
26+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
27+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
28+ ];
29
30 pub struct Base32Writer {
31 last_byte: u8,
32 @@ -82,10 +99,60 @@ impl Writer for Base32Writer {
33 }
34 }
35
36+ #[derive(Debug)]
37+ pub struct Base32Reader<'x> {
38+ bytes: Iter<'x, u8>,
39+ last_byte: u8,
40+ pos: usize,
41+ }
42+
43+ impl<'x> Base32Reader<'x> {
44+ pub fn new(bytes: &'x [u8]) -> Self {
45+ Base32Reader {
46+ bytes: bytes.iter(),
47+ pos: 0,
48+ last_byte: 0,
49+ }
50+ }
51+
52+ #[inline(always)]
53+ fn map_byte(&mut self) -> Option<u8> {
54+ match self.bytes.next() {
55+ Some(&byte) => match BASE32_INVERSE[byte as usize] {
56+ byte if byte != u8::MAX => {
57+ self.last_byte = byte;
58+ Some(byte)
59+ }
60+ _ => None,
61+ },
62+ _ => None,
63+ }
64+ }
65+ }
66+
67+ impl Iterator for Base32Reader<'_> {
68+ type Item = u8;
69+ fn next(&mut self) -> Option<Self::Item> {
70+ let pos = self.pos % 5;
71+ let last_byte = self.last_byte;
72+ let byte = self.map_byte()?;
73+ self.pos += 1;
74+
75+ match pos {
76+ 0 => ((byte << 3) | (self.map_byte().unwrap_or(0) >> 2)).into(),
77+ 1 => ((last_byte << 6) | (byte << 1) | (self.map_byte().unwrap_or(0) >> 4)).into(),
78+ 2 => ((last_byte << 4) | (byte >> 1)).into(),
79+ 3 => ((last_byte << 7) | (byte << 2) | (self.map_byte().unwrap_or(0) >> 3)).into(),
80+ 4 => ((last_byte << 5) | byte).into(),
81+ _ => None,
82+ }
83+ }
84+ }
85+
86 #[cfg(test)]
87 mod tests {
88 use crate::common::{
89- base32::Base32Writer,
90+ base32::{Base32Reader, Base32Writer},
91 crypto::{HashContext, HashImpl, Sha1},
92 headers::Writer,
93 };
94 @@ -96,11 +163,20 @@ mod tests {
95 ("one.example.net", "QSP4I4D24CRHOPDZ3O3ZIU2KSGS3X6Z6"),
96 ("two.example.net", "ZTZGRRV3F45A4U6HLDKBF3ZCOW4V2AJX"),
97 ] {
98+ // Encode
99 let mut writer = Base32Writer::with_capacity(10);
100 let mut hash = Sha1::hasher();
101 hash.write(test.as_bytes());
102- writer.write(hash.complete().as_ref());
103+ let hash = hash.complete();
104+ writer.write(hash.as_ref());
105 assert_eq!(writer.finalize(), expected_result);
106+
107+ // Decode
108+ let mut original = Vec::new();
109+ for byte in Base32Reader::new(expected_result.as_bytes()) {
110+ original.push(byte);
111+ }
112+ assert_eq!(original, hash.as_ref());
113 }
114 }
115 }
116 diff --git a/src/lib.rs b/src/lib.rs
117index 7b32ac8..9f54172 100644
118--- a/src/lib.rs
119+++ b/src/lib.rs
120 @@ -282,6 +282,7 @@ pub mod mta_sts;
121 pub mod report;
122 pub mod spf;
123
124+ pub use flate2;
125 pub use sha1;
126 pub use sha2;
127 pub use trust_dns_resolver;
128 diff --git a/src/report/arf/generate.rs b/src/report/arf/generate.rs
129index 01b99ec..a3c126c 100644
130--- a/src/report/arf/generate.rs
131+++ b/src/report/arf/generate.rs
132 @@ -11,7 +11,7 @@
133 use std::{fmt::Write, io, time::SystemTime};
134
135 use mail_builder::{
136- headers::{content_type::ContentType, HeaderType},
137+ headers::{address::Address, content_type::ContentType, HeaderType},
138 mime::{make_boundary, BodyPart, MimePart},
139 MessageBuilder,
140 };
141 @@ -22,8 +22,7 @@ use crate::report::{AuthFailureType, DeliveryResult, Feedback, FeedbackType, Ide
142 impl<'x> Feedback<'x> {
143 pub fn write_rfc5322(
144 &self,
145- from_name: &'x str,
146- from_addr: &'x str,
147+ from: impl Into<Address<'x>>,
148 to: &'x str,
149 subject: &'x str,
150 writer: impl io::Write,
151 @@ -90,7 +89,7 @@ impl<'x> Feedback<'x> {
152 }
153
154 MessageBuilder::new()
155- .from((from_name, from_addr))
156+ .from(from)
157 .header("To", HeaderType::Text(to.into()))
158 .header("Auto-Submitted", HeaderType::Text("auto-generated".into()))
159 .message_id(format!(
160 @@ -108,13 +107,12 @@ impl<'x> Feedback<'x> {
161
162 pub fn to_rfc5322(
163 &self,
164- from_name: &str,
165- from_addr: &str,
166- to: &str,
167- subject: &str,
168+ from: impl Into<Address<'x>>,
169+ to: &'x str,
170+ subject: &'x str,
171 ) -> io::Result<String> {
172 let mut buf = Vec::new();
173- self.write_rfc5322(from_name, from_addr, to, subject, &mut buf)?;
174+ self.write_rfc5322(from, to, subject, &mut buf)?;
175 String::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
176 }
177
178 @@ -287,8 +285,7 @@ mod test {
179
180 let message = feedback
181 .to_rfc5322(
182- "DMARC Reporter",
183- "no-reply@example.org",
184+ ("DMARC Reporter", "no-reply@example.org"),
185 "ruf@otherdomain.com",
186 "DMARC Authentication Failure Report",
187 )
188 diff --git a/src/report/dmarc/generate.rs b/src/report/dmarc/generate.rs
189index 69d23dd..9824b12 100644
190--- a/src/report/dmarc/generate.rs
191+++ b/src/report/dmarc/generate.rs
192 @@ -31,8 +31,7 @@ impl Report {
193 pub fn write_rfc5322<'x>(
194 &self,
195 submitter: &'x str,
196- from_name: &'x str,
197- from_addr: &'x str,
198+ from: impl Into<Address<'x>>,
199 to: impl Iterator<Item = &'x str>,
200 writer: impl io::Write,
201 ) -> io::Result<()> {
202 @@ -43,7 +42,7 @@ impl Report {
203 let compressed_bytes = e.finish()?;
204
205 MessageBuilder::new()
206- .from((from_name, from_addr))
207+ .from(from)
208 .header(
209 "To",
210 HeaderType::Address(Address::List(to.map(|to| (*to).into()).collect())),
211 @@ -85,12 +84,11 @@ impl Report {
212 pub fn to_rfc5322<'x>(
213 &self,
214 submitter: &'x str,
215- from_name: &'x str,
216- from_addr: &'x str,
217+ from: impl Into<Address<'x>>,
218 to: impl Iterator<Item = &'x str>,
219 ) -> io::Result<String> {
220 let mut buf = Vec::new();
221- self.write_rfc5322(submitter, from_name, from_addr, to, &mut buf)?;
222+ self.write_rfc5322(submitter, from, to, &mut buf)?;
223 String::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
224 }
225
226 @@ -524,9 +522,8 @@ mod test {
227 let message = report
228 .to_rfc5322(
229 "initech.net",
230- "Initech Industries",
231- "noreply-dmarc@initech.net",
232- &["dmarc-reports@example.org"],
233+ ("Initech Industries", "noreply-dmarc@initech.net"),
234+ ["dmarc-reports@example.org"].iter().copied(),
235 )
236 .unwrap();
237 let parsed_report = Report::parse_rfc5322(message.as_bytes()).unwrap();
238 diff --git a/src/report/tlsrpt/generate.rs b/src/report/tlsrpt/generate.rs
239index 62a395a..f3be0af 100644
240--- a/src/report/tlsrpt/generate.rs
241+++ b/src/report/tlsrpt/generate.rs
242 @@ -24,22 +24,32 @@ impl TlsReport {
243 &self,
244 report_domain: &'x str,
245 submitter: &'x str,
246- from_name: &'x str,
247- from_addr: &'x str,
248- to: &'x [&str],
249+ from: impl Into<Address<'x>>,
250+ to: impl Iterator<Item = &'x str>,
251 writer: impl io::Write,
252 ) -> io::Result<()> {
253 // Compress JSON report
254 let json = self.to_json();
255 let mut e = GzEncoder::new(Vec::with_capacity(json.len()), Compression::default());
256 io::Write::write_all(&mut e, json.as_bytes())?;
257- let compressed_bytes = e.finish()?;
258+ let bytes = e.finish()?;
259+ self.write_rfc5322_from_bytes(report_domain, submitter, from, to, &bytes, writer)
260+ }
261
262+ pub fn write_rfc5322_from_bytes<'x>(
263+ &self,
264+ report_domain: &str,
265+ submitter: &str,
266+ from: impl Into<Address<'x>>,
267+ to: impl Iterator<Item = &'x str>,
268+ bytes: &[u8],
269+ writer: impl io::Write,
270+ ) -> io::Result<()> {
271 MessageBuilder::new()
272- .from((from_name, from_addr))
273+ .from(from)
274 .header(
275 "To",
276- HeaderType::Address(Address::List(to.iter().map(|to| (*to).into()).collect())),
277+ HeaderType::Address(Address::List(to.map(|to| (*to).into()).collect())),
278 )
279 .message_id(format!("<{}@{}>", make_boundary("."), submitter))
280 .header("TLS-Report-Domain", HeaderType::Text(report_domain.into()))
281 @@ -69,7 +79,7 @@ impl TlsReport {
282 ),
283 MimePart::new(
284 ContentType::new("application/tlsrpt+gzip"),
285- BodyPart::Binary(compressed_bytes.into()),
286+ BodyPart::Binary(bytes.into()),
287 )
288 .attachment(format!(
289 "{}!{}!{}!{}.json.gz",
290 @@ -87,12 +97,11 @@ impl TlsReport {
291 &self,
292 report_domain: &'x str,
293 submitter: &'x str,
294- from_name: &'x str,
295- from_addr: &'x str,
296- to: &'x [&str],
297+ from: impl Into<Address<'x>>,
298+ to: impl Iterator<Item = &'x str>,
299 ) -> io::Result<String> {
300 let mut buf = Vec::new();
301- self.write_rfc5322(report_domain, submitter, from_name, from_addr, to, &mut buf)?;
302+ self.write_rfc5322(report_domain, submitter, from, to, &mut buf)?;
303 String::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))
304 }
305
306 @@ -110,12 +119,12 @@ mod test {
307 #[test]
308 fn tlsrpt_generate() {
309 let report = TlsReport {
310- organization_name: "Hello World, Inc.".to_string(),
311+ organization_name: "Hello World, Inc.".to_string().into(),
312 date_range: DateRange {
313 start_datetime: DateTime::from_timestamp(49823749),
314 end_datetime: DateTime::from_timestamp(49823899),
315 },
316- contact_info: "tls-report@hello-world.inc".to_string(),
317+ contact_info: "tls-report@hello-world.inc".to_string().into(),
318 report_id: "abc-123".to_string(),
319 policies: vec![],
320 };
321 @@ -124,9 +133,8 @@ mod test {
322 .to_rfc5322(
323 "hello-world.inc",
324 "example.org",
325- "mx.example.org",
326 "no-reply@example.org",
327- &["tls-reports@hello-world.inc"],
328+ ["tls-reports@hello-world.inc"].iter().copied(),
329 )
330 .unwrap();
331
332 diff --git a/src/report/tlsrpt/mod.rs b/src/report/tlsrpt/mod.rs
333index 9038919..1cf4c1d 100644
334--- a/src/report/tlsrpt/mod.rs
335+++ b/src/report/tlsrpt/mod.rs
336 @@ -20,14 +20,14 @@ pub mod parse;
337 pub struct TlsReport {
338 #[serde(rename = "organization-name")]
339 #[serde(default)]
340- pub organization_name: String,
341+ pub organization_name: Option<String>,
342
343 #[serde(rename = "date-range")]
344 pub date_range: DateRange,
345
346 #[serde(rename = "contact-info")]
347 #[serde(default)]
348- pub contact_info: String,
349+ pub contact_info: Option<String>,
350
351 #[serde(rename = "report-id")]
352 #[serde(default)]
353 @@ -80,7 +80,7 @@ pub struct Summary {
354 pub total_failure: u32,
355 }
356
357- #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
358+ #[derive(Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
359 pub struct FailureDetails {
360 #[serde(rename = "result-type")]
361 pub result_type: ResultType,
362 @@ -133,7 +133,7 @@ pub enum PolicyType {
363 Other,
364 }
365
366- #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
367+ #[derive(Debug, Default, Hash, PartialEq, Eq, Serialize, Deserialize)]
368 pub enum ResultType {
369 #[serde(rename = "starttls-not-supported")]
370 StartTlsNotSupported,