OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart.convert; | |
6 | |
7 /** | |
8 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. | |
9 * | |
10 * It encodes using the default base64 alphabet, | |
11 * decodes using both the base64 and base64url alphabets, | |
12 * does not allow invalid characters and requires padding. | |
13 * | |
14 * Examples: | |
15 * | |
16 * var encoded = BASE64.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, | |
17 * 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); | |
18 * var decoded = BASE64.decode("YmzDpWLDpnJncsO4ZAo="); | |
19 */ | |
20 const Base64Codec BASE64 = const Base64Codec(); | |
21 | |
22 /** | |
23 * A [base64url](https://tools.ietf.org/html/rfc4648) encoder and decoder. | |
24 * | |
25 * It encodes and decodes using the base64url alphabet, | |
26 * decodes using both the base64 and base64url alphabets, | |
27 * does not allow invalid characters and requires padding. | |
28 * | |
29 * Examples: | |
30 * | |
31 * var encoded = BASE64URL.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, | |
32 * 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); | |
33 * var decoded = BASE64URL.decode("YmzDpWLDpnJncsO4ZAo="); | |
34 */ | |
35 const Base64Codec BASE64URL = const Base64Codec.urlSafe(); | |
36 | |
37 // Constants used in more than one class. | |
38 const int _paddingChar = 0x3d; // '='. | |
39 | |
40 /** | |
41 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. | |
42 * | |
43 * A [Base64Codec] allows base64 encoding bytes into ASCII strings and | |
44 * decoding valid encodings back to bytes. | |
45 * | |
46 * This implementation only handles the simplest RFC 4648 base64 and base64url | |
47 * encodings. | |
48 * It does not allow invalid characters when decoding and it requires, | |
49 * and generates, padding so that the input is always a multiple of four | |
50 * characters. | |
51 */ | |
52 class Base64Codec extends Codec<List<int>, String> { | |
53 final Base64Encoder _encoder; | |
54 const Base64Codec() : _encoder = const Base64Encoder(); | |
55 const Base64Codec.urlSafe() : _encoder = const Base64Encoder.urlSafe(); | |
56 | |
57 Base64Encoder get encoder => _encoder; | |
58 | |
59 Base64Decoder get decoder => const Base64Decoder(); | |
60 } | |
61 | |
62 // ------------------------------------------------------------------------ | |
63 // Encoder | |
64 // ------------------------------------------------------------------------ | |
65 | |
66 /** | |
67 * Base64 and base64url encoding converter. | |
68 * | |
69 * Encodes lists of bytes using base64 or base64url encoding. | |
70 * | |
71 * The results are ASCII strings using a restricted alphabet. | |
72 */ | |
73 class Base64Encoder extends Converter<List<int>, String> { | |
74 final bool _urlSafe; | |
75 | |
76 const Base64Encoder() : _urlSafe = false; | |
77 const Base64Encoder.urlSafe() : _urlSafe = true; | |
78 | |
79 String convert(List<int> input) { | |
80 if (input.isEmpty) return ""; | |
81 var encoder = new _Base64Encoder(_urlSafe); | |
82 Uint8List buffer = encoder.encode(input, 0, input.length, true); | |
83 return new String.fromCharCodes(buffer); | |
84 } | |
85 | |
86 ByteConversionSink startChunkedConversion(Sink<String> sink) { | |
87 if (sink is StringConversionSink) { | |
88 return new _Utf8Base64EncoderSink(sink.asUtf8Sink(false), _urlSafe); | |
89 } | |
90 return new _AsciiBase64EncoderSink(sink, _urlSafe); | |
91 } | |
92 } | |
93 | |
94 /** | |
95 * Helper class for encoding bytes to base64. | |
96 */ | |
97 class _Base64Encoder { | |
98 /** The RFC 4648 base64 encoding alphabet. */ | |
99 static const String _base64Alphabet = | |
100 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
101 | |
102 /** The RFC 4648 base64url encoding alphabet. */ | |
103 static const String _base64urlAlphabet = | |
104 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; | |
105 | |
106 /** Shift-count to extract the values stored in [_state]. */ | |
107 static const int _valueShift = 2; | |
108 | |
109 /** Mask to extract the count value stored in [_state]. */ | |
110 static const int _countMask = 3; | |
111 | |
112 static const int _sixBitMask = 0x3F; | |
113 | |
114 /** | |
115 * Intermediate state between chunks. | |
116 * | |
117 * Encoding handles three bytes at a time. | |
118 * If fewer than three bytes has been seen, this value encodes | |
119 * the number of bytes seen (0, 1 or 2) and their values. | |
120 */ | |
121 int _state = 0; | |
122 | |
123 /** Alphabet used for encoding. */ | |
124 final String _alphabet; | |
125 | |
126 _Base64Encoder(bool urlSafe) | |
127 : _alphabet = urlSafe ? _base64urlAlphabet : _base64Alphabet; | |
128 | |
129 /** Encode count and bits into a value to be stored in [_state]. */ | |
130 static int _encodeState(int count, int bits) { | |
131 assert(count <= _countMask); | |
132 return bits << _valueShift | count; | |
133 } | |
134 | |
135 /** Extract bits from encoded state. */ | |
136 static int _stateBits(int state) => state >> _valueShift; | |
137 | |
138 /** Extract count from encoded state. */ | |
139 static int _stateCount(int state) => state & _countMask; | |
140 | |
141 /** | |
142 * Create a [Uint8List] with the provided length. | |
143 */ | |
144 Uint8List createBuffer(int bufferLength) => new Uint8List(bufferLength); | |
145 | |
146 /** | |
147 * Encode [bytes] from [start] to [end] and the bits in [_state]. | |
148 * | |
149 * Returns a [Uint8List] of the ASCII codes of the encoded data. | |
150 * | |
151 * If the input, including left over [_state] from earlier encodings, | |
152 * are not a multiple of three bytes, then the partial state is stored | |
153 * back into [_state]. | |
154 * If [isLast] is true, partial state is encoded in the output instead, | |
155 * with the necessary padding. | |
156 * | |
157 * Returns `null` if there is no output. | |
158 */ | |
159 Uint8List encode(List<int> bytes, int start, int end, bool isLast) { | |
160 assert(0 <= start); | |
161 assert(start <= end); | |
162 assert(bytes == null || end <= bytes.length); | |
163 int length = end - start; | |
164 | |
165 int count = _stateCount(_state); | |
166 int byteCount = (count + length); | |
167 int fullChunks = byteCount ~/ 3; | |
168 int partialChunkLength = byteCount - fullChunks * 3; | |
169 int bufferLength = fullChunks * 4; | |
170 if (isLast && partialChunkLength > 0) { | |
171 bufferLength += 4; // Room for padding. | |
172 } | |
173 var output = createBuffer(bufferLength); | |
174 _state = encodeChunk(_alphabet, bytes, start, end, isLast, | |
175 output, 0, _state); | |
176 if (bufferLength > 0) return output; | |
177 // If the input plus the data in state is still less than three bytes, | |
178 // there may not be any output. | |
179 return null; | |
180 } | |
181 | |
182 static int encodeChunk(String alphabet, | |
183 List<int> bytes, int start, int end, bool isLast, | |
184 Uint8List output, int outputIndex, int state) { | |
185 int bits = _stateBits(state); | |
186 // Count number of missing bytes in three-byte chunk. | |
187 int expectedChars = 3 - _stateCount(state); | |
188 | |
189 // The input must be a list of bytes (integers in the range 0..255). | |
190 // The value of `byteOr` will be the bitwise or of all the values in | |
191 // `bytes` and a later check will validate that they were all valid bytes. | |
192 int byteOr = 0; | |
193 for (int i = start; i < end; i++) { | |
194 int byte = bytes[i]; | |
195 byteOr |= byte; | |
196 bits = ((bits << 8) | byte) & 0xFFFFFF; // Never store more than 24 bits. | |
197 expectedChars--; | |
198 if (expectedChars == 0) { | |
199 output[outputIndex++] = | |
200 alphabet.codeUnitAt((bits >> 18) & _sixBitMask); | |
201 output[outputIndex++] = | |
202 alphabet.codeUnitAt((bits >> 12) & _sixBitMask); | |
203 output[outputIndex++] = | |
204 alphabet.codeUnitAt((bits >> 6) & _sixBitMask); | |
205 output[outputIndex++] = | |
206 alphabet.codeUnitAt(bits & _sixBitMask); | |
207 expectedChars = 3; | |
208 bits = 0; | |
209 } | |
210 } | |
211 if (byteOr >= 0 && byteOr <= 255) { | |
212 if (isLast && expectedChars < 3) { | |
213 writeFinalChunk(alphabet, output, outputIndex, 3 - expectedChars, bits); | |
214 return 0; | |
215 } | |
216 return _encodeState(3 - expectedChars, bits); | |
217 } | |
218 | |
219 // There was an invalid byte value somewhere in the input - find it! | |
220 int i = start; | |
221 while (i < end) { | |
222 int byte = bytes[i]; | |
223 if (byte < 0 || byte > 255) break; | |
224 i++; | |
225 } | |
226 throw new ArgumentError.value(bytes, | |
227 "Not a byte value at index $i: 0x${bytes[i].toRadixString(16)}"); | |
228 } | |
229 | |
230 /** | |
231 * Writes a final encoded four-character chunk. | |
232 * | |
233 * Only used when the [_state] contains a partial (1 or 2 byte) | |
234 * input. | |
235 */ | |
236 static void writeFinalChunk(String alphabet, | |
237 Uint8List output, int outputIndex, | |
238 int count, int bits) { | |
239 assert(count > 0); | |
240 if (count == 1) { | |
241 output[outputIndex++] = | |
242 alphabet.codeUnitAt((bits >> 2) & _sixBitMask); | |
243 output[outputIndex++] = | |
244 alphabet.codeUnitAt((bits << 4) & _sixBitMask); | |
245 output[outputIndex++] = _paddingChar; | |
246 output[outputIndex++] = _paddingChar; | |
247 } else { | |
248 assert(count == 2); | |
249 output[outputIndex++] = | |
250 alphabet.codeUnitAt((bits >> 10) & _sixBitMask); | |
251 output[outputIndex++] = | |
252 alphabet.codeUnitAt((bits >> 4) & _sixBitMask); | |
253 output[outputIndex++] = | |
254 alphabet.codeUnitAt((bits << 2) & _sixBitMask); | |
255 output[outputIndex++] = _paddingChar; | |
256 } | |
257 } | |
258 } | |
259 | |
260 class _BufferCachingBase64Encoder extends _Base64Encoder { | |
261 /** | |
262 * Reused buffer. | |
263 * | |
264 * When the buffer isn't released to the sink, only used to create another | |
265 * value (a string), the buffer can be reused between chunks. | |
266 */ | |
267 Uint8List bufferCache; | |
268 | |
269 _BufferCachingBase64Encoder(bool urlSafe) : super(urlSafe); | |
270 | |
271 Uint8List createBuffer(int bufferLength) { | |
272 if (bufferCache == null || bufferCache.length < bufferLength) { | |
273 bufferCache = new Uint8List(bufferLength); | |
274 } | |
275 // Return a view of the buffer, so it has the reuested length. | |
276 return new Uint8List.view(bufferCache.buffer, 0, bufferLength); | |
277 } | |
278 } | |
279 | |
280 abstract class _Base64EncoderSink extends ByteConversionSinkBase { | |
281 void add(List<int> source) { | |
282 _add(source, 0, source.length, false); | |
283 } | |
284 | |
285 void close() { | |
286 _add(null, 0, 0, true); | |
287 } | |
288 | |
289 void addSlice(List<int> source, int start, int end, bool isLast) { | |
290 if (end == null) throw new ArgumentError.notNull("end"); | |
291 RangeError.checkValidRange(start, end, source.length); | |
292 _add(source, start, end, isLast); | |
293 } | |
294 | |
295 void _add(List<int> source, int start, int end, bool isLast); | |
296 } | |
297 | |
298 class _AsciiBase64EncoderSink extends _Base64EncoderSink { | |
299 final Sink<String> _sink; | |
300 final _Base64Encoder _encoder; | |
301 | |
302 _AsciiBase64EncoderSink(this._sink, bool urlSafe) | |
303 : _encoder = new _BufferCachingBase64Encoder(urlSafe); | |
304 | |
305 void _add(List<int> source, int start, int end, bool isLast) { | |
306 Uint8List buffer = _encoder.encode(source, start, end, isLast); | |
307 if (buffer != null) { | |
308 String string = new String.fromCharCodes(buffer); | |
309 _sink.add(string); | |
310 } | |
311 if (isLast) { | |
312 _sink.close(); | |
313 } | |
314 } | |
315 } | |
316 | |
317 class _Utf8Base64EncoderSink extends _Base64EncoderSink { | |
318 final ByteConversionSink _sink; | |
319 final _Base64Encoder _encoder; | |
320 | |
321 _Utf8Base64EncoderSink(this._sink, bool urlSafe) | |
322 : _encoder = new _Base64Encoder(urlSafe); | |
323 | |
324 void _add(List<int> source, int start, int end, bool isLast) { | |
325 Uint8List buffer = _encoder.encode(source, start, end, isLast); | |
326 if (buffer != null) { | |
327 _sink.addSlice(buffer, 0, buffer.length, isLast); | |
328 } | |
329 } | |
330 } | |
331 | |
332 // ------------------------------------------------------------------------ | |
333 // Decoder | |
334 // ------------------------------------------------------------------------ | |
335 | |
336 /** | |
337 * Decoder for base64 encoded data. | |
338 * | |
339 * This decoder accepts both base64 and base64url ("url-safe") encodings. | |
340 * | |
341 * The encoding is required to be properly padded. | |
342 */ | |
343 class Base64Decoder extends Converter<String, List<int>> { | |
344 | |
345 const Base64Decoder(); | |
346 | |
347 List<int> convert(String input, [int start = 0, int end]) { | |
348 end = RangeError.checkValidRange(start, end, input.length); | |
349 if (start == end) return new Uint8List(0); | |
350 var decoder = new _Base64Decoder(); | |
351 Uint8List buffer = decoder.decode(input, start, end); | |
352 decoder.close(input, end); | |
353 return buffer; | |
354 } | |
355 | |
356 StringConversionSink startChunkedConversion(Sink<List<int>> sink) { | |
357 return new _Base64DecoderSink(sink); | |
358 } | |
359 } | |
360 | |
361 /** | |
362 * Helper class implementing base64 decoding with intermediate state. | |
363 */ | |
364 class _Base64Decoder { | |
365 /** Shift-count to extract the values stored in [_state]. */ | |
366 static const int _valueShift = 2; | |
367 | |
368 /** Mask to extract the count value stored in [_state]. */ | |
369 static const int _countMask = 3; | |
370 | |
371 /** Invalid character in decoding table. */ | |
372 static const int _invalid = -2; | |
373 | |
374 /** Padding character in decoding table. */ | |
375 static const int _padding = -1; | |
376 | |
377 // Shorthands to make the table more readable. | |
378 static const int __ = _invalid; | |
379 static const int _p = _padding; | |
380 | |
381 /** | |
382 * Mapping from ASCII characters to their index in the base64 alphabet. | |
383 * | |
384 * Uses [_invalid] for invalid indices and [_padding] for the padding | |
385 * character. | |
386 * | |
387 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the | |
388 * 62nd and 63rd alphabet characters), and considers `%` a padding | |
389 * character, which mush then be followed by `3D`, the percent-escape | |
390 * for `=`. | |
391 */ | |
392 static final List<int> _inverseAlphabet = new Int8List.fromList([ | |
393 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | |
394 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | |
395 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, | |
396 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, | |
397 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | |
398 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63, | |
399 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, | |
400 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, __, __, __, __, __, | |
401 ]); | |
402 | |
403 // Character constants. | |
404 static const int _char_percent = 0x25; // '%'. | |
405 static const int _char_3 = 0x33; // '3'. | |
406 static const int _char_d = 0x64; // 'd'. | |
407 | |
408 /** | |
409 * Maintains the intermediate state of a partly-decoded input. | |
410 * | |
411 * Base64 is decoded in chunks of four characters. If a chunk does not | |
412 * contain a full block, the decoded bits (six per character) of the | |
413 * available characters are stored in [_state] until the next call to | |
414 * [_decode] or [_close]. | |
415 * | |
416 * If no padding has been seen, the value is | |
417 * `numberOfCharactersSeen | (decodedBits << 2)` | |
418 * where `numberOfCharactersSeen` is between 0 and 3 and decoded bits | |
419 * contains six bits per seen character. | |
420 * | |
421 * If padding has been seen the value is negative. It's the bitwise negation | |
422 * of the number of remanining allowed padding characters (always ~0 or ~1). | |
423 * | |
424 * A state of `0` or `~0` are valid places to end decoding, all other values | |
425 * mean that a four-character block has not been completed. | |
426 */ | |
427 int _state = 0; | |
428 | |
429 /** | |
430 * Encodes [count] and [bits] as a value to be stored in [_state]. | |
431 */ | |
432 static int _encodeCharacterState(int count, int bits) { | |
433 assert(count == (count & _countMask)); | |
434 return (bits << _valueShift | count); | |
435 } | |
436 | |
437 /** | |
438 * Extracts count from a [_state] value. | |
439 */ | |
440 static int _stateCount(int state) { | |
441 assert(state >= 0); | |
442 return state & _countMask; | |
443 } | |
444 | |
445 /** | |
446 * Extracts value bits from a [_state] value. | |
447 */ | |
448 static int _stateBits(int state) { | |
449 assert(state >= 0); | |
450 return state >> _valueShift; | |
451 } | |
452 | |
453 /** | |
454 * Encodes a number of expected padding characters to be stored in [_state]. | |
455 */ | |
456 static int _encodePaddingState(int expectedPadding) { | |
457 assert(expectedPadding >= 0); | |
458 assert(expectedPadding <= 5); | |
459 return -expectedPadding - 1; // ~expectedPadding adapted to dart2js. | |
460 } | |
461 | |
462 /** | |
463 * Extracts expected padding character count from a [_state] value. | |
464 */ | |
465 static int _statePadding(int state) { | |
466 assert(state < 0); | |
467 return -state - 1; // ~state adapted to dart2js. | |
468 } | |
469 | |
470 static bool _hasSeenPadding(int state) => state < 0; | |
471 | |
472 /** | |
473 * Decodes [input] from [start] to [end]. | |
474 * | |
475 * Returns a [Uint8List] with the decoded bytes. | |
476 * If a previous call had an incomplete four-character block, the bits from | |
477 * those are included in decoding | |
478 */ | |
479 Uint8List decode(String input, int start, int end) { | |
480 assert(0 <= start); | |
481 assert(start <= end); | |
482 assert(end <= input.length); | |
483 if (_hasSeenPadding(_state)) { | |
484 _state = _checkPadding(input, start, end, _state); | |
485 return null; | |
486 } | |
487 if (start == end) return new Uint8List(0); | |
488 Uint8List buffer = _allocateBuffer(input, start, end, _state); | |
489 _state = decodeChunk(input, start, end, buffer, 0, _state); | |
490 return buffer; | |
491 } | |
492 | |
493 /** Checks that [_state] represents a valid decoding. */ | |
494 void close(String input, int end) { | |
495 if (_state < _encodePaddingState(0)) { | |
496 throw new FormatException("Missing padding character", input, end); | |
497 } | |
498 if (_state > 0) { | |
499 throw new FormatException("Invalid length, must be multiple of four", | |
500 input, end); | |
501 } | |
502 _state = _encodePaddingState(0); | |
503 } | |
504 | |
505 /** | |
506 * Decodes [input] from [start] to [end]. | |
507 * | |
508 * Includes the state returned by a previous call in the decoding. | |
509 * Writes the decoding to [output] at [outIndex], and there must | |
510 * be room in the output. | |
511 */ | |
512 static int decodeChunk(String input, int start, int end, | |
513 Uint8List output, int outIndex, | |
514 int state) { | |
515 assert(!_hasSeenPadding(state)); | |
516 const int asciiMask = 127; | |
517 const int asciiMax = 127; | |
518 const int eightBitMask = 0xFF; | |
519 const int bitsPerCharacter = 6; | |
520 | |
521 int bits = _stateBits(state); | |
522 int count = _stateCount(state); | |
523 // String contents should be all ASCII. | |
524 // Instead of checking for each character, we collect the bitwise-or of | |
525 // all the characters in `charOr` and later validate that all characters | |
526 // were ASCII. | |
527 int charOr = 0; | |
528 for (int i = start; i < end; i++) { | |
529 var char = input.codeUnitAt(i); | |
530 charOr |= char; | |
531 int code = _inverseAlphabet[char & asciiMask]; | |
532 if (code >= 0) { | |
533 bits = ((bits << bitsPerCharacter) | code) & 0xFFFFFF; | |
534 count = (count + 1) & 3; | |
535 if (count == 0) { | |
536 assert(outIndex + 3 <= output.length); | |
537 output[outIndex++] = (bits >> 16) & eightBitMask; | |
538 output[outIndex++] = (bits >> 8) & eightBitMask; | |
539 output[outIndex++] = bits & eightBitMask; | |
540 bits = 0; | |
541 } | |
542 continue; | |
543 } else if (code == _padding && count > 1) { | |
544 if (charOr < 0 || charOr > asciiMax) break; | |
545 if (count == 3) { | |
546 if ((bits & 0x03) != 0) { | |
547 throw new FormatException( | |
548 "Invalid encoding before padding", input, i); | |
549 } | |
550 output[outIndex++] = bits >> 10; | |
551 output[outIndex++] = bits >> 2; | |
552 } else { | |
553 if ((bits & 0x0F) != 0) { | |
554 throw new FormatException( | |
555 "Invalid encoding before padding", input, i); | |
556 } | |
557 output[outIndex++] = bits >> 4; | |
558 } | |
559 // Expected padding is the number of expected padding characters, | |
560 // where `=` counts as three and `%3D` counts as one per character. | |
561 // | |
562 // Expect either zero or one padding depending on count (2 or 3), | |
563 // plus two more characters if the code was `%` (a partial padding). | |
564 int expectedPadding = (3 - count) * 3; | |
565 if (char == _char_percent) expectedPadding += 2; | |
566 state = _encodePaddingState(expectedPadding); | |
567 return _checkPadding(input, i + 1, end, state); | |
568 } | |
569 throw new FormatException("Invalid character", input, i); | |
570 } | |
571 if (charOr >= 0 && charOr <= asciiMax) { | |
572 return _encodeCharacterState(count, bits); | |
573 } | |
574 // There is an invalid (non-ASCII) character in the input. | |
575 int i; | |
576 for (i = start; i < end; i++) { | |
577 int char = input.codeUnitAt(i); | |
578 if (char < 0 || char > asciiMax) break; | |
579 } | |
580 throw new FormatException("Invalid character", input, i); | |
581 } | |
582 | |
583 /** | |
584 * Allocates a buffer with room for the decoding of a substring of [input]. | |
585 * | |
586 * Includes room for the characters in [state], and handles padding correctly. | |
587 */ | |
588 static Uint8List _allocateBuffer(String input, int start, int end, | |
589 int state) { | |
590 assert(state >= 0); | |
591 int paddingStart = _trimPaddingChars(input, start, end); | |
592 int length = _stateCount(state) + (paddingStart - start); | |
593 // Three bytes per full four bytes in the input. | |
594 int bufferLength = (length >> 2) * 3; | |
595 // If padding was seen, then this is the last chunk, and the final partial | |
596 // chunk should be decoded too. | |
597 int remainderLength = length & 3; | |
598 if (remainderLength != 0 && paddingStart < end) { | |
599 bufferLength += remainderLength - 1; | |
600 } | |
601 if (bufferLength > 0) return new Uint8List(bufferLength); | |
602 // If the input plus state is less than four characters, and it's not | |
603 // at the end of input, no buffer is needed. | |
604 return null; | |
605 } | |
606 | |
607 /** | |
608 * Returns the position of the start of padding at the end of the input. | |
609 * | |
610 * Returns the end of input if there is no padding. | |
611 * | |
612 * This is used to ensure that the decoding buffer has the exact size | |
613 * it needs when input is valid, and at least enough bytes to reach the error | |
614 * when input is invalid. | |
615 * | |
616 * Never count more than two padding sequences since any more than that | |
617 * will raise an error anyway, and we only care about being precise for | |
618 * successful conversions. | |
619 */ | |
620 static int _trimPaddingChars(String input, int start, int end) { | |
621 // This may count '%=' as two paddings. That's ok, it will err later, | |
622 // but the buffer will be large enough to reach the error. | |
623 int padding = 0; | |
624 int index = end; | |
625 int newEnd = end; | |
626 while (index > start && padding < 2) { | |
627 index--; | |
628 int char = input.codeUnitAt(index); | |
629 if (char == _paddingChar) { | |
630 padding++; | |
631 newEnd = index; | |
632 continue; | |
633 } | |
634 if ((char | 0x20) == _char_d) { | |
635 if (index == start) break; | |
636 index--; | |
637 char = input.codeUnitAt(index); | |
638 } | |
639 if (char == _char_3) { | |
640 if (index == start) break; | |
641 index--; | |
642 char = input.codeUnitAt(index); | |
643 } | |
644 if (char == _char_percent) { | |
645 padding++; | |
646 newEnd = index; | |
647 continue; | |
648 } | |
649 break; | |
650 } | |
651 return newEnd; | |
652 } | |
653 | |
654 /** | |
655 * Check that the remainder of the string is valid padding. | |
656 * | |
657 * Valid padding is a correct number (0, 1 or 2) of `=` characters | |
658 * or `%3D` sequences depending on the number of preceding base64 characters. | |
659 * The [state] parameter encodes which padding continuations are allowed | |
660 * as the number of expected characters. That number is the number of | |
661 * expected padding characters times 3 minus the number of padding characters | |
662 * seen so far, where `=` counts as 3 counts as three characters, | |
663 * and the padding sequence `%3D` counts as one character per character. | |
664 * | |
665 * The number of missing characters is always between 0 and 5 because we | |
666 * only call this function after having seen at least one `=` or `%` | |
667 * character. | |
668 * If the number of missing characters is not 3 or 0, we have seen (at least) | |
669 * a `%` character and expects the rest of the `%3D` sequence, and a `=` is | |
670 * not allowed. When missing 3 characters, either `=` or `%` is allowed. | |
671 * | |
672 * When the value is 0, no more padding (or any other character) is allowed. | |
673 */ | |
674 static int _checkPadding(String input, int start, int end, int state) { | |
675 assert(_hasSeenPadding(state)); | |
676 if (start == end) return state; | |
677 int expectedPadding = _statePadding(state); | |
678 assert(expectedPadding >= 0); | |
679 assert(expectedPadding < 6); | |
680 while (expectedPadding > 0) { | |
681 int char = input.codeUnitAt(start); | |
682 if (expectedPadding == 3) { | |
683 if (char == _paddingChar) { | |
684 expectedPadding -= 3; | |
685 start++; | |
686 break; | |
687 } | |
688 if (char == _char_percent) { | |
689 expectedPadding--; | |
690 start++; | |
691 if (start == end) break; | |
692 char = input.codeUnitAt(start); | |
693 } else { | |
694 break; | |
695 } | |
696 } | |
697 // Partial padding means we have seen part of a "%3D" escape. | |
698 int expectedPartialPadding = expectedPadding; | |
699 if (expectedPartialPadding > 3) expectedPartialPadding -= 3; | |
700 if (expectedPartialPadding == 2) { | |
701 // Expects '3' | |
702 if (char != _char_3) break; | |
703 start++; | |
704 expectedPadding--; | |
705 if (start == end) break; | |
706 char = input.codeUnitAt(start); | |
707 } | |
708 // Expects 'D' or 'd'. | |
709 if ((char | 0x20) != _char_d) break; | |
710 start++; | |
711 expectedPadding--; | |
712 if (start == end) break; | |
713 } | |
714 if (start != end) { | |
715 throw new FormatException("Invalid padding character", | |
716 input, start); | |
717 } | |
718 return _encodePaddingState(expectedPadding); | |
719 } | |
720 } | |
721 | |
722 class _Base64DecoderSink extends StringConversionSinkBase { | |
723 /** Output sink */ | |
724 final Sink<List<int>> _sink; | |
725 final _Base64Decoder _decoder = new _Base64Decoder(); | |
726 | |
727 _Base64DecoderSink(this._sink); | |
728 | |
729 void add(String string) { | |
730 if (string.isEmpty) return; | |
731 Uint8List buffer = _decoder.decode(string, 0, string.length); | |
732 if (buffer != null) _sink.add(buffer); | |
733 } | |
734 | |
735 void close() { | |
736 _decoder.close(null, null); | |
737 _sink.close(); | |
738 } | |
739 | |
740 void addSlice(String string, int start, int end, bool isLast) { | |
741 end = RangeError.checkValidRange(start, end, string.length); | |
742 if (start == end) return; | |
743 Uint8List buffer = _decoder.decode(string, start, end); | |
744 if (buffer != null) _sink.add(buffer); | |
745 if (isLast) { | |
746 _decoder.close(string, end); | |
747 _sink.close(); | |
748 } | |
749 } | |
750 } | |
OLD | NEW |