Skip to main content

mini_mail_auth/dkim/
canonicalize.rs

1use super::{Canonicalization, Signature};
2use crate::common::headers::{HeaderStream, Writable, Writer};
3
4pub struct CanonicalBody<'a> {
5    canonicalization: Canonicalization,
6    body: &'a [u8],
7}
8
9impl Writable for CanonicalBody<'_> {
10    fn write(self, hasher: &mut impl Writer) {
11        let mut crlf_seq = 0;
12        match self.canonicalization {
13            Canonicalization::Relaxed => {
14                let mut last_ch = 0;
15                let mut is_empty = true;
16                for &ch in self.body {
17                    match ch {
18                        b' ' | b'\t' => {
19                            while crlf_seq > 0 {
20                                hasher.write(b"\r\n");
21                                crlf_seq -= 1;
22                            }
23                            is_empty = false;
24                        }
25                        b'\n' => crlf_seq += 1,
26                        b'\r' => {}
27                        _ => {
28                            while crlf_seq > 0 {
29                                hasher.write(b"\r\n");
30                                crlf_seq -= 1;
31                            }
32                            if last_ch == b' ' || last_ch == b'\t' {
33                                hasher.write(b" ");
34                            }
35                            hasher.write(&[ch]);
36                            is_empty = false;
37                        }
38                    }
39                    last_ch = ch;
40                }
41                if !is_empty {
42                    hasher.write(b"\r\n");
43                }
44            }
45            Canonicalization::Simple => {
46                for &ch in self.body {
47                    match ch {
48                        b'\n' => crlf_seq += 1,
49                        b'\r' => {}
50                        _ => {
51                            while crlf_seq > 0 {
52                                hasher.write(b"\r\n");
53                                crlf_seq -= 1;
54                            }
55                            hasher.write(&[ch]);
56                        }
57                    }
58                }
59                if crlf_seq == 0 && !self.body.is_empty() {
60                    hasher.write(b"\r\n");
61                }
62            }
63        }
64    }
65}
66
67impl Canonicalization {
68    pub fn canonicalize_headers<'a>(
69        &self,
70        headers: impl Iterator<Item = (&'a [u8], &'a [u8])>,
71        hasher: &mut impl Writer,
72    ) {
73        match self {
74            Canonicalization::Relaxed => {
75                for (name, value) in headers {
76                    for &ch in name {
77                        if !ch.is_ascii_whitespace() {
78                            hasher.write(&[ch.to_ascii_lowercase()]);
79                        }
80                    }
81                    hasher.write(b":");
82                    let mut bw = 0;
83                    let mut last_ch = 0;
84                    for &ch in value {
85                        if !ch.is_ascii_whitespace() {
86                            if [b' ', b'\t'].contains(&last_ch) && bw > 0 {
87                                hasher.write_len(b" ", &mut bw);
88                            }
89                            hasher.write_len(&[ch], &mut bw);
90                        }
91                        last_ch = ch;
92                    }
93                    if last_ch == b'\n' {
94                        hasher.write(b"\r\n");
95                    }
96                }
97            }
98            Canonicalization::Simple => {
99                for (name, value) in headers {
100                    hasher.write(name);
101                    hasher.write(b":");
102                    hasher.write(value);
103                }
104            }
105        }
106    }
107
108    pub fn canonical_headers<'a>(
109        &self,
110        headers: Vec<(&'a [u8], &'a [u8])>,
111    ) -> CanonicalHeaders<'a> {
112        CanonicalHeaders {
113            canonicalization: *self,
114            headers,
115        }
116    }
117
118    pub fn canonical_body<'a>(&self, body: &'a [u8], l: u64) -> CanonicalBody<'a> {
119        CanonicalBody {
120            canonicalization: *self,
121            body: if l == 0 || body.is_empty() {
122                body
123            } else {
124                &body[..std::cmp::min(l as usize, body.len())]
125            },
126        }
127    }
128
129    pub fn serialize_name(&self, writer: &mut impl Writer) {
130        writer.write(match self {
131            Canonicalization::Relaxed => b"relaxed",
132            Canonicalization::Simple => b"simple",
133        });
134    }
135}
136
137impl Signature {
138    pub fn canonicalize<'x>(
139        &self,
140        mut message: impl HeaderStream<'x>,
141    ) -> (usize, CanonicalHeaders<'x>, Vec<String>, CanonicalBody<'x>) {
142        let mut headers = Vec::with_capacity(self.h.len());
143        let mut found_headers = vec![false; self.h.len()];
144        let mut signed_headers = Vec::with_capacity(self.h.len());
145
146        while let Some((name, value)) = message.next_header() {
147            if let Some(pos) = self
148                .h
149                .iter()
150                .position(|header| name.eq_ignore_ascii_case(header.as_bytes()))
151            {
152                headers.push((name, value));
153                found_headers[pos] = true;
154                signed_headers.push(std::str::from_utf8(name).unwrap().into());
155            }
156        }
157
158        let body = message.body();
159        let body_len = body.len();
160        let canonical_headers = self.ch.canonical_headers(headers);
161        let canonical_body = self.ch.canonical_body(body, u64::MAX);
162
163        signed_headers.reverse();
164        for (header, found) in self.h.iter().zip(found_headers) {
165            if !found {
166                signed_headers.push(header.to_string());
167            }
168        }
169
170        (body_len, canonical_headers, signed_headers, canonical_body)
171    }
172}
173
174pub struct CanonicalHeaders<'a> {
175    canonicalization: Canonicalization,
176    headers: Vec<(&'a [u8], &'a [u8])>,
177}
178
179impl Writable for CanonicalHeaders<'_> {
180    fn write(self, writer: &mut impl Writer) {
181        self.canonicalization
182            .canonicalize_headers(self.headers.into_iter().rev(), writer)
183    }
184}