Chromium Code Reviews| Index: sdk/lib/convert/base64.dart |
| diff --git a/sdk/lib/convert/base64.dart b/sdk/lib/convert/base64.dart |
| index 0254f853dbacd699704530b9be2596bbff3411b8..5c95c5c07c4888c3f1b96a1d828c8ca907ab021a 100644 |
| --- a/sdk/lib/convert/base64.dart |
| +++ b/sdk/lib/convert/base64.dart |
| @@ -57,6 +57,133 @@ class Base64Codec extends Codec<List<int>, String> { |
| Base64Encoder get encoder => _encoder; |
| Base64Decoder get decoder => const Base64Decoder(); |
| + |
| + /** |
| + * Validates and normalizes the base64 encoded data in [source]. |
| + * |
| + * Only acts on the substring from [start] to [end], with [end] |
| + * defaulting to the end of the string. |
| + * |
| + * Normalization will: |
| + * * Unescape any `%`-escapes. |
| + * * Only allow valid characters (`A`-`Z`, `a`-`z`, `0`-`9`, `/` and `+`). |
| + * * Normalize a `_` or `-` character to `/` or `+`. |
| + * * Validate that existing padding (trailing `=` characters) is correct. |
| + * * If no padding exists, add correct padding if necessary and possible. |
| + * * Validate that the length is correct (a multiplum of four). |
|
floitsch
2017/02/15 17:03:09
multiple ?
Lasse Reichstein Nielsen
2017/02/17 11:22:09
Done.
|
| + */ |
| + String normalize(String source, [int start = 0, int end]) { |
| + end = RangeError.checkValidRange(start, end, source.length); |
| + const int percent = 0x25; |
| + const int equals = 0x3d; |
| + StringBuffer buffer = null; |
| + int sliceStart = start; |
| + var alphabet = _Base64Encoder._base64Alphabet; |
| + var inverseAlphabet = _Base64Decoder._inverseAlphabet; |
| + int firstPadding = -1; |
| + int firstPaddingSourceIndex = -1; |
| + int paddingCount = 0; |
| + for (int i = start; i < end;) { |
| + int sliceEnd = i; |
| + int char = source.codeUnitAt(i++); |
| + int originalChar = char; |
| + // Normalize char, keep originalChar to see if it matches the source. |
| + if (char == percent) { |
| + originalChar = -1; |
|
floitsch
2017/02/15 17:03:09
Add comment explaining that you pick '-1' because
Lasse Reichstein Nielsen
2017/02/17 11:22:09
It's actually not necessary to set it at all - a '
|
| + if (i + 2 <= end) { |
| + char = parseHexByte(source, i); // May be negative. |
| + i += 2; |
| + } else { |
| + char = 0; // Invalid. |
|
floitsch
2017/02/15 17:03:09
If this is invalid, why it is set to 0, and not `_
Lasse Reichstein Nielsen
2017/02/17 11:22:09
I'll set it to -1, so it'll just fall through to t
|
| + } |
| + } |
| + if (0 <= char && char <= 127) { |
| + int value = inverseAlphabet[char]; |
| + if (value >= 0) { |
| + char = alphabet.codeUnitAt(value); |
| + if (char == originalChar) continue; |
| + } else if (value == _Base64Decoder._padding) { |
| + if (firstPadding < 0) { |
| + // Mark position in normalized output where padding occurs. |
| + firstPadding = (buffer?.length ?? 0) + (sliceEnd - sliceStart); |
| + firstPaddingSourceIndex = sliceEnd; |
| + } |
| + paddingCount++; |
| + if (originalChar == equals) continue; |
| + } |
| + if (value != _Base64Decoder._invalid) { |
| + buffer ??= new StringBuffer(); |
| + buffer.write(source.substring(sliceStart, sliceEnd)); |
| + buffer.writeCharCode(char); |
| + sliceStart = i; |
| + continue; |
| + } |
| + } |
| + throw new FormatException("Invalid base64 data", source, sliceEnd); |
| + } |
| + if (buffer != null) { |
| + buffer.write(source.substring(sliceStart, end)); |
| + if (firstPadding >= 0) { |
| + // There was padding in the source. Check that it is valid: |
| + // * result length a multiple of four |
| + // * one or two padding characters at the end. |
| + _checkPadding(source, firstPaddingSourceIndex, end, |
| + firstPadding, paddingCount, buffer.length); |
| + } else { |
| + // Length of last chunk (1-4 chars) in the encoding. |
| + int endLength = ((buffer.length - 1) % 4) + 1; |
| + if (endLength == 1) { |
| + // The data must have length 0, 2 or 3 modulo 4. |
| + throw new FormatException("Invalid base64 encoding length ", |
| + source, end); |
| + } |
| + while (endLength < 4) { |
| + buffer.write("="); |
| + endLength++; |
| + } |
| + } |
| + return source.replaceRange(start, end, buffer.toString()); |
| + } |
| + // Original was already normalized, only check padding. |
| + int length = end - start; |
| + if (firstPadding >= 0) { |
| + _checkPadding(source, firstPaddingSourceIndex, end, |
| + firstPadding, paddingCount, length); |
| + } else { |
| + // No padding given, so add some if needed it. |
| + int endLength = length % 4; |
| + if (endLength == 1) { |
| + // The data must have length 0, 2 or 3 modulo 4. |
| + throw new FormatException("Invalid base64 encoding length ", |
| + source, end); |
| + } |
| + if (endLength > 1) { |
| + // There is no "insertAt" on String, but this works as well. |
| + source = source.replaceRange(end, end, (endLength == 2) ? "==" : "="); |
| + } |
| + } |
| + return source; |
| + } |
| + |
| + static int _checkPadding(String source, int sourceIndex, int sourceEnd, |
| + int firstPadding, int paddingCount, int length) { |
| + if (length % 4 != 0) { |
| + throw new FormatException( |
| + "Invalid base64 padding, padded length must be multiplum of four, " |
|
floitsch
2017/02/15 17:03:09
multiple
Lasse Reichstein Nielsen
2017/02/17 11:22:09
Done.
|
| + "is $length", |
| + source, sourceEnd); |
| + } |
| + if (firstPadding + paddingCount != length) { |
| + throw new FormatException( |
| + "Invalid base64 padding, '=' not at the end", |
| + source, sourceIndex); |
| + } |
| + if (paddingCount > 2) { |
| + throw new FormatException( |
| + "Invalid base64 padding, more than two '=' characters", |
| + source, sourceIndex); |
| + } |
| + } |
| } |
| // ------------------------------------------------------------------------ |
| @@ -389,7 +516,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 then be followed by `3D`, the percent-escape |
| + * character, which must then be followed by `3D`, the percent-escape |
| * for `=`. |
| */ |
| static final List<int> _inverseAlphabet = new Int8List.fromList([ |