Index: lib/src/bound_multipart_stream.dart |
diff --git a/lib/src/bound_multipart_stream.dart b/lib/src/bound_multipart_stream.dart |
index ab06dabeb74b610cc9ef73cb0cc664b2a282724e..270503a73e85a84ab4a087577224816d7130a47e 100644 |
--- a/lib/src/bound_multipart_stream.dart |
+++ b/lib/src/bound_multipart_stream.dart |
@@ -19,8 +19,9 @@ bool _isTokenChar(int byte) { |
int _toLowerCase(int byte) { |
const delta = CharCode.LOWER_A - CharCode.UPPER_A; |
- return (CharCode.UPPER_A <= byte && byte <= CharCode.UPPER_Z) ? |
- byte + delta : byte; |
+ return (CharCode.UPPER_A <= byte && byte <= CharCode.UPPER_Z) |
+ ? byte + delta |
+ : byte; |
} |
void _expectByteValue(int val1, int val2) { |
@@ -42,354 +43,339 @@ class _MimeMultipart extends MimeMultipart { |
_MimeMultipart(this.headers, this._stream); |
StreamSubscription<List<int>> listen(void onData(List<int> data), |
- {void onDone(), |
- Function onError, |
- bool cancelOnError}) { |
+ {void onDone(), Function onError, bool cancelOnError}) { |
return _stream.listen(onData, |
- onDone: onDone, |
- onError: onError, |
- cancelOnError: cancelOnError); |
+ onDone: onDone, onError: onError, cancelOnError: cancelOnError); |
} |
} |
class BoundMultipartStream { |
- static const int _START = 0; |
- static const int _BOUNDARY_ENDING = 1; |
- static const int _BOUNDARY_END = 2; |
- static const int _HEADER_START = 3; |
- static const int _HEADER_FIELD = 4; |
- static const int _HEADER_VALUE_START = 5; |
- static const int _HEADER_VALUE = 6; |
- static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7; |
- static const int _HEADER_VALUE_FOLD_OR_END = 8; |
- static const int _HEADER_ENDING = 9; |
- static const int _CONTENT = 10; |
- static const int _LAST_BOUNDARY_DASH2 = 11; |
- static const int _LAST_BOUNDARY_ENDING = 12; |
- static const int _LAST_BOUNDARY_END = 13; |
- static const int _DONE = 14; |
- static const int _FAIL = 15; |
- |
- final List<int> _boundary; |
- final List<int> _headerField = []; |
- final List<int> _headerValue = []; |
- |
- // The following states belong to `_controller`, state changes will not be |
- // immediately acted upon but rather only after the current |
- // `_multipartController` is done. |
- static const int _CONTROLLER_STATE_IDLE = 0; |
- static const int _CONTROLLER_STATE_ACTIVE = 1; |
- static const int _CONTROLLER_STATE_PAUSED = 2; |
- static const int _CONTROLLER_STATE_CANCELED = 3; |
- |
- int _controllerState = _CONTROLLER_STATE_IDLE; |
- |
- StreamController _controller; |
- |
- Stream<MimeMultipart> get stream => _controller.stream; |
- |
- StreamSubscription _subscription; |
- |
- StreamController _multipartController; |
- Map<String, String> _headers; |
- |
- int _state = _START; |
- int _boundaryIndex = 2; |
- |
- // Current index in the data buffer. If index is negative then it |
- // is the index into the artificial prefix of the boundary string. |
- int _index; |
- List<int> _buffer; |
- |
- BoundMultipartStream(this._boundary, Stream<List<int>> stream) { |
- _controller = new StreamController( |
- sync: true, |
- onPause: _pauseStream, |
- onResume: _resumeStream, |
- onCancel: () { |
- _controllerState = _CONTROLLER_STATE_CANCELED; |
- _tryPropagateControllerState(); |
- }, |
- onListen: () { |
- _controllerState = _CONTROLLER_STATE_ACTIVE; |
- _subscription = stream.listen( |
- (data) { |
- assert(_buffer == null); |
- _subscription.pause(); |
- _buffer = data; |
- _index = 0; |
- _parse(); |
- }, |
- onDone: () { |
- if (_state != _DONE) { |
- _controller.addError( |
- new MimeMultipartException("Bad multipart ending")); |
- } |
- _controller.close(); |
- }, |
- onError: _controller.addError); |
- }); |
- } |
- |
- void _resumeStream() { |
- assert (_controllerState == _CONTROLLER_STATE_PAUSED); |
- _controllerState = _CONTROLLER_STATE_ACTIVE; |
- _tryPropagateControllerState(); |
- } |
- |
- void _pauseStream() { |
- _controllerState = _CONTROLLER_STATE_PAUSED; |
- _tryPropagateControllerState(); |
- } |
- |
- void _tryPropagateControllerState() { |
- if (_multipartController == null) { |
- switch (_controllerState) { |
- case _CONTROLLER_STATE_ACTIVE: |
- if (_subscription.isPaused) _subscription.resume(); |
- break; |
- case _CONTROLLER_STATE_PAUSED: |
- if (!_subscription.isPaused) _subscription.pause(); |
- break; |
- case _CONTROLLER_STATE_CANCELED: |
- _subscription.cancel(); |
- break; |
- default: |
- throw new StateError("This code should never be reached."); |
- } |
- } |
- } |
- |
- void _parse() { |
- // Number of boundary bytes to artificially place before the supplied data. |
- int boundaryPrefix = 0; |
- // Position where content starts. Will be null if no known content |
- // start exists. Will be negative of the content starts in the |
- // boundary prefix. Will be zero or position if the content starts |
- // in the current buffer. |
- int contentStartIndex; |
- |
- // Function to report content data for the current part. The data |
- // reported is from the current content start index up til the |
- // current index. As the data can be artificially prefixed with a |
- // prefix of the boundary both the content start index and index |
- // can be negative. |
- void reportData() { |
- if (contentStartIndex < 0) { |
- var contentLength = boundaryPrefix + _index - _boundaryIndex; |
- if (contentLength <= boundaryPrefix) { |
- _multipartController.add( |
- _boundary.sublist(0, contentLength)); |
- } else { |
- _multipartController.add( |
- _boundary.sublist(0, boundaryPrefix)); |
- _multipartController.add( |
- _buffer.sublist(0, contentLength - boundaryPrefix)); |
- } |
- } else { |
- var contentEndIndex = _index - _boundaryIndex; |
- _multipartController.add( |
- _buffer.sublist(contentStartIndex, contentEndIndex)); |
- } |
- } |
- |
- if (_state == _CONTENT && _boundaryIndex == 0) { |
- contentStartIndex = 0; |
- } else { |
- contentStartIndex = null; |
- } |
- // The data to parse might be "artificially" prefixed with a |
- // partial match of the boundary. |
- boundaryPrefix = _boundaryIndex; |
- |
- while ((_index < _buffer.length) && _state != _FAIL && _state != _DONE) { |
- int byte; |
- if (_index < 0) { |
- byte = _boundary[boundaryPrefix + _index]; |
- } else { |
- byte = _buffer[_index]; |
- } |
- switch (_state) { |
- case _START: |
- if (byte == _boundary[_boundaryIndex]) { |
- _boundaryIndex++; |
- if (_boundaryIndex == _boundary.length) { |
- _state = _BOUNDARY_ENDING; |
- _boundaryIndex = 0; |
- } |
- } else { |
- // Restart matching of the boundary. |
- _index = _index - _boundaryIndex; |
- _boundaryIndex = 0; |
- } |
- break; |
- |
- case _BOUNDARY_ENDING: |
- if (byte == CharCode.CR) { |
- _state = _BOUNDARY_END; |
- } else if (byte == CharCode.DASH) { |
- _state = _LAST_BOUNDARY_DASH2; |
- } else { |
- _expectWhitespace(byte); |
- } |
- break; |
- |
- case _BOUNDARY_END: |
- _expectByteValue(byte, CharCode.LF); |
- if (_multipartController != null) { |
- _multipartController.close(); |
- _multipartController = null; |
- _tryPropagateControllerState(); |
- } |
- _state = _HEADER_START; |
- break; |
- |
- case _HEADER_START: |
- _headers = new Map<String, String>(); |
- if (byte == CharCode.CR) { |
- _state = _HEADER_ENDING; |
- } else { |
- // Start of new header field. |
- _headerField.add(_toLowerCase(byte)); |
- _state = _HEADER_FIELD; |
- } |
- break; |
- |
- case _HEADER_FIELD: |
- if (byte == CharCode.COLON) { |
- _state = _HEADER_VALUE_START; |
- } else { |
- if (!_isTokenChar(byte)) { |
- throw new MimeMultipartException("Invalid header field name"); |
- } |
- _headerField.add(_toLowerCase(byte)); |
- } |
- break; |
- |
- case _HEADER_VALUE_START: |
- if (byte == CharCode.CR) { |
- _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
- } else if (byte != CharCode.SP && byte != CharCode.HT) { |
- // Start of new header value. |
- _headerValue.add(byte); |
- _state = _HEADER_VALUE; |
- } |
- break; |
- |
- case _HEADER_VALUE: |
- if (byte == CharCode.CR) { |
- _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
- } else { |
- _headerValue.add(byte); |
- } |
- break; |
- |
- case _HEADER_VALUE_FOLDING_OR_ENDING: |
- _expectByteValue(byte, CharCode.LF); |
- _state = _HEADER_VALUE_FOLD_OR_END; |
- break; |
- |
- case _HEADER_VALUE_FOLD_OR_END: |
- if (byte == CharCode.SP || byte == CharCode.HT) { |
- _state = _HEADER_VALUE_START; |
- } else { |
- String headerField = UTF8.decode(_headerField); |
- String headerValue = UTF8.decode(_headerValue); |
- _headers[headerField.toLowerCase()] = headerValue; |
- _headerField.clear(); |
- _headerValue.clear(); |
- if (byte == CharCode.CR) { |
- _state = _HEADER_ENDING; |
- } else { |
- // Start of new header field. |
- _headerField.add(_toLowerCase(byte)); |
- _state = _HEADER_FIELD; |
- } |
- } |
- break; |
- |
- case _HEADER_ENDING: |
- _expectByteValue(byte, CharCode.LF); |
- _multipartController = new StreamController( |
- sync: true, |
- onListen: () { |
- if (_subscription.isPaused) _subscription.resume(); |
- }, |
- onPause: _subscription.pause, |
- onResume: _subscription.resume); |
- _controller.add( |
- new _MimeMultipart(_headers, _multipartController.stream)); |
- _headers = null; |
- _state = _CONTENT; |
- contentStartIndex = _index + 1; |
- break; |
- |
- case _CONTENT: |
- if (byte == _boundary[_boundaryIndex]) { |
- _boundaryIndex++; |
- if (_boundaryIndex == _boundary.length) { |
- if (contentStartIndex != null) { |
- _index++; |
- reportData(); |
- _index--; |
- } |
- _multipartController.close(); |
- _multipartController = null; |
- _tryPropagateControllerState(); |
- _boundaryIndex = 0; |
- _state = _BOUNDARY_ENDING; |
- } |
- } else { |
- // Restart matching of the boundary. |
- _index = _index - _boundaryIndex; |
- if (contentStartIndex == null) contentStartIndex = _index; |
- _boundaryIndex = 0; |
- } |
- break; |
- |
- case _LAST_BOUNDARY_DASH2: |
- _expectByteValue(byte, CharCode.DASH); |
- _state = _LAST_BOUNDARY_ENDING; |
- break; |
- |
- case _LAST_BOUNDARY_ENDING: |
- if (byte == CharCode.CR) { |
- _state = _LAST_BOUNDARY_END; |
- } else { |
- _expectWhitespace(byte); |
- } |
- break; |
- |
- case _LAST_BOUNDARY_END: |
- _expectByteValue(byte, CharCode.LF); |
- if (_multipartController != null) { |
- _multipartController.close(); |
- _multipartController = null; |
- _tryPropagateControllerState(); |
- } |
- _state = _DONE; |
- break; |
- |
- default: |
- // Should be unreachable. |
- assert(false); |
- break; |
- } |
- |
- // Move to the next byte. |
- _index++; |
- } |
- |
- // Report any known content. |
- if (_state == _CONTENT && contentStartIndex != null) { |
- reportData(); |
- } |
- |
- // Resume if at end. |
- if (_index == _buffer.length) { |
- _buffer = null; |
- _index = null; |
- _subscription.resume(); |
- } |
- } |
+ static const int _START = 0; |
+ static const int _BOUNDARY_ENDING = 1; |
+ static const int _BOUNDARY_END = 2; |
+ static const int _HEADER_START = 3; |
+ static const int _HEADER_FIELD = 4; |
+ static const int _HEADER_VALUE_START = 5; |
+ static const int _HEADER_VALUE = 6; |
+ static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7; |
+ static const int _HEADER_VALUE_FOLD_OR_END = 8; |
+ static const int _HEADER_ENDING = 9; |
+ static const int _CONTENT = 10; |
+ static const int _LAST_BOUNDARY_DASH2 = 11; |
+ static const int _LAST_BOUNDARY_ENDING = 12; |
+ static const int _LAST_BOUNDARY_END = 13; |
+ static const int _DONE = 14; |
+ static const int _FAIL = 15; |
+ |
+ final List<int> _boundary; |
+ final List<int> _headerField = []; |
+ final List<int> _headerValue = []; |
+ |
+ // The following states belong to `_controller`, state changes will not be |
+ // immediately acted upon but rather only after the current |
+ // `_multipartController` is done. |
+ static const int _CONTROLLER_STATE_IDLE = 0; |
+ static const int _CONTROLLER_STATE_ACTIVE = 1; |
+ static const int _CONTROLLER_STATE_PAUSED = 2; |
+ static const int _CONTROLLER_STATE_CANCELED = 3; |
+ |
+ int _controllerState = _CONTROLLER_STATE_IDLE; |
+ |
+ StreamController _controller; |
+ |
+ Stream<MimeMultipart> get stream => _controller.stream; |
+ |
+ StreamSubscription _subscription; |
+ |
+ StreamController _multipartController; |
+ Map<String, String> _headers; |
+ |
+ int _state = _START; |
+ int _boundaryIndex = 2; |
+ |
+ // Current index in the data buffer. If index is negative then it |
+ // is the index into the artificial prefix of the boundary string. |
+ int _index; |
+ List<int> _buffer; |
+ |
+ BoundMultipartStream(this._boundary, Stream<List<int>> stream) { |
+ _controller = new StreamController( |
+ sync: true, |
+ onPause: _pauseStream, |
+ onResume: _resumeStream, onCancel: () { |
+ _controllerState = _CONTROLLER_STATE_CANCELED; |
+ _tryPropagateControllerState(); |
+ }, onListen: () { |
+ _controllerState = _CONTROLLER_STATE_ACTIVE; |
+ _subscription = stream.listen((data) { |
+ assert(_buffer == null); |
+ _subscription.pause(); |
+ _buffer = data; |
+ _index = 0; |
+ _parse(); |
+ }, onDone: () { |
+ if (_state != _DONE) { |
+ _controller |
+ .addError(new MimeMultipartException("Bad multipart ending")); |
+ } |
+ _controller.close(); |
+ }, onError: _controller.addError); |
+ }); |
+ } |
+ |
+ void _resumeStream() { |
+ assert(_controllerState == _CONTROLLER_STATE_PAUSED); |
+ _controllerState = _CONTROLLER_STATE_ACTIVE; |
+ _tryPropagateControllerState(); |
+ } |
+ |
+ void _pauseStream() { |
+ _controllerState = _CONTROLLER_STATE_PAUSED; |
+ _tryPropagateControllerState(); |
+ } |
+ |
+ void _tryPropagateControllerState() { |
+ if (_multipartController == null) { |
+ switch (_controllerState) { |
+ case _CONTROLLER_STATE_ACTIVE: |
+ if (_subscription.isPaused) _subscription.resume(); |
+ break; |
+ case _CONTROLLER_STATE_PAUSED: |
+ if (!_subscription.isPaused) _subscription.pause(); |
+ break; |
+ case _CONTROLLER_STATE_CANCELED: |
+ _subscription.cancel(); |
+ break; |
+ default: |
+ throw new StateError("This code should never be reached."); |
+ } |
+ } |
+ } |
+ |
+ void _parse() { |
+ // Number of boundary bytes to artificially place before the supplied data. |
+ int boundaryPrefix = 0; |
+ // Position where content starts. Will be null if no known content |
+ // start exists. Will be negative of the content starts in the |
+ // boundary prefix. Will be zero or position if the content starts |
+ // in the current buffer. |
+ int contentStartIndex; |
+ |
+ // Function to report content data for the current part. The data |
+ // reported is from the current content start index up til the |
+ // current index. As the data can be artificially prefixed with a |
+ // prefix of the boundary both the content start index and index |
+ // can be negative. |
+ void reportData() { |
+ if (contentStartIndex < 0) { |
+ var contentLength = boundaryPrefix + _index - _boundaryIndex; |
+ if (contentLength <= boundaryPrefix) { |
+ _multipartController.add(_boundary.sublist(0, contentLength)); |
+ } else { |
+ _multipartController.add(_boundary.sublist(0, boundaryPrefix)); |
+ _multipartController |
+ .add(_buffer.sublist(0, contentLength - boundaryPrefix)); |
+ } |
+ } else { |
+ var contentEndIndex = _index - _boundaryIndex; |
+ _multipartController |
+ .add(_buffer.sublist(contentStartIndex, contentEndIndex)); |
+ } |
+ } |
+ |
+ if (_state == _CONTENT && _boundaryIndex == 0) { |
+ contentStartIndex = 0; |
+ } else { |
+ contentStartIndex = null; |
+ } |
+ // The data to parse might be "artificially" prefixed with a |
+ // partial match of the boundary. |
+ boundaryPrefix = _boundaryIndex; |
+ |
+ while ((_index < _buffer.length) && _state != _FAIL && _state != _DONE) { |
+ int byte; |
+ if (_index < 0) { |
+ byte = _boundary[boundaryPrefix + _index]; |
+ } else { |
+ byte = _buffer[_index]; |
+ } |
+ switch (_state) { |
+ case _START: |
+ if (byte == _boundary[_boundaryIndex]) { |
+ _boundaryIndex++; |
+ if (_boundaryIndex == _boundary.length) { |
+ _state = _BOUNDARY_ENDING; |
+ _boundaryIndex = 0; |
+ } |
+ } else { |
+ // Restart matching of the boundary. |
+ _index = _index - _boundaryIndex; |
+ _boundaryIndex = 0; |
+ } |
+ break; |
+ |
+ case _BOUNDARY_ENDING: |
+ if (byte == CharCode.CR) { |
+ _state = _BOUNDARY_END; |
+ } else if (byte == CharCode.DASH) { |
+ _state = _LAST_BOUNDARY_DASH2; |
+ } else { |
+ _expectWhitespace(byte); |
+ } |
+ break; |
+ |
+ case _BOUNDARY_END: |
+ _expectByteValue(byte, CharCode.LF); |
+ if (_multipartController != null) { |
+ _multipartController.close(); |
+ _multipartController = null; |
+ _tryPropagateControllerState(); |
+ } |
+ _state = _HEADER_START; |
+ break; |
+ |
+ case _HEADER_START: |
+ _headers = new Map<String, String>(); |
+ if (byte == CharCode.CR) { |
+ _state = _HEADER_ENDING; |
+ } else { |
+ // Start of new header field. |
+ _headerField.add(_toLowerCase(byte)); |
+ _state = _HEADER_FIELD; |
+ } |
+ break; |
+ |
+ case _HEADER_FIELD: |
+ if (byte == CharCode.COLON) { |
+ _state = _HEADER_VALUE_START; |
+ } else { |
+ if (!_isTokenChar(byte)) { |
+ throw new MimeMultipartException("Invalid header field name"); |
+ } |
+ _headerField.add(_toLowerCase(byte)); |
+ } |
+ break; |
+ |
+ case _HEADER_VALUE_START: |
+ if (byte == CharCode.CR) { |
+ _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
+ } else if (byte != CharCode.SP && byte != CharCode.HT) { |
+ // Start of new header value. |
+ _headerValue.add(byte); |
+ _state = _HEADER_VALUE; |
+ } |
+ break; |
+ |
+ case _HEADER_VALUE: |
+ if (byte == CharCode.CR) { |
+ _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
+ } else { |
+ _headerValue.add(byte); |
+ } |
+ break; |
+ |
+ case _HEADER_VALUE_FOLDING_OR_ENDING: |
+ _expectByteValue(byte, CharCode.LF); |
+ _state = _HEADER_VALUE_FOLD_OR_END; |
+ break; |
+ |
+ case _HEADER_VALUE_FOLD_OR_END: |
+ if (byte == CharCode.SP || byte == CharCode.HT) { |
+ _state = _HEADER_VALUE_START; |
+ } else { |
+ String headerField = UTF8.decode(_headerField); |
+ String headerValue = UTF8.decode(_headerValue); |
+ _headers[headerField.toLowerCase()] = headerValue; |
+ _headerField.clear(); |
+ _headerValue.clear(); |
+ if (byte == CharCode.CR) { |
+ _state = _HEADER_ENDING; |
+ } else { |
+ // Start of new header field. |
+ _headerField.add(_toLowerCase(byte)); |
+ _state = _HEADER_FIELD; |
+ } |
+ } |
+ break; |
+ |
+ case _HEADER_ENDING: |
+ _expectByteValue(byte, CharCode.LF); |
+ _multipartController = new StreamController(sync: true, onListen: () { |
+ if (_subscription.isPaused) _subscription.resume(); |
+ }, onPause: _subscription.pause, onResume: _subscription.resume); |
+ _controller |
+ .add(new _MimeMultipart(_headers, _multipartController.stream)); |
+ _headers = null; |
+ _state = _CONTENT; |
+ contentStartIndex = _index + 1; |
+ break; |
+ |
+ case _CONTENT: |
+ if (byte == _boundary[_boundaryIndex]) { |
+ _boundaryIndex++; |
+ if (_boundaryIndex == _boundary.length) { |
+ if (contentStartIndex != null) { |
+ _index++; |
+ reportData(); |
+ _index--; |
+ } |
+ _multipartController.close(); |
+ _multipartController = null; |
+ _tryPropagateControllerState(); |
+ _boundaryIndex = 0; |
+ _state = _BOUNDARY_ENDING; |
+ } |
+ } else { |
+ // Restart matching of the boundary. |
+ _index = _index - _boundaryIndex; |
+ if (contentStartIndex == null) contentStartIndex = _index; |
+ _boundaryIndex = 0; |
+ } |
+ break; |
+ |
+ case _LAST_BOUNDARY_DASH2: |
+ _expectByteValue(byte, CharCode.DASH); |
+ _state = _LAST_BOUNDARY_ENDING; |
+ break; |
+ |
+ case _LAST_BOUNDARY_ENDING: |
+ if (byte == CharCode.CR) { |
+ _state = _LAST_BOUNDARY_END; |
+ } else { |
+ _expectWhitespace(byte); |
+ } |
+ break; |
+ |
+ case _LAST_BOUNDARY_END: |
+ _expectByteValue(byte, CharCode.LF); |
+ if (_multipartController != null) { |
+ _multipartController.close(); |
+ _multipartController = null; |
+ _tryPropagateControllerState(); |
+ } |
+ _state = _DONE; |
+ break; |
+ |
+ default: |
+ // Should be unreachable. |
+ assert(false); |
+ break; |
+ } |
+ |
+ // Move to the next byte. |
+ _index++; |
+ } |
+ |
+ // Report any known content. |
+ if (_state == _CONTENT && contentStartIndex != null) { |
+ reportData(); |
+ } |
+ |
+ // Resume if at end. |
+ if (_index == _buffer.length) { |
+ _buffer = null; |
+ _index = null; |
+ _subscription.resume(); |
+ } |
+ } |
} |