mail_auth/arc/
parse.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use mail_parser::decoders::base64::base64_decode_stream;
8
9use crate::{
10    common::{crypto::Algorithm, parse::TagParser},
11    dkim::{parse::SignatureParser, Canonicalization},
12    Error,
13};
14
15use super::{ChainValidation, Results, Seal, Signature};
16
17use crate::common::parse::*;
18
19pub(crate) const CV: u64 = (b'c' as u64) | ((b'v' as u64) << 8);
20
21impl Signature {
22    #[allow(clippy::while_let_on_iterator)]
23    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
24        let mut signature = Signature {
25            a: Algorithm::RsaSha256,
26            d: "".into(),
27            s: "".into(),
28            b: Vec::with_capacity(0),
29            bh: Vec::with_capacity(0),
30            h: Vec::with_capacity(0),
31            z: Vec::with_capacity(0),
32            l: 0,
33            x: 0,
34            t: 0,
35            i: 0,
36            ch: Canonicalization::Simple,
37            cb: Canonicalization::Simple,
38        };
39        let header_len = header.len();
40        let mut header = header.iter();
41
42        while let Some(key) = header.key() {
43            match key {
44                I => {
45                    signature.i = header.number().unwrap_or(0) as u32;
46                    if !(1..=50).contains(&signature.i) {
47                        return Err(Error::ArcInvalidInstance(signature.i));
48                    }
49                }
50                A => {
51                    signature.a = header.algorithm()?;
52                }
53                B => {
54                    signature.b =
55                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
56                }
57                BH => {
58                    signature.bh =
59                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
60                }
61                C => {
62                    let (ch, cb) = header.canonicalization(Canonicalization::Simple)?;
63                    signature.ch = ch;
64                    signature.cb = cb;
65                }
66                D => signature.d = header.text(true),
67                H => signature.h = header.items(),
68                L => signature.l = header.number().unwrap_or(0),
69                S => signature.s = header.text(true),
70                T => signature.t = header.number().unwrap_or(0),
71                X => signature.x = header.number().unwrap_or(0),
72                Z => signature.z = header.headers_qp(),
73                _ => header.ignore(),
74            }
75        }
76
77        if !signature.d.is_empty()
78            && !signature.s.is_empty()
79            && !signature.b.is_empty()
80            && !signature.bh.is_empty()
81            && !signature.h.is_empty()
82        {
83            Ok(signature)
84        } else {
85            Err(Error::MissingParameters)
86        }
87    }
88}
89
90impl Seal {
91    #[allow(clippy::while_let_on_iterator)]
92    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
93        let mut seal = Seal {
94            a: Algorithm::RsaSha256,
95            d: "".into(),
96            s: "".into(),
97            b: Vec::with_capacity(0),
98            t: 0,
99            i: 0,
100            cv: ChainValidation::None,
101        };
102        let header_len = header.len();
103        let mut header = header.iter();
104        let mut cv = None;
105
106        while let Some(key) = header.key() {
107            match key {
108                I => {
109                    seal.i = header.number().unwrap_or(0) as u32;
110                }
111                A => {
112                    seal.a = header.algorithm()?;
113                }
114                B => {
115                    seal.b =
116                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
117                }
118                D => seal.d = header.text(true),
119                S => seal.s = header.text(true),
120                T => seal.t = header.number().unwrap_or(0),
121                CV => {
122                    match header.next_skip_whitespaces().unwrap_or(0) {
123                        b'n' | b'N' if header.match_bytes(b"one") => {
124                            cv = ChainValidation::None.into();
125                        }
126                        b'f' | b'F' if header.match_bytes(b"ail") => {
127                            cv = ChainValidation::Fail.into();
128                        }
129                        b'p' | b'P' if header.match_bytes(b"ass") => {
130                            cv = ChainValidation::Pass.into();
131                        }
132                        _ => return Err(Error::ArcInvalidCV),
133                    }
134                    if !header.seek_tag_end() {
135                        return Err(Error::ArcInvalidCV);
136                    }
137                }
138                H => {
139                    return Err(Error::ArcHasHeaderTag);
140                }
141                _ => header.ignore(),
142            }
143        }
144        seal.cv = cv.ok_or(Error::ArcInvalidCV)?;
145
146        if !(1..=50).contains(&seal.i) {
147            Err(Error::ArcInvalidInstance(seal.i))
148        } else if !seal.d.is_empty() && !seal.s.is_empty() && !seal.b.is_empty() {
149            Ok(seal)
150        } else {
151            Err(Error::MissingParameters)
152        }
153    }
154}
155
156impl Results {
157    #[allow(clippy::while_let_on_iterator)]
158    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
159        let mut results = Results { i: 0 };
160        let mut header = header.iter();
161
162        while let Some(key) = header.key() {
163            match key {
164                I => {
165                    results.i = header.number().unwrap_or(0) as u32;
166                    break;
167                }
168                _ => header.ignore(),
169            }
170        }
171
172        if (1..=50).contains(&results.i) {
173            Ok(results)
174        } else {
175            Err(Error::ArcInvalidInstance(results.i))
176        }
177    }
178}