pem/
parser.rs

1pub struct Captures<'a> {
2    pub begin: &'a [u8],
3    pub headers: &'a [u8],
4    pub data: &'a [u8],
5    pub end: &'a [u8],
6}
7
8pub fn parse_captures(input: &[u8]) -> Option<Captures<'_>> {
9    parser_inner(input).map(|(_, cap)| cap)
10}
11pub fn parse_captures_iter(input: &[u8]) -> CaptureMatches<'_> {
12    CaptureMatches { input }
13}
14
15pub struct CaptureMatches<'a> {
16    input: &'a [u8],
17}
18impl<'a> Iterator for CaptureMatches<'a> {
19    type Item = Captures<'a>;
20    fn next(&mut self) -> Option<Self::Item> {
21        if self.input.is_empty() {
22            return None;
23        }
24        match parser_inner(self.input) {
25            Some((remaining, captures)) => {
26                self.input = remaining;
27                Some(captures)
28            }
29            None => {
30                self.input = &[];
31                None
32            }
33        }
34    }
35}
36
37fn parse_begin(input: &[u8]) -> Option<(&[u8], &[u8])> {
38    let (input, _) = read_until(input, b"-----BEGIN ")?;
39    let (input, begin) = read_until(input, b"-----")?;
40    let input = skip_whitespace(input);
41    Some((input, begin))
42}
43
44fn parse_payload(input: &[u8]) -> Option<(&[u8], &[u8])> {
45    read_until(input, b"-----END ")
46}
47
48fn extract_headers_and_data(input: &[u8]) -> (&[u8], &[u8]) {
49    if let Some((rest, headers)) = read_until(input, b"\n\n") {
50        (headers, rest)
51    } else if let Some((rest, headers)) = read_until(input, b"\r\n\r\n") {
52        (headers, rest)
53    } else {
54        (&[], input)
55    }
56}
57
58fn parse_end(input: &[u8]) -> Option<(&[u8], &[u8])> {
59    let (remaining, end) = read_until(input, b"-----")?;
60    let remaining = skip_whitespace(remaining);
61    Some((remaining, end))
62}
63
64fn parser_inner(input: &[u8]) -> Option<(&[u8], Captures<'_>)> {
65    // Should be equivalent to the regex
66    // "(?s)-----BEGIN (?P<begin>.*?)-----[ \t\n\r]*(?P<data>.*?)-----END (?P<end>.*?)-----[ \t\n\r]*"
67
68    // (?s)                                      # Enable dotall (. matches all characters incl \n)
69    // -----BEGIN (?P<begin>.*?)-----[ \t\n\r]*  # Parse begin
70    // (?P<data>.*?)                             # Parse data
71    // -----END (?P<end>.*?)-----[ \t\n\r]*      # Parse end
72
73    let (input, begin) = parse_begin(input)?;
74    let (input, payload) = parse_payload(input)?;
75    let (headers, data) = extract_headers_and_data(payload);
76    let (remaining, end) = parse_end(input)?;
77
78    let captures = Captures {
79        begin,
80        headers,
81        data,
82        end,
83    };
84    Some((remaining, captures))
85}
86
87// Equivalent to the regex [ \t\n\r]*
88fn skip_whitespace(mut input: &[u8]) -> &[u8] {
89    while let Some(b) = input.first() {
90        match b {
91            b' ' | b'\t' | b'\n' | b'\r' => {
92                input = &input[1..];
93            }
94            _ => break,
95        }
96    }
97    input
98}
99// Equivalent to (.*?) followed by a string
100// Returns the remaining input (after the secondary matched string) and the matched data
101fn read_until<'a>(input: &'a [u8], marker: &[u8]) -> Option<(&'a [u8], &'a [u8])> {
102    // If there is no end condition, short circuit
103    if marker.is_empty() {
104        return Some((&[], input));
105    }
106    let mut index = 0;
107    let mut found = 0;
108    while input.len() - index >= marker.len() - found {
109        if input[index] == marker[found] {
110            found += 1;
111        } else {
112            found = 0;
113        }
114        index += 1;
115        if found == marker.len() {
116            let remaining = &input[index..];
117            let matched = &input[..index - found];
118            return Some((remaining, matched));
119        }
120    }
121    None
122}