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 17 matching lines...) Expand all Loading... |
28 * | 28 * |
29 * Examples: | 29 * Examples: |
30 * | 30 * |
31 * var encoded = BASE64URL.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, | 31 * var encoded = BASE64URL.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, |
32 * 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); | 32 * 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); |
33 * var decoded = BASE64URL.decode("YmzDpWLDpnJncsO4ZAo="); | 33 * var decoded = BASE64URL.decode("YmzDpWLDpnJncsO4ZAo="); |
34 */ | 34 */ |
35 const Base64Codec BASE64URL = const Base64Codec.urlSafe(); | 35 const Base64Codec BASE64URL = const Base64Codec.urlSafe(); |
36 | 36 |
37 // Constants used in more than one class. | 37 // Constants used in more than one class. |
38 const int _paddingChar = 0x3d; // '='. | 38 const int _paddingChar = 0x3d; // '='. |
39 | 39 |
40 /** | 40 /** |
41 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. | 41 * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. |
42 * | 42 * |
43 * A [Base64Codec] allows base64 encoding bytes into ASCII strings and | 43 * A [Base64Codec] allows base64 encoding bytes into ASCII strings and |
44 * decoding valid encodings back to bytes. | 44 * decoding valid encodings back to bytes. |
45 * | 45 * |
46 * This implementation only handles the simplest RFC 4648 base64 and base64url | 46 * This implementation only handles the simplest RFC 4648 base64 and base64url |
47 * encodings. | 47 * encodings. |
48 * It does not allow invalid characters when decoding and it requires, | 48 * It does not allow invalid characters when decoding and it requires, |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
83 int firstPadding = -1; | 83 int firstPadding = -1; |
84 int firstPaddingSourceIndex = -1; | 84 int firstPaddingSourceIndex = -1; |
85 int paddingCount = 0; | 85 int paddingCount = 0; |
86 for (int i = start; i < end;) { | 86 for (int i = start; i < end;) { |
87 int sliceEnd = i; | 87 int sliceEnd = i; |
88 int char = source.codeUnitAt(i++); | 88 int char = source.codeUnitAt(i++); |
89 int originalChar = char; | 89 int originalChar = char; |
90 // Normalize char, keep originalChar to see if it matches the source. | 90 // Normalize char, keep originalChar to see if it matches the source. |
91 if (char == percent) { | 91 if (char == percent) { |
92 if (i + 2 <= end) { | 92 if (i + 2 <= end) { |
93 char = parseHexByte(source, i); // May be negative. | 93 char = parseHexByte(source, i); // May be negative. |
94 i += 2; | 94 i += 2; |
95 // We know that %25 isn't valid, but our table considers it | 95 // We know that %25 isn't valid, but our table considers it |
96 // a potential padding start, so skip the checks. | 96 // a potential padding start, so skip the checks. |
97 if (char == percent) char = -1; | 97 if (char == percent) char = -1; |
98 } else { | 98 } else { |
99 // An invalid HEX escape (too short). | 99 // An invalid HEX escape (too short). |
100 // Just skip past the handling and reach the throw below. | 100 // Just skip past the handling and reach the throw below. |
101 char = -1; | 101 char = -1; |
102 } | 102 } |
103 } | 103 } |
(...skipping 23 matching lines...) Expand all Loading... |
127 } | 127 } |
128 } | 128 } |
129 throw new FormatException("Invalid base64 data", source, sliceEnd); | 129 throw new FormatException("Invalid base64 data", source, sliceEnd); |
130 } | 130 } |
131 if (buffer != null) { | 131 if (buffer != null) { |
132 buffer.write(source.substring(sliceStart, end)); | 132 buffer.write(source.substring(sliceStart, end)); |
133 if (firstPadding >= 0) { | 133 if (firstPadding >= 0) { |
134 // There was padding in the source. Check that it is valid: | 134 // There was padding in the source. Check that it is valid: |
135 // * result length a multiple of four | 135 // * result length a multiple of four |
136 // * one or two padding characters at the end. | 136 // * one or two padding characters at the end. |
137 _checkPadding(source, firstPaddingSourceIndex, end, | 137 _checkPadding(source, firstPaddingSourceIndex, end, firstPadding, |
138 firstPadding, paddingCount, buffer.length); | 138 paddingCount, buffer.length); |
139 } else { | 139 } else { |
140 // Length of last chunk (1-4 chars) in the encoding. | 140 // Length of last chunk (1-4 chars) in the encoding. |
141 int endLength = ((buffer.length - 1) % 4) + 1; | 141 int endLength = ((buffer.length - 1) % 4) + 1; |
142 if (endLength == 1) { | 142 if (endLength == 1) { |
143 // The data must have length 0, 2 or 3 modulo 4. | 143 // The data must have length 0, 2 or 3 modulo 4. |
144 throw new FormatException("Invalid base64 encoding length ", | 144 throw new FormatException( |
145 source, end); | 145 "Invalid base64 encoding length ", source, end); |
146 } | 146 } |
147 while (endLength < 4) { | 147 while (endLength < 4) { |
148 buffer.write("="); | 148 buffer.write("="); |
149 endLength++; | 149 endLength++; |
150 } | 150 } |
151 } | 151 } |
152 return source.replaceRange(start, end, buffer.toString()); | 152 return source.replaceRange(start, end, buffer.toString()); |
153 } | 153 } |
154 // Original was already normalized, only check padding. | 154 // Original was already normalized, only check padding. |
155 int length = end - start; | 155 int length = end - start; |
156 if (firstPadding >= 0) { | 156 if (firstPadding >= 0) { |
157 _checkPadding(source, firstPaddingSourceIndex, end, | 157 _checkPadding(source, firstPaddingSourceIndex, end, firstPadding, |
158 firstPadding, paddingCount, length); | 158 paddingCount, length); |
159 } else { | 159 } else { |
160 // No padding given, so add some if needed it. | 160 // No padding given, so add some if needed it. |
161 int endLength = length % 4; | 161 int endLength = length % 4; |
162 if (endLength == 1) { | 162 if (endLength == 1) { |
163 // The data must have length 0, 2 or 3 modulo 4. | 163 // The data must have length 0, 2 or 3 modulo 4. |
164 throw new FormatException("Invalid base64 encoding length ", | 164 throw new FormatException( |
165 source, end); | 165 "Invalid base64 encoding length ", source, end); |
166 } | 166 } |
167 if (endLength > 1) { | 167 if (endLength > 1) { |
168 // There is no "insertAt" on String, but this works as well. | 168 // There is no "insertAt" on String, but this works as well. |
169 source = source.replaceRange(end, end, (endLength == 2) ? "==" : "="); | 169 source = source.replaceRange(end, end, (endLength == 2) ? "==" : "="); |
170 } | 170 } |
171 } | 171 } |
172 return source; | 172 return source; |
173 } | 173 } |
174 | 174 |
175 static int _checkPadding(String source, int sourceIndex, int sourceEnd, | 175 static int _checkPadding(String source, int sourceIndex, int sourceEnd, |
176 int firstPadding, int paddingCount, int length) { | 176 int firstPadding, int paddingCount, int length) { |
177 if (length % 4 != 0) { | 177 if (length % 4 != 0) { |
178 throw new FormatException( | 178 throw new FormatException( |
179 "Invalid base64 padding, padded length must be multiple of four, " | 179 "Invalid base64 padding, padded length must be multiple of four, " |
180 "is $length", | 180 "is $length", |
181 source, sourceEnd); | 181 source, |
| 182 sourceEnd); |
182 } | 183 } |
183 if (firstPadding + paddingCount != length) { | 184 if (firstPadding + paddingCount != length) { |
184 throw new FormatException( | 185 throw new FormatException( |
185 "Invalid base64 padding, '=' not at the end", | 186 "Invalid base64 padding, '=' not at the end", source, sourceIndex); |
186 source, sourceIndex); | |
187 } | 187 } |
188 if (paddingCount > 2) { | 188 if (paddingCount > 2) { |
189 throw new FormatException( | 189 throw new FormatException( |
190 "Invalid base64 padding, more than two '=' characters", | 190 "Invalid base64 padding, more than two '=' characters", |
191 source, sourceIndex); | 191 source, |
| 192 sourceIndex); |
192 } | 193 } |
193 } | 194 } |
194 } | 195 } |
195 | 196 |
196 // ------------------------------------------------------------------------ | 197 // ------------------------------------------------------------------------ |
197 // Encoder | 198 // Encoder |
198 // ------------------------------------------------------------------------ | 199 // ------------------------------------------------------------------------ |
199 | 200 |
200 /** | 201 /** |
201 * Base64 and base64url encoding converter. | 202 * Base64 and base64url encoding converter. |
202 * | 203 * |
203 * Encodes lists of bytes using base64 or base64url encoding. | 204 * Encodes lists of bytes using base64 or base64url encoding. |
204 * | 205 * |
205 * The results are ASCII strings using a restricted alphabet. | 206 * The results are ASCII strings using a restricted alphabet. |
206 */ | 207 */ |
207 class Base64Encoder extends Converter<List<int>, String> | 208 class Base64Encoder extends Converter<List<int>, String> |
208 implements ChunkedConverter<List<int>, String, List<int>, String> { | 209 implements ChunkedConverter<List<int>, String, List<int>, String> { |
209 | |
210 final bool _urlSafe; | 210 final bool _urlSafe; |
211 | 211 |
212 const Base64Encoder() : _urlSafe = false; | 212 const Base64Encoder() : _urlSafe = false; |
213 const Base64Encoder.urlSafe() : _urlSafe = true; | 213 const Base64Encoder.urlSafe() : _urlSafe = true; |
214 | 214 |
215 String convert(List<int> input) { | 215 String convert(List<int> input) { |
216 if (input.isEmpty) return ""; | 216 if (input.isEmpty) return ""; |
217 var encoder = new _Base64Encoder(_urlSafe); | 217 var encoder = new _Base64Encoder(_urlSafe); |
218 Uint8List buffer = encoder.encode(input, 0, input.length, true); | 218 Uint8List buffer = encoder.encode(input, 0, input.length, true); |
219 return new String.fromCharCodes(buffer); | 219 return new String.fromCharCodes(buffer); |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
297 assert(start <= end); | 297 assert(start <= end); |
298 assert(bytes == null || end <= bytes.length); | 298 assert(bytes == null || end <= bytes.length); |
299 int length = end - start; | 299 int length = end - start; |
300 | 300 |
301 int count = _stateCount(_state); | 301 int count = _stateCount(_state); |
302 int byteCount = (count + length); | 302 int byteCount = (count + length); |
303 int fullChunks = byteCount ~/ 3; | 303 int fullChunks = byteCount ~/ 3; |
304 int partialChunkLength = byteCount - fullChunks * 3; | 304 int partialChunkLength = byteCount - fullChunks * 3; |
305 int bufferLength = fullChunks * 4; | 305 int bufferLength = fullChunks * 4; |
306 if (isLast && partialChunkLength > 0) { | 306 if (isLast && partialChunkLength > 0) { |
307 bufferLength += 4; // Room for padding. | 307 bufferLength += 4; // Room for padding. |
308 } | 308 } |
309 var output = createBuffer(bufferLength); | 309 var output = createBuffer(bufferLength); |
310 _state = encodeChunk(_alphabet, bytes, start, end, isLast, | 310 _state = |
311 output, 0, _state); | 311 encodeChunk(_alphabet, bytes, start, end, isLast, output, 0, _state); |
312 if (bufferLength > 0) return output; | 312 if (bufferLength > 0) return output; |
313 // If the input plus the data in state is still less than three bytes, | 313 // If the input plus the data in state is still less than three bytes, |
314 // there may not be any output. | 314 // there may not be any output. |
315 return null; | 315 return null; |
316 } | 316 } |
317 | 317 |
318 static int encodeChunk(String alphabet, | 318 static int encodeChunk(String alphabet, List<int> bytes, int start, int end, |
319 List<int> bytes, int start, int end, bool isLast, | 319 bool isLast, Uint8List output, int outputIndex, int state) { |
320 Uint8List output, int outputIndex, int state) { | |
321 int bits = _stateBits(state); | 320 int bits = _stateBits(state); |
322 // Count number of missing bytes in three-byte chunk. | 321 // Count number of missing bytes in three-byte chunk. |
323 int expectedChars = 3 - _stateCount(state); | 322 int expectedChars = 3 - _stateCount(state); |
324 | 323 |
325 // The input must be a list of bytes (integers in the range 0..255). | 324 // The input must be a list of bytes (integers in the range 0..255). |
326 // The value of `byteOr` will be the bitwise or of all the values in | 325 // The value of `byteOr` will be the bitwise or of all the values in |
327 // `bytes` and a later check will validate that they were all valid bytes. | 326 // `bytes` and a later check will validate that they were all valid bytes. |
328 int byteOr = 0; | 327 int byteOr = 0; |
329 for (int i = start; i < end; i++) { | 328 for (int i = start; i < end; i++) { |
330 int byte = bytes[i]; | 329 int byte = bytes[i]; |
331 byteOr |= byte; | 330 byteOr |= byte; |
332 bits = ((bits << 8) | byte) & 0xFFFFFF; // Never store more than 24 bits. | 331 bits = ((bits << 8) | byte) & 0xFFFFFF; // Never store more than 24 bits. |
333 expectedChars--; | 332 expectedChars--; |
334 if (expectedChars == 0) { | 333 if (expectedChars == 0) { |
335 output[outputIndex++] = | 334 output[outputIndex++] = alphabet.codeUnitAt((bits >> 18) & _sixBitMask); |
336 alphabet.codeUnitAt((bits >> 18) & _sixBitMask); | 335 output[outputIndex++] = alphabet.codeUnitAt((bits >> 12) & _sixBitMask); |
337 output[outputIndex++] = | 336 output[outputIndex++] = alphabet.codeUnitAt((bits >> 6) & _sixBitMask); |
338 alphabet.codeUnitAt((bits >> 12) & _sixBitMask); | 337 output[outputIndex++] = alphabet.codeUnitAt(bits & _sixBitMask); |
339 output[outputIndex++] = | |
340 alphabet.codeUnitAt((bits >> 6) & _sixBitMask); | |
341 output[outputIndex++] = | |
342 alphabet.codeUnitAt(bits & _sixBitMask); | |
343 expectedChars = 3; | 338 expectedChars = 3; |
344 bits = 0; | 339 bits = 0; |
345 } | 340 } |
346 } | 341 } |
347 if (byteOr >= 0 && byteOr <= 255) { | 342 if (byteOr >= 0 && byteOr <= 255) { |
348 if (isLast && expectedChars < 3) { | 343 if (isLast && expectedChars < 3) { |
349 writeFinalChunk(alphabet, output, outputIndex, 3 - expectedChars, bits); | 344 writeFinalChunk(alphabet, output, outputIndex, 3 - expectedChars, bits); |
350 return 0; | 345 return 0; |
351 } | 346 } |
352 return _encodeState(3 - expectedChars, bits); | 347 return _encodeState(3 - expectedChars, bits); |
353 } | 348 } |
354 | 349 |
355 // There was an invalid byte value somewhere in the input - find it! | 350 // There was an invalid byte value somewhere in the input - find it! |
356 int i = start; | 351 int i = start; |
357 while (i < end) { | 352 while (i < end) { |
358 int byte = bytes[i]; | 353 int byte = bytes[i]; |
359 if (byte < 0 || byte > 255) break; | 354 if (byte < 0 || byte > 255) break; |
360 i++; | 355 i++; |
361 } | 356 } |
362 throw new ArgumentError.value(bytes, | 357 throw new ArgumentError.value( |
363 "Not a byte value at index $i: 0x${bytes[i].toRadixString(16)}"); | 358 bytes, "Not a byte value at index $i: 0x${bytes[i].toRadixString(16)}"); |
364 } | 359 } |
365 | 360 |
366 /** | 361 /** |
367 * Writes a final encoded four-character chunk. | 362 * Writes a final encoded four-character chunk. |
368 * | 363 * |
369 * Only used when the [_state] contains a partial (1 or 2 byte) | 364 * Only used when the [_state] contains a partial (1 or 2 byte) |
370 * input. | 365 * input. |
371 */ | 366 */ |
372 static void writeFinalChunk(String alphabet, | 367 static void writeFinalChunk( |
373 Uint8List output, int outputIndex, | 368 String alphabet, Uint8List output, int outputIndex, int count, int bits) { |
374 int count, int bits) { | |
375 assert(count > 0); | 369 assert(count > 0); |
376 if (count == 1) { | 370 if (count == 1) { |
377 output[outputIndex++] = | 371 output[outputIndex++] = alphabet.codeUnitAt((bits >> 2) & _sixBitMask); |
378 alphabet.codeUnitAt((bits >> 2) & _sixBitMask); | 372 output[outputIndex++] = alphabet.codeUnitAt((bits << 4) & _sixBitMask); |
379 output[outputIndex++] = | |
380 alphabet.codeUnitAt((bits << 4) & _sixBitMask); | |
381 output[outputIndex++] = _paddingChar; | 373 output[outputIndex++] = _paddingChar; |
382 output[outputIndex++] = _paddingChar; | 374 output[outputIndex++] = _paddingChar; |
383 } else { | 375 } else { |
384 assert(count == 2); | 376 assert(count == 2); |
385 output[outputIndex++] = | 377 output[outputIndex++] = alphabet.codeUnitAt((bits >> 10) & _sixBitMask); |
386 alphabet.codeUnitAt((bits >> 10) & _sixBitMask); | 378 output[outputIndex++] = alphabet.codeUnitAt((bits >> 4) & _sixBitMask); |
387 output[outputIndex++] = | 379 output[outputIndex++] = alphabet.codeUnitAt((bits << 2) & _sixBitMask); |
388 alphabet.codeUnitAt((bits >> 4) & _sixBitMask); | |
389 output[outputIndex++] = | |
390 alphabet.codeUnitAt((bits << 2) & _sixBitMask); | |
391 output[outputIndex++] = _paddingChar; | 380 output[outputIndex++] = _paddingChar; |
392 } | 381 } |
393 } | 382 } |
394 } | 383 } |
395 | 384 |
396 class _BufferCachingBase64Encoder extends _Base64Encoder { | 385 class _BufferCachingBase64Encoder extends _Base64Encoder { |
397 /** | 386 /** |
398 * Reused buffer. | 387 * Reused buffer. |
399 * | 388 * |
400 * When the buffer isn't released to the sink, only used to create another | 389 * When the buffer isn't released to the sink, only used to create another |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
471 | 460 |
472 /** | 461 /** |
473 * Decoder for base64 encoded data. | 462 * Decoder for base64 encoded data. |
474 * | 463 * |
475 * This decoder accepts both base64 and base64url ("url-safe") encodings. | 464 * This decoder accepts both base64 and base64url ("url-safe") encodings. |
476 * | 465 * |
477 * The encoding is required to be properly padded. | 466 * The encoding is required to be properly padded. |
478 */ | 467 */ |
479 class Base64Decoder extends Converter<String, List<int>> | 468 class Base64Decoder extends Converter<String, List<int>> |
480 implements ChunkedConverter<String, List<int>, String, List<int>> { | 469 implements ChunkedConverter<String, List<int>, String, List<int>> { |
481 | |
482 const Base64Decoder(); | 470 const Base64Decoder(); |
483 | 471 |
484 List<int> convert(String input, [int start = 0, int end]) { | 472 List<int> convert(String input, [int start = 0, int end]) { |
485 end = RangeError.checkValidRange(start, end, input.length); | 473 end = RangeError.checkValidRange(start, end, input.length); |
486 if (start == end) return new Uint8List(0); | 474 if (start == end) return new Uint8List(0); |
487 var decoder = new _Base64Decoder(); | 475 var decoder = new _Base64Decoder(); |
488 Uint8List buffer = decoder.decode(input, start, end); | 476 Uint8List buffer = decoder.decode(input, start, end); |
489 decoder.close(input, end); | 477 decoder.close(input, end); |
490 return buffer; | 478 return buffer; |
491 } | 479 } |
(...skipping 28 matching lines...) Expand all Loading... |
520 * | 508 * |
521 * Uses [_invalid] for invalid indices and [_padding] for the padding | 509 * Uses [_invalid] for invalid indices and [_padding] for the padding |
522 * character. | 510 * character. |
523 * | 511 * |
524 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the | 512 * Accepts the "URL-safe" alphabet as well (`-` and `_` are the |
525 * 62nd and 63rd alphabet characters), and considers `%` a padding | 513 * 62nd and 63rd alphabet characters), and considers `%` a padding |
526 * character, which must then be followed by `3D`, the percent-escape | 514 * character, which must then be followed by `3D`, the percent-escape |
527 * for `=`. | 515 * for `=`. |
528 */ | 516 */ |
529 static final List<int> _inverseAlphabet = new Int8List.fromList([ | 517 static final List<int> _inverseAlphabet = new Int8List.fromList([ |
530 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 518 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // |
531 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, | 519 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // |
532 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, | 520 __, __, __, __, __, _p, __, __, __, __, __, 62, __, 62, __, 63, // |
533 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, | 521 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, __, __, __, _p, __, __, // |
534 __, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, | 522 __, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, // |
535 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63, | 523 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, __, __, __, __, 63, // |
536 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, | 524 __, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // |
537 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, __, __, __, __, __, | 525 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, __, __, __, __, __, // |
538 ]); | 526 ]); |
539 | 527 |
540 // Character constants. | 528 // Character constants. |
541 static const int _char_percent = 0x25; // '%'. | 529 static const int _char_percent = 0x25; // '%'. |
542 static const int _char_3 = 0x33; // '3'. | 530 static const int _char_3 = 0x33; // '3'. |
543 static const int _char_d = 0x64; // 'd'. | 531 static const int _char_d = 0x64; // 'd'. |
544 | 532 |
545 /** | 533 /** |
546 * Maintains the intermediate state of a partly-decoded input. | 534 * Maintains the intermediate state of a partly-decoded input. |
547 * | 535 * |
548 * Base64 is decoded in chunks of four characters. If a chunk does not | 536 * Base64 is decoded in chunks of four characters. If a chunk does not |
549 * contain a full block, the decoded bits (six per character) of the | 537 * contain a full block, the decoded bits (six per character) of the |
550 * available characters are stored in [_state] until the next call to | 538 * available characters are stored in [_state] until the next call to |
551 * [_decode] or [_close]. | 539 * [_decode] or [_close]. |
552 * | 540 * |
553 * If no padding has been seen, the value is | 541 * If no padding has been seen, the value is |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
586 assert(state >= 0); | 574 assert(state >= 0); |
587 return state >> _valueShift; | 575 return state >> _valueShift; |
588 } | 576 } |
589 | 577 |
590 /** | 578 /** |
591 * Encodes a number of expected padding characters to be stored in [_state]. | 579 * Encodes a number of expected padding characters to be stored in [_state]. |
592 */ | 580 */ |
593 static int _encodePaddingState(int expectedPadding) { | 581 static int _encodePaddingState(int expectedPadding) { |
594 assert(expectedPadding >= 0); | 582 assert(expectedPadding >= 0); |
595 assert(expectedPadding <= 5); | 583 assert(expectedPadding <= 5); |
596 return -expectedPadding - 1; // ~expectedPadding adapted to dart2js. | 584 return -expectedPadding - 1; // ~expectedPadding adapted to dart2js. |
597 } | 585 } |
598 | 586 |
599 /** | 587 /** |
600 * Extracts expected padding character count from a [_state] value. | 588 * Extracts expected padding character count from a [_state] value. |
601 */ | 589 */ |
602 static int _statePadding(int state) { | 590 static int _statePadding(int state) { |
603 assert(state < 0); | 591 assert(state < 0); |
604 return -state - 1; // ~state adapted to dart2js. | 592 return -state - 1; // ~state adapted to dart2js. |
605 } | 593 } |
606 | 594 |
607 static bool _hasSeenPadding(int state) => state < 0; | 595 static bool _hasSeenPadding(int state) => state < 0; |
608 | 596 |
609 /** | 597 /** |
610 * Decodes [input] from [start] to [end]. | 598 * Decodes [input] from [start] to [end]. |
611 * | 599 * |
612 * Returns a [Uint8List] with the decoded bytes. | 600 * Returns a [Uint8List] with the decoded bytes. |
613 * If a previous call had an incomplete four-character block, the bits from | 601 * If a previous call had an incomplete four-character block, the bits from |
614 * those are included in decoding | 602 * those are included in decoding |
(...skipping 11 matching lines...) Expand all Loading... |
626 _state = decodeChunk(input, start, end, buffer, 0, _state); | 614 _state = decodeChunk(input, start, end, buffer, 0, _state); |
627 return buffer; | 615 return buffer; |
628 } | 616 } |
629 | 617 |
630 /** Checks that [_state] represents a valid decoding. */ | 618 /** Checks that [_state] represents a valid decoding. */ |
631 void close(String input, int end) { | 619 void close(String input, int end) { |
632 if (_state < _encodePaddingState(0)) { | 620 if (_state < _encodePaddingState(0)) { |
633 throw new FormatException("Missing padding character", input, end); | 621 throw new FormatException("Missing padding character", input, end); |
634 } | 622 } |
635 if (_state > 0) { | 623 if (_state > 0) { |
636 throw new FormatException("Invalid length, must be multiple of four", | 624 throw new FormatException( |
637 input, end); | 625 "Invalid length, must be multiple of four", input, end); |
638 } | 626 } |
639 _state = _encodePaddingState(0); | 627 _state = _encodePaddingState(0); |
640 } | 628 } |
641 | 629 |
642 /** | 630 /** |
643 * Decodes [input] from [start] to [end]. | 631 * Decodes [input] from [start] to [end]. |
644 * | 632 * |
645 * Includes the state returned by a previous call in the decoding. | 633 * Includes the state returned by a previous call in the decoding. |
646 * Writes the decoding to [output] at [outIndex], and there must | 634 * Writes the decoding to [output] at [outIndex], and there must |
647 * be room in the output. | 635 * be room in the output. |
648 */ | 636 */ |
649 static int decodeChunk(String input, int start, int end, | 637 static int decodeChunk(String input, int start, int end, Uint8List output, |
650 Uint8List output, int outIndex, | 638 int outIndex, int state) { |
651 int state) { | |
652 assert(!_hasSeenPadding(state)); | 639 assert(!_hasSeenPadding(state)); |
653 const int asciiMask = 127; | 640 const int asciiMask = 127; |
654 const int asciiMax = 127; | 641 const int asciiMax = 127; |
655 const int eightBitMask = 0xFF; | 642 const int eightBitMask = 0xFF; |
656 const int bitsPerCharacter = 6; | 643 const int bitsPerCharacter = 6; |
657 | 644 |
658 int bits = _stateBits(state); | 645 int bits = _stateBits(state); |
659 int count = _stateCount(state); | 646 int count = _stateCount(state); |
660 // String contents should be all ASCII. | 647 // String contents should be all ASCII. |
661 // Instead of checking for each character, we collect the bitwise-or of | 648 // Instead of checking for each character, we collect the bitwise-or of |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
715 if (char < 0 || char > asciiMax) break; | 702 if (char < 0 || char > asciiMax) break; |
716 } | 703 } |
717 throw new FormatException("Invalid character", input, i); | 704 throw new FormatException("Invalid character", input, i); |
718 } | 705 } |
719 | 706 |
720 /** | 707 /** |
721 * Allocates a buffer with room for the decoding of a substring of [input]. | 708 * Allocates a buffer with room for the decoding of a substring of [input]. |
722 * | 709 * |
723 * Includes room for the characters in [state], and handles padding correctly. | 710 * Includes room for the characters in [state], and handles padding correctly. |
724 */ | 711 */ |
725 static Uint8List _allocateBuffer(String input, int start, int end, | 712 static Uint8List _allocateBuffer( |
726 int state) { | 713 String input, int start, int end, int state) { |
727 assert(state >= 0); | 714 assert(state >= 0); |
728 int paddingStart = _trimPaddingChars(input, start, end); | 715 int paddingStart = _trimPaddingChars(input, start, end); |
729 int length = _stateCount(state) + (paddingStart - start); | 716 int length = _stateCount(state) + (paddingStart - start); |
730 // Three bytes per full four bytes in the input. | 717 // Three bytes per full four bytes in the input. |
731 int bufferLength = (length >> 2) * 3; | 718 int bufferLength = (length >> 2) * 3; |
732 // If padding was seen, then this is the last chunk, and the final partial | 719 // If padding was seen, then this is the last chunk, and the final partial |
733 // chunk should be decoded too. | 720 // chunk should be decoded too. |
734 int remainderLength = length & 3; | 721 int remainderLength = length & 3; |
735 if (remainderLength != 0 && paddingStart < end) { | 722 if (remainderLength != 0 && paddingStart < end) { |
736 bufferLength += remainderLength - 1; | 723 bufferLength += remainderLength - 1; |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
842 if (start == end) break; | 829 if (start == end) break; |
843 char = input.codeUnitAt(start); | 830 char = input.codeUnitAt(start); |
844 } | 831 } |
845 // Expects 'D' or 'd'. | 832 // Expects 'D' or 'd'. |
846 if ((char | 0x20) != _char_d) break; | 833 if ((char | 0x20) != _char_d) break; |
847 start++; | 834 start++; |
848 expectedPadding--; | 835 expectedPadding--; |
849 if (start == end) break; | 836 if (start == end) break; |
850 } | 837 } |
851 if (start != end) { | 838 if (start != end) { |
852 throw new FormatException("Invalid padding character", | 839 throw new FormatException("Invalid padding character", input, start); |
853 input, start); | |
854 } | 840 } |
855 return _encodePaddingState(expectedPadding); | 841 return _encodePaddingState(expectedPadding); |
856 } | 842 } |
857 } | 843 } |
858 | 844 |
859 class _Base64DecoderSink extends StringConversionSinkBase { | 845 class _Base64DecoderSink extends StringConversionSinkBase { |
860 /** Output sink */ | 846 /** Output sink */ |
861 final Sink<List<int>> _sink; | 847 final Sink<List<int>> _sink; |
862 final _Base64Decoder _decoder = new _Base64Decoder(); | 848 final _Base64Decoder _decoder = new _Base64Decoder(); |
863 | 849 |
(...skipping 14 matching lines...) Expand all Loading... |
878 end = RangeError.checkValidRange(start, end, string.length); | 864 end = RangeError.checkValidRange(start, end, string.length); |
879 if (start == end) return; | 865 if (start == end) return; |
880 Uint8List buffer = _decoder.decode(string, start, end); | 866 Uint8List buffer = _decoder.decode(string, start, end); |
881 if (buffer != null) _sink.add(buffer); | 867 if (buffer != null) _sink.add(buffer); |
882 if (isLast) { | 868 if (isLast) { |
883 _decoder.close(string, end); | 869 _decoder.close(string, end); |
884 _sink.close(); | 870 _sink.close(); |
885 } | 871 } |
886 } | 872 } |
887 } | 873 } |
OLD | NEW |