Skip to content

Commit

Permalink
crypto.pem: add decode_only and general improvements to decoding (#18908
Browse files Browse the repository at this point in the history
)
  • Loading branch information
phoreverpheebs committed Jul 19, 2023
1 parent 511274a commit d66b097
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 6 deletions.
31 changes: 28 additions & 3 deletions vlib/crypto/pem/decode.v
Expand Up @@ -2,25 +2,50 @@ module pem

import encoding.base64

// decode_only reads `data` and returns the first parsed PEM Block. `none` is returned
// when a header is expected, but not present or when a start of '-----BEGIN' or end of '-----END'
// can't be found.
//
// use decode if you still need the unparsed rest of the string.
[inline]
pub fn decode_only(data string) ?Block {
block, _ := decode_internal(data)?
return block
}

// decode reads `data` and returns the first parsed PEM Block along with the rest of
// the string. `none` is returned when a header is expected, but not present
// or when a start of '-----BEGIN' or end of '-----END' can't be found in `data`
// or when a start of '-----BEGIN' or end of '-----END' can't be found.
//
// use decode_only if you do not need the unparsed rest of the string.
[direct_array_access; inline]
pub fn decode(data string) ?(Block, string) {
block, rest := decode_internal(data)?
return block, rest[rest.index(pem_end)? + pem_end.len..].all_after_first(pem_eol)
}

// decode_internal allows `decode` variations to deal with the rest of the data as
// they want to. for example Block.decode could have hindered performance with the final
// indexing into `rest` that `decode_partial` does.
[direct_array_access]
fn decode_internal(data string) ?(Block, string) {
// direct_array_access safety: since we use the string.index method here,
// we won't get an invalid index since it would otherwise return `none`
mut rest := data[data.index(pem_begin)?..]
mut block := Block.new(rest[pem_begin.len..].all_before(pem_eol))
block.headers, rest = parse_headers(rest[pem_begin.len..].all_after(pem_eol).trim_left(' \n\t\v\f\r'))?

block_end_index := rest.index(pem_end)?
b64_data := rest[..block_end_index].replace_each(['\r', '', '\n', '', '\t', '', ' ', ''])

block_data_len := block_end_index / 4 * 3
block.data = []u8{len: block_data_len, cap: block_data_len + 3, init: 0}
decoded_len := base64.decode_in_buffer(&b64_data, &block.data[0])
block.data = block.data[..decoded_len]

return block, rest[rest.index(pem_end)? + pem_end.len..].all_after_first(pem_eol)
return block, rest
}

[direct_array_access]
fn parse_headers(block string) ?(map[string][]string, string) {
headers_str := block.all_before(pem_end).all_before('\n\n')

Expand Down
12 changes: 9 additions & 3 deletions vlib/crypto/pem/pem_test.v
Expand Up @@ -5,6 +5,7 @@ fn test_decode_rfc1421() {
for i in 0 .. pem.test_data_rfc1421.len {
decoded, rest := decode(pem.test_data_rfc1421[i]) or { Block{}, '' }
assert decoded == pem.expected_results_rfc1421[i]
assert decoded == decode_only(pem.test_data_rfc1421[i]) or { Block{} }
assert rest == ''
}
}
Expand All @@ -13,6 +14,7 @@ fn test_decode() {
for i in 0 .. pem.test_data.len {
decoded, rest := decode(pem.test_data[i]) or { Block{}, '' }
assert decoded == pem.expected_results[i]
assert decoded == decode_only(pem.test_data[i]) or { Block{} }
assert rest == pem.expected_rest[i]
}
}
Expand All @@ -23,6 +25,7 @@ fn test_encode_rfc1421() {
decoded, rest := decode(encoded) or { Block{}, '' }
assert rest == ''
assert decoded == pem.expected_results_rfc1421[i]
assert decoded == decode_only(encoded) or { Block{} }
}
}

Expand All @@ -32,6 +35,7 @@ fn test_encode() {
decoded, rest := decode(encoded) or { Block{}, '' }
assert rest == ''
assert decoded == pem.expected_results[i]
assert decoded == decode_only(encoded) or { Block{} }
}
}

Expand All @@ -41,15 +45,17 @@ fn test_encode_config() {
decoded, rest := decode(encoded) or { Block{}, '' }
assert rest == ''
assert decoded == pem.expected_results[i]
assert decoded == decode_only(encoded) or { Block{} }
}
}

fn test_decode_no_pem() {
for test in pem.test_data_no_pem {
if _, _ := decode(test) {
assert false, 'Block.decode_partial should return `none` on input without PEM data'
} else {
assert true
assert false, 'decode should return `none` on input without PEM data'
}
if _ := decode_only(test) {
assert false, 'decode_only should return `none` on input without PEM data'
}
}
}
Expand Down

0 comments on commit d66b097

Please sign in to comment.