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 multiple of four). |
| 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 if (i + 2 <= end) { |
| 93 char = parseHexByte(source, i); // May be negative. |
| 94 i += 2; |
| 95 // We know that %25 isn't valid, but our table considers it |
| 96 // a potential padding start, so skip the checks. |
| 97 if (char == percent) char = -1; |
| 98 } else { |
| 99 // An invalid HEX escape (too short). |
| 100 // Just skip past the handling and reach the throw below. |
| 101 char = -1; |
| 102 } |
| 103 } |
| 104 // If char is negative here, hex-decoding failed in some way. |
| 105 if (0 <= char && char <= 127) { |
| 106 int value = inverseAlphabet[char]; |
| 107 if (value >= 0) { |
| 108 char = alphabet.codeUnitAt(value); |
| 109 if (char == originalChar) continue; |
| 110 } else if (value == _Base64Decoder._padding) { |
| 111 // We have ruled out percent, so char is '='. |
| 112 if (firstPadding < 0) { |
| 113 // Mark position in normalized output where padding occurs. |
| 114 firstPadding = (buffer?.length ?? 0) + (sliceEnd - sliceStart); |
| 115 firstPaddingSourceIndex = sliceEnd; |
| 116 } |
| 117 paddingCount++; |
| 118 // It could have been an escaped equals (%3D). |
| 119 if (originalChar == equals) continue; |
| 120 } |
| 121 if (value != _Base64Decoder._invalid) { |
| 122 buffer ??= new StringBuffer(); |
| 123 buffer.write(source.substring(sliceStart, sliceEnd)); |
| 124 buffer.writeCharCode(char); |
| 125 sliceStart = i; |
| 126 continue; |
| 127 } |
| 128 } |
| 129 throw new FormatException("Invalid base64 data", source, sliceEnd); |
| 130 } |
| 131 if (buffer != null) { |
| 132 buffer.write(source.substring(sliceStart, end)); |
| 133 if (firstPadding >= 0) { |
| 134 // There was padding in the source. Check that it is valid: |
| 135 // * result length a multiple of four |
| 136 // * one or two padding characters at the end. |
| 137 _checkPadding(source, firstPaddingSourceIndex, end, |
| 138 firstPadding, paddingCount, buffer.length); |
| 139 } else { |
| 140 // Length of last chunk (1-4 chars) in the encoding. |
| 141 int endLength = ((buffer.length - 1) % 4) + 1; |
| 142 if (endLength == 1) { |
| 143 // The data must have length 0, 2 or 3 modulo 4. |
| 144 throw new FormatException("Invalid base64 encoding length ", |
| 145 source, end); |
| 146 } |
| 147 while (endLength < 4) { |
| 148 buffer.write("="); |
| 149 endLength++; |
| 150 } |
| 151 } |
| 152 return source.replaceRange(start, end, buffer.toString()); |
| 153 } |
| 154 // Original was already normalized, only check padding. |
| 155 int length = end - start; |
| 156 if (firstPadding >= 0) { |
| 157 _checkPadding(source, firstPaddingSourceIndex, end, |
| 158 firstPadding, paddingCount, length); |
| 159 } else { |
| 160 // No padding given, so add some if needed it. |
| 161 int endLength = length % 4; |
| 162 if (endLength == 1) { |
| 163 // The data must have length 0, 2 or 3 modulo 4. |
| 164 throw new FormatException("Invalid base64 encoding length ", |
| 165 source, end); |
| 166 } |
| 167 if (endLength > 1) { |
| 168 // There is no "insertAt" on String, but this works as well. |
| 169 source = source.replaceRange(end, end, (endLength == 2) ? "==" : "="); |
| 170 } |
| 171 } |
| 172 return source; |
| 173 } |
| 174 |
| 175 static int _checkPadding(String source, int sourceIndex, int sourceEnd, |
| 176 int firstPadding, int paddingCount, int length) { |
| 177 if (length % 4 != 0) { |
| 178 throw new FormatException( |
| 179 "Invalid base64 padding, padded length must be multiple of four, " |
| 180 "is $length", |
| 181 source, sourceEnd); |
| 182 } |
| 183 if (firstPadding + paddingCount != length) { |
| 184 throw new FormatException( |
| 185 "Invalid base64 padding, '=' not at the end", |
| 186 source, sourceIndex); |
| 187 } |
| 188 if (paddingCount > 2) { |
| 189 throw new FormatException( |
| 190 "Invalid base64 padding, more than two '=' characters", |
| 191 source, sourceIndex); |
| 192 } |
| 193 } |
60 } | 194 } |
61 | 195 |
62 // ------------------------------------------------------------------------ | 196 // ------------------------------------------------------------------------ |
63 // Encoder | 197 // Encoder |
64 // ------------------------------------------------------------------------ | 198 // ------------------------------------------------------------------------ |
65 | 199 |
66 /** | 200 /** |
67 * Base64 and base64url encoding converter. | 201 * Base64 and base64url encoding converter. |
68 * | 202 * |
69 * Encodes lists of bytes using base64 or base64url encoding. | 203 * 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; | 516 static const int _p = _padding; |
383 | 517 |
384 /** | 518 /** |
385 * Mapping from ASCII characters to their index in the base64 alphabet. | 519 * Mapping from ASCII characters to their index in the base64 alphabet. |
386 * | 520 * |
387 * Uses [_invalid] for invalid indices and [_padding] for the padding | 521 * Uses [_invalid] for invalid indices and [_padding] for the padding |
388 * character. | 522 * character. |
389 * | 523 * |
390 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the | 524 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the |
391 * 62nd and 63rd alphabet characters), and considers `%` a padding | 525 * 62nd and 63rd alphabet characters), and considers `%` a padding |
392 * character, which mush then be followed by `3D`, the percent-escape | 526 * character, which must then be followed by `3D`, the percent-escape |
393 * for `=`. | 527 * for `=`. |
394 */ | 528 */ |
395 static final List<int> _inverseAlphabet = new Int8List.fromList([ | 529 static final List<int> _inverseAlphabet = new Int8List.fromList([ |
396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 530 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, |
397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 531 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, |
398 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, | 532 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, |
399 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, | 533 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, | 534 __, 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, | 535 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, | 536 __, 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); | 878 end = RangeError.checkValidRange(start, end, string.length); |
745 if (start == end) return; | 879 if (start == end) return; |
746 Uint8List buffer = _decoder.decode(string, start, end); | 880 Uint8List buffer = _decoder.decode(string, start, end); |
747 if (buffer != null) _sink.add(buffer); | 881 if (buffer != null) _sink.add(buffer); |
748 if (isLast) { | 882 if (isLast) { |
749 _decoder.close(string, end); | 883 _decoder.close(string, end); |
750 _sink.close(); | 884 _sink.close(); |
751 } | 885 } |
752 } | 886 } |
753 } | 887 } |
OLD | NEW |