diff --git a/doc/langref.html.in b/doc/langref.html.in index d3ca772aa6..e8f76e230b 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5414,8 +5414,9 @@ export fn decode_base_64(dest_ptr: &u8, dest_len: usize, { const src = source_ptr[0..source_len]; const dest = dest_ptr[0..dest_len]; - const decoded_size = base64.calcDecodedSizeExactUnsafe(src, base64.standard_pad_char); - base64.decodeExactUnsafe(dest[0..decoded_size], src, base64.standard_alphabet_unsafe); + const base64_decoder = base64.standard_decoder_unsafe; + const decoded_size = base64_decoder.calcSize(src); + base64_decoder.decode(dest[0..decoded_size], src); return decoded_size; } diff --git a/example/mix_o_files/base64.zig b/example/mix_o_files/base64.zig index a7cdc9d439..49c9bc6012 100644 --- a/example/mix_o_files/base64.zig +++ b/example/mix_o_files/base64.zig @@ -3,7 +3,8 @@ const base64 = @import("std").base64; export fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize { const src = source_ptr[0..source_len]; const dest = dest_ptr[0..dest_len]; - const decoded_size = base64.calcDecodedSizeExactUnsafe(src, base64.standard_pad_char); - base64.decodeExactUnsafe(dest[0..decoded_size], src, base64.standard_alphabet_unsafe); + const base64_decoder = base64.standard_decoder_unsafe; + const decoded_size = base64_decoder.calcSize(src); + base64_decoder.decode(dest[0..decoded_size], src); return decoded_size; } diff --git a/std/base64.zig b/std/base64.zig index 84b442212a..25e438c4fb 100644 --- a/std/base64.zig +++ b/std/base64.zig @@ -3,64 +3,85 @@ const mem = @import("mem.zig"); pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; pub const standard_pad_char = '='; +pub const standard_encoder = Base64Encoder.init(standard_alphabet_chars, standard_pad_char); -/// ceil(source_len * 4/3) -pub fn calcEncodedSize(source_len: usize) -> usize { - return @divTrunc(source_len + 2, 3) * 4; -} +pub const Base64Encoder = struct { + alphabet_chars: []const u8, + pad_char: u8, -/// dest.len must be what you get from ::calcEncodedSize. -/// It is assumed that alphabet_chars and pad_char are all unique characters. -pub fn encode(dest: []u8, source: []const u8, alphabet_chars: []const u8, pad_char: u8) { - assert(alphabet_chars.len == 64); - assert(dest.len == calcEncodedSize(source.len)); + /// a bunch of assertions, then simply pass the data right through. + pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64Encoder { + assert(alphabet_chars.len == 64); + var char_in_alphabet = []bool{false} ** 256; + for (alphabet_chars) |c| { + assert(!char_in_alphabet[c]); + assert(c != pad_char); + char_in_alphabet[c] = true; + } - var i: usize = 0; - var out_index: usize = 0; - while (i + 2 < source.len) : (i += 3) { - dest[out_index] = alphabet_chars[(source[i] >> 2) & 0x3f]; - out_index += 1; - - dest[out_index] = alphabet_chars[((source[i] & 0x3) << 4) | - ((source[i + 1] & 0xf0) >> 4)]; - out_index += 1; - - dest[out_index] = alphabet_chars[((source[i + 1] & 0xf) << 2) | - ((source[i + 2] & 0xc0) >> 6)]; - out_index += 1; - - dest[out_index] = alphabet_chars[source[i + 2] & 0x3f]; - out_index += 1; + return Base64Encoder{ + .alphabet_chars = alphabet_chars, + .pad_char = pad_char, + }; } - if (i < source.len) { - dest[out_index] = alphabet_chars[(source[i] >> 2) & 0x3f]; - out_index += 1; + /// ceil(source_len * 4/3) + pub fn calcSize(source_len: usize) -> usize { + return @divTrunc(source_len + 2, 3) * 4; + } - if (i + 1 == source.len) { - dest[out_index] = alphabet_chars[(source[i] & 0x3) << 4]; + /// dest.len must be what you get from ::calcSize. + pub fn encode(encoder: &const Base64Encoder, dest: []u8, source: []const u8) { + assert(dest.len == Base64Encoder.calcSize(source.len)); + + var i: usize = 0; + var out_index: usize = 0; + while (i + 2 < source.len) : (i += 3) { + dest[out_index] = encoder.alphabet_chars[(source[i] >> 2) & 0x3f]; out_index += 1; - dest[out_index] = pad_char; - out_index += 1; - } else { - dest[out_index] = alphabet_chars[((source[i] & 0x3) << 4) | + dest[out_index] = encoder.alphabet_chars[((source[i] & 0x3) << 4) | ((source[i + 1] & 0xf0) >> 4)]; out_index += 1; - dest[out_index] = alphabet_chars[(source[i + 1] & 0xf) << 2]; + dest[out_index] = encoder.alphabet_chars[((source[i + 1] & 0xf) << 2) | + ((source[i + 2] & 0xc0) >> 6)]; + out_index += 1; + + dest[out_index] = encoder.alphabet_chars[source[i + 2] & 0x3f]; out_index += 1; } - dest[out_index] = pad_char; - out_index += 1; + if (i < source.len) { + dest[out_index] = encoder.alphabet_chars[(source[i] >> 2) & 0x3f]; + out_index += 1; + + if (i + 1 == source.len) { + dest[out_index] = encoder.alphabet_chars[(source[i] & 0x3) << 4]; + out_index += 1; + + dest[out_index] = encoder.pad_char; + out_index += 1; + } else { + dest[out_index] = encoder.alphabet_chars[((source[i] & 0x3) << 4) | + ((source[i + 1] & 0xf0) >> 4)]; + out_index += 1; + + dest[out_index] = encoder.alphabet_chars[(source[i + 1] & 0xf) << 2]; + out_index += 1; + } + + dest[out_index] = encoder.pad_char; + out_index += 1; + } } -} +}; -pub const standard_alphabet = Base64Alphabet.init(standard_alphabet_chars, standard_pad_char); +pub const standard_decoder = Base64Decoder.init(standard_alphabet_chars, standard_pad_char); +error InvalidPadding; +error InvalidCharacter; -/// For use with ::decodeExact. -pub const Base64Alphabet = struct { +pub const Base64Decoder = struct { /// e.g. 'A' => 0. /// undefined for any value not in the 64 alphabet chars. char_to_index: [256]u8, @@ -68,10 +89,10 @@ pub const Base64Alphabet = struct { char_in_alphabet: [256]bool, pad_char: u8, - pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64Alphabet { + pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64Decoder { assert(alphabet_chars.len == 64); - var result = Base64Alphabet{ + var result = Base64Decoder{ .char_to_index = undefined, .char_in_alphabet = []bool{false} ** 256, .pad_char = pad_char, @@ -87,197 +108,193 @@ pub const Base64Alphabet = struct { return result; } -}; -error InvalidPadding; -/// For use with ::decodeExact. -/// If the encoded buffer is detected to be invalid, returns error.InvalidPadding. -pub fn calcDecodedSizeExact(encoded: []const u8, pad_char: u8) -> %usize { - if (encoded.len % 4 != 0) return error.InvalidPadding; - return calcDecodedSizeExactUnsafe(encoded, pad_char); -} - -error InvalidCharacter; -/// dest.len must be what you get from ::calcDecodedSizeExact. -/// invalid characters result in error.InvalidCharacter. -/// invalid padding results in error.InvalidPadding. -pub fn decodeExact(dest: []u8, source: []const u8, alphabet: &const Base64Alphabet) -> %void { - assert(dest.len == %%calcDecodedSizeExact(source, alphabet.pad_char)); - assert(source.len % 4 == 0); - - var src_cursor: usize = 0; - var dest_cursor: usize = 0; - - while (src_cursor < source.len) : (src_cursor += 4) { - if (!alphabet.char_in_alphabet[source[src_cursor + 0]]) return error.InvalidCharacter; - if (!alphabet.char_in_alphabet[source[src_cursor + 1]]) return error.InvalidCharacter; - if (src_cursor < source.len - 4 or source[src_cursor + 3] != alphabet.pad_char) { - // common case - if (!alphabet.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; - if (!alphabet.char_in_alphabet[source[src_cursor + 3]]) return error.InvalidCharacter; - dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 | - alphabet.char_to_index[source[src_cursor + 1]] >> 4; - dest[dest_cursor + 1] = alphabet.char_to_index[source[src_cursor + 1]] << 4 | - alphabet.char_to_index[source[src_cursor + 2]] >> 2; - dest[dest_cursor + 2] = alphabet.char_to_index[source[src_cursor + 2]] << 6 | - alphabet.char_to_index[source[src_cursor + 3]]; - dest_cursor += 3; - } else if (source[src_cursor + 2] != alphabet.pad_char) { - // one pad char - if (!alphabet.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; - dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 | - alphabet.char_to_index[source[src_cursor + 1]] >> 4; - dest[dest_cursor + 1] = alphabet.char_to_index[source[src_cursor + 1]] << 4 | - alphabet.char_to_index[source[src_cursor + 2]] >> 2; - if (alphabet.char_to_index[source[src_cursor + 2]] << 6 != 0) return error.InvalidPadding; - dest_cursor += 2; - } else { - // two pad chars - dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 | - alphabet.char_to_index[source[src_cursor + 1]] >> 4; - if (alphabet.char_to_index[source[src_cursor + 1]] << 4 != 0) return error.InvalidPadding; - dest_cursor += 1; - } + /// If the encoded buffer is detected to be invalid, returns error.InvalidPadding. + pub fn calcSize(decoder: &const Base64Decoder, source: []const u8) -> %usize { + if (source.len % 4 != 0) return error.InvalidPadding; + return calcDecodedSizeExactUnsafe(source, decoder.pad_char); } - assert(src_cursor == source.len); - assert(dest_cursor == dest.len); -} + /// dest.len must be what you get from ::calcSize. + /// invalid characters result in error.InvalidCharacter. + /// invalid padding results in error.InvalidPadding. + pub fn decode(decoder: &const Base64Decoder, dest: []u8, source: []const u8) -> %void { + assert(dest.len == %%decoder.calcSize(source)); + assert(source.len % 4 == 0); -/// For use with ::decodeWithIgnore. -pub const Base64AlphabetWithIgnore = struct { - alphabet: Base64Alphabet, + var src_cursor: usize = 0; + var dest_cursor: usize = 0; + + while (src_cursor < source.len) : (src_cursor += 4) { + if (!decoder.char_in_alphabet[source[src_cursor + 0]]) return error.InvalidCharacter; + if (!decoder.char_in_alphabet[source[src_cursor + 1]]) return error.InvalidCharacter; + if (src_cursor < source.len - 4 or source[src_cursor + 3] != decoder.pad_char) { + // common case + if (!decoder.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; + if (!decoder.char_in_alphabet[source[src_cursor + 3]]) return error.InvalidCharacter; + dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | + decoder.char_to_index[source[src_cursor + 1]] >> 4; + dest[dest_cursor + 1] = decoder.char_to_index[source[src_cursor + 1]] << 4 | + decoder.char_to_index[source[src_cursor + 2]] >> 2; + dest[dest_cursor + 2] = decoder.char_to_index[source[src_cursor + 2]] << 6 | + decoder.char_to_index[source[src_cursor + 3]]; + dest_cursor += 3; + } else if (source[src_cursor + 2] != decoder.pad_char) { + // one pad char + if (!decoder.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter; + dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | + decoder.char_to_index[source[src_cursor + 1]] >> 4; + dest[dest_cursor + 1] = decoder.char_to_index[source[src_cursor + 1]] << 4 | + decoder.char_to_index[source[src_cursor + 2]] >> 2; + if (decoder.char_to_index[source[src_cursor + 2]] << 6 != 0) return error.InvalidPadding; + dest_cursor += 2; + } else { + // two pad chars + dest[dest_cursor + 0] = decoder.char_to_index[source[src_cursor + 0]] << 2 | + decoder.char_to_index[source[src_cursor + 1]] >> 4; + if (decoder.char_to_index[source[src_cursor + 1]] << 4 != 0) return error.InvalidPadding; + dest_cursor += 1; + } + } + + assert(src_cursor == source.len); + assert(dest_cursor == dest.len); + } +}; + +error OutputTooSmall; + +pub const Base64DecoderWithIgnore = struct { + decoder: Base64Decoder, char_is_ignored: [256]bool, - pub fn init(alphabet_chars: []const u8, pad_char: u8, ignore_chars: []const u8) -> Base64AlphabetWithIgnore { - var result = Base64AlphabetWithIgnore { - .alphabet = Base64Alphabet.init(alphabet_chars, pad_char), + pub fn init(alphabet_chars: []const u8, pad_char: u8, ignore_chars: []const u8) -> Base64DecoderWithIgnore { + var result = Base64DecoderWithIgnore { + .decoder = Base64Decoder.init(alphabet_chars, pad_char), .char_is_ignored = []bool{false} ** 256, }; for (ignore_chars) |c| { - assert(!result.alphabet.char_in_alphabet[c]); + assert(!result.decoder.char_in_alphabet[c]); assert(!result.char_is_ignored[c]); - assert(result.alphabet.pad_char != c); + assert(result.decoder.pad_char != c); result.char_is_ignored[c] = true; } return result; } -}; -/// For use with ::decodeWithIgnore. -/// If no characters end up being ignored, this will be the exact decoded size. -pub fn calcDecodedSizeUpperBound(encoded_len: usize) -> %usize { - return @divTrunc(encoded_len, 4) * 3; -} - -error OutputTooSmall; -/// Invalid characters that are not ignored results in error.InvalidCharacter. -/// Invalid padding results in error.InvalidPadding. -/// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcDecodedSizeUpperBound. -/// Returns the number of bytes writen to dest. -pub fn decodeWithIgnore(dest: []u8, source: []const u8, alphabet_with_ignore: &const Base64AlphabetWithIgnore) -> %usize { - const alphabet = &const alphabet_with_ignore.alphabet; - - var src_cursor: usize = 0; - var dest_cursor: usize = 0; - - while (true) { - // get the next 4 chars, if available - var next_4_chars: [4]u8 = undefined; - var available_chars: usize = 0; - var pad_char_count: usize = 0; - while (available_chars < 4 and src_cursor < source.len) { - var c = source[src_cursor]; - src_cursor += 1; - - if (alphabet.char_in_alphabet[c]) { - // normal char - next_4_chars[available_chars] = c; - available_chars += 1; - } else if (alphabet_with_ignore.char_is_ignored[c]) { - // we're told to skip this one - continue; - } else if (c == alphabet.pad_char) { - // the padding has begun. count the pad chars. - pad_char_count += 1; - while (src_cursor < source.len) { - c = source[src_cursor]; - src_cursor += 1; - if (c == alphabet.pad_char) { - pad_char_count += 1; - if (pad_char_count > 2) return error.InvalidCharacter; - } else if (alphabet_with_ignore.char_is_ignored[c]) { - // we can even ignore chars during the padding - continue; - } else return error.InvalidCharacter; - } - break; - } else return error.InvalidCharacter; - } - - switch (available_chars) { - 4 => { - // common case - if (dest_cursor + 3 > dest.len) return error.OutputTooSmall; - assert(pad_char_count == 0); - dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 | - alphabet.char_to_index[next_4_chars[1]] >> 4; - dest[dest_cursor + 1] = alphabet.char_to_index[next_4_chars[1]] << 4 | - alphabet.char_to_index[next_4_chars[2]] >> 2; - dest[dest_cursor + 2] = alphabet.char_to_index[next_4_chars[2]] << 6 | - alphabet.char_to_index[next_4_chars[3]]; - dest_cursor += 3; - continue; - }, - 3 => { - if (dest_cursor + 2 > dest.len) return error.OutputTooSmall; - if (pad_char_count != 1) return error.InvalidPadding; - dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 | - alphabet.char_to_index[next_4_chars[1]] >> 4; - dest[dest_cursor + 1] = alphabet.char_to_index[next_4_chars[1]] << 4 | - alphabet.char_to_index[next_4_chars[2]] >> 2; - if (alphabet.char_to_index[next_4_chars[2]] << 6 != 0) return error.InvalidPadding; - dest_cursor += 2; - break; - }, - 2 => { - if (dest_cursor + 1 > dest.len) return error.OutputTooSmall; - if (pad_char_count != 2) return error.InvalidPadding; - dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 | - alphabet.char_to_index[next_4_chars[1]] >> 4; - if (alphabet.char_to_index[next_4_chars[1]] << 4 != 0) return error.InvalidPadding; - dest_cursor += 1; - break; - }, - 1 => { - return error.InvalidPadding; - }, - 0 => { - if (pad_char_count != 0) return error.InvalidPadding; - break; - }, - else => unreachable, - } + /// If no characters end up being ignored or padding, this will be the exact decoded size. + pub fn calcSizeUpperBound(encoded_len: usize) -> %usize { + return @divTrunc(encoded_len, 4) * 3; } - assert(src_cursor == source.len); + /// Invalid characters that are not ignored result in error.InvalidCharacter. + /// Invalid padding results in error.InvalidPadding. + /// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcSizeUpperBound. + /// Returns the number of bytes writen to dest. + pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) -> %usize { + const decoder = &const decoder_with_ignore.decoder; - return dest_cursor; -} + var src_cursor: usize = 0; + var dest_cursor: usize = 0; -pub const standard_alphabet_unsafe = Base64AlphabetUnsafe.init(standard_alphabet_chars, standard_pad_char); + while (true) { + // get the next 4 chars, if available + var next_4_chars: [4]u8 = undefined; + var available_chars: usize = 0; + var pad_char_count: usize = 0; + while (available_chars < 4 and src_cursor < source.len) { + var c = source[src_cursor]; + src_cursor += 1; -/// For use with ::decodeExactUnsafe. -pub const Base64AlphabetUnsafe = struct { + if (decoder.char_in_alphabet[c]) { + // normal char + next_4_chars[available_chars] = c; + available_chars += 1; + } else if (decoder_with_ignore.char_is_ignored[c]) { + // we're told to skip this one + continue; + } else if (c == decoder.pad_char) { + // the padding has begun. count the pad chars. + pad_char_count += 1; + while (src_cursor < source.len) { + c = source[src_cursor]; + src_cursor += 1; + if (c == decoder.pad_char) { + pad_char_count += 1; + if (pad_char_count > 2) return error.InvalidCharacter; + } else if (decoder_with_ignore.char_is_ignored[c]) { + // we can even ignore chars during the padding + continue; + } else return error.InvalidCharacter; + } + break; + } else return error.InvalidCharacter; + } + + switch (available_chars) { + 4 => { + // common case + if (dest_cursor + 3 > dest.len) return error.OutputTooSmall; + assert(pad_char_count == 0); + dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | + decoder.char_to_index[next_4_chars[1]] >> 4; + dest[dest_cursor + 1] = decoder.char_to_index[next_4_chars[1]] << 4 | + decoder.char_to_index[next_4_chars[2]] >> 2; + dest[dest_cursor + 2] = decoder.char_to_index[next_4_chars[2]] << 6 | + decoder.char_to_index[next_4_chars[3]]; + dest_cursor += 3; + continue; + }, + 3 => { + if (dest_cursor + 2 > dest.len) return error.OutputTooSmall; + if (pad_char_count != 1) return error.InvalidPadding; + dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | + decoder.char_to_index[next_4_chars[1]] >> 4; + dest[dest_cursor + 1] = decoder.char_to_index[next_4_chars[1]] << 4 | + decoder.char_to_index[next_4_chars[2]] >> 2; + if (decoder.char_to_index[next_4_chars[2]] << 6 != 0) return error.InvalidPadding; + dest_cursor += 2; + break; + }, + 2 => { + if (dest_cursor + 1 > dest.len) return error.OutputTooSmall; + if (pad_char_count != 2) return error.InvalidPadding; + dest[dest_cursor + 0] = decoder.char_to_index[next_4_chars[0]] << 2 | + decoder.char_to_index[next_4_chars[1]] >> 4; + if (decoder.char_to_index[next_4_chars[1]] << 4 != 0) return error.InvalidPadding; + dest_cursor += 1; + break; + }, + 1 => { + return error.InvalidPadding; + }, + 0 => { + if (pad_char_count != 0) return error.InvalidPadding; + break; + }, + else => unreachable, + } + } + + assert(src_cursor == source.len); + + return dest_cursor; + } +}; + + +pub const standard_decoder_unsafe = Base64DecoderUnsafe.init(standard_alphabet_chars, standard_pad_char); + +pub const Base64DecoderUnsafe = struct { /// e.g. 'A' => 0. /// undefined for any value not in the 64 alphabet chars. char_to_index: [256]u8, pad_char: u8, - pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64AlphabetUnsafe { + pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64DecoderUnsafe { assert(alphabet_chars.len == 64); - var result = Base64AlphabetUnsafe { + var result = Base64DecoderUnsafe { .char_to_index = undefined, .pad_char = pad_char, }; @@ -287,68 +304,72 @@ pub const Base64AlphabetUnsafe = struct { } return result; } + + /// The source buffer must be valid. + pub fn calcSize(decoder: &const Base64DecoderUnsafe, source: []const u8) -> usize { + return calcDecodedSizeExactUnsafe(source, decoder.pad_char); + } + + /// dest.len must be what you get from ::calcDecodedSizeExactUnsafe. + /// invalid characters or padding will result in undefined values. + pub fn decode(decoder: &const Base64DecoderUnsafe, dest: []u8, source: []const u8) { + assert(dest.len == decoder.calcSize(source)); + + var src_index: usize = 0; + var dest_index: usize = 0; + var in_buf_len: usize = source.len; + + while (in_buf_len > 0 and source[in_buf_len - 1] == decoder.pad_char) { + in_buf_len -= 1; + } + + while (in_buf_len > 4) { + dest[dest_index] = decoder.char_to_index[source[src_index + 0]] << 2 | + decoder.char_to_index[source[src_index + 1]] >> 4; + dest_index += 1; + + dest[dest_index] = decoder.char_to_index[source[src_index + 1]] << 4 | + decoder.char_to_index[source[src_index + 2]] >> 2; + dest_index += 1; + + dest[dest_index] = decoder.char_to_index[source[src_index + 2]] << 6 | + decoder.char_to_index[source[src_index + 3]]; + dest_index += 1; + + src_index += 4; + in_buf_len -= 4; + } + + if (in_buf_len > 1) { + dest[dest_index] = decoder.char_to_index[source[src_index + 0]] << 2 | + decoder.char_to_index[source[src_index + 1]] >> 4; + dest_index += 1; + } + if (in_buf_len > 2) { + dest[dest_index] = decoder.char_to_index[source[src_index + 1]] << 4 | + decoder.char_to_index[source[src_index + 2]] >> 2; + dest_index += 1; + } + if (in_buf_len > 3) { + dest[dest_index] = decoder.char_to_index[source[src_index + 2]] << 6 | + decoder.char_to_index[source[src_index + 3]]; + dest_index += 1; + } + } }; -/// For use with ::decodeExactUnsafe. -/// The encoded buffer must be valid. -pub fn calcDecodedSizeExactUnsafe(encoded: []const u8, pad_char: u8) -> usize { - if (encoded.len == 0) return 0; - var result = @divExact(encoded.len, 4) * 3; - if (encoded[encoded.len - 1] == pad_char) { +fn calcDecodedSizeExactUnsafe(source: []const u8, pad_char: u8) -> usize { + if (source.len == 0) return 0; + var result = @divExact(source.len, 4) * 3; + if (source[source.len - 1] == pad_char) { result -= 1; - if (encoded[encoded.len - 2] == pad_char) { + if (source[source.len - 2] == pad_char) { result -= 1; } } return result; } -/// dest.len must be what you get from ::calcDecodedSizeExactUnsafe. -/// invalid characters or padding will result in undefined values. -pub fn decodeExactUnsafe(dest: []u8, source: []const u8, alphabet: &const Base64AlphabetUnsafe) { - assert(dest.len == calcDecodedSizeExactUnsafe(source, alphabet.pad_char)); - - var src_index: usize = 0; - var dest_index: usize = 0; - var in_buf_len: usize = source.len; - - while (in_buf_len > 0 and source[in_buf_len - 1] == alphabet.pad_char) { - in_buf_len -= 1; - } - - while (in_buf_len > 4) { - dest[dest_index] = alphabet.char_to_index[source[src_index + 0]] << 2 | - alphabet.char_to_index[source[src_index + 1]] >> 4; - dest_index += 1; - - dest[dest_index] = alphabet.char_to_index[source[src_index + 1]] << 4 | - alphabet.char_to_index[source[src_index + 2]] >> 2; - dest_index += 1; - - dest[dest_index] = alphabet.char_to_index[source[src_index + 2]] << 6 | - alphabet.char_to_index[source[src_index + 3]]; - dest_index += 1; - - src_index += 4; - in_buf_len -= 4; - } - - if (in_buf_len > 1) { - dest[dest_index] = alphabet.char_to_index[source[src_index + 0]] << 2 | - alphabet.char_to_index[source[src_index + 1]] >> 4; - dest_index += 1; - } - if (in_buf_len > 2) { - dest[dest_index] = alphabet.char_to_index[source[src_index + 1]] << 4 | - alphabet.char_to_index[source[src_index + 2]] >> 2; - dest_index += 1; - } - if (in_buf_len > 3) { - dest[dest_index] = alphabet.char_to_index[source[src_index + 2]] << 6 | - alphabet.char_to_index[source[src_index + 3]]; - dest_index += 1; - } -} test "base64" { @setEvalBranchQuota(5000); @@ -391,74 +412,74 @@ fn testBase64() -> %void { } fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) -> %void { - // encode + // Base64Encoder { var buffer: [0x100]u8 = undefined; - var encoded = buffer[0..calcEncodedSize(expected_decoded.len)]; - encode(encoded, expected_decoded, standard_alphabet_chars, standard_pad_char); + var encoded = buffer[0..Base64Encoder.calcSize(expected_decoded.len)]; + standard_encoder.encode(encoded, expected_decoded); assert(mem.eql(u8, encoded, expected_encoded)); } - // decodeExact + // Base64Decoder { var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..%return calcDecodedSizeExact(expected_encoded, standard_pad_char)]; - %return decodeExact(decoded, expected_encoded, standard_alphabet); + var decoded = buffer[0..%return standard_decoder.calcSize(expected_encoded)]; + %return standard_decoder.decode(decoded, expected_encoded); assert(mem.eql(u8, decoded, expected_decoded)); } - // decodeWithIgnore + // Base64DecoderWithIgnore { - const standard_alphabet_ignore_nothing = Base64AlphabetWithIgnore.init( + const standard_decoder_ignore_nothing = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, ""); var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..%return calcDecodedSizeUpperBound(expected_encoded.len)]; - var written = %return decodeWithIgnore(decoded, expected_encoded, standard_alphabet_ignore_nothing); + var decoded = buffer[0..%return Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)]; + var written = %return standard_decoder_ignore_nothing.decode(decoded, expected_encoded); assert(written <= decoded.len); assert(mem.eql(u8, decoded[0..written], expected_decoded)); } - // decodeExactUnsafe + // Base64DecoderUnsafe { var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..calcDecodedSizeExactUnsafe(expected_encoded, standard_pad_char)]; - decodeExactUnsafe(decoded, expected_encoded, standard_alphabet_unsafe); + var decoded = buffer[0..standard_decoder_unsafe.calcSize(expected_encoded)]; + standard_decoder_unsafe.decode(decoded, expected_encoded); assert(mem.eql(u8, decoded, expected_decoded)); } } fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) -> %void { - const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init( + const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..%return calcDecodedSizeUpperBound(encoded.len)]; - var written = %return decodeWithIgnore(decoded, encoded, standard_alphabet_ignore_space); + var decoded = buffer[0..%return Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)]; + var written = %return standard_decoder_ignore_space.decode(decoded, encoded); assert(mem.eql(u8, decoded[0..written], expected_decoded)); } error ExpectedError; fn testError(encoded: []const u8, expected_err: error) -> %void { - const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init( + const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; - if (calcDecodedSizeExact(encoded, standard_pad_char)) |decoded_size| { + if (standard_decoder.calcSize(encoded)) |decoded_size| { var decoded = buffer[0..decoded_size]; - if (decodeExact(decoded, encoded, standard_alphabet)) |_| { + if (standard_decoder.decode(decoded, encoded)) |_| { return error.ExpectedError; } else |err| if (err != expected_err) return err; } else |err| if (err != expected_err) return err; - if (decodeWithIgnore(buffer[0..], encoded, standard_alphabet_ignore_space)) |_| { + if (standard_decoder_ignore_space.decode(buffer[0..], encoded)) |_| { return error.ExpectedError; } else |err| if (err != expected_err) return err; } fn testOutputTooSmallError(encoded: []const u8) -> %void { - const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init( + const standard_decoder_ignore_space = Base64DecoderWithIgnore.init( standard_alphabet_chars, standard_pad_char, " "); var buffer: [0x100]u8 = undefined; var decoded = buffer[0..calcDecodedSizeExactUnsafe(encoded, standard_pad_char) - 1]; - if (decodeWithIgnore(decoded, encoded, standard_alphabet_ignore_space)) |_| { + if (standard_decoder_ignore_space.decode(decoded, encoded)) |_| { return error.ExpectedError; } else |err| if (err != error.OutputTooSmall) return err; } diff --git a/std/os/index.zig b/std/os/index.zig index 872564224c..e6a5fc4d15 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -622,7 +622,9 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: } // here we replace the standard +/ with -_ so that it can be used in a file name -const b64_fs_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const b64_fs_encoder = base64.Base64Encoder.init( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + base64.standard_pad_char); pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void { if (symLink(allocator, existing_path, new_path)) { @@ -634,12 +636,12 @@ pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: } var rand_buf: [12]u8 = undefined; - const tmp_path = %return allocator.alloc(u8, new_path.len + base64.calcEncodedSize(rand_buf.len)); + const tmp_path = %return allocator.alloc(u8, new_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); defer allocator.free(tmp_path); mem.copy(u8, tmp_path[0..], new_path); while (true) { %return getRandomBytes(rand_buf[0..]); - base64.encode(tmp_path[new_path.len..], rand_buf, b64_fs_alphabet_chars, base64.standard_pad_char); + b64_fs_encoder.encode(tmp_path[new_path.len..], rand_buf); if (symLink(allocator, existing_path, tmp_path)) { return rename(allocator, tmp_path, new_path); } else |err| { @@ -717,11 +719,11 @@ pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []con /// Guaranteed to be atomic. pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: usize) -> %void { var rand_buf: [12]u8 = undefined; - const tmp_path = %return allocator.alloc(u8, dest_path.len + base64.calcEncodedSize(rand_buf.len)); + const tmp_path = %return allocator.alloc(u8, dest_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); defer allocator.free(tmp_path); mem.copy(u8, tmp_path[0..], dest_path); %return getRandomBytes(rand_buf[0..]); - base64.encode(tmp_path[dest_path.len..], rand_buf, b64_fs_alphabet_chars, base64.standard_pad_char); + b64_fs_encoder.encode(tmp_path[dest_path.len..], rand_buf); var out_file = %return io.File.openWriteMode(tmp_path, mode, allocator); defer out_file.close();