| 1 | module pem |
| 2 | |
| 3 | import 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] |
| 11 | pub 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] |
| 22 | pub 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] |
| 31 | fn 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] |
| 50 | fn 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 | |