| Index: mojo/public/dart/third_party/mime/lib/src/bound_multipart_stream.dart
|
| diff --git a/mojo/public/dart/third_party/mime/lib/src/bound_multipart_stream.dart b/mojo/public/dart/third_party/mime/lib/src/bound_multipart_stream.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ab06dabeb74b610cc9ef73cb0cc664b2a282724e
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/mime/lib/src/bound_multipart_stream.dart
|
| @@ -0,0 +1,395 @@
|
| +// Copyright (c) 2014, 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.
|
| +library mime.bound_multipart_stream;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:convert';
|
| +
|
| +import 'mime_shared.dart';
|
| +import 'char_code.dart';
|
| +
|
| +// Bytes for '()<>@,;:\\"/[]?={} \t'.
|
| +const _SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47, 91, 93,
|
| + 63, 61, 123, 125, 32, 9];
|
| +
|
| +bool _isTokenChar(int byte) {
|
| + return byte > 31 && byte < 128 && _SEPARATORS.indexOf(byte) == -1;
|
| +}
|
| +
|
| +int _toLowerCase(int byte) {
|
| + const delta = CharCode.LOWER_A - CharCode.UPPER_A;
|
| + return (CharCode.UPPER_A <= byte && byte <= CharCode.UPPER_Z) ?
|
| + byte + delta : byte;
|
| +}
|
| +
|
| +void _expectByteValue(int val1, int val2) {
|
| + if (val1 != val2) {
|
| + throw new MimeMultipartException("Failed to parse multipart mime 1");
|
| + }
|
| +}
|
| +
|
| +void _expectWhitespace(int byte) {
|
| + if (byte != CharCode.SP && byte != CharCode.HT) {
|
| + throw new MimeMultipartException("Failed to parse multipart mime 2");
|
| + }
|
| +}
|
| +
|
| +class _MimeMultipart extends MimeMultipart {
|
| + final Map<String, String> headers;
|
| + final Stream<List<int>> _stream;
|
| +
|
| + _MimeMultipart(this.headers, this._stream);
|
| +
|
| + StreamSubscription<List<int>> listen(void onData(List<int> data),
|
| + {void onDone(),
|
| + Function onError,
|
| + bool cancelOnError}) {
|
| + return _stream.listen(onData,
|
| + 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();
|
| + }
|
| + }
|
| +}
|
|
|