Skip to main content

mail_auth/headers/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7//! Header field types and output traits shared by every protocol module.
8//!
9//! - [`Header`] pairs a parsed authentication header with its raw name and
10//!   value (RFC 5322 Section 2.2).
11//! - [`HeaderWriter`] is implemented by every header this crate generates
12//!   (`DKIM-Signature`, `ARC-Seal` and friends, `DKIM2-Signature`,
13//!   `Message-Instance`, `Authentication-Results`, `Received-SPF`).
14//! - [`Writer`] and [`Writable`] are the byte sink and source used for
15//!   header output, canonicalization and hashing.
16//! - [`HeaderFolder`] folds long header lines (RFC 5322 Section 2.2.3).
17
18use encodify::{Fold, base64};
19
20mod parse;
21
22pub(crate) use parse::{AuthenticatedHeader, ChainedHeaderIterator, HeaderIterator, HeaderParser};
23
24impl<'x, T> Header<'x, T> {
25    /// Creates a header from its raw name, raw value and parsed form.
26    pub fn new(name: &'x [u8], value: &'x [u8], header: T) -> Self {
27        Header {
28            name,
29            value,
30            header,
31        }
32    }
33}
34
35pub(crate) trait HeaderStream<'x> {
36    fn next_header(&mut self) -> Option<(&'x [u8], &'x [u8])>;
37    fn body(&mut self) -> &'x [u8];
38}
39
40pub(crate) const MAX_HEADER_LINE_LEN: usize = 76;
41const MEMCHR_MIN_LEN: usize = 16;
42const BASE64_GROUP_LEN: usize = 4;
43const BASE64_FOLD: Fold<'static> =
44    Fold::new(MAX_HEADER_LINE_LEN - 1, b"\r\n\t", 0).with_granularity(BASE64_GROUP_LEN);
45
46/// A [`Writer`] adapter that folds header lines (RFC 5322 Section 2.2.3).
47///
48/// Written data is split after each `;`, and a fold (`CRLF` followed by a
49/// tab) is inserted before a piece that would push the line past 76
50/// characters. Base64 data written with [`Writer::write_base64`] is folded
51/// on four-character boundaries. Pieces longer than a line are split.
52pub struct HeaderFolder<'x, W: Writer> {
53    writer: &'x mut W,
54    bytes_left: usize,
55}
56
57impl<'x, W: Writer> HeaderFolder<'x, W> {
58    /// Wraps `writer`, starting at the beginning of a line.
59    pub fn new(writer: &'x mut W) -> Self {
60        HeaderFolder {
61            writer,
62            bytes_left: MAX_HEADER_LINE_LEN,
63        }
64    }
65
66    #[inline(always)]
67    fn write_chunk(&mut self, chunk: &[u8]) {
68        if chunk == b"\r\n" {
69            self.writer.write(chunk);
70            self.bytes_left = MAX_HEADER_LINE_LEN;
71        } else if chunk.len() < self.bytes_left {
72            self.writer.write(chunk);
73            self.bytes_left -= chunk.len();
74        } else if chunk.len() >= MAX_HEADER_LINE_LEN {
75            let mut add_new_line = self.bytes_left != MAX_HEADER_LINE_LEN;
76            let mut last_piece_len = MAX_HEADER_LINE_LEN;
77            for chunk in chunk.chunks(MAX_HEADER_LINE_LEN) {
78                if add_new_line {
79                    self.writer.write(b"\r\n\t");
80                }
81                add_new_line = true;
82                self.writer.write(chunk);
83                last_piece_len = chunk.len();
84            }
85            self.bytes_left = MAX_HEADER_LINE_LEN - last_piece_len;
86        } else {
87            self.writer.write(b"\r\n\t");
88            self.writer.write(chunk);
89            self.bytes_left = MAX_HEADER_LINE_LEN - chunk.len();
90        }
91    }
92}
93
94#[inline(always)]
95fn find_semicolon(buf: &[u8]) -> Option<usize> {
96    if buf.len() >= MEMCHR_MIN_LEN {
97        memchr::memchr(b';', buf)
98    } else {
99        buf.iter().position(|ch| *ch == b';')
100    }
101}
102
103impl<'x, W: Writer> Writer for HeaderFolder<'x, W> {
104    fn write(&mut self, buf: &[u8]) {
105        let mut rest = buf;
106        while !rest.is_empty() {
107            let (chunk, tail) = match find_semicolon(rest) {
108                Some(pos) => rest.split_at(pos + 1),
109                None => (rest, Default::default()),
110            };
111            self.write_chunk(chunk);
112            rest = tail;
113        }
114    }
115
116    fn write_base64(&mut self, bytes: &[u8]) {
117        let mut column = MAX_HEADER_LINE_LEN.saturating_sub(self.bytes_left);
118        base64::STANDARD.encode_folded(bytes, &mut column, BASE64_FOLD, |piece| {
119            self.writer.write(piece)
120        });
121        self.bytes_left = MAX_HEADER_LINE_LEN.saturating_sub(column);
122    }
123}
124
125/// An authentication header found in a message, as returned by
126/// [`AuthenticatedMessage`](crate::AuthenticatedMessage) accessors such as
127/// `dkim_signatures()` and `errors()`.
128///
129/// `T` is the parsed form (for example a DKIM
130/// [`Signature`](crate::dkim::Signature)) or, for headers that failed to
131/// parse, the [`Error`](crate::Error) explaining why.
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct Header<'x, T> {
134    /// The raw field name, without the colon.
135    pub name: &'x [u8],
136    /// The raw field value, after the colon, including folding and the
137    /// trailing line ending.
138    pub value: &'x [u8],
139    /// The parsed header, or the parse error.
140    pub header: T,
141}
142
143pub(crate) const HEADER_CAPACITY: usize = 512;
144
145/// A header that can be written to a message.
146pub trait HeaderWriter: Sized {
147    /// Writes the complete header field, including the field name and the
148    /// trailing CRLF, to `writer`.
149    fn write_header(&self, writer: &mut impl Writer);
150    /// Returns the complete header field as a string, including the field name
151    /// and the trailing CRLF. Invalid UTF-8 is replaced with U+FFFD.
152    fn to_header(&self) -> String {
153        let mut buf = Vec::with_capacity(HEADER_CAPACITY);
154        self.write_header(&mut buf);
155        String::from_utf8(buf)
156            .unwrap_or_else(|err| String::from_utf8_lossy(err.as_bytes()).into_owned())
157    }
158}
159
160/// A source of bytes that can be streamed into a [`Writer`].
161///
162/// Used as the input of signing and hashing so that data can be produced
163/// without first collecting it into a buffer.
164pub trait Writable {
165    /// Writes all bytes to `writer`.
166    fn write(self, writer: &mut impl Writer);
167}
168
169impl Writable for &[u8] {
170    fn write(self, writer: &mut impl Writer) {
171        writer.write(self);
172    }
173}
174
175/// A byte sink: a buffer, a hash context or a [`HeaderFolder`].
176pub trait Writer {
177    /// Appends `buf`.
178    fn write(&mut self, buf: &[u8]);
179
180    /// Appends `buf` and adds its length to `len`.
181    fn write_len(&mut self, buf: &[u8], len: &mut usize) {
182        self.write(buf);
183        *len += buf.len();
184    }
185
186    /// Appends `bytes` encoded as standard Base64 with padding (RFC 4648
187    /// Section 4).
188    fn write_base64(&mut self, bytes: &[u8]) {
189        base64::STANDARD.encode_chunks(bytes, |chunk| self.write(chunk));
190    }
191}
192
193impl Writer for Vec<u8> {
194    fn write(&mut self, buf: &[u8]) {
195        self.extend(buf);
196    }
197}
198
199impl Writer for &mut Vec<u8> {
200    fn write(&mut self, buf: &[u8]) {
201        self.extend(buf);
202    }
203}
204
205const MAX_U64_DIGITS: usize = 20;
206
207pub(crate) struct IntegerBuffer([u8; MAX_U64_DIGITS]);
208
209impl IntegerBuffer {
210    pub(crate) const fn new() -> Self {
211        IntegerBuffer([0; MAX_U64_DIGITS])
212    }
213
214    pub(crate) fn digits(&mut self, value: u64) -> &[u8] {
215        let mut value = value;
216        let mut pos = MAX_U64_DIGITS;
217        loop {
218            pos -= 1;
219            if let Some(digit) = self.0.get_mut(pos) {
220                *digit = b'0' + (value % 10) as u8;
221            }
222            value /= 10;
223            if value == 0 || pos == 0 {
224                break;
225            }
226        }
227        self.0.get(pos..).unwrap_or_default()
228    }
229
230    pub(crate) fn text(&mut self, value: u64) -> &str {
231        std::str::from_utf8(self.digits(value)).unwrap_or_default()
232    }
233}
234
235pub(crate) fn write_integer(writer: &mut impl Writer, value: u64) {
236    let mut buffer = IntegerBuffer::new();
237    writer.write(buffer.digits(value));
238}
239
240#[cfg(test)]
241mod tests;