1use crate::SecurityTxtOptions;
2
3use super::parse_error::ParseError;
4
5use nom::{
6 branch::alt,
7 bytes::complete::{tag, take_while, take_while1},
8 character::complete::{line_ending, none_of, one_of},
9 combinator::{all_consuming, opt, peek, recognize},
10 multi::{many0, many1, many1_count, separated_list1},
11 sequence::{delimited, preceded, separated_pair, terminated, tuple},
12 IResult,
13};
14
15#[derive(Debug, PartialEq)]
16pub(crate) struct PGPSignature<'a> {
17 pub signature: &'a str,
18 pub keys: Vec<(&'a str, &'a str)>,
19}
20
21#[derive(Debug, PartialEq)]
22pub(crate) struct PGPCleartextMessage<'a> {
23 pub hash_armor_headers: Vec<Vec<&'a str>>,
24 pub cleartext: String,
25 pub signature: PGPSignature<'a>,
26}
27
28impl PGPCleartextMessage<'_> {}
29
30pub(crate) struct PGPCleartextMessageParser {
31 options: SecurityTxtOptions,
32}
33
34impl PGPCleartextMessageParser {
35 pub fn new(options: &SecurityTxtOptions) -> Self {
36 Self {
37 options: options.clone(),
38 }
39 }
40
41 pub fn parse<'a>(&'a self, text: &'a str) -> Result<PGPCleartextMessage<'a>, ParseError> {
42 let (_, msg) = self.signed_parser(text)?;
43 Ok(msg)
44 }
45
46 fn lf_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
47 match self.options.strict {
48 true => line_ending(i),
49 false => tag("\n")(i),
50 }
51 }
52
53 fn signed_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, PGPCleartextMessage<'a>> {
59 let (_, (_, hash_armor_headers, _, cleartext, signature)) = all_consuming(tuple((
60 |x| self.cleartext_header_parser(x),
61 many1(|x| self.hash_header_parser(x)),
62 |x| self.lf_parser(x),
63 |x| self.cleartext_parser(x),
64 |x| self.signature_parser(x),
65 )))(i)?;
66
67 Ok((
68 i,
69 PGPCleartextMessage {
70 hash_armor_headers,
71 cleartext,
72 signature,
73 },
74 ))
75 }
76
77 fn cleartext_header_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
79 terminated(tag("-----BEGIN PGP SIGNED MESSAGE-----"), |x| self.lf_parser(x))(i)
80 }
81
82 fn hash_header_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, Vec<&'a str>> {
84 delimited(
85 tag("Hash: "),
86 separated_list1(tag(","), |x| self.hash_alg_parser(x)),
87 |x| self.lf_parser(x),
88 )(i)
89 }
90
91 fn hash_alg_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
96 self.token_parser(i)
97 }
98
99 fn token_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
103 take_while1(is_token_char)(i)
104 }
105
106 fn cleartext_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, String> {
109 let (i, lines) = many0(alt((|x| self.line_dash_parser(x), |x| self.line_nodash_parser(x))))(i)?;
110 Ok((i, lines.join("")))
111 }
112
113 fn line_dash_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
116 preceded(
117 tag("- "),
118 recognize(tuple((
119 one_of("-"),
120 take_while(|x| x != '\r' && x != '\n'),
121 line_ending,
122 ))),
123 )(i)
124 }
125
126 fn line_nodash_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
129 preceded(
130 opt(tag("- ")),
131 recognize(tuple((
132 peek(none_of("-")),
133 take_while(|x| x != '\r' && x != '\n'),
134 line_ending,
135 ))),
136 )(i)
137 }
138
139 fn signature_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, PGPSignature<'a>> {
145 let (i, (_, keys, _, signature, _)) = tuple((
146 |x| self.armor_header_parser(x),
147 |x| self.armor_keys_parser(x),
148 |x| self.lf_parser(x),
149 |x| self.signature_data_parser(x),
150 |x| self.armor_tail_parser(x),
151 ))(i)?;
152
153 Ok((i, PGPSignature { signature, keys }))
154 }
155
156 fn armor_header_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
158 terminated(tag("-----BEGIN PGP SIGNATURE-----"), |x| self.lf_parser(x))(i)
159 }
160
161 fn armor_keys_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, Vec<(&'a str, &'a str)>> {
164 many0(terminated(
165 separated_pair(
166 |x| self.token_parser(x),
167 tag(": "),
168 take_while(|x| is_vchar(x) || is_wsp(x)),
169 ),
170 |x| self.lf_parser(x),
171 ))(i)
172 }
173
174 fn armor_tail_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
176 terminated(tag("-----END PGP SIGNATURE-----"), |x| self.lf_parser(x))(i)
177 }
178
179 fn signature_data_parser<'a>(&'a self, i: &'a str) -> IResult<&'a str, &'a str> {
183 recognize(many1_count(terminated(take_while1(is_signature_data_char), |x| {
184 self.lf_parser(x)
185 })))(i)
186 }
187}
188
189fn is_token_char(i: char) -> bool {
196 let tspecials = "()<>@,;:\\\"/[]?=";
197 i != ' ' && !i.is_ascii_control() && tspecials.find(i).is_none()
198}
199
200fn is_signature_data_char(i: char) -> bool {
201 matches!(i, 'a'..='z' | 'A'..='Z' | '0'..='9' | '=' | '+' | '/')
202}
203
204fn is_vchar(i: char) -> bool {
207 matches!(i, '\x21'..='\x7E')
208}
209
210fn is_wsp(i: char) -> bool {
213 i == ' ' || i == '\t'
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 const SIGNATURE_DATA: &str = "iHUEARYKAB0WIQSsP2kEdoKDVFpSg6u3rK+YCkjapwUCY9qRaQAKCRC3rK+YCkja\r
221pwALAP9LEHSYMDW4h8QRHg4MwCzUdnbjBLIvpq4QTo3dIqCUPwEA31MsEf95OKCh\r
222MTHYHajOzjwpwlQVrjkK419igx4imgk=\r
223=KONn\r
224";
225
226 #[test]
227 fn test_parse() {
228 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
229 let txt = format!(
230 "-----BEGIN PGP SIGNED MESSAGE-----\r
231Hash: SHA512\r
232\r
233Test\r
234- Test\r
235-----BEGIN PGP SIGNATURE-----\r
236\r
237{SIGNATURE_DATA}-----END PGP SIGNATURE-----\r
238"
239 );
240 let msg = PGPCleartextMessage {
241 hash_armor_headers: vec![vec!["SHA512"]],
242 cleartext: "Test\r\nTest\r\n".into(),
243 signature: PGPSignature {
244 signature: SIGNATURE_DATA,
245 keys: vec![],
246 },
247 };
248 assert_eq!(signed_parser.parse(&txt), Ok(msg));
249 }
250
251 #[test]
252 fn test_hash_header_parser() {
253 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
254 let test_vector = vec![
255 ("Hash: SHA512\r\n", vec!["SHA512"]),
256 ("Hash: SHA256,SHA512\r\n", vec!["SHA256", "SHA512"]),
257 ];
258
259 for (input, result) in test_vector {
260 assert_eq!(signed_parser.hash_header_parser(input), Ok(("", result)));
261 }
262 }
263
264 #[test]
265 fn test_token_parser() {
266 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
267 let test_vector = vec![("SHA512\r\n", "SHA512", "\r\n")];
268
269 for (input, result, leftover) in test_vector {
270 assert_eq!(signed_parser.token_parser(input), Ok((leftover, result)));
271 }
272 }
273
274 #[test]
275 fn test_line_dash_parser() {
276 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
277 let test_vector = vec![("- -test\r\n", "-test\r\n")];
278
279 for (input, result) in test_vector {
280 assert_eq!(signed_parser.line_dash_parser(input), Ok(("", result)));
281 }
282 }
283
284 #[test]
285 fn test_line_nodash_parser() {
286 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
287 let test_vector = vec![("test\r\n", "test\r\n")];
288
289 for (input, result) in test_vector {
290 assert_eq!(signed_parser.line_nodash_parser(input), Ok(("", result)));
291 }
292 }
293
294 #[test]
295 fn test_signature_parser() {
296 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
297 let input = format!(
298 "-----BEGIN PGP SIGNATURE-----\r
299\r
300{SIGNATURE_DATA}-----END PGP SIGNATURE-----\r
301"
302 );
303 let signature = PGPSignature {
304 signature: SIGNATURE_DATA,
305 keys: vec![],
306 };
307
308 assert_eq!(signed_parser.signature_parser(&input), Ok(("", signature)));
309 }
310
311 #[test]
312 fn test_armor_header_parser() {
313 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
314 let input = "-----BEGIN PGP SIGNATURE-----\r\n";
315 assert_eq!(
316 signed_parser.armor_header_parser(input),
317 Ok(("", "-----BEGIN PGP SIGNATURE-----"))
318 );
319 }
320
321 #[test]
322 fn test_armor_tail_parser() {
323 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
324 let input = "-----END PGP SIGNATURE-----\r\n";
325 assert_eq!(
326 signed_parser.armor_tail_parser(input),
327 Ok(("", "-----END PGP SIGNATURE-----"))
328 );
329 }
330
331 #[test]
332 fn test_armor_keys_parser() {
333 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
334 let test_vector = vec![
335 ("", vec![]),
336 ("test: \r\n", vec![("test", "")]),
337 ("test: test\r\n", vec![("test", "test")]),
338 ];
339
340 for (input, result) in test_vector {
341 assert_eq!(signed_parser.armor_keys_parser(input), Ok(("", result)));
342 }
343 }
344
345 #[test]
346 fn test_signature_data_parser() {
347 let signed_parser = PGPCleartextMessageParser::new(&Default::default());
348 let test_vector = vec![
349 "iHUEARYKAB0WIQSsP2kEdoKDVFpSg6u3rK+YCkjapwUCY9qRaQAKCRC3rK+YCkja\r\npwALAP9LEHSYMDW4h8QRHg4MwCzUdnbjBLIvpq4QTo3dIqCUPwEA31MsEf95OKCh\r\nMTHYHajOzjwpwlQVrjkK419igx4imgk\r\n=KONn\r\n",
350 ];
351
352 for input in test_vector {
353 assert_eq!(signed_parser.signature_data_parser(input), Ok(("", input)));
354 }
355 }
356}