| 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 |