1use crate::{canonicalization, hash, DKIMError};
2use nom::bytes::complete::{tag, take_while1};
3use nom::character::complete::alpha1;
4use nom::combinator::opt;
5use nom::multi::fold_many0;
6use nom::sequence::{delimited, pair, preceded, terminated};
7use nom::IResult;
8
9#[derive(Clone, Debug, PartialEq)]
10pub struct Tag {
12 pub name: String,
14 pub value: String,
16 pub raw_value: String,
18}
19
20pub fn tag_list(input: &str) -> IResult<&str, Vec<Tag>> {
24 let (input, start) = tag_spec(input)?;
25
26 terminated(
27 fold_many0(
28 preceded(tag(";"), tag_spec),
29 move || vec![start.clone()],
30 |mut acc: Vec<Tag>, item| {
31 acc.push(item);
32 acc
33 },
34 ),
35 opt(tag(";")),
36 )(input)
37}
38
39fn tag_spec(input: &str) -> IResult<&str, Tag> {
41 let (input, name) = delimited(opt(fws), tag_name, opt(fws))(input)?;
42 let (input, _) = tag("=")(input)?;
43
44 let value_input = input;
46 let (_, raw_value) = delimited(opt(fws), raw_tag_value, opt(fws))(value_input)?;
47 let (input, value) = delimited(opt(fws), tag_value, opt(fws))(value_input)?;
48
49 Ok((
50 input,
51 Tag {
52 name: name.to_owned(),
53 value,
54 raw_value,
55 },
56 ))
57}
58
59fn tag_name(input: &str) -> IResult<&str, &str> {
62 alpha1(input)
63}
64
65fn tag_value(input: &str) -> IResult<&str, String> {
69 let is_valchar = |c| ('!'..=':').contains(&c) || ('<'..='~').contains(&c);
70 match opt(take_while1(is_valchar))(input)? {
71 (input, Some(start)) => fold_many0(
72 preceded(fws, take_while1(is_valchar)),
73 || start.to_owned(),
74 |mut acc: String, item| {
75 acc += item;
76 acc
77 },
78 )(input),
79 (input, None) => Ok((input, "".to_string())),
80 }
81}
82
83fn raw_tag_value(input: &str) -> IResult<&str, String> {
84 let is_valchar = |c| ('!'..=':').contains(&c) || ('<'..='~').contains(&c);
85 match opt(take_while1(is_valchar))(input)? {
86 (input, Some(start)) => fold_many0(
87 pair(fws, take_while1(is_valchar)),
88 || start.to_owned(),
89 |mut acc: String, item| {
90 acc += &(item.0.to_owned() + item.1);
91 acc
92 },
93 )(input),
94 (input, None) => Ok((input, "".to_string())),
95 }
96}
97
98fn fws(input: &str) -> IResult<&str, &str> {
100 take_while1(|c| c == ' ' || c == '\t' || c == '\r' || c == '\n')(input)
101}
102
103pub(crate) fn parse_hash_algo(value: &str) -> Result<hash::HashAlgo, DKIMError> {
104 use hash::HashAlgo;
105 match value {
106 "rsa-sha1" => Ok(HashAlgo::RsaSha1),
107 "rsa-sha256" => Ok(HashAlgo::RsaSha256),
108 "ed25519-sha256" => Ok(HashAlgo::Ed25519Sha256),
109 e => Err(DKIMError::UnsupportedHashAlgorithm(e.to_string())),
110 }
111}
112
113pub(crate) fn parse_canonicalization(
116 value: Option<&str>,
117) -> Result<(canonicalization::Type, canonicalization::Type), DKIMError> {
118 use canonicalization::Type::{Relaxed, Simple};
119 match value {
120 None => Ok((Simple, Simple)),
121 Some(s) => match s {
122 "simple/simple" => Ok((Simple, Simple)),
123 "relaxed/simple" => Ok((Relaxed, Simple)),
124 "simple/relaxed" => Ok((Simple, Relaxed)),
125 "relaxed/relaxed" => Ok((Relaxed, Relaxed)),
126 "relaxed" => Ok((Relaxed, Simple)),
127 "simple" => Ok((Simple, Simple)),
128 v => Err(DKIMError::UnsupportedCanonicalizationType(v.to_owned())),
129 },
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_canonicalization_empty() {
139 use canonicalization::Type::Simple;
140 assert_eq!(parse_canonicalization(None).unwrap(), (Simple, Simple));
141 }
142
143 #[test]
144 fn test_canonicalization_one_algo() {
145 use canonicalization::Type::{Relaxed, Simple};
146
147 assert_eq!(
148 parse_canonicalization(Some("simple")).unwrap(),
149 (Simple, Simple)
150 );
151 assert_eq!(
152 parse_canonicalization(Some("relaxed")).unwrap(),
153 (Relaxed, Simple)
154 );
155 }
156
157 #[test]
158 fn test_tag_list() {
159 assert_eq!(
160 tag_list("a = a/1@.-:= ").unwrap(),
161 (
162 "",
163 vec![Tag {
164 name: "a".to_string(),
165 value: "a/1@.-:=".to_string(),
166 raw_value: "a/1@.-:=".to_string()
167 }]
168 )
169 );
170 assert_eq!(
171 tag_list("a= a ; b = a\n bc").unwrap(),
172 (
173 "",
174 vec![
175 Tag {
176 name: "a".to_string(),
177 value: "a".to_string(),
178 raw_value: "a".to_string()
179 },
180 Tag {
181 name: "b".to_string(),
182 value: "abc".to_string(),
183 raw_value: "a\n bc".to_string()
184 }
185 ]
186 )
187 );
188 }
189
190 #[test]
191 fn test_tag_spec() {
192 assert_eq!(
193 tag_spec("a=b").unwrap(),
194 (
195 "",
196 Tag {
197 name: "a".to_string(),
198 value: "b".to_string(),
199 raw_value: "b".to_string()
200 }
201 )
202 );
203 assert_eq!(
204 tag_spec("a=b c d e f").unwrap(),
205 (
206 "",
207 Tag {
208 name: "a".to_string(),
209 value: "bcdef".to_string(),
210 raw_value: "b c d e f".to_string()
211 }
212 )
213 );
214 }
215
216 #[test]
217 fn test_tag_list_dns() {
218 assert_eq!(
219 tag_list("k=rsa; p=kEy+/").unwrap(),
220 (
221 "",
222 vec![
223 Tag {
224 name: "k".to_string(),
225 value: "rsa".to_string(),
226 raw_value: "rsa".to_string()
227 },
228 Tag {
229 name: "p".to_string(),
230 value: "kEy+/".to_string(),
231 raw_value: "kEy+/".to_string()
232 }
233 ]
234 )
235 );
236 }
237}