Chromium Code Reviews| 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..fe1d40f1117756a56ec9b4356047fa952f82cb20 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. |
|
Søren Gjesse
2013/05/03 08:30:48
Maybe add some code to show how to use it to colle
Anders Johnsen
2013/05/03 10:22:54
That's actually quite complicated (header parsing,
|
| + */ |
| +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,50 @@ 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: () { |
|
Søren Gjesse
2013/05/03 08:30:48
Do you need to specify this empty onCancel closure
Anders Johnsen
2013/05/03 10:22:54
Done.
|
| + }); |
| + 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 +147,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 +174,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 +194,7 @@ class _MimeMultipartParser { |
| } |
| } else { |
| // Restart matching of the boundary. |
| - index = index - _boundaryIndex; |
| + _index = _index - _boundaryIndex; |
| _boundaryIndex = 0; |
| } |
| break; |
| @@ -152,103 +224,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 (_toLowerCase(byte) == _toLowerCase(_boundary[_boundaryIndex])) { |
|
Søren Gjesse
2013/05/03 08:30:48
I know I worte this code, but looking at RFC2046 I
Anders Johnsen
2013/05/03 10:22:54
Done.
|
| + _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 +346,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 +358,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) { |
| @@ -301,28 +387,17 @@ class _MimeMultipartParser { |
| void _expect(int val1, int val2) { |
| if (val1 != val2) { |
| + print("BAD"); |
|
Søren Gjesse
2013/05/03 08:30:48
Debug print.
Anders Johnsen
2013/05/03 10:22:54
Done.
|
| throw new MimeParserException("Failed to parse multipart mime 1"); |
| } |
| } |
| void _expectWS(int byte) { |
| if (byte != _CharCode.SP && byte != _CharCode.HT) { |
| + print("BAD"); |
|
Søren Gjesse
2013/05/03 08:30:48
Debug print.
Anders Johnsen
2013/05/03 10:22:54
Done.
|
| 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; |
| } |