Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. | 8 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. |
| 9 * | 9 * |
| 10 * It encodes using the default base64 alphabet, | 10 * It encodes using the default base64 alphabet, |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 50 * characters. | 50 * characters. |
| 51 */ | 51 */ |
| 52 class Base64Codec extends Codec<List<int>, String> { | 52 class Base64Codec extends Codec<List<int>, String> { |
| 53 final Base64Encoder _encoder; | 53 final Base64Encoder _encoder; |
| 54 const Base64Codec() : _encoder = const Base64Encoder(); | 54 const Base64Codec() : _encoder = const Base64Encoder(); |
| 55 const Base64Codec.urlSafe() : _encoder = const Base64Encoder.urlSafe(); | 55 const Base64Codec.urlSafe() : _encoder = const Base64Encoder.urlSafe(); |
| 56 | 56 |
| 57 Base64Encoder get encoder => _encoder; | 57 Base64Encoder get encoder => _encoder; |
| 58 | 58 |
| 59 Base64Decoder get decoder => const Base64Decoder(); | 59 Base64Decoder get decoder => const Base64Decoder(); |
| 60 | |
| 61 /** | |
| 62 * Validates and normalizes the base64 encoded data in [source]. | |
| 63 * | |
| 64 * Only acts on the substring from [start] to [end], with [end] | |
| 65 * defaulting to the end of the string. | |
| 66 * | |
| 67 * Normalization will: | |
| 68 * * Unescape any `%`-escapes. | |
| 69 * * Only allow valid characters (`A`-`Z`, `a`-`z`, `0`-`9`, `/` and `+`). | |
| 70 * * Normalize a `_` or `-` character to `/` or `+`. | |
| 71 * * Validate that existing padding (trailing `=` characters) is correct. | |
| 72 * * If no padding exists, add correct padding if necessary and possible. | |
| 73 * * Validate that the length is correct (a multiplum of four). | |
|
floitsch
2017/02/15 17:03:09
multiple ?
Lasse Reichstein Nielsen
2017/02/17 11:22:09
Done.
| |
| 74 */ | |
| 75 String normalize(String source, [int start = 0, int end]) { | |
| 76 end = RangeError.checkValidRange(start, end, source.length); | |
| 77 const int percent = 0x25; | |
| 78 const int equals = 0x3d; | |
| 79 StringBuffer buffer = null; | |
| 80 int sliceStart = start; | |
| 81 var alphabet = _Base64Encoder._base64Alphabet; | |
| 82 var inverseAlphabet = _Base64Decoder._inverseAlphabet; | |
| 83 int firstPadding = -1; | |
| 84 int firstPaddingSourceIndex = -1; | |
| 85 int paddingCount = 0; | |
| 86 for (int i = start; i < end;) { | |
| 87 int sliceEnd = i; | |
| 88 int char = source.codeUnitAt(i++); | |
| 89 int originalChar = char; | |
| 90 // Normalize char, keep originalChar to see if it matches the source. | |
| 91 if (char == percent) { | |
| 92 originalChar = -1; | |
|
floitsch
2017/02/15 17:03:09
Add comment explaining that you pick '-1' because
Lasse Reichstein Nielsen
2017/02/17 11:22:09
It's actually not necessary to set it at all - a '
| |
| 93 if (i + 2 <= end) { | |
| 94 char = parseHexByte(source, i); // May be negative. | |
| 95 i += 2; | |
| 96 } else { | |
| 97 char = 0; // Invalid. | |
|
floitsch
2017/02/15 17:03:09
If this is invalid, why it is set to 0, and not `_
Lasse Reichstein Nielsen
2017/02/17 11:22:09
I'll set it to -1, so it'll just fall through to t
| |
| 98 } | |
| 99 } | |
| 100 if (0 <= char && char <= 127) { | |
| 101 int value = inverseAlphabet[char]; | |
| 102 if (value >= 0) { | |
| 103 char = alphabet.codeUnitAt(value); | |
| 104 if (char == originalChar) continue; | |
| 105 } else if (value == _Base64Decoder._padding) { | |
| 106 if (firstPadding < 0) { | |
| 107 // Mark position in normalized output where padding occurs. | |
| 108 firstPadding = (buffer?.length ?? 0) + (sliceEnd - sliceStart); | |
| 109 firstPaddingSourceIndex = sliceEnd; | |
| 110 } | |
| 111 paddingCount++; | |
| 112 if (originalChar == equals) continue; | |
| 113 } | |
| 114 if (value != _Base64Decoder._invalid) { | |
| 115 buffer ??= new StringBuffer(); | |
| 116 buffer.write(source.substring(sliceStart, sliceEnd)); | |
| 117 buffer.writeCharCode(char); | |
| 118 sliceStart = i; | |
| 119 continue; | |
| 120 } | |
| 121 } | |
| 122 throw new FormatException("Invalid base64 data", source, sliceEnd); | |
| 123 } | |
| 124 if (buffer != null) { | |
| 125 buffer.write(source.substring(sliceStart, end)); | |
| 126 if (firstPadding >= 0) { | |
| 127 // There was padding in the source. Check that it is valid: | |
| 128 // * result length a multiple of four | |
| 129 // * one or two padding characters at the end. | |
| 130 _checkPadding(source, firstPaddingSourceIndex, end, | |
| 131 firstPadding, paddingCount, buffer.length); | |
| 132 } else { | |
| 133 // Length of last chunk (1-4 chars) in the encoding. | |
| 134 int endLength = ((buffer.length - 1) % 4) + 1; | |
| 135 if (endLength == 1) { | |
| 136 // The data must have length 0, 2 or 3 modulo 4. | |
| 137 throw new FormatException("Invalid base64 encoding length ", | |
| 138 source, end); | |
| 139 } | |
| 140 while (endLength < 4) { | |
| 141 buffer.write("="); | |
| 142 endLength++; | |
| 143 } | |
| 144 } | |
| 145 return source.replaceRange(start, end, buffer.toString()); | |
| 146 } | |
| 147 // Original was already normalized, only check padding. | |
| 148 int length = end - start; | |
| 149 if (firstPadding >= 0) { | |
| 150 _checkPadding(source, firstPaddingSourceIndex, end, | |
| 151 firstPadding, paddingCount, length); | |
| 152 } else { | |
| 153 // No padding given, so add some if needed it. | |
| 154 int endLength = length % 4; | |
| 155 if (endLength == 1) { | |
| 156 // The data must have length 0, 2 or 3 modulo 4. | |
| 157 throw new FormatException("Invalid base64 encoding length ", | |
| 158 source, end); | |
| 159 } | |
| 160 if (endLength > 1) { | |
| 161 // There is no "insertAt" on String, but this works as well. | |
| 162 source = source.replaceRange(end, end, (endLength == 2) ? "==" : "="); | |
| 163 } | |
| 164 } | |
| 165 return source; | |
| 166 } | |
| 167 | |
| 168 static int _checkPadding(String source, int sourceIndex, int sourceEnd, | |
| 169 int firstPadding, int paddingCount, int length) { | |
| 170 if (length % 4 != 0) { | |
| 171 throw new FormatException( | |
| 172 "Invalid base64 padding, padded length must be multiplum of four, " | |
|
floitsch
2017/02/15 17:03:09
multiple
Lasse Reichstein Nielsen
2017/02/17 11:22:09
Done.
| |
| 173 "is $length", | |
| 174 source, sourceEnd); | |
| 175 } | |
| 176 if (firstPadding + paddingCount != length) { | |
| 177 throw new FormatException( | |
| 178 "Invalid base64 padding, '=' not at the end", | |
| 179 source, sourceIndex); | |
| 180 } | |
| 181 if (paddingCount > 2) { | |
| 182 throw new FormatException( | |
| 183 "Invalid base64 padding, more than two '=' characters", | |
| 184 source, sourceIndex); | |
| 185 } | |
| 186 } | |
| 60 } | 187 } |
| 61 | 188 |
| 62 // ------------------------------------------------------------------------ | 189 // ------------------------------------------------------------------------ |
| 63 // Encoder | 190 // Encoder |
| 64 // ------------------------------------------------------------------------ | 191 // ------------------------------------------------------------------------ |
| 65 | 192 |
| 66 /** | 193 /** |
| 67 * Base64 and base64url encoding converter. | 194 * Base64 and base64url encoding converter. |
| 68 * | 195 * |
| 69 * Encodes lists of bytes using base64 or base64url encoding. | 196 * Encodes lists of bytes using base64 or base64url encoding. |
| (...skipping 312 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 382 static const int _p = _padding; | 509 static const int _p = _padding; |
| 383 | 510 |
| 384 /** | 511 /** |
| 385 * Mapping from ASCII characters to their index in the base64 alphabet. | 512 * Mapping from ASCII characters to their index in the base64 alphabet. |
| 386 * | 513 * |
| 387 * Uses [_invalid] for invalid indices and [_padding] for the padding | 514 * Uses [_invalid] for invalid indices and [_padding] for the padding |
| 388 * character. | 515 * character. |
| 389 * | 516 * |
| 390 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the | 517 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the |
| 391 * 62nd and 63rd alphabet characters), and considers `%` a padding | 518 * 62nd and 63rd alphabet characters), and considers `%` a padding |
| 392 * character, which mush then be followed by `3D`, the percent-escape | 519 * character, which must then be followed by `3D`, the percent-escape |
| 393 * for `=`. | 520 * for `=`. |
| 394 */ | 521 */ |
| 395 static final List<int> _inverseAlphabet = new Int8List.fromList([ | 522 static final List<int> _inverseAlphabet = new Int8List.fromList([ |
| 396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 523 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, |
| 397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 524 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, |
| 398 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, | 525 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, |
| 399 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, | 526 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, |
| 400 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 527 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 401 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63, | 528 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63, |
| 402 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, | 529 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
| (...skipping 341 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 744 end = RangeError.checkValidRange(start, end, string.length); | 871 end = RangeError.checkValidRange(start, end, string.length); |
| 745 if (start == end) return; | 872 if (start == end) return; |
| 746 Uint8List buffer = _decoder.decode(string, start, end); | 873 Uint8List buffer = _decoder.decode(string, start, end); |
| 747 if (buffer != null) _sink.add(buffer); | 874 if (buffer != null) _sink.add(buffer); |
| 748 if (isLast) { | 875 if (isLast) { |
| 749 _decoder.close(string, end); | 876 _decoder.close(string, end); |
| 750 _sink.close(); | 877 _sink.close(); |
| 751 } | 878 } |
| 752 } | 879 } |
| 753 } | 880 } |
| OLD | NEW |