Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(302)

Unified Diff: sdk/lib/io/mime_multipart_parser.dart

Issue 14905003: Update mime_multipart_parser to be a StreamTransformer, public available. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: review changes Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tests/standalone/io/mime_multipart_parser_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/io/mime_multipart_parser.dart
diff --git a/sdk/lib/io/mime_multipart_parser.dart b/sdk/lib/io/mime_multipart_parser.dart
index e757c75df553b2ac9d907e94cf00d81c9d6bd2d9..55451e983931828643804774af198a148c2953ca 100644
--- a/sdk/lib/io/mime_multipart_parser.dart
+++ b/sdk/lib/io/mime_multipart_parser.dart
@@ -4,45 +4,84 @@
part of dart.io;
+
+/**
+ * A Mime Multipart class representing each part parsed by
+ * [MimeMultipartTransformer]. The data is streamed in as it become available.
+ */
+abstract class MimeMultipart extends Stream<List<int>> {
+ Map<String, String> get headers;
+}
+
+class _MimeMultipartImpl extends MimeMultipart {
+ final Map<String, String> headers;
+ final Stream<List<int>> _stream;
+
+ _MimeMultipartImpl(this.headers, this._stream);
+
+ StreamSubscription<List<int>> listen(void onData(List<int> data),
+ {void onDone(),
+ void onError(error),
+ bool cancelOnError}) {
+ return _stream.listen(onData,
+ onDone: onDone,
+ onError: onError,
+ cancelOnError: cancelOnError);
+ }
+}
+
/**
* Parser for MIME multipart types of data as described in RFC 2046
- * section 5.1.1. The data to parse is supplied through the [:update:]
- * method. As the data is parsed the following callbacks are called:
- *
- * [:partStart;
- * [:headerReceived;
- * [:headersComplete;
- * [:partDataReceived;
- * [:partEnd;
- * [:error:]
+ * section 5.1.1. The data is transformed into [MimeMultipart] objects, each
+ * of them streaming the multipart data.
*/
+class MimeMultipartTransformer
+ implements StreamTransformer<List<int>, MimeMultipart> {
+ static const int _START = 0;
+ static const int _FIRST_BOUNDARY_ENDING = 111;
+ static const int _FIRST_BOUNDARY_END = 112;
+ 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 _FAILURE = 15;
+
+ StreamController _controller;
+ StreamSubscription _subscription;
+
+ StreamController _multipartController;
+ Map<String, String> _headers;
-class _MimeMultipartParser {
- const int _START = 0;
- const int _FIRST_BOUNDARY_ENDING = 111;
- const int _FIRST_BOUNDARY_END = 112;
- const int _BOUNDARY_ENDING = 1;
- const int _BOUNDARY_END = 2;
- const int _HEADER_START = 3;
- const int _HEADER_FIELD = 4;
- const int _HEADER_VALUE_START = 5;
- const int _HEADER_VALUE = 6;
- const int _HEADER_VALUE_FOLDING_OR_ENDING = 7;
- const int _HEADER_VALUE_FOLD_OR_END = 8;
- const int _HEADER_ENDING = 9;
- const int _CONTENT = 10;
- const int _LAST_BOUNDARY_DASH2 = 11;
- const int _LAST_BOUNDARY_ENDING = 12;
- const int _LAST_BOUNDARY_END = 13;
- const int _DONE = 14;
- const int _FAILURE = 15;
-
- // Construct a new MIME multipart parser with the boundary
- // [boundary]. The boundary should be as specified in the content
- // type parameter, that is without the -- prefix.
- _MimeMultipartParser(String boundary) {
+ List<int> _boundary;
+ int _state = _START;
+ int _boundaryIndex = 0;
+
+ // 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;
+
+ StringBuffer _headerField = new StringBuffer();
+ StringBuffer _headerValue = new StringBuffer();
+
+ /**
+ * Construct a new MIME multipart parser with the boundary
+ * [boundary]. The boundary should be as specified in the content
+ * type parameter, that is without the -- prefix.
+ */
+ MimeMultipartTransformer(String boundary) {
List<int> charCodes = boundary.codeUnits;
- _boundary = new List<int>(4 + charCodes.length);
+ _boundary = new Uint8List(4 + charCodes.length);
// Set-up the matching boundary preceding it with CRLF and two
// dashes.
_boundary[0] = _CharCode.CR;
@@ -50,15 +89,51 @@ class _MimeMultipartParser {
_boundary[2] = _CharCode.DASH;
_boundary[3] = _CharCode.DASH;
_boundary.setRange(4, 4 + charCodes.length, charCodes);
- _state = _START;
- _headerField = new StringBuffer();
- _headerValue = new StringBuffer();
}
- int update(List<int> buffer, int offset, int count) {
- // 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;
+ void _resumeStream() {
+ _subscription.resume();
+ }
+
+ void _pauseStream() {
+ _subscription.pause();
+ }
+
+ Stream<MimeMultipart> bind(Stream<List<int>> stream) {
+ _controller = new StreamController(
+ onPause: () {
+ _pauseStream();
+ },
+ onResume: () {
+ _resumeStream();
+ },
+ onListen: () {
+ _subscription = stream.listen(
+ (data) {
+ assert(_buffer == null);
+ _pauseStream();
+ _buffer = data;
+ _index = 0;
+ _parse();
+ },
+ onDone: () {
+ if (_state != _DONE) {
+ _controller.addError(
+ new MimeParserException("Bad multipart ending"));
+ }
+ _controller.close();
+ },
+ onError: (error) {
+ _controller.addError(error);
+ });
+ },
+ onCancel: () {
+ _subscription.cancel();
+ });
+ return _controller.stream;
+ }
+
+ 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
@@ -73,29 +148,24 @@ class _MimeMultipartParser {
// prefix of the boundary both the content start index and index
// can be negative.
void reportData() {
- if (partDataReceived == null) return;
-
if (contentStartIndex < 0) {
- var contentLength = boundaryPrefix + index - _boundaryIndex;
+ var contentLength = boundaryPrefix + _index - _boundaryIndex;
if (contentLength <= boundaryPrefix) {
- partDataReceived(
+ _multipartController.add(
_boundary.sublist(0, contentLength));
} else {
- partDataReceived(
+ _multipartController.add(
_boundary.sublist(0, boundaryPrefix));
- partDataReceived(
- buffer.sublist(0, contentLength - boundaryPrefix));
+ _multipartController.add(
+ _buffer.sublist(0, contentLength - boundaryPrefix));
}
} else {
- var contentEndIndex = index - _boundaryIndex;
- partDataReceived(
- buffer.sublist(contentStartIndex, contentEndIndex));
+ var contentEndIndex = _index - _boundaryIndex;
+ _multipartController.add(
+ _buffer.sublist(contentStartIndex, contentEndIndex));
}
}
- // Prepare for processing the buffer.
- index = offset;
- int lastIndex = offset + count;
if (_state == _CONTENT && _boundaryIndex == 0) {
contentStartIndex = 0;
} else {
@@ -105,12 +175,15 @@ class _MimeMultipartParser {
// partial match of the boundary.
boundaryPrefix = _boundaryIndex;
- while ((index < lastIndex) && _state != _FAILURE && _state != _DONE) {
+ while ((_index < _buffer.length) && _state != _FAILURE && _state != _DONE) {
+ if (_multipartController != null && _multipartController.isPaused) {
+ return;
+ }
int byte;
- if (index < 0) {
- byte = _boundary[boundaryPrefix + index];
+ if (_index < 0) {
+ byte = _boundary[boundaryPrefix + _index];
} else {
- byte = buffer[index];
+ byte = _buffer[_index];
}
switch (_state) {
case _START:
@@ -122,7 +195,7 @@ class _MimeMultipartParser {
}
} else {
// Restart matching of the boundary.
- index = index - _boundaryIndex;
+ _index = _index - _boundaryIndex;
_boundaryIndex = 0;
}
break;
@@ -152,103 +225,112 @@ class _MimeMultipartParser {
case _BOUNDARY_END:
_expect(byte, _CharCode.LF);
- if (partEnd != null) {
- partEnd(false);
- }
+ _multipartController.close();
+ _multipartController = null;
_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.writeCharCode(_toLowerCase(byte));
- _state = _HEADER_FIELD;
- }
- break;
+ } else {
+ // Start of new header field.
+ _headerField.writeCharCode(_toLowerCase(byte));
+ _state = _HEADER_FIELD;
+ }
+ break;
- case _HEADER_FIELD:
- if (byte == _CharCode.COLON) {
- _state = _HEADER_VALUE_START;
- } else {
- if (!_isTokenChar(byte)) {
- throw new MimeParserException("Invalid header field name");
- }
- _headerField.writeCharCode(_toLowerCase(byte));
+ case _HEADER_FIELD:
+ if (byte == _CharCode.COLON) {
+ _state = _HEADER_VALUE_START;
+ } else {
+ if (!_isTokenChar(byte)) {
+ throw new MimeParserException("Invalid header field name");
}
- break;
+ _headerField.writeCharCode(_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.writeCharCode(byte);
- _state = _HEADER_VALUE;
- }
- 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.writeCharCode(byte);
+ _state = _HEADER_VALUE;
+ }
+ break;
+
+ case _HEADER_VALUE:
+ if (byte == _CharCode.CR) {
+ _state = _HEADER_VALUE_FOLDING_OR_ENDING;
+ } else {
+ _headerValue.writeCharCode(byte);
+ }
+ break;
+
+ case _HEADER_VALUE_FOLDING_OR_ENDING:
+ _expect(byte, _CharCode.LF);
+ _state = _HEADER_VALUE_FOLD_OR_END;
+ break;
- case _HEADER_VALUE:
+ case _HEADER_VALUE_FOLD_OR_END:
+ if (byte == _CharCode.SP || byte == _CharCode.HT) {
+ _state = _HEADER_VALUE_START;
+ } else {
+ String headerField = _headerField.toString();
+ String headerValue =_headerValue.toString();
+ _headers[headerField] = headerValue;
+ _headerField = new StringBuffer();
+ _headerValue = new StringBuffer();
if (byte == _CharCode.CR) {
- _state = _HEADER_VALUE_FOLDING_OR_ENDING;
+ _state = _HEADER_ENDING;
} else {
- _headerValue.writeCharCode(byte);
+ // Start of new header field.
+ _headerField.writeCharCode(_toLowerCase(byte));
+ _state = _HEADER_FIELD;
}
- break;
+ }
+ break;
- case _HEADER_VALUE_FOLDING_OR_ENDING:
- _expect(byte, _CharCode.LF);
- _state = _HEADER_VALUE_FOLD_OR_END;
- break;
+ case _HEADER_ENDING:
+ _expect(byte, _CharCode.LF);
+ _multipartController = new StreamController(
+ onPause: () {
+ _pauseStream();
+ },
+ onResume: () {
+ _resumeStream();
+ _parse();
+ });
+ _controller.add(
+ new _MimeMultipartImpl(_headers, _multipartController.stream));
+ _headers = null;
+ _state = _CONTENT;
+ contentStartIndex = _index + 1;
+ break;
- case _HEADER_VALUE_FOLD_OR_END:
- if (byte == _CharCode.SP || byte == _CharCode.HT) {
- _state = _HEADER_VALUE_START;
- } else {
- String headerField = _headerField.toString();
- String headerValue =_headerValue.toString();
- if (headerReceived != null) {
- headerReceived(headerField, headerValue);
- }
- _headerField = new StringBuffer();
- _headerValue = new StringBuffer();
- if (byte == _CharCode.CR) {
- _state = _HEADER_ENDING;
- } else {
- // Start of new header field.
- _headerField.writeCharCode(_toLowerCase(byte));
- _state = _HEADER_FIELD;
- }
- }
- break;
-
- case _HEADER_ENDING:
- _expect(byte, _CharCode.LF);
- if (headersComplete != null) headersComplete();
- _state = _CONTENT;
- contentStartIndex = index + 1;
- break;
-
- case _CONTENT:
- if (_toLowerCase(byte) == _toLowerCase(_boundary[_boundaryIndex])) {
- _boundaryIndex++;
- if (_boundaryIndex == _boundary.length) {
- if (contentStartIndex != null) {
- index++;
- reportData();
- index--;
- }
- _boundaryIndex = 0;
- _state = _BOUNDARY_ENDING;
+ case _CONTENT:
+ if (byte == _boundary[_boundaryIndex]) {
+ _boundaryIndex++;
+ if (_boundaryIndex == _boundary.length) {
+ if (contentStartIndex != null) {
+ _index++;
+ reportData();
+ _index--;
}
- } else {
- // Restart matching of the boundary.
- index = index - _boundaryIndex;
- if (contentStartIndex == null) contentStartIndex = index;
+ _multipartController.close();
_boundaryIndex = 0;
+ _state = _BOUNDARY_ENDING;
}
- break;
+ } else {
+ // Restart matching of the boundary.
+ _index = _index - _boundaryIndex;
+ if (contentStartIndex == null) contentStartIndex = _index;
+ _boundaryIndex = 0;
+ }
+ break;
case _LAST_BOUNDARY_DASH2:
_expect(byte, _CharCode.DASH);
@@ -265,9 +347,8 @@ class _MimeMultipartParser {
case _LAST_BOUNDARY_END:
_expect(byte, _CharCode.LF);
- if (partEnd != null) {
- partEnd(true);
- }
+ _multipartController.close();
+ _multipartController = null;
_state = _DONE;
break;
@@ -278,14 +359,20 @@ class _MimeMultipartParser {
}
// Move to the next byte.
- index++;
+ _index++;
}
// Report any known content.
if (_state == _CONTENT && contentStartIndex != null) {
reportData();
}
- return index - offset;
+
+ // Resume if at end.
+ if (_index == _buffer.length) {
+ _buffer = null;
+ _index = null;
+ _resumeStream();
+ }
}
bool _isTokenChar(int byte) {
@@ -310,19 +397,6 @@ class _MimeMultipartParser {
throw new MimeParserException("Failed to parse multipart mime 2");
}
}
-
- List<int> _boundary;
- int _state;
- int _boundaryIndex = 0;
-
- StringBuffer _headerField;
- StringBuffer _headerValue;
-
- Function partStart;
- Function headerReceived;
- Function headersComplete;
- Function partDataReceived;
- Function partEnd;
}
« no previous file with comments | « no previous file | tests/standalone/io/mime_multipart_parser_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698