Index: lib/src/base64.dart |
diff --git a/lib/src/base64.dart b/lib/src/base64.dart |
index 7ccf4aa4fb97544666fe61e1034c1b7571030b8a..00ff11e84bd6cde58b3b23403f3cdaa05b6e47d3 100644 |
--- a/lib/src/base64.dart |
+++ b/lib/src/base64.dart |
@@ -1,3 +1,7 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
part of crypto; |
const Base64Codec BASE64 = const Base64Codec(); |
@@ -30,14 +34,18 @@ const List<String> _URL_SAFE_CHARACTERS = const ['+', '/']; |
const List<String> _URL_UNSAFE_CHARACTERS = const ['-', '_']; |
const int _LINE_LENGTH = 76; |
-const int _PAD = 61; // '=' |
const int _CR = 13; // '\r' |
const int _LF = 10; // '\n' |
+const List<int> _PAD_BYTES = const [61]; // '=' |
+const List<int> _ENCODED_PAD_BYTES = const [37, 51, 68]; // '%3D' |
+const String _PAD = "="; |
+const String _ENCODED_PAD = "%3D"; |
class Base64Codec extends Codec<List<int>, String> { |
final bool _urlSafe; |
final bool _addLineSeparator; |
+ final bool _encodePaddingCharacter; |
/** |
* Instantiates a new [Base64Codec]. |
@@ -57,27 +65,41 @@ class Base64Codec extends Codec<List<int>, String> { |
* optional line separator (CR + LF) for each 76 char output. |
* |
* The default value of [addLineSeparator] if `false`. |
+ * |
+ * If [encodePaddingCharacter] is `true` `encode` converts `=` to `%3D`. |
+ * The default value of [encodePaddingCharacter] is `false`. |
*/ |
- const Base64Codec({bool urlSafe: false, bool addLineSeparator: false}) |
+ const Base64Codec({bool urlSafe: false, |
+ bool addLineSeparator: false, |
+ bool encodePaddingCharacter: false}) |
: _urlSafe = urlSafe, |
- _addLineSeparator = addLineSeparator; |
+ _addLineSeparator = addLineSeparator, |
+ _encodePaddingCharacter = encodePaddingCharacter; |
String get name => "base64"; |
String encode(List<int> bytes, |
{bool urlSafe, |
- bool addLineSeparator}) { |
+ bool addLineSeparator, |
+ bool encodePaddingCharacter}) { |
if (urlSafe == null) urlSafe = _urlSafe; |
if (addLineSeparator == null) addLineSeparator = _addLineSeparator; |
- return new Base64Encoder(urlSafe: urlSafe, |
- addLineSeparator: addLineSeparator).convert(bytes); |
+ if (encodePaddingCharacter == null) { |
+ encodePaddingCharacter = _encodePaddingCharacter; |
+ } |
+ return new Base64Encoder( |
+ urlSafe: urlSafe, |
+ addLineSeparator: addLineSeparator, |
+ encodePaddingCharacter: encodePaddingCharacter) |
+ .convert(bytes); |
} |
- Base64Encoder get encoder => new Base64Encoder( |
- urlSafe: _urlSafe, |
- addLineSeparator: _addLineSeparator); |
+ Base64Encoder get encoder => |
+ new Base64Encoder(urlSafe: _urlSafe, |
+ addLineSeparator: _addLineSeparator, |
+ encodePaddingCharacter: _encodePaddingCharacter); |
Base64Decoder get decoder => new Base64Decoder(); |
@@ -90,6 +112,8 @@ class Base64Codec extends Codec<List<int>, String> { |
class Base64Encoder extends Converter<List<int>, String> { |
final bool _urlSafe; |
final bool _addLineSeparator; |
+ final bool _encodePaddingCharacter; |
+ final List<int> _pad; |
/** |
* Instantiates a new [Base64Encoder]. |
@@ -109,10 +133,17 @@ class Base64Encoder extends Converter<List<int>, String> { |
* for each 76 char output. |
* |
* The default value of [addLineSeparator] if `false`. |
+ * |
+ * If [encodePaddingCharacter] is `true` `encode` converts `=` to `%3D`. |
+ * The default value of [encodePaddingCharacter] is `false`. |
*/ |
- const Base64Encoder({bool urlSafe: false, bool addLineSeparator: false}) |
+ const Base64Encoder({bool urlSafe: false, |
+ bool addLineSeparator: false, |
+ bool encodePaddingCharacter: false}) |
: _urlSafe = urlSafe, |
- _addLineSeparator = addLineSeparator; |
+ _addLineSeparator = addLineSeparator, |
+ _encodePaddingCharacter = encodePaddingCharacter, |
+ _pad = encodePaddingCharacter == true ? _ENCODED_PAD_BYTES : _PAD_BYTES; |
/** |
* Converts [bytes] to its Base64 representation as a string. |
@@ -120,7 +151,6 @@ class Base64Encoder extends Converter<List<int>, String> { |
* if [start] and [end] are provided, only the sublist |
* `bytes.sublist(start, end)` is converted. |
*/ |
- |
String convert(List<int> bytes, [int start = 0, int end]) { |
int bytes_length = bytes.length; |
RangeError.checkValidRange(start, end, bytes_length); |
@@ -135,7 +165,13 @@ class Base64Encoder extends Converter<List<int>, String> { |
final int chunkLength = length - remainderLength; |
// Size of base output. |
int baseOutputLength = ((length ~/ 3) * 4); |
- int remainderOutputLength = ((remainderLength > 0) ? 4 : 0); |
+ int remainderOutputLength; |
+ if(_encodePaddingCharacter) { |
+ remainderOutputLength = ((remainderLength > 0) ? 6 : 0); |
+ } else { |
+ remainderOutputLength = ((remainderLength > 0) ? 4 : 0); |
+ } |
+ |
int outputLength = baseOutputLength + remainderOutputLength; |
// Add extra for line separators. |
if (_addLineSeparator) { |
@@ -167,15 +203,15 @@ class Base64Encoder extends Converter<List<int>, String> { |
int x = bytes[i]; |
out[j++] = lookup.codeUnitAt(x >> 2); |
out[j++] = lookup.codeUnitAt((x << 4) & 0x3F); |
- out[j++] = _PAD; |
- out[j++] = _PAD; |
+ out.setRange(j, j + _pad.length, _pad); |
+ out.setRange(j + _pad.length, j + 2 * _pad.length, _pad); |
} else if (remainderLength == 2) { |
int x = bytes[i]; |
int y = bytes[i + 1]; |
out[j++] = lookup.codeUnitAt(x >> 2); |
out[j++] = lookup.codeUnitAt(((x << 4) | (y >> 4)) & 0x3F); |
out[j++] = lookup.codeUnitAt((y << 2) & 0x3F); |
- out[j++] = _PAD; |
+ out.setRange(j, j + _pad.length, _pad); |
} |
return new String.fromCharCodes(out); |
@@ -242,24 +278,32 @@ class Base64Decoder extends Converter<String, List<int>> { |
*/ |
const Base64Decoder(); |
- List<int> convert(String input, {bool alwaysPadding: false}) { |
+ List<int> convert(String input) { |
int length = input.length; |
if (length == 0) { |
return new List<int>(0); |
} |
+ bool expectedSafe = false; |
+ bool expectedUnsafe = false; |
+ |
+ int normalLength = 0; |
+ int i = 0; |
// Count '\r', '\n' and illegal characters, check if |
// '/', '+' / '-', '_' are used consistently, for illegal characters, |
// throw an exception. |
- int extrasLength = 0; |
- bool expectedSafe = false; |
- bool expectedUnsafe = false; |
- for (int i = 0; i < length; i++) { |
- int c = _decodeTable[input.codeUnitAt(i)]; |
- if (c < 0) { |
- extrasLength++; |
- if (c == -2) { |
+ while (i < length) { |
+ int codeUnit = input.codeUnitAt(i); |
+ int c = _decodeTable[codeUnit]; |
+ if (c == -2) { |
+ if (codeUnit == _ENCODED_PAD_BYTES[0] && |
+ i < length - 2 && |
+ input.codeUnitAt(i + 1) == _ENCODED_PAD_BYTES[1] && |
+ input.codeUnitAt(i + 2) == _ENCODED_PAD_BYTES[2]) { |
+ normalLength++; |
+ i += 2; |
+ } else { |
throw new FormatException('Invalid character', input, i); |
} |
} else if (input[i] == _URL_UNSAFE_CHARACTERS[0] || |
@@ -277,21 +321,34 @@ class Base64Decoder extends Converter<String, List<int>> { |
} |
expectedSafe = true; |
} |
+ if (c >= 0) normalLength++; |
+ i++; |
} |
- if ((length - extrasLength) % 4 != 0) { |
+ if (normalLength % 4 != 0) { |
throw new FormatException('''Size of Base 64 characters in Input |
- must be a multiple of 4''', input, length - extrasLength); |
+ must be a multiple of 4''', input, normalLength); |
} |
// Count pad characters. |
int padLength = 0; |
- for (int i = length - 1; i >= 0; i--) { |
+ i = length - 1; |
+ while(i >= 0) { |
int currentCodeUnit = input.codeUnitAt(i); |
- if (_decodeTable[currentCodeUnit] > 0) break; |
- if (currentCodeUnit == _PAD) padLength++; |
+ if (currentCodeUnit == _ENCODED_PAD_BYTES[2] && |
+ i >= 2 && |
+ input.codeUnitAt(i - 1) == _ENCODED_PAD_BYTES[1] && |
+ input.codeUnitAt(i - 2) == _ENCODED_PAD_BYTES[0]) { |
+ padLength++; |
+ i -= 2; |
+ } else if (_decodeTable[currentCodeUnit] > 0) { |
+ break; |
+ } else if (currentCodeUnit == _PAD_BYTES[0]) { |
+ padLength++; |
+ } |
+ i--; |
} |
- int outputLength = (((length - extrasLength) * 6) >> 3) - padLength; |
+ int outputLength = ((normalLength * 6) >> 3) - padLength; |
List<int> out = new List<int>(outputLength); |
for (int i = 0, o = 0; o < outputLength; ) { |
@@ -330,12 +387,40 @@ class _Base64DecoderSink extends ChunkedConversionSink<String> { |
String _buffer = ""; |
bool _isSafe = false; |
bool _isUnsafe = false; |
+ int _expectPaddingCount = 3; |
_Base64DecoderSink(this._outSink); |
void add(String chunk) { |
+ if (chunk.isEmpty) return; |
+ |
int nextBufferLength = (chunk.length + _buffer.length) % 4; |
+ if (chunk.length >= _expectPaddingCount && |
+ chunk.substring(0, _expectPaddingCount) == |
+ _ENCODED_PAD.substring(3 - _expectPaddingCount, 3)) { |
+ chunk = _PAD + chunk.substring(_expectPaddingCount); |
+ _expectPaddingCount = 3; |
+ } else if(chunk.length < _expectPaddingCount && |
+ chunk == _ENCODED_PAD.substring( |
+ 3 - _expectPaddingCount, |
+ 3 - _expectPaddingCount + chunk.length)) { |
+ _expectPaddingCount -= chunk.length; |
+ chunk = ""; |
+ } |
+ |
+ if (chunk.length > 1 && |
+ chunk[chunk.length - 2] == _ENCODED_PAD[0] && |
+ chunk[chunk.length - 1] == _ENCODED_PAD[1]) { |
+ _expectPaddingCount = 1; |
+ chunk = chunk.substring(0, chunk.length - 2); |
+ } else if (!chunk.isEmpty && chunk[chunk.length - 1] == _ENCODED_PAD[0]) { |
+ _expectPaddingCount = 2; |
+ chunk = chunk.substring(0, chunk.length - 1); |
+ } |
+ |
+ chunk = chunk.replaceAll(_ENCODED_PAD, _PAD); |
+ |
if (chunk.length + _buffer.length >= 4) { |
int remainder = chunk.length - nextBufferLength; |
String decodable = _buffer + chunk.substring(0, remainder); |
@@ -365,13 +450,20 @@ class _Base64DecoderSink extends ChunkedConversionSink<String> { |
} |
void close() { |
- if (!_buffer.isEmpty) { |
+ if (_expectPaddingCount == 0 && |
+ _buffer.length == 3) { |
+ _outSink.add(_buffer + _PAD); |
+ } else if (_expectPaddingCount < 3 && |
+ _buffer.length + 3 - _expectPaddingCount == 4) { |
+ _outSink.add(_buffer + _ENCODED_PAD.substring(0, 3 - _expectPaddingCount)); |
+ } else if (_expectPaddingCount != 3 || !_buffer.isEmpty) { |
throw new FormatException( |
- "Size of Base 64 input must be a multiple of 4", |
- _buffer, |
- _buffer.length); |
+ "Size of Base 64 input must be a multiple of 4", |
+ _buffer + _PAD.substring(0, 3 - _expectPaddingCount), |
+ _buffer.length + 3 - _expectPaddingCount); |
} |
_outSink.close(); |
} |
} |
+ |