Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Side by Side Diff: pkg/dev_compiler/tool/input_sdk/lib/convert/base64.dart

Issue 2698353003: unfork DDC's copy of most SDK libraries (Closed)
Patch Set: revert core_patch Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698