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 |