use logos::{Lexer, Logos};
use crate::dkim::*;
use crate::error::{DkimSignatureError, DkimTagValueError};
#[cfg(feature = "mail_parser")]
use mail_parser::HeaderValue;
#[derive(Debug, Logos)]
pub enum DkimFieldValueToken<'hdr> {
#[regex(r"[^;]+", |lex| lex.slice(), priority = 1)]
MaybeValue(&'hdr str),
#[token(";", priority = 2)]
FieldSep,
}
#[derive(Debug, Logos)]
#[logos(skip r"[ \t\r\n]+")]
pub enum DkimFieldKeyToken<'hdr> {
#[token("v", priority = 1)]
TagV, #[token("a", priority = 1)]
TagA,
#[token("bh", priority = 1)]
TagBh,
#[token("c", priority = 1)]
TagC,
#[token("d", priority = 1)]
TagD,
#[token("h", priority = 1)]
TagH,
#[token("i", priority = 1)]
TagI,
#[token("l", priority = 1)]
TagL,
#[token("q", priority = 1)]
TagQ,
#[token("s", priority = 1)]
TagS,
#[token("t", priority = 1)]
TagT,
#[token("x", priority = 1)]
TagX,
#[token("z", priority = 1)]
TagZ,
#[token(";", priority = 1)]
FieldSep,
#[token("=", priority = 1)]
Equal,
#[token("b", priority = 2)]
TagB,
#[regex(r"([dvacdhilqtxvz][a-z]+|b[a-gi-z]|[efgjkmnopryu][a-z]*)", |lex| lex.slice(), priority = 3)]
MaybeTag(&'hdr str),
}
#[derive(Clone, Debug, PartialEq)]
pub enum DkimTagChoice<'hdr> {
V,
A,
B,
Bh,
C,
D,
H,
I,
L,
Q,
S,
T,
X,
Z,
Unknown(&'hdr str),
}
impl<'hdr> DkimTagChoice<'hdr> {
fn from_token(token: DkimFieldKeyToken<'hdr>) -> Option<Self> {
let ret = match token {
DkimFieldKeyToken::TagV => Self::V,
DkimFieldKeyToken::TagA => Self::A,
DkimFieldKeyToken::TagB => Self::B,
DkimFieldKeyToken::TagBh => Self::Bh,
DkimFieldKeyToken::TagC => Self::C,
DkimFieldKeyToken::TagD => Self::D,
DkimFieldKeyToken::TagH => Self::H,
DkimFieldKeyToken::TagI => Self::I,
DkimFieldKeyToken::TagL => Self::L,
DkimFieldKeyToken::TagQ => Self::Q,
DkimFieldKeyToken::TagS => Self::S,
DkimFieldKeyToken::TagT => Self::T,
DkimFieldKeyToken::TagX => Self::X,
DkimFieldKeyToken::TagZ => Self::Z,
DkimFieldKeyToken::MaybeTag(tag) => Self::Unknown(tag),
_ => return None,
};
Some(ret)
}
}
#[derive(Debug, PartialEq)]
enum Stage<'hdr> {
WantTag,
WantEq(DkimTagChoice<'hdr>),
}
#[derive(Debug, Default, PartialEq)]
struct ParsedDkimSignature<'hdr> {
pub v: Option<DkimVersion<'hdr>>,
pub a: Option<DkimAlgorithm<'hdr>>,
pub b: Option<&'hdr str>,
pub bh: Option<&'hdr str>,
pub c: Option<DkimCanonicalization<'hdr>>,
pub d: Option<&'hdr str>,
pub h: Option<&'hdr str>,
pub i: Option<&'hdr str>,
pub l: Option<&'hdr str>,
pub q: Option<&'hdr str>,
pub s: Option<&'hdr str>,
pub t: Option<DkimTimestamp<'hdr>>,
pub x: Option<DkimTimestamp<'hdr>>,
pub z: Option<&'hdr str>,
pub raw: Option<&'hdr str>,
}
impl<'hdr> ParsedDkimSignature<'hdr> {
fn add_tag_value(
&mut self,
tag: DkimTagChoice<'hdr>,
val: &'hdr str,
) -> Result<(), DkimTagValueError> {
match tag {
DkimTagChoice::V => self.v = Some(val.try_into()?),
DkimTagChoice::A => self.a = Some(val.try_into()?),
DkimTagChoice::B => self.b = Some(val),
DkimTagChoice::Bh => self.bh = Some(val),
DkimTagChoice::C => self.c = Some(val.try_into()?),
DkimTagChoice::D => self.d = Some(val),
DkimTagChoice::H => self.h = Some(val),
DkimTagChoice::I => self.i = Some(val),
DkimTagChoice::L => self.l = Some(val),
DkimTagChoice::Q => self.q = Some(val),
DkimTagChoice::S => self.s = Some(val),
DkimTagChoice::T => self.t = Some(val.try_into()?),
DkimTagChoice::X => self.x = Some(val.try_into()?),
DkimTagChoice::Z => self.z = Some(val),
DkimTagChoice::Unknown(_) => {}
}
Ok(())
}
}
impl<'hdr> TryFrom<ParsedDkimSignature<'hdr>> for DkimSignature<'hdr> {
type Error = DkimSignatureError<'hdr>;
fn try_from(p: ParsedDkimSignature<'hdr>) -> Result<Self, Self::Error> {
let version = match p.v {
Some(val) => val,
None => return Err(DkimSignatureError::MissingVersion),
};
let algorithm = match p.a {
Some(val) => val,
None => return Err(DkimSignatureError::MissingAlgorithm),
};
let signature = match p.b {
Some(val) => val,
None => return Err(DkimSignatureError::MissingSignature),
};
let body_hash = match p.bh {
Some(val) => val,
None => return Err(DkimSignatureError::MissingBodyHash),
};
let responsible_sdid = match p.d {
Some(val) => val,
None => return Err(DkimSignatureError::MissingResponsibleSdid),
};
let signed_header_fields = match p.h {
Some(val) => val,
None => return Err(DkimSignatureError::MissingSignedHeaderFields),
};
let selector = match p.s {
Some(val) => val,
None => return Err(DkimSignatureError::MissingSelector),
};
let c = p.c;
let i = p.i;
let l = p.l;
let q = p.q;
let t = p.t;
let x = p.x;
let z = p.z;
let raw = p.raw;
Ok(Self {
v: version,
a: algorithm,
b: signature,
bh: body_hash,
d: responsible_sdid,
h: signed_header_fields,
s: selector,
c,
i,
l,
q,
t,
x,
z,
raw,
})
}
}
impl<'hdr> TryFrom<&'hdr HeaderValue<'hdr>> for DkimSignature<'hdr> {
type Error = DkimSignatureError<'hdr>;
fn try_from(hval: &'hdr HeaderValue<'hdr>) -> Result<Self, Self::Error> {
let text = match hval.as_text() {
None => return Err(DkimSignatureError::NoTagFound),
Some(text) => text,
};
let mut tag_lexer = DkimFieldKeyToken::lexer(text);
let mut stage = Stage::WantTag;
let mut res = ParsedDkimSignature {
raw: Some(text),
..Default::default()
};
while let Some(token) = tag_lexer.next() {
match token {
Ok(DkimFieldKeyToken::Equal) if stage != Stage::WantTag => {
stage = match stage {
Stage::WantEq(ref key_tag) => {
let mut value_lexer: Lexer<'hdr, DkimFieldValueToken<'hdr>> =
tag_lexer.morph();
for value_token in value_lexer.by_ref() {
match value_token {
Ok(DkimFieldValueToken::MaybeValue(value)) => {
res.add_tag_value(key_tag.clone(), value)?;
}
Ok(DkimFieldValueToken::FieldSep) => {
break;
}
Err(_) => return Err(DkimSignatureError::ParseValueUnmatch),
}
}
tag_lexer = value_lexer.morph();
Stage::WantTag
}
_ => return Err(DkimSignatureError::UnexpectedEqual),
};
}
Ok(maybe_tag_token) if stage == Stage::WantTag => {
let current_tag = DkimTagChoice::from_token(maybe_tag_token);
stage = match current_tag {
None => return Err(DkimSignatureError::NoTagFound),
Some(tag) => Stage::WantEq(tag),
};
}
_ => {
let cut_slice = &tag_lexer.source()[tag_lexer.span().start..];
let cut_span =
&tag_lexer.source()[tag_lexer.span().start..tag_lexer.span().end];
let detail = crate::error::ParsingDetail {
component: "parse_dkim_signature",
span_start: tag_lexer.span().start,
span_end: tag_lexer.span().end,
source: tag_lexer.source(),
clipped_span: cut_span,
clipped_remaining: cut_slice,
};
return Err(DkimSignatureError::ParsingDetailed(detail));
}
}
}
res.try_into()
}
}