| // Copyright (c) 2012, 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. |
| |
| part of dart.io; |
| |
| /** |
| * 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:] |
| */ |
| |
| 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> charCodes = boundary.codeUnits; |
| _boundary = new List<int>(4 + charCodes.length); |
| // Set-up the matching boundary preceding it with CRLF and two |
| // dashes. |
| _boundary[0] = _CharCode.CR; |
| _boundary[1] = _CharCode.LF; |
| _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; |
| // 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 (partDataReceived == null) return; |
| |
| if (contentStartIndex < 0) { |
| var contentLength = boundaryPrefix + index - _boundaryIndex; |
| if (contentLength <= boundaryPrefix) { |
| partDataReceived( |
| _boundary.sublist(0, contentLength)); |
| } else { |
| partDataReceived( |
| _boundary.sublist(0, boundaryPrefix)); |
| partDataReceived( |
| buffer.sublist(0, contentLength - boundaryPrefix)); |
| } |
| } else { |
| var contentEndIndex = index - _boundaryIndex; |
| partDataReceived( |
| buffer.sublist(contentStartIndex, contentEndIndex)); |
| } |
| } |
| |
| // Prepare for processing the buffer. |
| index = offset; |
| int lastIndex = offset + count; |
| 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 < lastIndex) && _state != _FAILURE && _state != _DONE) { |
| int byte; |
| if (index < 0) { |
| byte = _boundary[boundaryPrefix + index]; |
| } else { |
| byte = buffer[index]; |
| } |
| switch (_state) { |
| case _START: |
| if (_toLowerCase(byte) == _toLowerCase(_boundary[_boundaryIndex])) { |
| _boundaryIndex++; |
| if (_boundaryIndex == _boundary.length) { |
| _state = _FIRST_BOUNDARY_ENDING; |
| _boundaryIndex = 0; |
| } |
| } else { |
| // Restart matching of the boundary. |
| index = index - _boundaryIndex; |
| _boundaryIndex = 0; |
| } |
| break; |
| |
| case _FIRST_BOUNDARY_ENDING: |
| if (byte == _CharCode.CR) { |
| _state = _FIRST_BOUNDARY_END; |
| } else { |
| _expectWS(byte); |
| } |
| break; |
| |
| case _FIRST_BOUNDARY_END: |
| _expect(byte, _CharCode.LF); |
| _state = _HEADER_START; |
| break; |
| |
| case _BOUNDARY_ENDING: |
| if (byte == _CharCode.CR) { |
| _state = _BOUNDARY_END; |
| } else if (byte == _CharCode.DASH) { |
| _state = _LAST_BOUNDARY_DASH2; |
| } else { |
| _expectWS(byte); |
| } |
| break; |
| |
| case _BOUNDARY_END: |
| _expect(byte, _CharCode.LF); |
| if (partEnd != null) { |
| partEnd(false); |
| } |
| _state = _HEADER_START; |
| break; |
| |
| case _HEADER_START: |
| if (byte == _CharCode.CR) { |
| _state = _HEADER_ENDING; |
| } 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)); |
| } |
| 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_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; |
| } |
| } 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); |
| _state = _LAST_BOUNDARY_ENDING; |
| break; |
| |
| case _LAST_BOUNDARY_ENDING: |
| if (byte == _CharCode.CR) { |
| _state = _LAST_BOUNDARY_END; |
| } else { |
| _expectWS(byte); |
| } |
| break; |
| |
| case _LAST_BOUNDARY_END: |
| _expect(byte, _CharCode.LF); |
| if (partEnd != null) { |
| partEnd(true); |
| } |
| _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(); |
| } |
| return index - offset; |
| } |
| |
| bool _isTokenChar(int byte) { |
| return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
| } |
| |
| int _toLowerCase(int byte) { |
| final int aCode = "A".codeUnitAt(0); |
| final int zCode = "Z".codeUnitAt(0); |
| final int delta = "a".codeUnitAt(0) - aCode; |
| return (aCode <= byte && byte <= zCode) ? byte + delta : byte; |
| } |
| |
| void _expect(int val1, int val2) { |
| if (val1 != val2) { |
| throw new MimeParserException("Failed to parse multipart mime 1"); |
| } |
| } |
| |
| void _expectWS(int byte) { |
| if (byte != _CharCode.SP && byte != _CharCode.HT) { |
| 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; |
| } |
| |
| |
| class MimeParserException implements Exception { |
| const MimeParserException([String this.message = ""]); |
| String toString() => "MimeParserException: $message"; |
| final String message; |
| } |