pem/
parser.rs

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