Simplify base64 streaming decoder, drop decoding check.

BUG=
R=sgjesse@google.com

Review URL: https://codereview.chromium.org//1255293010 .
diff --git a/lib/src/base64.dart b/lib/src/base64.dart
index 00ff11e..b4c03a8 100644
--- a/lib/src/base64.dart
+++ b/lib/src/base64.dart
@@ -30,9 +30,6 @@
 const String _encodeTable =
       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
-const List<String> _URL_SAFE_CHARACTERS = const ['+', '/'];
-const List<String> _URL_UNSAFE_CHARACTERS = const ['-', '_'];
-
 const int _LINE_LENGTH = 76;
 const int _CR = 13;  // '\r'
 const int _LF = 10;  // '\n'
@@ -284,9 +281,6 @@
       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
@@ -306,20 +300,6 @@
         } else {
           throw new FormatException('Invalid character', input, i);
         }
-      } else if (input[i] == _URL_UNSAFE_CHARACTERS[0] ||
-                 input[i] == _URL_UNSAFE_CHARACTERS[1]) {
-
-        if (expectedSafe) {
-          throw new FormatException('Unsafe character in URL-safe string',
-                                    input, i);
-        }
-        expectedUnsafe = true;
-      } else if (input[i] == _URL_SAFE_CHARACTERS[0] ||
-                 input[i] == _URL_SAFE_CHARACTERS[1]) {
-        if (expectedUnsafe) {
-          throw new FormatException('Invalid character', input, i);
-        }
-        expectedSafe = true;
       }
       if (c >= 0) normalLength++;
       i++;
@@ -384,83 +364,33 @@
 
   final Base64Decoder _decoder = new Base64Decoder();
   final ChunkedConversionSink<List<int>> _outSink;
-  String _buffer = "";
-  bool _isSafe = false;
-  bool _isUnsafe = false;
-  int _expectPaddingCount = 3;
+  String _unconverted = "";
 
   _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 (_unconverted.isNotEmpty) {
+      chunk = _unconverted + 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);
-      _buffer = chunk.substring(remainder);
-
-      for (int i = 0;i < decodable.length; i++) {
-        if (decodable[i] == _URL_UNSAFE_CHARACTERS[0] ||
-            decodable[i] == _URL_UNSAFE_CHARACTERS[1]) {
-          if (_isSafe) {
-            throw new FormatException('Unsafe character in URL-safe string',
-                                       decodable, i);
-          }
-          _isUnsafe = true;
-        } else if (decodable[i] == _URL_SAFE_CHARACTERS[0] ||
-                   decodable[i] == _URL_SAFE_CHARACTERS[1]) {
-          if (_isUnsafe) {
-            throw new FormatException('Invalid character', decodable, i);
-          }
-          _isSafe = true;
-        }
-      }
-
-      _outSink.add(_decoder.convert(decodable));
-    } else {
-      _buffer += chunk;
+    int decodableLength = chunk.length;
+     // If chunk ends in "%" or "%3", it may be a partial encoded pad.
+     // If chunk is smaller than 4 characters, don't bother checking.
+    if (chunk.length > 3 &&
+      chunk.contains(_ENCODED_PAD[0], chunk.length - 2)) {
+      decodableLength = chunk.lastIndexOf(_ENCODED_PAD[0]);
+    }
+    decodableLength -= decodableLength % 4;
+    _unconverted = chunk.substring(decodableLength);
+    if (decodableLength > 0) {
+      _outSink.add(_decoder.convert(chunk.substring(0, decodableLength)));
     }
   }
 
   void close() {
-    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 + _PAD.substring(0, 3 - _expectPaddingCount),
-        _buffer.length + 3 - _expectPaddingCount);
+    if (_unconverted.isNotEmpty) {
+      _outSink.add(_decoder.convert(_unconverted));
     }
     _outSink.close();
   }
diff --git a/test/base64_test.dart b/test/base64_test.dart
index 76b586e..879239b 100644
--- a/test/base64_test.dart
+++ b/test/base64_test.dart
@@ -17,8 +17,6 @@
   test('decoder for malformed input', _testDecoderForMalformedInput);
   test('encode decode lists', _testEncodeDecodeLists);
   test('url safe encode-decode', _testUrlSafeEncodeDecode);
-  test('consistent safe/unsafe character decoding',
-       _testConsistentSafeUnsafeDecode);
   test('percent-encoded padding character encode-decode',
        _testPaddingCharacter);
   test('streaming encoder', _testStreamingEncoder);
@@ -29,8 +27,6 @@
        _testStreamingEncoderForDecompositions);
   test('streaming decoder for different decompositions of a string',
        _testStreamingDecoderForDecompositions);
-  test('consistent safe/unsafe character streaming decoding',
-       _testConsistentSafeUnsafeStreamDecode);
   test('streaming for encoded padding character',
        _testStreamingForEncodedPadding);
   test('old api', _testOldApi);
@@ -84,10 +80,6 @@
 
 const _DECOMPOSITION_ENCODED = 'xBCexA==';
 
-const _INCONSISTENT_SAFE_RESULT = 'A+_x';
-
-const _INCONSISTENT_SAFE_STREAMING_RESULT = const ['A+AAA', '_x='];
-
 // Test data with only zeroes.
 var inputsWithZeroes = [[0, 0, 0], [0, 0], [0], []];
 const _RESULTS_WITH_ZEROS = const ['AAAA', 'AAA=', 'AA==', ''];
@@ -237,19 +229,6 @@
   expect(streamedResult, encUrlSafe);
 }
 
-void _testConsistentSafeUnsafeDecode() {
-  expect(() {
-    BASE64.decode(_INCONSISTENT_SAFE_RESULT);
-  }, throwsFormatException);
-}
-
-Future _testConsistentSafeUnsafeStreamDecode() {
-  expect(new Stream.fromIterable(_INCONSISTENT_SAFE_STREAMING_RESULT)
-                   .transform(BASE64.decoder)
-                   .toList(),
-         throwsFormatException);
-}
-
 Future _testStreamingForEncodedPadding() async {
   List<String> withEncodedPadding = ['AA%', '3D', '%', '3', 'DEFGZ'];
   List<int> decoded = BASE64.decode('AA==EFGZ');