diff --git a/vlib/encoding/base64/base64.v b/vlib/encoding/base64/base64.v index a7a838ee8a..cea93a4aec 100644 --- a/vlib/encoding/base64/base64.v +++ b/vlib/encoding/base64/base64.v @@ -16,7 +16,20 @@ const ( // decode decodes the base64 encoded `string` value passed in `data`. // Please note: If you need to decode many strings repeatedly, take a look at `decode_in_buffer`. // Example: assert base64.decode('ViBpbiBiYXNlIDY0') == 'V in base 64' -pub fn decode(data string) string { +pub fn decode(data string) []byte { + size := data.len * 3 / 4 + if size <= 0 { + return [] + } + unsafe { + buffer := malloc(size) + n := decode_in_buffer(data, buffer) + return array{element_size: 1, data: buffer, len: n, cap: size} + } +} + +// decode_str is the string variant of decode +pub fn decode_str(data string) string { size := data.len * 3 / 4 if size <= 0 { return '' @@ -27,41 +40,64 @@ pub fn decode(data string) string { } } -// encode encodes the `string` value passed in `data` to base64. +// encode encodes the `[]byte` value passed in `data` to base64. // Please note: base64 encoding returns a `string` that is ~ 4/3 larger than the input. // Please note: If you need to encode many strings repeatedly, take a look at `encode_in_buffer`. // Example: assert base64.encode('V in base 64') == 'ViBpbiBiYXNlIDY0' -pub fn encode(data string) string { - size := 4 * ((data.len + 2) / 3) +pub fn encode(data []byte) string { + return alloc_and_encode(data.data, data.len) +} + +// encode_str is the string variant of encode +pub fn encode_str(data string) string { + return alloc_and_encode(data.str, data.len) +} + +// alloc_and_encode is a private function that allocates and encodes data into a string +// Used by encode and encode_str +fn alloc_and_encode(src byteptr, len int) string { + size := 4 * ((len + 2) / 3) if size <= 0 { return '' } unsafe { buffer := malloc(size) - return tos(buffer, encode_in_buffer(data, buffer)) + return tos(buffer, encode_from_buffer(buffer, src, len)) } } -// decode_url returns a decoded URL `string` version of +// url_decode returns a decoded URL `string` version of // the a base64 url encoded `string` passed in `data`. -pub fn decode_url(data string) string { - mut result := data.replace('-', '+') // 62nd char of encoding - result = data.replace('_', '/') // 63rd char of encoding +pub fn url_decode(data string) []byte { + mut result := data.replace_each(['-', '+', '_', '/']) match result.len % 4 { // Pad with trailing '='s 2 { result += '==' } // 2 pad chars 3 { result += '=' } // 1 pad char else {} // no padding } - return decode(data) + return decode(result) } -// encode_url returns a base64 URL encoded `string` version +// url_decode_str is the string variant of url_decode +pub fn url_decode_str(data string) string { + mut result := data.replace_each(['-', '+', '_', '/']) + match result.len % 4 { // Pad with trailing '='s + 2 { result += '==' } // 2 pad chars + 3 { result += '=' } // 1 pad char + else {} // no padding + } + return decode_str(result) +} + +// url_encode returns a base64 URL encoded `string` version // of the value passed in `data`. -pub fn encode_url(data string) string { - mut result := encode(data) - // 62nd char of encoding, 63rd char of encoding, remove any trailing '='s - result = result.replace_each(['+', '-', '/', '_', '=', '']) - return result +pub fn url_encode(data []byte) string { + return encode(data).replace_each(['+', '-', '/', '_', '=', '']) +} + +// url_encode_str is the string variant of url_encode +pub fn url_encode_str(data string) string { + return encode_str(data).replace_each(['+', '-', '/', '_', '=', '']) } // decode_in_buffer decodes the base64 encoded `string` reference passed in `data` into `buffer`. @@ -123,25 +159,28 @@ pub fn decode_in_buffer(data &string, buffer byteptr) int { return output_length } -// encode_in_buffer base64 encodes the `string` reference passed in `data` into `buffer`. +// encode_in_buffer base64 encodes the `[]byte` passed in `data` into `buffer`. // encode_in_buffer returns the size of the encoded data in the buffer. // Please note: The buffer should be large enough (i.e. 4/3 of the data.len, or larger) to hold the encoded data. // Please note: The function does NOT allocate new memory, and is suitable for handling very large strings. -pub fn encode_in_buffer(data &string, buffer byteptr) int { - input_length := data.len +pub fn encode_in_buffer(data []byte, buffer byteptr) int { + return encode_from_buffer(buffer, data.data, data.len) +} + +// encode_from_buffer will perform encoding from any type of src buffer +// and write the bytes into `dest`. +// Please note: The `dest` buffer should be large enough (i.e. 4/3 of the src_len, or larger) to hold the encoded data. +// Please note: This function is for internal base64 encoding +fn encode_from_buffer(dest byteptr, src byteptr, src_len int) int { + input_length := src_len output_length := 4 * ((input_length + 2) / 3) mut i := 0 mut j := 0 - mut d := byteptr(0) - mut b := byteptr(0) - mut etable := byteptr(0) - unsafe { - d = data.str - b = buffer - etable = enc_table.str - } + mut d := src + mut b := dest + mut etable := byteptr(enc_table.str) for i < input_length { mut octet_a := 0 mut octet_b := 0 diff --git a/vlib/encoding/base64/base64_memory_test.v b/vlib/encoding/base64/base64_memory_test.v index cb85183fe2..a1ac47a59b 100644 --- a/vlib/encoding/base64/base64_memory_test.v +++ b/vlib/encoding/base64/base64_memory_test.v @@ -4,7 +4,7 @@ fn test_long_encoding() { repeats := 1000 input_size := 3000 - s_original := 'a'.repeat(input_size) + s_original := []byte{len: input_size, init: `a`} s_encoded := base64.encode(s_original) s_decoded := base64.decode(s_encoded) diff --git a/vlib/encoding/base64/base64_test.v b/vlib/encoding/base64/base64_test.v index 2206f688cc..b64467d8f5 100644 --- a/vlib/encoding/base64/base64_test.v +++ b/vlib/encoding/base64/base64_test.v @@ -40,15 +40,32 @@ const ( ) fn test_decode() { - assert base64.decode(man_pair.encoded) == man_pair.decoded + assert base64.decode(man_pair.encoded) == man_pair.decoded.bytes() // Test for incorrect padding. - assert base64.decode('aGk') == 'hi' - assert base64.decode('aGk=') == 'hi' - assert base64.decode('aGk==') == 'hi' + assert base64.decode('aGk') == 'hi'.bytes() + assert base64.decode('aGk=') == 'hi'.bytes() + assert base64.decode('aGk==') == 'hi'.bytes() for i, p in pairs { got := base64.decode(p.encoded) + if got != p.decoded.bytes() { + eprintln('pairs[${i}]: expected = ${p.decoded}, got = ${got}') + assert false + } + } +} + +fn test_decode_str() { + assert base64.decode_str(man_pair.encoded) == man_pair.decoded + + // Test for incorrect padding. + assert base64.decode_str('aGk') == 'hi' + assert base64.decode_str('aGk=') == 'hi' + assert base64.decode_str('aGk==') == 'hi' + + for i, p in pairs { + got := base64.decode_str(p.encoded) if got != p.decoded { eprintln('pairs[${i}]: expected = ${p.decoded}, got = ${got}') assert false @@ -57,10 +74,10 @@ fn test_decode() { } fn test_encode() { - assert base64.encode(man_pair.decoded) == man_pair.encoded + assert base64.encode(man_pair.decoded.bytes()) == man_pair.encoded for i, p in pairs { - got := base64.encode(p.decoded) + got := base64.encode(p.decoded.bytes()) if got != p.encoded { eprintln('pairs[${i}]: expected = ${p.encoded}, got = ${got}') assert false @@ -68,12 +85,54 @@ fn test_encode() { } } -fn test_encode_url() { - test := base64.encode_url('Hello Base64Url encoding!') +fn test_encode_str() { + assert base64.encode_str(man_pair.decoded) == man_pair.encoded + + for i, p in pairs { + got := base64.encode_str(p.decoded) + if got != p.encoded { + eprintln('pairs[${i}]: expected = ${p.encoded}, got = ${got}') + assert false + } + } +} + +fn test_url_encode() { + test := base64.url_encode('Hello Base64Url encoding!'.bytes()) assert test == 'SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ' } -fn test_decode_url() { - test := base64.decode_url("SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ") +fn test_url_encode_str() { + test := base64.url_encode_str('Hello Base64Url encoding!') + assert test == 'SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ' +} + +fn test_url_decode() { + test := base64.url_decode("SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ") + assert test == 'Hello Base64Url encoding!'.bytes() +} + +fn test_url_decode_str() { + test := base64.url_decode_str("SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ") assert test == 'Hello Base64Url encoding!' } + +fn test_encode_null_byte() { + assert base64.encode([byte(`A`) 0 `C`]) == 'QQBD' +} + +fn test_encode_null_byte_str() { + // While this works, bytestr() does a memcpy + s := [byte(`A`) 0 `C`].bytestr() + assert base64.encode_str(s) == 'QQBD' +} + +fn test_decode_null_byte() { + assert base64.decode('QQBD') == [byte(`A`) 0 `C`] +} + +fn test_decode_null_byte_str() { + // While this works, bytestr() does a memcpy + s := [byte(`A`) 0 `C`].bytestr() + assert base64.decode_str('QQBD') == s +} diff --git a/vlib/net/smtp/smtp.v b/vlib/net/smtp/smtp.v index 3f4ee79902..1755bb9c33 100644 --- a/vlib/net/smtp/smtp.v +++ b/vlib/net/smtp/smtp.v @@ -149,7 +149,7 @@ fn (mut c Client) send_auth() ? { sb.write_b(0) sb.write_string(c.password) a := sb.str() - auth := 'AUTH PLAIN ${base64.encode(a)}\r\n' + auth := 'AUTH PLAIN ${base64.encode_str(a)}\r\n' c.send_str(auth) ? c.expect_reply(.auth_ok) ? } diff --git a/vlib/x/websocket/handshake.v b/vlib/x/websocket/handshake.v index 9e15b13b0d..6fbe1373d1 100644 --- a/vlib/x/websocket/handshake.v +++ b/vlib/x/websocket/handshake.v @@ -7,7 +7,7 @@ import strings // handshake manages the websocket handshake process fn (mut ws Client) handshake() ? { nonce := get_nonce(ws.nonce_size) - seckey := base64.encode(nonce) + seckey := base64.encode_str(nonce) mut sb := strings.new_builder(1024) defer { unsafe { sb.free() } diff --git a/vlib/x/websocket/utils.v b/vlib/x/websocket/utils.v index ed26db6894..4e48359b06 100644 --- a/vlib/x/websocket/utils.v +++ b/vlib/x/websocket/utils.v @@ -35,7 +35,7 @@ fn create_key_challenge_response(seckey string) ?string { sha1buf := seckey + guid shabytes := sha1buf.bytes() hash := sha1.sum(shabytes) - b64 := base64.encode(unsafe { tos(hash.data, hash.len) }) + b64 := base64.encode(hash) unsafe { hash.free() shabytes.free()