Chromium Code Reviews| Index: sdk/lib/convert/base64.dart |
| diff --git a/sdk/lib/convert/base64.dart b/sdk/lib/convert/base64.dart |
| index eb7519a2710175d9ace3aaaea344e44d9a498f8d..8cc05f3cd557346f35d591684020af6cf81d2088 100644 |
| --- a/sdk/lib/convert/base64.dart |
| +++ b/sdk/lib/convert/base64.dart |
| @@ -5,13 +5,11 @@ |
| part of dart.convert; |
| /** |
| - * An instance of [Base64Codec]. |
| + * A [base64](https://tools.ietf.org/html/rfc4648) encoder and decoder. |
| * |
| - * This instance provides a convenient access to |
| - * [base64](https://tools.ietf.org/html/rfc4648) encoding and decoding. |
| - * |
| - * It encodes and decodes using the default base64 alphabet, does not allow |
| - * any invalid characters when decoding, and requires padding. |
| + * It encodes using the default base64 alphabet, |
| + * decodes using both the base64 and base64url alphabets, |
| + * does not allow invalid characters and requires padding. |
| * |
| * Examples: |
| * |
| @@ -21,6 +19,21 @@ part of dart.convert; |
| */ |
| const Base64Codec BASE64 = const Base64Codec(); |
| +/** |
| + * A [base64url](https://tools.ietf.org/html/rfc4648) encoder and decoder. |
| + * |
| + * It encodes and decodes using the base64url alphabet, |
| + * decodes using both the base64 and base64url alphabets, |
| + * does not allow invalid characters and requires padding. |
| + * |
| + * Examples: |
| + * |
| + * var encoded = BASE64.encode([0x62, 0x6c, 0xc3, 0xa5, 0x62, 0xc3, 0xa6, |
| + * 0x72, 0x67, 0x72, 0xc3, 0xb8, 0x64]); |
| + * var decoded = BASE64.decode("YmzDpWLDpnJncsO4ZAo="); |
| + */ |
| +const Base64Codec BASE64URL = const Base64Codec.urlSafe(); |
|
Lasse Reichstein Nielsen
2016/04/05 11:07:44
We could (and maybe should) change the type to Cod
floitsch
2016/04/05 13:45:33
With the ChunkedConverter change it is better, if
|
| + |
| // Constants used in more than one class. |
| const int _paddingChar = 0x3d; // '='. |
| @@ -30,15 +43,18 @@ const int _paddingChar = 0x3d; // '='. |
| * A [Base64Codec] allows base64 encoding bytes into ASCII strings and |
| * decoding valid encodings back to bytes. |
| * |
| - * This implementation only handles the simplest RFC 4648 base-64 encoding. |
| + * This implementation only handles the simplest RFC 4648 base64 and base64url |
| + * encodings. |
| * It does not allow invalid characters when decoding and it requires, |
| * and generates, padding so that the input is always a multiple of four |
| * characters. |
| */ |
| class Base64Codec extends Codec<List<int>, String> { |
| - const Base64Codec(); |
| + final Base64Encoder _encoder; |
| + const Base64Codec() : _encoder = const Base64Encoder(); |
| + const Base64Codec.urlSafe() : _encoder = const Base64Encoder.urlSafe(); |
| - Base64Encoder get encoder => const Base64Encoder(); |
| + Base64Encoder get encoder => _encoder; |
| Base64Decoder get decoder => const Base64Decoder(); |
| } |
| @@ -48,44 +64,53 @@ class Base64Codec extends Codec<List<int>, String> { |
| // ------------------------------------------------------------------------ |
| /** |
| - * Base-64 encoding converter. |
| + * Base64 and base64url encoding converter. |
| * |
| - * Encodes lists of bytes using base64 encoding. |
| - * The result are ASCII strings using a restricted alphabet. |
| + * Encodes lists of bytes using base64 or base64url encoding. |
| + * |
| + * The results are ASCII strings using a restricted alphabet. |
| */ |
| class Base64Encoder extends Converter<List<int>, String> { |
| - const Base64Encoder(); |
| + final bool _urlSafe; |
| + |
| + const Base64Encoder() : _urlSafe = false; |
| + const Base64Encoder.urlSafe() : _urlSafe = true; |
| String convert(List<int> input) { |
| if (input.isEmpty) return ""; |
| - var encoder = new _Base64Encoder(); |
| + var encoder = new _Base64Encoder(_urlSafe); |
| Uint8List buffer = encoder.encode(input, 0, input.length, true); |
| return new String.fromCharCodes(buffer); |
| } |
| ByteConversionSink startChunkedConversion(Sink<String> sink) { |
| if (sink is StringConversionSink) { |
| - return new _Utf8Base64EncoderSink(sink.asUtf8Sink(false)); |
| + return new _Utf8Base64EncoderSink(sink.asUtf8Sink(false), _urlSafe); |
| } |
| - return new _AsciiBase64EncoderSink(sink); |
| + return new _AsciiBase64EncoderSink(sink, _urlSafe); |
| } |
| Stream<String> bind(Stream<List<int>> stream) { |
| return new Stream<String>.eventTransformed( |
| stream, |
| (EventSink sink) => |
| - new _ConverterStreamEventSink<List<int>, String>(this, sink)); |
| + new _ConverterStreamEventSink<List<int>, String>( |
| + this, sink, _urlSafe)); |
| } |
| } |
| /** |
| - * Helper class for encoding bytes to BASE-64. |
| + * Helper class for encoding bytes to base64. |
| */ |
| class _Base64Encoder { |
| /** The RFC 4648 base64 encoding alphabet. */ |
| static const String _base64Alphabet = |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| + /** The RFC 4648 base64url encoding alphabet. */ |
| + static const String _base64urlAlphabet = |
| + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; |
| + |
| /** Shift-count to extract the values stored in [_state]. */ |
| static const int _valueShift = 2; |
| @@ -103,6 +128,12 @@ class _Base64Encoder { |
| */ |
| int _state = 0; |
| + /** Alphabet used for encoding. */ |
| + final String _alphabet; |
| + |
| + _Base64Encoder(bool urlSafe) |
| + : _alphabet = urlSafe ? _base64urlAlphabet : _base64Alphabet; |
| + |
| /** Encode count and bits into a value to be stored in [_state]. */ |
| static int _encodeState(int count, int bits) { |
| assert(count <= _countMask); |
| @@ -148,15 +179,17 @@ class _Base64Encoder { |
| bufferLength += 4; // Room for padding. |
| } |
| var output = createBuffer(bufferLength); |
| - _state = encodeChunk(bytes, start, end, isLast, output, 0, _state); |
| + _state = encodeChunk(_alphabet, bytes, start, end, isLast, |
| + output, 0, _state); |
| if (bufferLength > 0) return output; |
| // If the input plus the data in state is still less than three bytes, |
| // there may not be any output. |
| return null; |
| } |
| - static int encodeChunk(List<int> bytes, int start, int end, bool isLast, |
| - Uint8List output, int outputIndex, int state) { |
| + static int encodeChunk(String alphabet, |
| + List<int> bytes, int start, int end, bool isLast, |
| + Uint8List output, int outputIndex, int state) { |
| int bits = _stateBits(state); |
| // Count number of missing bytes in three-byte chunk. |
| int expectedChars = 3 - _stateCount(state); |
| @@ -172,20 +205,20 @@ class _Base64Encoder { |
| expectedChars--; |
| if (expectedChars == 0) { |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 18) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 18) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 12) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 12) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 6) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 6) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt(bits & _sixBitMask); |
| + alphabet.codeUnitAt(bits & _sixBitMask); |
| expectedChars = 3; |
| bits = 0; |
| } |
| } |
| if (byteOr >= 0 && byteOr <= 255) { |
| if (isLast && expectedChars < 3) { |
| - writeFinalChunk(output, outputIndex, 3 - expectedChars, bits); |
| + writeFinalChunk(alphabet, output, outputIndex, 3 - expectedChars, bits); |
| return 0; |
| } |
| return _encodeState(3 - expectedChars, bits); |
| @@ -208,24 +241,25 @@ class _Base64Encoder { |
| * Only used when the [_state] contains a partial (1 or 2 byte) |
| * input. |
| */ |
| - static void writeFinalChunk(Uint8List output, int outputIndex, |
| + static void writeFinalChunk(String alphabet, |
| + Uint8List output, int outputIndex, |
| int count, int bits) { |
| assert(count > 0); |
| if (count == 1) { |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 2) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 2) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits << 4) & _sixBitMask); |
| + alphabet.codeUnitAt((bits << 4) & _sixBitMask); |
| output[outputIndex++] = _paddingChar; |
| output[outputIndex++] = _paddingChar; |
| } else { |
| assert(count == 2); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 10) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 10) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits >> 4) & _sixBitMask); |
| + alphabet.codeUnitAt((bits >> 4) & _sixBitMask); |
| output[outputIndex++] = |
| - _base64Alphabet.codeUnitAt((bits << 2) & _sixBitMask); |
| + alphabet.codeUnitAt((bits << 2) & _sixBitMask); |
| output[outputIndex++] = _paddingChar; |
| } |
| } |
| @@ -240,6 +274,8 @@ class _BufferCachingBase64Encoder extends _Base64Encoder { |
| */ |
| Uint8List bufferCache; |
| + _BufferCachingBase64Encoder(bool urlSafe) : super(urlSafe); |
| + |
| Uint8List createBuffer(int bufferLength) { |
| if (bufferCache == null || bufferCache.length < bufferLength) { |
| bufferCache = new Uint8List(bufferLength); |
| @@ -268,11 +304,11 @@ abstract class _Base64EncoderSink extends ByteConversionSinkBase { |
| } |
| class _AsciiBase64EncoderSink extends _Base64EncoderSink { |
| - final _Base64Encoder _encoder = new _BufferCachingBase64Encoder(); |
| - |
| final Sink<String> _sink; |
| + final _Base64Encoder _encoder; |
| - _AsciiBase64EncoderSink(this._sink); |
| + _AsciiBase64EncoderSink(this._sink, bool urlSafe) |
| + : _encoder = new _BufferCachingBase64Encoder(urlSafe); |
| void _add(List<int> source, int start, int end, bool isLast) { |
| Uint8List buffer = _encoder.encode(source, start, end, isLast); |
| @@ -288,9 +324,10 @@ class _AsciiBase64EncoderSink extends _Base64EncoderSink { |
| class _Utf8Base64EncoderSink extends _Base64EncoderSink { |
| final ByteConversionSink _sink; |
| - final _Base64Encoder _encoder = new _Base64Encoder(); |
| + final _Base64Encoder _encoder; |
| - _Utf8Base64EncoderSink(this._sink); |
| + _Utf8Base64EncoderSink(this._sink, bool urlSafe) |
| + : _encoder = new _Base64Encoder(urlSafe); |
| void _add(List<int> source, int start, int end, bool isLast) { |
| Uint8List buffer = _encoder.encode(source, start, end, isLast); |
| @@ -304,6 +341,13 @@ class _Utf8Base64EncoderSink extends _Base64EncoderSink { |
| // Decoder |
| // ------------------------------------------------------------------------ |
| +/** |
| + * Decoder for base64 encoded data. |
| + * |
| + * This decoder accepts both base64 and base64url ("url-safe") encodings. |
| + * |
| + * The encoding is required to be properly padded. |
| + */ |
| class Base64Decoder extends Converter<String, List<int>> { |
| const Base64Decoder(); |
| @@ -349,7 +393,7 @@ class _Base64Decoder { |
| * |
| * Accepts the "URL-safe" alphabet as well (`-` and `_` are the |
| * 62nd and 63rd alphabet characters), and considers `%` a padding |
| - * character which mush be followed by `3D`, the percent-escape |
| + * character, which mush then be followed by `3D`, the percent-escape |
| * for `=`. |
| */ |
| static final List<int> _inverseAlphabet = new Int8List.fromList([ |
| @@ -371,7 +415,7 @@ class _Base64Decoder { |
| /** |
| * Maintains the intermediate state of a partly-decoded input. |
| * |
| - * BASE-64 is decoded in chunks of four characters. If a chunk does not |
| + * Base64 is decoded in chunks of four characters. If a chunk does not |
| * contain a full block, the decoded bits (six per character) of the |
| * available characters are stored in [_state] until the next call to |
| * [_decode] or [_close]. |
| @@ -618,7 +662,7 @@ class _Base64Decoder { |
| * Check that the remainder of the string is valid padding. |
| * |
| * Valid padding is a correct number (0, 1 or 2) of `=` characters |
| - * or `%3D` sequences depending on the number of preceding BASE-64 characters. |
| + * or `%3D` sequences depending on the number of preceding base64 characters. |
| * The [state] parameter encodes which padding continuations are allowed |
| * as the number of expected characters. That number is the number of |
| * expected padding characters times 3 minus the number of padding characters |