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

Side by Side Diff: sdk/lib/convert/base64.dart

Issue 1419983003: Add support for decoding URL-safe encoded base64. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years, 2 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
« no previous file with comments | « no previous file | tests/lib/convert/base64_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.convert; 5 part of dart.convert;
6 6
7 /** 7 /**
8 * An instance of [Base64Codec]. 8 * An instance of [Base64Codec].
9 * 9 *
10 * This instance provides a convenient access to 10 * This instance provides a convenient access to
(...skipping 292 matching lines...) Expand 10 before | Expand all | Expand 10 after
303 // ------------------------------------------------------------------------ 303 // ------------------------------------------------------------------------
304 // Decoder 304 // Decoder
305 // ------------------------------------------------------------------------ 305 // ------------------------------------------------------------------------
306 306
307 class Base64Decoder extends Converter<String, List<int>> { 307 class Base64Decoder extends Converter<String, List<int>> {
308 const Base64Decoder(); 308 const Base64Decoder();
309 309
310 List<int> convert(String input) { 310 List<int> convert(String input) {
311 if (input.isEmpty) return new Uint8List(0); 311 if (input.isEmpty) return new Uint8List(0);
312 int length = input.length; 312 int length = input.length;
313 if (length % 4 != 0) {
314 throw new FormatException("Invalid length, must be multiple of four",
315 input, length);
316 }
317 var decoder = new _Base64Decoder(); 313 var decoder = new _Base64Decoder();
318 Uint8List buffer = decoder.decode(input, 0, input.length); 314 Uint8List buffer = decoder.decode(input, 0, input.length);
319 decoder.close(input, input.length); 315 decoder.close(input, input.length);
320 return buffer; 316 return buffer;
321 } 317 }
322 318
323 StringConversionSink startChunkedConversion(Sink<List<int>> sink) { 319 StringConversionSink startChunkedConversion(Sink<List<int>> sink) {
324 return new _Base64DecoderSink(sink); 320 return new _Base64DecoderSink(sink);
325 } 321 }
326 } 322 }
(...skipping 16 matching lines...) Expand all
343 339
344 // Shorthands to make the table more readable. 340 // Shorthands to make the table more readable.
345 static const int __ = _invalid; 341 static const int __ = _invalid;
346 static const int _p = _padding; 342 static const int _p = _padding;
347 343
348 /** 344 /**
349 * Mapping from ASCII characters to their index in the base64 alphabet. 345 * Mapping from ASCII characters to their index in the base64 alphabet.
350 * 346 *
351 * Uses [_invalid] for invalid indices and [_padding] for the padding 347 * Uses [_invalid] for invalid indices and [_padding] for the padding
352 * character. 348 * character.
349 *
350 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the
351 * 62nd and 63rd alphabet characters), and considers `%` a padding
352 * character which mush be followed by `3D`, the percent-escape
353 * for `=`.
353 */ 354 */
354 static final List<int> _inverseAlphabet = new Int8List.fromList([ 355 static final List<int> _inverseAlphabet = new Int8List.fromList([
355 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 356 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
356 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 357 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
357 __, __, __, __, __, __, __, __, __, __, __, 62, __, __, __, 63, 358 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63,
358 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, 359 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __,
359 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 360 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
360 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, __, 361 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63,
361 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 362 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
362 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, __, __, __, __, __, 363 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, __, __, __, __, __,
363 ]); 364 ]);
364 365
366 // Character constants.
367 static const int _char_percent = 0x25; // '%'.
368 static const int _char_3 = 0x33; // '3'.
369 static const int _char_d = 0x64; // 'd'.
370
365 /** 371 /**
366 * Maintains the intermediate state of a partly-decoded input. 372 * Maintains the intermediate state of a partly-decoded input.
367 * 373 *
368 * BASE-64 is decoded in chunks of four characters. If a chunk does not 374 * BASE-64 is decoded in chunks of four characters. If a chunk does not
369 * contain a full block, the decoded bits (six per character) of the 375 * contain a full block, the decoded bits (six per character) of the
370 * available characters are stored in [_state] until the next call to 376 * available characters are stored in [_state] until the next call to
371 * [_decode] or [_close]. 377 * [_decode] or [_close].
372 * 378 *
373 * If no padding has been seen, the value is 379 * If no padding has been seen, the value is
374 * `numberOfCharactersSeen | (decodedBits << 2)` 380 * `numberOfCharactersSeen | (decodedBits << 2)`
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
491 count = (count + 1) & 3; 497 count = (count + 1) & 3;
492 if (count == 0) { 498 if (count == 0) {
493 assert(outIndex + 3 <= output.length); 499 assert(outIndex + 3 <= output.length);
494 output[outIndex++] = (bits >> 16) & eightBitMask; 500 output[outIndex++] = (bits >> 16) & eightBitMask;
495 output[outIndex++] = (bits >> 8) & eightBitMask; 501 output[outIndex++] = (bits >> 8) & eightBitMask;
496 output[outIndex++] = bits & eightBitMask; 502 output[outIndex++] = bits & eightBitMask;
497 bits = 0; 503 bits = 0;
498 } 504 }
499 continue; 505 continue;
500 } else if (code == _padding && count > 1) { 506 } else if (code == _padding && count > 1) {
507 if (charOr < 0 || charOr > asciiMax) break;
501 if (count == 3) { 508 if (count == 3) {
502 if ((bits & 0x03) != 0) { 509 if ((bits & 0x03) != 0) {
503 throw new FormatException( 510 throw new FormatException(
504 "Invalid encoding before padding", input, i); 511 "Invalid encoding before padding", input, i);
505 } 512 }
506 output[outIndex++] = bits >> 10; 513 output[outIndex++] = bits >> 10;
507 output[outIndex++] = bits >> 2; 514 output[outIndex++] = bits >> 2;
508 } else { 515 } else {
509 if ((bits & 0x0F) != 0) { 516 if ((bits & 0x0F) != 0) {
510 throw new FormatException( 517 throw new FormatException(
511 "Invalid encoding before padding", input, i); 518 "Invalid encoding before padding", input, i);
512 } 519 }
513 output[outIndex++] = bits >> 4; 520 output[outIndex++] = bits >> 4;
514 } 521 }
515 int expectedPadding = 3 - count; 522 // Expected padding is the number of expected padding characters,
523 // where `=` counts as three and `%3D` counts as one per character.
524 //
525 // Expect either zero or one padding depending on count (2 or 3),
526 // plus two more characters if the code was `%` (a partial padding).
527 int expectedPadding = (3 - count) * 3;
528 if (char == _char_percent) expectedPadding += 2;
516 state = _encodePaddingState(expectedPadding); 529 state = _encodePaddingState(expectedPadding);
517 return _checkPadding(input, i + 1, end, state); 530 return _checkPadding(input, i + 1, end, state);
518 } 531 }
519 throw new FormatException("Invalid character", input, i); 532 throw new FormatException("Invalid character", input, i);
520 } 533 }
521 if (charOr >= 0 && charOr <= asciiMax) { 534 if (charOr >= 0 && charOr <= asciiMax) {
522 return _encodeCharacterState(count, bits); 535 return _encodeCharacterState(count, bits);
523 } 536 }
524 // There is an invalid (non-ASCII) character in the input. 537 // There is an invalid (non-ASCII) character in the input.
525 int i; 538 int i;
526 for (i = start; i < end; i++) { 539 for (i = start; i < end; i++) {
527 int char = input.codeUnitAt(i); 540 int char = input.codeUnitAt(i);
528 if (char < 0 || char > asciiMax) break; 541 if (char < 0 || char > asciiMax) break;
529 } 542 }
530 throw new FormatException("Invalid character", input, i); 543 throw new FormatException("Invalid character", input, i);
531 } 544 }
532 545
533 /** 546 /**
534 * Allocates a buffer with room for the decoding of a substring of [input]. 547 * Allocates a buffer with room for the decoding of a substring of [input].
535 * 548 *
536 * Includes room for the characters in [state], and handles padding correctly. 549 * Includes room for the characters in [state], and handles padding correctly.
537 */ 550 */
538 static Uint8List _allocateBuffer(String input, int start, int end, 551 static Uint8List _allocateBuffer(String input, int start, int end,
539 int state) { 552 int state) {
540 assert(state >= 0); 553 assert(state >= 0);
541 int padding = 0; 554 int paddingStart = _trimPaddingChars(input, start, end);
542 int length = _stateCount(state) + (end - start); 555 int length = _stateCount(state) + (paddingStart - start);
543 if (end > start && input.codeUnitAt(end - 1) == _paddingChar) {
544 padding++;
545 if (end - 1 > start && input.codeUnitAt(end - 2) == _paddingChar) {
546 padding++;
547 }
548 }
549 // Three bytes per full four bytes in the input. 556 // Three bytes per full four bytes in the input.
550 int bufferLength = (length >> 2) * 3; 557 int bufferLength = (length >> 2) * 3;
551 // If padding was seen, then remove the padding if it was counter, or 558 // If padding was seen, then this is the last chunk, and the final partial
552 // add the last partial chunk it it wasn't counted. 559 // chunk should be decoded too.
553 int remainderLength = length & 3; 560 int remainderLength = length & 3;
554 if (remainderLength == 0) { 561 if (remainderLength != 0 && paddingStart < end) {
555 bufferLength -= padding; 562 bufferLength += remainderLength - 1;
556 } else if (padding != 0 && remainderLength - padding > 1) {
557 bufferLength += remainderLength - 1 - padding;
558 } 563 }
559 if (bufferLength > 0) return new Uint8List(bufferLength); 564 if (bufferLength > 0) return new Uint8List(bufferLength);
560 // If the input plus state is less than four characters, no buffer 565 // If the input plus state is less than four characters, and it's not
561 // is needed. 566 // at the end of input, no buffer is needed.
562 return null; 567 return null;
563 } 568 }
564 569
565 /** 570 /**
571 * Returns the position of the start of padding at the end of the input.
572 *
573 * This is used to ensure that the decoding buffer has the exact size
574 * it needs when input is valid, and at least enough bytes reach the error
nweiz 2015/10/22 20:14:30 "bytes reach" -> "bytes to reach"
Lasse Reichstein Nielsen 2015/10/23 11:03:35 Done.
575 * when input is invalid.
576 *
577 * Never count more than two padding sequences since any more than that
578 * will raise an error anyway, and we only care about being precise for
579 * successful conversions.
580 */
581 static int _trimPaddingChars(String input, int start, int end) {
582 // This may count '%=' as two paddings. That's ok, it will err later,
583 // but the buffer will be large enough to reach the error.
584 int padding = 0;
585 int index = end;
586 int newEnd = end;
587 while (index > start && padding < 2) {
588 index--;
589 int char = input.codeUnitAt(index);
590 if (char == _paddingChar) {
591 padding++;
592 newEnd = index;
593 continue;
594 }
595 if ((char | 0x20) == _char_d) {
596 if (index == start) break;
597 index--;
598 char = input.codeUnitAt(index);
599 }
600 if (char == _char_3) {
601 if (index == start) break;
602 index--;
603 char = input.codeUnitAt(index);
604 }
605 if (char == _char_percent) {
606 padding++;
607 newEnd = index;
608 continue;
609 }
610 break;
611 }
612 return newEnd;
613 }
614
615 /**
566 * Check that the remainder of the string is valid padding. 616 * Check that the remainder of the string is valid padding.
567 * 617 *
568 * That means zero or one padding character (depending on [_state]) 618 * That means zero or one padding character (depending on [_state])
nweiz 2015/10/22 20:14:30 Aren't two padding characters also valid?
Lasse Reichstein Nielsen 2015/10/23 11:03:35 Yes, but we only get here after seeing the first o
569 * and nothing else. 619 * and nothing else.
570 */ 620 */
571 static int _checkPadding(String input, int start, int end, int state) { 621 static int _checkPadding(String input, int start, int end, int state) {
572 assert(_hasSeenPadding(state)); 622 assert(_hasSeenPadding(state));
573 if (start == end) return state; 623 if (start == end) return state;
574 int expectedPadding = _statePadding(state); 624 int expectedPadding = _statePadding(state);
575 if (expectedPadding > 0) { 625 assert(expectedPadding >= 0);
576 int firstChar = input.codeUnitAt(start); 626 assert(expectedPadding < 6);
577 if (firstChar != _paddingChar) { 627 while (expectedPadding > 0) {
578 throw new FormatException("Missing padding character", input, start); 628 int char = input.codeUnitAt(start);
629 if (expectedPadding == 3) {
630 if (char == _paddingChar) {
631 expectedPadding -= 3;
632 start++;
633 break;
634 }
635 if (char == _char_percent) {
636 expectedPadding--;
637 start++;
638 if (start == end) break;
639 char = input.codeUnitAt(start);
640 } else {
641 break;
642 }
579 } 643 }
580 state = _encodePaddingState(0); 644 // Partial padding means we have seen part of a "%3D" escape.
645 int expectedPartialPadding = expectedPadding;
646 if (expectedPartialPadding > 3) expectedPartialPadding -= 3;
647 if (expectedPartialPadding == 2) {
648 // Expects '3'
649 if (char != _char_3) break;
650 start++;
651 expectedPadding--;
652 if (start == end) break;
653 char = input.codeUnitAt(start);
654 }
655 // Expects 'D' or 'd'.
656 if ((char | 0x20) != _char_d) break;
581 start++; 657 start++;
658 expectedPadding--;
659 if (start == end) break;
582 } 660 }
583 if (start != end) { 661 if (start != end) {
584 throw new FormatException("Invalid character after padding", 662 throw new FormatException("Invalid padding character",
585 input, start); 663 input, start);
586 } 664 }
587 return state; 665 return _encodePaddingState(expectedPadding);
588 } 666 }
589 } 667 }
590 668
591 class _Base64DecoderSink extends StringConversionSinkBase { 669 class _Base64DecoderSink extends StringConversionSinkBase {
592 /** Output sink */ 670 /** Output sink */
593 final ChunkedConversionSink<List<int>> _sink; 671 final ChunkedConversionSink<List<int>> _sink;
594 final _Base64Decoder _decoder = new _Base64Decoder(); 672 final _Base64Decoder _decoder = new _Base64Decoder();
595 673
596 _Base64DecoderSink(this._sink); 674 _Base64DecoderSink(this._sink);
597 675
(...skipping 12 matching lines...) Expand all
610 end = RangeError.checkValidRange(start, end, string.length); 688 end = RangeError.checkValidRange(start, end, string.length);
611 if (start == end) return; 689 if (start == end) return;
612 Uint8List buffer = _decoder.decode(string, start, end); 690 Uint8List buffer = _decoder.decode(string, start, end);
613 if (buffer != null) _sink.add(buffer); 691 if (buffer != null) _sink.add(buffer);
614 if (isLast) { 692 if (isLast) {
615 _decoder.close(string, end); 693 _decoder.close(string, end);
616 _sink.close(); 694 _sink.close();
617 } 695 }
618 } 696 }
619 } 697 }
OLDNEW
« no previous file with comments | « no previous file | tests/lib/convert/base64_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698