1use logos::{Lexer, Logos};
4
5use crate::dkim::*;
6
7use crate::error::{DkimSignatureError, DkimTagValueError};
8
9#[cfg(feature = "mail_parser")]
10use mail_parser::HeaderValue;
11
12#[derive(Debug, Logos)]
13pub enum DkimFieldValueToken<'hdr> {
14 #[regex(r"[^;]+", |lex| lex.slice(), priority = 1)]
15 MaybeValue(&'hdr str),
16
17 #[token(";", priority = 2)]
18 FieldSep,
19}
20
21#[derive(Debug, Logos)]
23#[logos(skip r"[ \t\r\n]+")]
24pub enum DkimFieldKeyToken<'hdr> {
25 #[token("v", priority = 1)]
26 TagV, #[token("a", priority = 1)]
29 TagA,
30
31 #[token("bh", priority = 1)]
32 TagBh,
33
34 #[token("c", priority = 1)]
35 TagC,
36
37 #[token("d", priority = 1)]
38 TagD,
39
40 #[token("h", priority = 1)]
41 TagH,
42
43 #[token("i", priority = 1)]
44 TagI,
45
46 #[token("l", priority = 1)]
47 TagL,
48
49 #[token("q", priority = 1)]
50 TagQ,
51
52 #[token("s", priority = 1)]
53 TagS,
54
55 #[token("t", priority = 1)]
56 TagT,
57
58 #[token("x", priority = 1)]
59 TagX,
60
61 #[token("z", priority = 1)]
62 TagZ,
63
64 #[token(";", priority = 1)]
65 FieldSep,
66
67 #[token("=", priority = 1)]
68 Equal,
69
70 #[token("b", priority = 2)]
72 TagB,
73
74 #[regex(r"([dvacdhilqtxvz][a-z]+|b[a-gi-z]|[efgjkmnopryu][a-z]*)", |lex| lex.slice(), priority = 3)]
76 MaybeTag(&'hdr str),
77}
78
79#[derive(Clone, Debug, PartialEq)]
80pub enum DkimTagChoice<'hdr> {
81 V,
82 A,
83 B,
84 Bh,
85 C,
86 D,
87 H,
88 I,
89 L,
90 Q,
91 S,
92 T,
93 X,
94 Z,
95 Unknown(&'hdr str),
97}
98
99impl<'hdr> DkimTagChoice<'hdr> {
100 fn from_token(token: DkimFieldKeyToken<'hdr>) -> Option<Self> {
101 let ret = match token {
102 DkimFieldKeyToken::TagV => Self::V,
103 DkimFieldKeyToken::TagA => Self::A,
104 DkimFieldKeyToken::TagB => Self::B,
105 DkimFieldKeyToken::TagBh => Self::Bh,
106 DkimFieldKeyToken::TagC => Self::C,
107 DkimFieldKeyToken::TagD => Self::D,
108 DkimFieldKeyToken::TagH => Self::H,
109 DkimFieldKeyToken::TagI => Self::I,
110 DkimFieldKeyToken::TagL => Self::L,
111 DkimFieldKeyToken::TagQ => Self::Q,
112 DkimFieldKeyToken::TagS => Self::S,
113 DkimFieldKeyToken::TagT => Self::T,
114 DkimFieldKeyToken::TagX => Self::X,
115 DkimFieldKeyToken::TagZ => Self::Z,
116 DkimFieldKeyToken::MaybeTag(tag) => Self::Unknown(tag),
117 _ => return None,
118 };
119 Some(ret)
120 }
121}
122
123#[derive(Debug, PartialEq)]
124enum Stage<'hdr> {
125 WantTag,
126 WantEq(DkimTagChoice<'hdr>),
127}
128
129#[derive(Debug, Default, PartialEq)]
132struct ParsedDkimSignature<'hdr> {
133 pub v: Option<DkimVersion<'hdr>>,
135 pub a: Option<DkimAlgorithm<'hdr>>,
137 pub b: Option<&'hdr str>,
139 pub bh: Option<&'hdr str>,
141 pub c: Option<DkimCanonicalization<'hdr>>,
143 pub d: Option<&'hdr str>,
145 pub h: Option<&'hdr str>,
147 pub i: Option<&'hdr str>,
149 pub l: Option<&'hdr str>,
151 pub q: Option<&'hdr str>,
153 pub s: Option<&'hdr str>,
155 pub t: Option<DkimTimestamp<'hdr>>,
157 pub x: Option<DkimTimestamp<'hdr>>,
159 pub z: Option<&'hdr str>,
161 pub raw: Option<&'hdr str>,
163}
164
165impl<'hdr> ParsedDkimSignature<'hdr> {
166 fn add_tag_value(
167 &mut self,
168 tag: DkimTagChoice<'hdr>,
169 val: &'hdr str,
170 ) -> Result<(), DkimTagValueError> {
171 match tag {
172 DkimTagChoice::V => self.v = Some(val.try_into()?),
173 DkimTagChoice::A => self.a = Some(val.try_into()?),
174 DkimTagChoice::B => self.b = Some(val),
175 DkimTagChoice::Bh => self.bh = Some(val),
176 DkimTagChoice::C => self.c = Some(val.try_into()?),
177 DkimTagChoice::D => self.d = Some(val),
178 DkimTagChoice::H => self.h = Some(val),
179 DkimTagChoice::I => self.i = Some(val),
180 DkimTagChoice::L => self.l = Some(val),
181 DkimTagChoice::Q => self.q = Some(val),
182 DkimTagChoice::S => self.s = Some(val),
183 DkimTagChoice::T => self.t = Some(val.try_into()?),
184 DkimTagChoice::X => self.x = Some(val.try_into()?),
185 DkimTagChoice::Z => self.z = Some(val),
186 DkimTagChoice::Unknown(_) => {}
188 }
189 Ok(())
190 }
191}
192
193impl<'hdr> TryFrom<ParsedDkimSignature<'hdr>> for DkimSignature<'hdr> {
195 type Error = DkimSignatureError<'hdr>;
196
197 fn try_from(p: ParsedDkimSignature<'hdr>) -> Result<Self, Self::Error> {
198 let version = match p.v {
200 Some(val) => val,
201 None => return Err(DkimSignatureError::MissingVersion),
202 };
203 let algorithm = match p.a {
204 Some(val) => val,
205 None => return Err(DkimSignatureError::MissingAlgorithm),
206 };
207 let signature = match p.b {
208 Some(val) => val,
209 None => return Err(DkimSignatureError::MissingSignature),
210 };
211 let body_hash = match p.bh {
212 Some(val) => val,
213 None => return Err(DkimSignatureError::MissingBodyHash),
214 };
215 let responsible_sdid = match p.d {
216 Some(val) => val,
217 None => return Err(DkimSignatureError::MissingResponsibleSdid),
218 };
219 let signed_header_fields = match p.h {
220 Some(val) => val,
221 None => return Err(DkimSignatureError::MissingSignedHeaderFields),
222 };
223 let selector = match p.s {
224 Some(val) => val,
225 None => return Err(DkimSignatureError::MissingSelector),
226 };
227 let c = p.c;
229 let i = p.i;
230 let l = p.l;
231 let q = p.q;
232 let t = p.t;
233 let x = p.x;
234 let z = p.z;
235 let raw = p.raw;
236 Ok(Self {
237 v: version,
238 a: algorithm,
239 b: signature,
240 bh: body_hash,
241 d: responsible_sdid,
242 h: signed_header_fields,
243 s: selector,
244 c,
245 i,
246 l,
247 q,
248 t,
249 x,
250 z,
251 raw,
252 })
253 }
254}
255
256impl<'hdr> TryFrom<&'hdr HeaderValue<'hdr>> for DkimSignature<'hdr> {
257 type Error = DkimSignatureError<'hdr>;
258
259 fn try_from(hval: &'hdr HeaderValue<'hdr>) -> Result<Self, Self::Error> {
260 let text = match hval.as_text() {
261 None => return Err(DkimSignatureError::NoTagFound),
262 Some(text) => text,
263 };
264
265 let mut tag_lexer = DkimFieldKeyToken::lexer(text);
266 let mut stage = Stage::WantTag;
267 let mut res = ParsedDkimSignature {
268 raw: Some(text),
269 ..Default::default()
270 };
271
272 while let Some(token) = tag_lexer.next() {
273 match token {
274 Ok(DkimFieldKeyToken::Equal) if stage != Stage::WantTag => {
275 stage = match stage {
276 Stage::WantEq(ref key_tag) => {
277 let mut value_lexer: Lexer<'hdr, DkimFieldValueToken<'hdr>> =
278 tag_lexer.morph();
279
280 for value_token in value_lexer.by_ref() {
281 match value_token {
282 Ok(DkimFieldValueToken::MaybeValue(value)) => {
283 res.add_tag_value(key_tag.clone(), value)?;
284 }
285 Ok(DkimFieldValueToken::FieldSep) => {
286 break;
287 }
288 Err(_) => return Err(DkimSignatureError::ParseValueUnmatch),
289 }
290 }
291 tag_lexer = value_lexer.morph();
292 Stage::WantTag
293 }
294 _ => return Err(DkimSignatureError::UnexpectedEqual),
295 };
296 }
297 Ok(maybe_tag_token) if stage == Stage::WantTag => {
298 let current_tag = DkimTagChoice::from_token(maybe_tag_token);
299 stage = match current_tag {
300 None => return Err(DkimSignatureError::NoTagFound),
301 Some(tag) => Stage::WantEq(tag),
302 };
303 }
304 _ => {
305 let cut_slice = &tag_lexer.source()[tag_lexer.span().start..];
306 let cut_span =
307 &tag_lexer.source()[tag_lexer.span().start..tag_lexer.span().end];
308
309 let detail = crate::error::ParsingDetail {
310 component: "parse_dkim_signature",
311 span_start: tag_lexer.span().start,
312 span_end: tag_lexer.span().end,
313 source: tag_lexer.source(),
314 clipped_span: cut_span,
315 clipped_remaining: cut_slice,
316 };
317
318 return Err(DkimSignatureError::ParsingDetailed(detail));
319 }
320 }
321 }
322 res.try_into()
323 }
324}