v2 / vlib / crypto / pem / decode.v
94 lines · 78 sloc · 3.2 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1module pem
2
3import encoding.base64
4
5// decode_only reads `data` and returns the first parsed PEM Block. `none` is returned
6// when a header is expected, but not present or when a start of '-----BEGIN' or end of '-----END'
7// can't be found.
8//
9// use decode if you still need the unparsed rest of the string.
10@[inline]
11pub fn decode_only(data string) ?Block {
12 block, _ := decode_internal(data)?
13 return block
14}
15
16// decode reads `data` and returns the first parsed PEM Block along with the rest of
17// the string. `none` is returned when a header is expected, but not present
18// or when a start of '-----BEGIN' or end of '-----END' can't be found.
19//
20// use decode_only if you do not need the unparsed rest of the string.
21@[direct_array_access; inline]
22pub fn decode(data string) ?(Block, string) {
23 block, rest := decode_internal(data)?
24 return block, rest[rest.index(pem_end)? + pem_end.len..].all_after_first(pem_eol)
25}
26
27// decode_internal allows `decode` variations to deal with the rest of the data as
28// they want to. for example Block.decode could have hindered performance with the final
29// indexing into `rest` that `decode_partial` does.
30@[direct_array_access]
31fn decode_internal(data string) ?(Block, string) {
32 // direct_array_access safety: since we use the string.index method here,
33 // we won't get an invalid index since it would otherwise return `none`
34 mut rest := data[data.index(pem_begin)?..]
35 mut block := Block.new(rest[pem_begin.len..].all_before(pem_eol))
36 block.headers, rest =
37 parse_headers(rest[pem_begin.len..].all_after(pem_eol).trim_left(' \n\t\v\f\r'))?
38
39 block_end_index := rest.index(pem_end)?
40 b64_data := rest[..block_end_index].replace_each(['\r', '', '\n', '', '\t', '', ' ', ''])
41 block_data_len := block_end_index / 4 * 3
42 block.data = []u8{len: block_data_len, cap: block_data_len + 3, init: 0}
43 decoded_len := base64.decode_in_buffer(&b64_data, &block.data[0])
44 block.data = block.data[..decoded_len]
45
46 return block, rest
47}
48
49@[direct_array_access]
50fn parse_headers(block string) ?(map[string][]string, string) {
51 headers_str := block.all_before(pem_end).all_before('\n\n')
52
53 // check that something was split or if it's empty
54 if headers_str.len == block.all_before(pem_end).len || headers_str.len == 0 {
55 return map[string][]string{}, block
56 }
57
58 // separate lines instead of iterating over them,
59 // so that we can manually index them
60 headers_separated := headers_str.split_into_lines()
61
62 // index the key/value separator ':', otherwise
63 // return none because it should exist
64 // the initialisation of this function already tells us headers are present
65 mut colon_index := headers_separated[0].index(colon) or { return none }
66
67 mut headers := map[string][]string{}
68 mut index := 0
69
70 for index < headers_separated.len - 1 {
71 line := headers_separated[index]
72 if line.len == 0 {
73 break
74 }
75
76 key := line[..colon_index].trim_space()
77 mut val := line[colon_index + 1..].trim_space()
78
79 for colon_index = 0; index < headers_separated.len - 1 && colon_index == 0; {
80 index++
81 colon_index = headers_separated[index].index(colon) or {
82 val += headers_separated[index].trim_space()
83 0
84 }
85 }
86
87 if key !in headers {
88 headers[key] = []string{}
89 }
90 headers[key] << val
91 }
92
93 return headers, block.all_after('\n\n')
94}
95