OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library crypto.base64.deecoder; |
| 6 |
| 7 import 'dart:convert'; |
| 8 import 'dart:typed_data'; |
| 9 |
| 10 import 'package:charcode/ascii.dart'; |
| 11 |
| 12 import 'decoder_sink.dart'; |
| 13 |
| 14 /// A mapping from ASCII character codes to their corresponding Base64 values. |
| 15 /// |
| 16 /// Characters with a value of `null` can't be decoded directly. This includes |
| 17 /// special values like CR, LF, `=`, and `%`. |
| 18 const _decodeTable = const [ |
| 19 null, null, null, null, null, null, null, null, null, null, null, null, null, |
| 20 null, null, null, null, null, null, null, null, null, null, null, null, null, |
| 21 null, null, null, null, null, null, null, null, null, null, null, null, null, |
| 22 null, null, null, null, 62, null, 62, null, 63, 52, 53, 54, 55, 56, 57, 58, |
| 23 59, 60, 61, null, null, null, null, null, null, null, 0, 1, 2, 3, 4, 5, 6, 7, |
| 24 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, null, |
| 25 null, null, null, 63, null, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, |
| 26 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 |
| 27 ]; |
| 28 |
| 29 /// An encoder that converts [Base64][rfc] strings to sequences of bytes. |
| 30 /// |
| 31 /// [rfc]: https://tools.ietf.org/html/rfc4648 |
| 32 class Base64Decoder extends Converter<String, List<int>> { |
| 33 const Base64Decoder(); |
| 34 |
| 35 List<int> convert(String input) { |
| 36 if (input.length == 0) return new Uint8List(0); |
| 37 |
| 38 // The length of the actual data sections in the input (not CRLFs). |
| 39 var dataLength = 0; |
| 40 |
| 41 // Count the data, and fail for invalid characters. |
| 42 for (var i = 0; i < input.length; i++) { |
| 43 var codeUnit = input.codeUnitAt(i); |
| 44 |
| 45 if (codeUnit == $cr || codeUnit == $lf) continue; |
| 46 |
| 47 if (codeUnit == $percent && |
| 48 i < input.length - 2 && |
| 49 input.codeUnitAt(i + 1) == $3 && |
| 50 input.codeUnitAt(i + 2) == $D) { |
| 51 dataLength++; |
| 52 i += 2; |
| 53 continue; |
| 54 } |
| 55 |
| 56 if (codeUnit != $equal && |
| 57 (codeUnit >= _decodeTable.length || _decodeTable[codeUnit] == null)) { |
| 58 throw new FormatException('Invalid character', input, i); |
| 59 } |
| 60 |
| 61 dataLength++; |
| 62 } |
| 63 |
| 64 if (dataLength % 4 != 0) { |
| 65 throw new FormatException( |
| 66 'Base64 input must encode a multiple of 4 bytes.', |
| 67 input, |
| 68 dataLength); |
| 69 } |
| 70 |
| 71 // Count the trailing pad characters. |
| 72 var padLength = 0; |
| 73 for (var i = input.length - 1; i >= 0; i--) { |
| 74 var codeUnit = input.codeUnitAt(i); |
| 75 if (codeUnit == $D && |
| 76 i >= 2 && |
| 77 input.codeUnitAt(i - 2) == $percent && |
| 78 input.codeUnitAt(i - 1) == $3) { |
| 79 padLength++; |
| 80 i -= 2; |
| 81 } else if (codeUnit == $equal) { |
| 82 padLength++; |
| 83 } else if (codeUnit != $cr && codeUnit != $lf) { |
| 84 break; |
| 85 } |
| 86 } |
| 87 var outputLength = ((dataLength * 6) >> 3) - padLength; |
| 88 var out = new Uint8List(outputLength); |
| 89 |
| 90 var inputIndex = 0; |
| 91 var outputIndex = 0; |
| 92 while (outputIndex < outputLength) { |
| 93 // Accumulate four 6-bit Base64 characters into a 32-bit chunk. |
| 94 var chunk = 0; |
| 95 for (var i = 0; i < 4; i++) { |
| 96 var codeUnit = input.codeUnitAt(inputIndex++); |
| 97 |
| 98 if (codeUnit == $equal || codeUnit == $percent) { |
| 99 // We've reached the end of the source. Pad out the rest of the chunk |
| 100 // with zeroes. |
| 101 chunk <<= (4 - i) * 6; |
| 102 break; |
| 103 } |
| 104 |
| 105 if (codeUnit == $cr || codeUnit == $lf) { |
| 106 i--; |
| 107 } else { |
| 108 chunk = (chunk << 6) | _decodeTable[codeUnit]; |
| 109 } |
| 110 } |
| 111 |
| 112 // Emit 8-bit pieces of the chunk to the output buffer. |
| 113 out[outputIndex++] = chunk >> 16; |
| 114 if (outputIndex >= outputLength) break; |
| 115 |
| 116 out[outputIndex++] = (chunk >> 8) & 0xFF; |
| 117 if (outputIndex >= outputLength) break; |
| 118 |
| 119 out[outputIndex++] = chunk & 0xFF; |
| 120 } |
| 121 |
| 122 return out; |
| 123 } |
| 124 |
| 125 Base64DecoderSink startChunkedConversion(Sink<List<int>> sink) { |
| 126 if (sink is! ByteConversionSink) sink = new ByteConversionSink.from(sink); |
| 127 return new Base64DecoderSink(sink); |
| 128 } |
| 129 } |
OLD | NEW |