authorgravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2017-11-17 23:42:21-07:00
committergravatar for thejoshwolfe@gmail.comJosh Wolfe <thejoshwolfe@gmail.com> 2017-11-20 23:26:45-07:00
loga44283b0b2e585d7e15d7c8e6574411b75c12a0a
treeeb65022cc018de1061d44e030e4ce1287b991041
parent339d48ac1558dcd1977574372becd21f7fc4a075

rework std.base64 api

* rename decode to decodeExactUnsafe. * add decodeExact, which checks for invalid chars and padding. * add decodeWithIgnore, which also allows ignoring chars. * alphabets are supplied to the decoders with their char-to-index mapping already built, which enables it to be done at comptime. * all decode/encode apis except decodeWithIgnore require dest to be the exactly correct length. This is calculated by a calc function corresponding to each api. These apis no longer return the dest parameter. * for decodeWithIgnore, an exact size cannot be known a priori. Instead, a calc function gives an upperbound, and a runtime error is returned in case of overflow. decodeWithIgnore returns the number of bytes written to dest. closes #611

4 files changed, 387 insertions(+), 104 deletions(-)

doc/langref.html.in+7-4
......@@ -5412,10 +5412,13 @@ const c = @cImport({
54125412export fn decode_base_64(dest_ptr: &amp;u8, dest_len: usize,
54135413 source_ptr: &amp;const u8, source_len: usize) -&gt; usize
54145414{
5415 const src = source_ptr[0...source_len];
5416 const dest = dest_ptr[0...dest_len];
5417 return base64.decode(dest, src).len;
5418}</code></pre>
5415 const src = source_ptr[0..source_len];
5416 const dest = dest_ptr[0..dest_len];
5417 const decoded_size = base64.calcDecodedSizeExactUnsafe(src, base64.standard_pad_char);
5418 base64.decodeExactUnsafe(dest[0..decoded_size], src, base64.standard_alphabet_unsafe);
5419 return decoded_size;
5420}
5421</code></pre>
54195422 <h4>test.c</h4>
54205423 <pre><code class="c">// This header is generated by zig from base64.zig
54215424#include "base64.h"
example/mix_o_files/base64.zig+3-1
......@@ -3,5 +3,7 @@ const base64 = @import("std").base64;
33export fn decode_base_64(dest_ptr: &u8, dest_len: usize, source_ptr: &const u8, source_len: usize) -> usize {
44 const src = source_ptr[0..source_len];
55 const dest = dest_ptr[0..dest_len];
6 return base64.decode(dest, src).len;
6 const decoded_size = base64.calcDecodedSizeExactUnsafe(src, base64.standard_pad_char);
7 base64.decodeExactUnsafe(dest[0..decoded_size], src, base64.standard_alphabet_unsafe);
8 return decoded_size;
79}
std/base64.zig+374-96
......@@ -1,100 +1,332 @@
11const assert = @import("debug.zig").assert;
22const mem = @import("mem.zig");
33
4pub const standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
4pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5pub const standard_pad_char = '=';
56
6pub fn encode(dest: []u8, source: []const u8) -> []u8 {
7 return encodeWithAlphabet(dest, source, standard_alphabet);
8}
9
10/// invalid characters in source are allowed, but they cause the value of dest to be undefined.
11pub fn decode(dest: []u8, source: []const u8) -> []u8 {
12 return decodeWithAlphabet(dest, source, standard_alphabet);
7/// ceil(source_len * 4/3)
8pub fn calcEncodedSize(source_len: usize) -> usize {
9 return @divTrunc(source_len + 2, 3) * 4;
1310}
1411
15pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
16 assert(alphabet.len == 65);
17 assert(dest.len >= calcEncodedSize(source.len));
12/// dest.len must be what you get from ::calcEncodedSize.
13/// It is assumed that alphabet_chars and pad_char are all unique characters.
14pub fn encode(dest: []u8, source: []const u8, alphabet_chars: []const u8, pad_char: u8) {
15 assert(alphabet_chars.len == 64);
16 assert(dest.len == calcEncodedSize(source.len));
1817
1918 var i: usize = 0;
2019 var out_index: usize = 0;
2120 while (i + 2 < source.len) : (i += 3) {
22 dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
21 dest[out_index] = alphabet_chars[(source[i] >> 2) & 0x3f];
2322 out_index += 1;
2423
25 dest[out_index] = alphabet[((source[i] & 0x3) << 4) |
24 dest[out_index] = alphabet_chars[((source[i] & 0x3) << 4) |
2625 ((source[i + 1] & 0xf0) >> 4)];
2726 out_index += 1;
2827
29 dest[out_index] = alphabet[((source[i + 1] & 0xf) << 2) |
28 dest[out_index] = alphabet_chars[((source[i + 1] & 0xf) << 2) |
3029 ((source[i + 2] & 0xc0) >> 6)];
3130 out_index += 1;
3231
33 dest[out_index] = alphabet[source[i + 2] & 0x3f];
32 dest[out_index] = alphabet_chars[source[i + 2] & 0x3f];
3433 out_index += 1;
3534 }
3635
3736 if (i < source.len) {
38 dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
37 dest[out_index] = alphabet_chars[(source[i] >> 2) & 0x3f];
3938 out_index += 1;
4039
4140 if (i + 1 == source.len) {
42 dest[out_index] = alphabet[(source[i] & 0x3) << 4];
41 dest[out_index] = alphabet_chars[(source[i] & 0x3) << 4];
4342 out_index += 1;
4443
45 dest[out_index] = alphabet[64];
44 dest[out_index] = pad_char;
4645 out_index += 1;
4746 } else {
48 dest[out_index] = alphabet[((source[i] & 0x3) << 4) |
47 dest[out_index] = alphabet_chars[((source[i] & 0x3) << 4) |
4948 ((source[i + 1] & 0xf0) >> 4)];
5049 out_index += 1;
5150
52 dest[out_index] = alphabet[(source[i + 1] & 0xf) << 2];
51 dest[out_index] = alphabet_chars[(source[i + 1] & 0xf) << 2];
5352 out_index += 1;
5453 }
5554
56 dest[out_index] = alphabet[64];
55 dest[out_index] = pad_char;
5756 out_index += 1;
5857 }
58}
59
60pub const standard_alphabet = Base64Alphabet.init(standard_alphabet_chars, standard_pad_char);
61
62/// For use with ::decodeExact.
63pub const Base64Alphabet = struct {
64 /// e.g. 'A' => 0.
65 /// undefined for any value not in the 64 alphabet chars.
66 char_to_index: [256]u8,
67 /// true only for the 64 chars in the alphabet, not the pad char.
68 char_in_alphabet: [256]bool,
69 pad_char: u8,
70
71 pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64Alphabet {
72 assert(alphabet_chars.len == 64);
73
74 var result = Base64Alphabet{
75 .char_to_index = undefined,
76 .char_in_alphabet = []bool{false} ** 256,
77 .pad_char = pad_char,
78 };
79
80 for (alphabet_chars) |c, i| {
81 assert(!result.char_in_alphabet[c]);
82 assert(c != pad_char);
83
84 result.char_to_index[c] = u8(i);
85 result.char_in_alphabet[c] = true;
86 }
87
88 return result;
89 }
90};
91
92error InvalidPadding;
93/// For use with ::decodeExact.
94/// If the encoded buffer is detected to be invalid, returns error.InvalidPadding.
95pub fn calcDecodedSizeExact(encoded: []const u8, pad_char: u8) -> %usize {
96 if (encoded.len % 4 != 0) return error.InvalidPadding;
97 return calcDecodedSizeExactUnsafe(encoded, pad_char);
98}
99
100error InvalidCharacter;
101/// dest.len must be what you get from ::calcDecodedSizeExact.
102/// invalid characters result in error.InvalidCharacter.
103/// invalid padding results in error.InvalidPadding.
104pub fn decodeExact(dest: []u8, source: []const u8, alphabet: &const Base64Alphabet) -> %void {
105 assert(dest.len == %%calcDecodedSizeExact(source, alphabet.pad_char));
106 assert(source.len % 4 == 0);
107
108 var src_cursor: usize = 0;
109 var dest_cursor: usize = 0;
110
111 while (src_cursor < source.len) : (src_cursor += 4) {
112 if (!alphabet.char_in_alphabet[source[src_cursor + 0]]) return error.InvalidCharacter;
113 if (!alphabet.char_in_alphabet[source[src_cursor + 1]]) return error.InvalidCharacter;
114 if (src_cursor < source.len - 4 or source[src_cursor + 3] != alphabet.pad_char) {
115 // common case
116 if (!alphabet.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter;
117 if (!alphabet.char_in_alphabet[source[src_cursor + 3]]) return error.InvalidCharacter;
118 dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 |
119 alphabet.char_to_index[source[src_cursor + 1]] >> 4;
120 dest[dest_cursor + 1] = alphabet.char_to_index[source[src_cursor + 1]] << 4 |
121 alphabet.char_to_index[source[src_cursor + 2]] >> 2;
122 dest[dest_cursor + 2] = alphabet.char_to_index[source[src_cursor + 2]] << 6 |
123 alphabet.char_to_index[source[src_cursor + 3]];
124 dest_cursor += 3;
125 } else if (source[src_cursor + 2] != alphabet.pad_char) {
126 // one pad char
127 if (!alphabet.char_in_alphabet[source[src_cursor + 2]]) return error.InvalidCharacter;
128 dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 |
129 alphabet.char_to_index[source[src_cursor + 1]] >> 4;
130 dest[dest_cursor + 1] = alphabet.char_to_index[source[src_cursor + 1]] << 4 |
131 alphabet.char_to_index[source[src_cursor + 2]] >> 2;
132 if (alphabet.char_to_index[source[src_cursor + 2]] << 6 != 0) return error.InvalidPadding;
133 dest_cursor += 2;
134 } else {
135 // two pad chars
136 dest[dest_cursor + 0] = alphabet.char_to_index[source[src_cursor + 0]] << 2 |
137 alphabet.char_to_index[source[src_cursor + 1]] >> 4;
138 if (alphabet.char_to_index[source[src_cursor + 1]] << 4 != 0) return error.InvalidPadding;
139 dest_cursor += 1;
140 }
141 }
59142
60 return dest[0..out_index];
143 assert(src_cursor == source.len);
144 assert(dest_cursor == dest.len);
61145}
62146
63/// invalid characters in source are allowed, but they cause the value of dest to be undefined.
64pub fn decodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
65 assert(alphabet.len == 65);
147/// For use with ::decodeWithIgnore.
148pub const Base64AlphabetWithIgnore = struct {
149 alphabet: Base64Alphabet,
150 char_is_ignored: [256]bool,
151 pub fn init(alphabet_chars: []const u8, pad_char: u8, ignore_chars: []const u8) -> Base64AlphabetWithIgnore {
152 var result = Base64AlphabetWithIgnore {
153 .alphabet = Base64Alphabet.init(alphabet_chars, pad_char),
154 .char_is_ignored = []bool{false} ** 256,
155 };
156
157 for (ignore_chars) |c| {
158 assert(!result.alphabet.char_in_alphabet[c]);
159 assert(!result.char_is_ignored[c]);
160 assert(result.alphabet.pad_char != c);
161 result.char_is_ignored[c] = true;
162 }
66163
67 var ascii6 = []u8{64} ** 256;
68 for (alphabet) |c, i| {
69 ascii6[c] = u8(i);
164 return result;
70165 }
166};
71167
72 return decodeWithAscii6BitMap(dest, source, ascii6[0..], alphabet[64]);
168/// For use with ::decodeWithIgnore.
169/// If no characters end up being ignored, this will be the exact decoded size.
170pub fn calcDecodedSizeUpperBound(encoded_len: usize) -> %usize {
171 return @divTrunc(encoded_len, 4) * 3;
73172}
74173
75pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8, pad_char: u8) -> []u8 {
76 assert(ascii6.len == 256);
77 assert(dest.len >= calcExactDecodedSizeWithPadChar(source, pad_char));
174error OutputTooSmall;
175/// Invalid characters that are not ignored results in error.InvalidCharacter.
176/// Invalid padding results in error.InvalidPadding.
177/// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcDecodedSizeUpperBound.
178/// Returns the number of bytes writen to dest.
179pub fn decodeWithIgnore(dest: []u8, source: []const u8, alphabet_with_ignore: &const Base64AlphabetWithIgnore) -> %usize {
180 const alphabet = &const alphabet_with_ignore.alphabet;
181
182 var src_cursor: usize = 0;
183 var dest_cursor: usize = 0;
184
185 while (true) {
186 // get the next 4 chars, if available
187 var next_4_chars: [4]u8 = undefined;
188 var available_chars: usize = 0;
189 var pad_char_count: usize = 0;
190 while (available_chars < 4 and src_cursor < source.len) {
191 var c = source[src_cursor];
192 src_cursor += 1;
193
194 if (alphabet.char_in_alphabet[c]) {
195 // normal char
196 next_4_chars[available_chars] = c;
197 available_chars += 1;
198 } else if (alphabet_with_ignore.char_is_ignored[c]) {
199 // we're told to skip this one
200 continue;
201 } else if (c == alphabet.pad_char) {
202 // the padding has begun. count the pad chars.
203 pad_char_count += 1;
204 while (src_cursor < source.len) {
205 c = source[src_cursor];
206 src_cursor += 1;
207 if (c == alphabet.pad_char) {
208 pad_char_count += 1;
209 if (pad_char_count > 2) return error.InvalidCharacter;
210 } else if (alphabet_with_ignore.char_is_ignored[c]) {
211 // we can even ignore chars during the padding
212 continue;
213 } else return error.InvalidCharacter;
214 }
215 break;
216 } else return error.InvalidCharacter;
217 }
218
219 switch (available_chars) {
220 4 => {
221 // common case
222 if (dest_cursor + 3 > dest.len) return error.OutputTooSmall;
223 assert(pad_char_count == 0);
224 dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 |
225 alphabet.char_to_index[next_4_chars[1]] >> 4;
226 dest[dest_cursor + 1] = alphabet.char_to_index[next_4_chars[1]] << 4 |
227 alphabet.char_to_index[next_4_chars[2]] >> 2;
228 dest[dest_cursor + 2] = alphabet.char_to_index[next_4_chars[2]] << 6 |
229 alphabet.char_to_index[next_4_chars[3]];
230 dest_cursor += 3;
231 continue;
232 },
233 3 => {
234 if (dest_cursor + 2 > dest.len) return error.OutputTooSmall;
235 if (pad_char_count != 1) return error.InvalidPadding;
236 dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 |
237 alphabet.char_to_index[next_4_chars[1]] >> 4;
238 dest[dest_cursor + 1] = alphabet.char_to_index[next_4_chars[1]] << 4 |
239 alphabet.char_to_index[next_4_chars[2]] >> 2;
240 if (alphabet.char_to_index[next_4_chars[2]] << 6 != 0) return error.InvalidPadding;
241 dest_cursor += 2;
242 break;
243 },
244 2 => {
245 if (dest_cursor + 1 > dest.len) return error.OutputTooSmall;
246 if (pad_char_count != 2) return error.InvalidPadding;
247 dest[dest_cursor + 0] = alphabet.char_to_index[next_4_chars[0]] << 2 |
248 alphabet.char_to_index[next_4_chars[1]] >> 4;
249 if (alphabet.char_to_index[next_4_chars[1]] << 4 != 0) return error.InvalidPadding;
250 dest_cursor += 1;
251 break;
252 },
253 1 => {
254 return error.InvalidPadding;
255 },
256 0 => {
257 if (pad_char_count != 0) return error.InvalidPadding;
258 break;
259 },
260 else => unreachable,
261 }
262 }
263
264 assert(src_cursor == source.len);
265
266 return dest_cursor;
267}
268
269pub const standard_alphabet_unsafe = Base64AlphabetUnsafe.init(standard_alphabet_chars, standard_pad_char);
270
271/// For use with ::decodeExactUnsafe.
272pub const Base64AlphabetUnsafe = struct {
273 /// e.g. 'A' => 0.
274 /// undefined for any value not in the 64 alphabet chars.
275 char_to_index: [256]u8,
276 pad_char: u8,
277
278 pub fn init(alphabet_chars: []const u8, pad_char: u8) -> Base64AlphabetUnsafe {
279 assert(alphabet_chars.len == 64);
280 var result = Base64AlphabetUnsafe {
281 .char_to_index = undefined,
282 .pad_char = pad_char,
283 };
284 for (alphabet_chars) |c, i| {
285 assert(c != pad_char);
286 result.char_to_index[c] = u8(i);
287 }
288 return result;
289 }
290};
291
292/// For use with ::decodeExactUnsafe.
293/// The encoded buffer must be valid.
294pub fn calcDecodedSizeExactUnsafe(encoded: []const u8, pad_char: u8) -> usize {
295 if (encoded.len == 0) return 0;
296 var result = @divExact(encoded.len, 4) * 3;
297 if (encoded[encoded.len - 1] == pad_char) {
298 result -= 1;
299 if (encoded[encoded.len - 2] == pad_char) {
300 result -= 1;
301 }
302 }
303 return result;
304}
305
306/// dest.len must be what you get from ::calcDecodedSizeExactUnsafe.
307/// invalid characters or padding will result in undefined values.
308pub fn decodeExactUnsafe(dest: []u8, source: []const u8, alphabet: &const Base64AlphabetUnsafe) {
309 assert(dest.len == calcDecodedSizeExactUnsafe(source, alphabet.pad_char));
78310
79311 var src_index: usize = 0;
80312 var dest_index: usize = 0;
81313 var in_buf_len: usize = source.len;
82314
83 while (in_buf_len > 0 and source[in_buf_len - 1] == pad_char) {
315 while (in_buf_len > 0 and source[in_buf_len - 1] == alphabet.pad_char) {
84316 in_buf_len -= 1;
85317 }
86318
87319 while (in_buf_len > 4) {
88 dest[dest_index] = ascii6[source[src_index + 0]] << 2 |
89 ascii6[source[src_index + 1]] >> 4;
320 dest[dest_index] = alphabet.char_to_index[source[src_index + 0]] << 2 |
321 alphabet.char_to_index[source[src_index + 1]] >> 4;
90322 dest_index += 1;
91323
92 dest[dest_index] = ascii6[source[src_index + 1]] << 4 |
93 ascii6[source[src_index + 2]] >> 2;
324 dest[dest_index] = alphabet.char_to_index[source[src_index + 1]] << 4 |
325 alphabet.char_to_index[source[src_index + 2]] >> 2;
94326 dest_index += 1;
95327
96 dest[dest_index] = ascii6[source[src_index + 2]] << 6 |
97 ascii6[source[src_index + 3]];
328 dest[dest_index] = alphabet.char_to_index[source[src_index + 2]] << 6 |
329 alphabet.char_to_index[source[src_index + 3]];
98330 dest_index += 1;
99331
100332 src_index += 4;
......@@ -102,85 +334,131 @@ pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8
102334 }
103335
104336 if (in_buf_len > 1) {
105 dest[dest_index] = ascii6[source[src_index + 0]] << 2 |
106 ascii6[source[src_index + 1]] >> 4;
337 dest[dest_index] = alphabet.char_to_index[source[src_index + 0]] << 2 |
338 alphabet.char_to_index[source[src_index + 1]] >> 4;
107339 dest_index += 1;
108340 }
109341 if (in_buf_len > 2) {
110 dest[dest_index] = ascii6[source[src_index + 1]] << 4 |
111 ascii6[source[src_index + 2]] >> 2;
342 dest[dest_index] = alphabet.char_to_index[source[src_index + 1]] << 4 |
343 alphabet.char_to_index[source[src_index + 2]] >> 2;
112344 dest_index += 1;
113345 }
114346 if (in_buf_len > 3) {
115 dest[dest_index] = ascii6[source[src_index + 2]] << 6 |
116 ascii6[source[src_index + 3]];
347 dest[dest_index] = alphabet.char_to_index[source[src_index + 2]] << 6 |
348 alphabet.char_to_index[source[src_index + 3]];
117349 dest_index += 1;
118350 }
119
120 return dest[0..dest_index];
121351}
122352
123pub fn calcEncodedSize(source_len: usize) -> usize {
124 return (((source_len * 4) / 3 + 3) / 4) * 4;
125}
126
127/// Computes the upper bound of the decoded size based only on the encoded length.
128/// To compute the exact decoded size, see ::calcExactDecodedSize
129pub fn calcMaxDecodedSize(encoded_len: usize) -> usize {
130 return @divExact(encoded_len * 3, 4);
353test "base64" {
354 @setEvalBranchQuota(5000);
355 %%testBase64();
356 comptime %%testBase64();
131357}
132358
133/// Computes the number of decoded bytes there will be. This function must
134/// be given the encoded buffer because there might be padding
135/// bytes at the end ('=' in the standard alphabet)
136pub fn calcExactDecodedSize(encoded: []const u8) -> usize {
137 return calcExactDecodedSizeWithAlphabet(encoded, standard_alphabet);
359fn testBase64() -> %void {
360 %return testAllApis("", "");
361 %return testAllApis("f", "Zg==");
362 %return testAllApis("fo", "Zm8=");
363 %return testAllApis("foo", "Zm9v");
364 %return testAllApis("foob", "Zm9vYg==");
365 %return testAllApis("fooba", "Zm9vYmE=");
366 %return testAllApis("foobar", "Zm9vYmFy");
367
368 %return testDecodeIgnoreSpace("", " ");
369 %return testDecodeIgnoreSpace("f", "Z g= =");
370 %return testDecodeIgnoreSpace("fo", " Zm8=");
371 %return testDecodeIgnoreSpace("foo", "Zm9v ");
372 %return testDecodeIgnoreSpace("foob", "Zm9vYg = = ");
373 %return testDecodeIgnoreSpace("fooba", "Zm9v YmE=");
374 %return testDecodeIgnoreSpace("foobar", " Z m 9 v Y m F y ");
375
376 // test getting some api errors
377 %return testError("A", error.InvalidPadding);
378 %return testError("AA", error.InvalidPadding);
379 %return testError("AAA", error.InvalidPadding);
380 %return testError("A..A", error.InvalidCharacter);
381 %return testError("AA=A", error.InvalidCharacter);
382 %return testError("AA/=", error.InvalidPadding);
383 %return testError("A/==", error.InvalidPadding);
384 %return testError("A===", error.InvalidCharacter);
385 %return testError("====", error.InvalidCharacter);
386
387 %return testOutputTooSmallError("AA==");
388 %return testOutputTooSmallError("AAA=");
389 %return testOutputTooSmallError("AAAA");
390 %return testOutputTooSmallError("AAAAAA==");
138391}
139392
140pub fn calcExactDecodedSizeWithAlphabet(encoded: []const u8, alphabet: []const u8) -> usize {
141 assert(alphabet.len == 65);
142 return calcExactDecodedSizeWithPadChar(encoded, alphabet[64]);
143}
393fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) -> %void {
394 // encode
395 {
396 var buffer: [0x100]u8 = undefined;
397 var encoded = buffer[0..calcEncodedSize(expected_decoded.len)];
398 encode(encoded, expected_decoded, standard_alphabet_chars, standard_pad_char);
399 assert(mem.eql(u8, encoded, expected_encoded));
400 }
144401
145pub fn calcExactDecodedSizeWithPadChar(encoded: []const u8, pad_char: u8) -> usize {
146 var buf_len = encoded.len;
402 // decodeExact
403 {
404 var buffer: [0x100]u8 = undefined;
405 var decoded = buffer[0..%return calcDecodedSizeExact(expected_encoded, standard_pad_char)];
406 %return decodeExact(decoded, expected_encoded, standard_alphabet);
407 assert(mem.eql(u8, decoded, expected_decoded));
408 }
147409
148 while (buf_len > 0 and encoded[buf_len - 1] == pad_char) {
149 buf_len -= 1;
410 // decodeWithIgnore
411 {
412 const standard_alphabet_ignore_nothing = Base64AlphabetWithIgnore.init(
413 standard_alphabet_chars, standard_pad_char, "");
414 var buffer: [0x100]u8 = undefined;
415 var decoded = buffer[0..%return calcDecodedSizeUpperBound(expected_encoded.len)];
416 var written = %return decodeWithIgnore(decoded, expected_encoded, standard_alphabet_ignore_nothing);
417 assert(written <= decoded.len);
418 assert(mem.eql(u8, decoded[0..written], expected_decoded));
150419 }
151420
152 return (buf_len * 3) / 4;
421 // decodeExactUnsafe
422 {
423 var buffer: [0x100]u8 = undefined;
424 var decoded = buffer[0..calcDecodedSizeExactUnsafe(expected_encoded, standard_pad_char)];
425 decodeExactUnsafe(decoded, expected_encoded, standard_alphabet_unsafe);
426 assert(mem.eql(u8, decoded, expected_decoded));
427 }
153428}
154429
155test "base64" {
156 testBase64();
157 comptime testBase64();
430fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) -> %void {
431 const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init(
432 standard_alphabet_chars, standard_pad_char, " ");
433 var buffer: [0x100]u8 = undefined;
434 var decoded = buffer[0..%return calcDecodedSizeUpperBound(encoded.len)];
435 var written = %return decodeWithIgnore(decoded, encoded, standard_alphabet_ignore_space);
436 assert(mem.eql(u8, decoded[0..written], expected_decoded));
158437}
159438
160fn testBase64() {
161 testBase64Case("", "");
162 testBase64Case("f", "Zg==");
163 testBase64Case("fo", "Zm8=");
164 testBase64Case("foo", "Zm9v");
165 testBase64Case("foob", "Zm9vYg==");
166 testBase64Case("fooba", "Zm9vYmE=");
167 testBase64Case("foobar", "Zm9vYmFy");
439error ExpectedError;
440fn testError(encoded: []const u8, expected_err: error) -> %void {
441 const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init(
442 standard_alphabet_chars, standard_pad_char, " ");
443 var buffer: [0x100]u8 = undefined;
444 if (calcDecodedSizeExact(encoded, standard_pad_char)) |decoded_size| {
445 var decoded = buffer[0..decoded_size];
446 if (decodeExact(decoded, encoded, standard_alphabet)) |_| {
447 return error.ExpectedError;
448 } else |err| if (err != expected_err) return err;
449 } else |err| if (err != expected_err) return err;
450
451 if (decodeWithIgnore(buffer[0..], encoded, standard_alphabet_ignore_space)) |_| {
452 return error.ExpectedError;
453 } else |err| if (err != expected_err) return err;
168454}
169455
170fn testBase64Case(expected_decoded: []const u8, expected_encoded: []const u8) {
171 const calculated_decoded_len = calcExactDecodedSize(expected_encoded);
172 assert(calculated_decoded_len == expected_decoded.len);
173
174 const calculated_encoded_len = calcEncodedSize(expected_decoded.len);
175 assert(calculated_encoded_len == expected_encoded.len);
176
177 var buf: [100]u8 = undefined;
178
179 const actual_decoded = decode(buf[0..], expected_encoded);
180 assert(actual_decoded.len == expected_decoded.len);
181 assert(mem.eql(u8, expected_decoded, actual_decoded));
182
183 const actual_encoded = encode(buf[0..], expected_decoded);
184 assert(actual_encoded.len == expected_encoded.len);
185 assert(mem.eql(u8, expected_encoded, actual_encoded));
456fn testOutputTooSmallError(encoded: []const u8) -> %void {
457 const standard_alphabet_ignore_space = Base64AlphabetWithIgnore.init(
458 standard_alphabet_chars, standard_pad_char, " ");
459 var buffer: [0x100]u8 = undefined;
460 var decoded = buffer[0..calcDecodedSizeExactUnsafe(encoded, standard_pad_char) - 1];
461 if (decodeWithIgnore(decoded, encoded, standard_alphabet_ignore_space)) |_| {
462 return error.ExpectedError;
463 } else |err| if (err != error.OutputTooSmall) return err;
186464}
std/os/index.zig+3-3
......@@ -622,7 +622,7 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path:
622622}
623623
624624// here we replace the standard +/ with -_ so that it can be used in a file name
625const b64_fs_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
625const b64_fs_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
626626
627627pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) -> %void {
628628 if (symLink(allocator, existing_path, new_path)) {
......@@ -639,7 +639,7 @@ pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path:
639639 mem.copy(u8, tmp_path[0..], new_path);
640640 while (true) {
641641 %return getRandomBytes(rand_buf[0..]);
642 _ = base64.encodeWithAlphabet(tmp_path[new_path.len..], rand_buf, b64_fs_alphabet);
642 base64.encode(tmp_path[new_path.len..], rand_buf, b64_fs_alphabet_chars, base64.standard_pad_char);
643643 if (symLink(allocator, existing_path, tmp_path)) {
644644 return rename(allocator, tmp_path, new_path);
645645 } else |err| {
......@@ -721,7 +721,7 @@ pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: [
721721 defer allocator.free(tmp_path);
722722 mem.copy(u8, tmp_path[0..], dest_path);
723723 %return getRandomBytes(rand_buf[0..]);
724 _ = base64.encodeWithAlphabet(tmp_path[dest_path.len..], rand_buf, b64_fs_alphabet);
724 base64.encode(tmp_path[dest_path.len..], rand_buf, b64_fs_alphabet_chars, base64.standard_pad_char);
725725
726726 var out_file = %return io.File.openWriteMode(tmp_path, mode, allocator);
727727 defer out_file.close();