| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 library mime.bound_multipart_stream; | 4 library mime.bound_multipart_stream; |
| 5 | 5 |
| 6 import 'dart:async'; | 6 import 'dart:async'; |
| 7 import 'dart:convert'; | 7 import 'dart:convert'; |
| 8 | 8 |
| 9 import 'mime_shared.dart'; | 9 import 'mime_shared.dart'; |
| 10 import 'char_code.dart'; | 10 import 'char_code.dart'; |
| 11 | 11 |
| 12 // Bytes for '()<>@,;:\\"/[]?={} \t'. | 12 // Bytes for '()<>@,;:\\"/[]?={} \t'. |
| 13 const _SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47, 91, 93, | 13 const _SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47, 91, 93, |
| 14 63, 61, 123, 125, 32, 9]; | 14 63, 61, 123, 125, 32, 9]; |
| 15 | 15 |
| 16 bool _isTokenChar(int byte) { | 16 bool _isTokenChar(int byte) { |
| 17 return byte > 31 && byte < 128 && _SEPARATORS.indexOf(byte) == -1; | 17 return byte > 31 && byte < 128 && _SEPARATORS.indexOf(byte) == -1; |
| 18 } | 18 } |
| 19 | 19 |
| 20 int _toLowerCase(int byte) { | 20 int _toLowerCase(int byte) { |
| 21 const delta = CharCode.LOWER_A - CharCode.UPPER_A; | 21 const delta = CharCode.LOWER_A - CharCode.UPPER_A; |
| 22 return (CharCode.UPPER_A <= byte && byte <= CharCode.UPPER_Z) ? | 22 return (CharCode.UPPER_A <= byte && byte <= CharCode.UPPER_Z) |
| 23 byte + delta : byte; | 23 ? byte + delta |
| 24 : byte; |
| 24 } | 25 } |
| 25 | 26 |
| 26 void _expectByteValue(int val1, int val2) { | 27 void _expectByteValue(int val1, int val2) { |
| 27 if (val1 != val2) { | 28 if (val1 != val2) { |
| 28 throw new MimeMultipartException("Failed to parse multipart mime 1"); | 29 throw new MimeMultipartException("Failed to parse multipart mime 1"); |
| 29 } | 30 } |
| 30 } | 31 } |
| 31 | 32 |
| 32 void _expectWhitespace(int byte) { | 33 void _expectWhitespace(int byte) { |
| 33 if (byte != CharCode.SP && byte != CharCode.HT) { | 34 if (byte != CharCode.SP && byte != CharCode.HT) { |
| 34 throw new MimeMultipartException("Failed to parse multipart mime 2"); | 35 throw new MimeMultipartException("Failed to parse multipart mime 2"); |
| 35 } | 36 } |
| 36 } | 37 } |
| 37 | 38 |
| 38 class _MimeMultipart extends MimeMultipart { | 39 class _MimeMultipart extends MimeMultipart { |
| 39 final Map<String, String> headers; | 40 final Map<String, String> headers; |
| 40 final Stream<List<int>> _stream; | 41 final Stream<List<int>> _stream; |
| 41 | 42 |
| 42 _MimeMultipart(this.headers, this._stream); | 43 _MimeMultipart(this.headers, this._stream); |
| 43 | 44 |
| 44 StreamSubscription<List<int>> listen(void onData(List<int> data), | 45 StreamSubscription<List<int>> listen(void onData(List<int> data), |
| 45 {void onDone(), | 46 {void onDone(), Function onError, bool cancelOnError}) { |
| 46 Function onError, | |
| 47 bool cancelOnError}) { | |
| 48 return _stream.listen(onData, | 47 return _stream.listen(onData, |
| 49 onDone: onDone, | 48 onDone: onDone, onError: onError, cancelOnError: cancelOnError); |
| 50 onError: onError, | |
| 51 cancelOnError: cancelOnError); | |
| 52 } | 49 } |
| 53 } | 50 } |
| 54 | 51 |
| 55 class BoundMultipartStream { | 52 class BoundMultipartStream { |
| 56 static const int _START = 0; | 53 static const int _START = 0; |
| 57 static const int _BOUNDARY_ENDING = 1; | 54 static const int _BOUNDARY_ENDING = 1; |
| 58 static const int _BOUNDARY_END = 2; | 55 static const int _BOUNDARY_END = 2; |
| 59 static const int _HEADER_START = 3; | 56 static const int _HEADER_START = 3; |
| 60 static const int _HEADER_FIELD = 4; | 57 static const int _HEADER_FIELD = 4; |
| 61 static const int _HEADER_VALUE_START = 5; | 58 static const int _HEADER_VALUE_START = 5; |
| 62 static const int _HEADER_VALUE = 6; | 59 static const int _HEADER_VALUE = 6; |
| 63 static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7; | 60 static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7; |
| 64 static const int _HEADER_VALUE_FOLD_OR_END = 8; | 61 static const int _HEADER_VALUE_FOLD_OR_END = 8; |
| 65 static const int _HEADER_ENDING = 9; | 62 static const int _HEADER_ENDING = 9; |
| 66 static const int _CONTENT = 10; | 63 static const int _CONTENT = 10; |
| 67 static const int _LAST_BOUNDARY_DASH2 = 11; | 64 static const int _LAST_BOUNDARY_DASH2 = 11; |
| 68 static const int _LAST_BOUNDARY_ENDING = 12; | 65 static const int _LAST_BOUNDARY_ENDING = 12; |
| 69 static const int _LAST_BOUNDARY_END = 13; | 66 static const int _LAST_BOUNDARY_END = 13; |
| 70 static const int _DONE = 14; | 67 static const int _DONE = 14; |
| 71 static const int _FAIL = 15; | 68 static const int _FAIL = 15; |
| 72 | 69 |
| 73 final List<int> _boundary; | 70 final List<int> _boundary; |
| 74 final List<int> _headerField = []; | 71 final List<int> _headerField = []; |
| 75 final List<int> _headerValue = []; | 72 final List<int> _headerValue = []; |
| 76 | 73 |
| 77 // The following states belong to `_controller`, state changes will not be | 74 // The following states belong to `_controller`, state changes will not be |
| 78 // immediately acted upon but rather only after the current | 75 // immediately acted upon but rather only after the current |
| 79 // `_multipartController` is done. | 76 // `_multipartController` is done. |
| 80 static const int _CONTROLLER_STATE_IDLE = 0; | 77 static const int _CONTROLLER_STATE_IDLE = 0; |
| 81 static const int _CONTROLLER_STATE_ACTIVE = 1; | 78 static const int _CONTROLLER_STATE_ACTIVE = 1; |
| 82 static const int _CONTROLLER_STATE_PAUSED = 2; | 79 static const int _CONTROLLER_STATE_PAUSED = 2; |
| 83 static const int _CONTROLLER_STATE_CANCELED = 3; | 80 static const int _CONTROLLER_STATE_CANCELED = 3; |
| 84 | 81 |
| 85 int _controllerState = _CONTROLLER_STATE_IDLE; | 82 int _controllerState = _CONTROLLER_STATE_IDLE; |
| 86 | 83 |
| 87 StreamController _controller; | 84 StreamController _controller; |
| 88 | 85 |
| 89 Stream<MimeMultipart> get stream => _controller.stream; | 86 Stream<MimeMultipart> get stream => _controller.stream; |
| 90 | 87 |
| 91 StreamSubscription _subscription; | 88 StreamSubscription _subscription; |
| 92 | 89 |
| 93 StreamController _multipartController; | 90 StreamController _multipartController; |
| 94 Map<String, String> _headers; | 91 Map<String, String> _headers; |
| 95 | 92 |
| 96 int _state = _START; | 93 int _state = _START; |
| 97 int _boundaryIndex = 2; | 94 int _boundaryIndex = 2; |
| 98 | 95 |
| 99 // Current index in the data buffer. If index is negative then it | 96 // Current index in the data buffer. If index is negative then it |
| 100 // is the index into the artificial prefix of the boundary string. | 97 // is the index into the artificial prefix of the boundary string. |
| 101 int _index; | 98 int _index; |
| 102 List<int> _buffer; | 99 List<int> _buffer; |
| 103 | 100 |
| 104 BoundMultipartStream(this._boundary, Stream<List<int>> stream) { | 101 BoundMultipartStream(this._boundary, Stream<List<int>> stream) { |
| 105 _controller = new StreamController( | 102 _controller = new StreamController( |
| 106 sync: true, | 103 sync: true, |
| 107 onPause: _pauseStream, | 104 onPause: _pauseStream, |
| 108 onResume: _resumeStream, | 105 onResume: _resumeStream, onCancel: () { |
| 109 onCancel: () { | 106 _controllerState = _CONTROLLER_STATE_CANCELED; |
| 110 _controllerState = _CONTROLLER_STATE_CANCELED; | 107 _tryPropagateControllerState(); |
| 111 _tryPropagateControllerState(); | 108 }, onListen: () { |
| 112 }, | 109 _controllerState = _CONTROLLER_STATE_ACTIVE; |
| 113 onListen: () { | 110 _subscription = stream.listen((data) { |
| 114 _controllerState = _CONTROLLER_STATE_ACTIVE; | 111 assert(_buffer == null); |
| 115 _subscription = stream.listen( | 112 _subscription.pause(); |
| 116 (data) { | 113 _buffer = data; |
| 117 assert(_buffer == null); | 114 _index = 0; |
| 118 _subscription.pause(); | 115 _parse(); |
| 119 _buffer = data; | 116 }, onDone: () { |
| 120 _index = 0; | 117 if (_state != _DONE) { |
| 121 _parse(); | 118 _controller |
| 122 }, | 119 .addError(new MimeMultipartException("Bad multipart ending")); |
| 123 onDone: () { | 120 } |
| 124 if (_state != _DONE) { | 121 _controller.close(); |
| 125 _controller.addError( | 122 }, onError: _controller.addError); |
| 126 new MimeMultipartException("Bad multipart ending")); | 123 }); |
| 127 } | 124 } |
| 128 _controller.close(); | 125 |
| 129 }, | 126 void _resumeStream() { |
| 130 onError: _controller.addError); | 127 assert(_controllerState == _CONTROLLER_STATE_PAUSED); |
| 131 }); | 128 _controllerState = _CONTROLLER_STATE_ACTIVE; |
| 132 } | 129 _tryPropagateControllerState(); |
| 133 | 130 } |
| 134 void _resumeStream() { | 131 |
| 135 assert (_controllerState == _CONTROLLER_STATE_PAUSED); | 132 void _pauseStream() { |
| 136 _controllerState = _CONTROLLER_STATE_ACTIVE; | 133 _controllerState = _CONTROLLER_STATE_PAUSED; |
| 137 _tryPropagateControllerState(); | 134 _tryPropagateControllerState(); |
| 138 } | 135 } |
| 139 | 136 |
| 140 void _pauseStream() { | 137 void _tryPropagateControllerState() { |
| 141 _controllerState = _CONTROLLER_STATE_PAUSED; | 138 if (_multipartController == null) { |
| 142 _tryPropagateControllerState(); | 139 switch (_controllerState) { |
| 143 } | 140 case _CONTROLLER_STATE_ACTIVE: |
| 144 | 141 if (_subscription.isPaused) _subscription.resume(); |
| 145 void _tryPropagateControllerState() { | 142 break; |
| 146 if (_multipartController == null) { | 143 case _CONTROLLER_STATE_PAUSED: |
| 147 switch (_controllerState) { | 144 if (!_subscription.isPaused) _subscription.pause(); |
| 148 case _CONTROLLER_STATE_ACTIVE: | 145 break; |
| 149 if (_subscription.isPaused) _subscription.resume(); | 146 case _CONTROLLER_STATE_CANCELED: |
| 150 break; | 147 _subscription.cancel(); |
| 151 case _CONTROLLER_STATE_PAUSED: | 148 break; |
| 152 if (!_subscription.isPaused) _subscription.pause(); | 149 default: |
| 153 break; | 150 throw new StateError("This code should never be reached."); |
| 154 case _CONTROLLER_STATE_CANCELED: | 151 } |
| 155 _subscription.cancel(); | 152 } |
| 156 break; | 153 } |
| 157 default: | 154 |
| 158 throw new StateError("This code should never be reached."); | 155 void _parse() { |
| 159 } | 156 // Number of boundary bytes to artificially place before the supplied data. |
| 160 } | 157 int boundaryPrefix = 0; |
| 161 } | 158 // Position where content starts. Will be null if no known content |
| 162 | 159 // start exists. Will be negative of the content starts in the |
| 163 void _parse() { | 160 // boundary prefix. Will be zero or position if the content starts |
| 164 // Number of boundary bytes to artificially place before the supplied data. | 161 // in the current buffer. |
| 165 int boundaryPrefix = 0; | 162 int contentStartIndex; |
| 166 // Position where content starts. Will be null if no known content | 163 |
| 167 // start exists. Will be negative of the content starts in the | 164 // Function to report content data for the current part. The data |
| 168 // boundary prefix. Will be zero or position if the content starts | 165 // reported is from the current content start index up til the |
| 169 // in the current buffer. | 166 // current index. As the data can be artificially prefixed with a |
| 170 int contentStartIndex; | 167 // prefix of the boundary both the content start index and index |
| 171 | 168 // can be negative. |
| 172 // Function to report content data for the current part. The data | 169 void reportData() { |
| 173 // reported is from the current content start index up til the | 170 if (contentStartIndex < 0) { |
| 174 // current index. As the data can be artificially prefixed with a | 171 var contentLength = boundaryPrefix + _index - _boundaryIndex; |
| 175 // prefix of the boundary both the content start index and index | 172 if (contentLength <= boundaryPrefix) { |
| 176 // can be negative. | 173 _multipartController.add(_boundary.sublist(0, contentLength)); |
| 177 void reportData() { | 174 } else { |
| 178 if (contentStartIndex < 0) { | 175 _multipartController.add(_boundary.sublist(0, boundaryPrefix)); |
| 179 var contentLength = boundaryPrefix + _index - _boundaryIndex; | 176 _multipartController |
| 180 if (contentLength <= boundaryPrefix) { | 177 .add(_buffer.sublist(0, contentLength - boundaryPrefix)); |
| 181 _multipartController.add( | 178 } |
| 182 _boundary.sublist(0, contentLength)); | 179 } else { |
| 183 } else { | 180 var contentEndIndex = _index - _boundaryIndex; |
| 184 _multipartController.add( | 181 _multipartController |
| 185 _boundary.sublist(0, boundaryPrefix)); | 182 .add(_buffer.sublist(contentStartIndex, contentEndIndex)); |
| 186 _multipartController.add( | 183 } |
| 187 _buffer.sublist(0, contentLength - boundaryPrefix)); | 184 } |
| 188 } | 185 |
| 189 } else { | 186 if (_state == _CONTENT && _boundaryIndex == 0) { |
| 190 var contentEndIndex = _index - _boundaryIndex; | 187 contentStartIndex = 0; |
| 191 _multipartController.add( | 188 } else { |
| 192 _buffer.sublist(contentStartIndex, contentEndIndex)); | 189 contentStartIndex = null; |
| 193 } | 190 } |
| 194 } | 191 // The data to parse might be "artificially" prefixed with a |
| 195 | 192 // partial match of the boundary. |
| 196 if (_state == _CONTENT && _boundaryIndex == 0) { | 193 boundaryPrefix = _boundaryIndex; |
| 197 contentStartIndex = 0; | 194 |
| 198 } else { | 195 while ((_index < _buffer.length) && _state != _FAIL && _state != _DONE) { |
| 199 contentStartIndex = null; | 196 int byte; |
| 200 } | 197 if (_index < 0) { |
| 201 // The data to parse might be "artificially" prefixed with a | 198 byte = _boundary[boundaryPrefix + _index]; |
| 202 // partial match of the boundary. | 199 } else { |
| 203 boundaryPrefix = _boundaryIndex; | 200 byte = _buffer[_index]; |
| 204 | 201 } |
| 205 while ((_index < _buffer.length) && _state != _FAIL && _state != _DONE) { | 202 switch (_state) { |
| 206 int byte; | 203 case _START: |
| 207 if (_index < 0) { | 204 if (byte == _boundary[_boundaryIndex]) { |
| 208 byte = _boundary[boundaryPrefix + _index]; | 205 _boundaryIndex++; |
| 209 } else { | 206 if (_boundaryIndex == _boundary.length) { |
| 210 byte = _buffer[_index]; | 207 _state = _BOUNDARY_ENDING; |
| 211 } | 208 _boundaryIndex = 0; |
| 212 switch (_state) { | 209 } |
| 213 case _START: | 210 } else { |
| 214 if (byte == _boundary[_boundaryIndex]) { | 211 // Restart matching of the boundary. |
| 215 _boundaryIndex++; | 212 _index = _index - _boundaryIndex; |
| 216 if (_boundaryIndex == _boundary.length) { | 213 _boundaryIndex = 0; |
| 217 _state = _BOUNDARY_ENDING; | 214 } |
| 218 _boundaryIndex = 0; | 215 break; |
| 219 } | 216 |
| 220 } else { | 217 case _BOUNDARY_ENDING: |
| 221 // Restart matching of the boundary. | 218 if (byte == CharCode.CR) { |
| 222 _index = _index - _boundaryIndex; | 219 _state = _BOUNDARY_END; |
| 223 _boundaryIndex = 0; | 220 } else if (byte == CharCode.DASH) { |
| 224 } | 221 _state = _LAST_BOUNDARY_DASH2; |
| 225 break; | 222 } else { |
| 226 | 223 _expectWhitespace(byte); |
| 227 case _BOUNDARY_ENDING: | 224 } |
| 228 if (byte == CharCode.CR) { | 225 break; |
| 229 _state = _BOUNDARY_END; | 226 |
| 230 } else if (byte == CharCode.DASH) { | 227 case _BOUNDARY_END: |
| 231 _state = _LAST_BOUNDARY_DASH2; | 228 _expectByteValue(byte, CharCode.LF); |
| 232 } else { | 229 if (_multipartController != null) { |
| 233 _expectWhitespace(byte); | 230 _multipartController.close(); |
| 234 } | 231 _multipartController = null; |
| 235 break; | 232 _tryPropagateControllerState(); |
| 236 | 233 } |
| 237 case _BOUNDARY_END: | 234 _state = _HEADER_START; |
| 238 _expectByteValue(byte, CharCode.LF); | 235 break; |
| 239 if (_multipartController != null) { | 236 |
| 240 _multipartController.close(); | 237 case _HEADER_START: |
| 241 _multipartController = null; | 238 _headers = new Map<String, String>(); |
| 242 _tryPropagateControllerState(); | 239 if (byte == CharCode.CR) { |
| 243 } | 240 _state = _HEADER_ENDING; |
| 244 _state = _HEADER_START; | 241 } else { |
| 245 break; | 242 // Start of new header field. |
| 246 | 243 _headerField.add(_toLowerCase(byte)); |
| 247 case _HEADER_START: | 244 _state = _HEADER_FIELD; |
| 248 _headers = new Map<String, String>(); | 245 } |
| 249 if (byte == CharCode.CR) { | 246 break; |
| 250 _state = _HEADER_ENDING; | 247 |
| 251 } else { | 248 case _HEADER_FIELD: |
| 252 // Start of new header field. | 249 if (byte == CharCode.COLON) { |
| 253 _headerField.add(_toLowerCase(byte)); | 250 _state = _HEADER_VALUE_START; |
| 254 _state = _HEADER_FIELD; | 251 } else { |
| 255 } | 252 if (!_isTokenChar(byte)) { |
| 256 break; | 253 throw new MimeMultipartException("Invalid header field name"); |
| 257 | 254 } |
| 258 case _HEADER_FIELD: | 255 _headerField.add(_toLowerCase(byte)); |
| 259 if (byte == CharCode.COLON) { | 256 } |
| 260 _state = _HEADER_VALUE_START; | 257 break; |
| 261 } else { | 258 |
| 262 if (!_isTokenChar(byte)) { | 259 case _HEADER_VALUE_START: |
| 263 throw new MimeMultipartException("Invalid header field name"); | 260 if (byte == CharCode.CR) { |
| 264 } | 261 _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
| 265 _headerField.add(_toLowerCase(byte)); | 262 } else if (byte != CharCode.SP && byte != CharCode.HT) { |
| 266 } | 263 // Start of new header value. |
| 267 break; | 264 _headerValue.add(byte); |
| 268 | 265 _state = _HEADER_VALUE; |
| 269 case _HEADER_VALUE_START: | 266 } |
| 270 if (byte == CharCode.CR) { | 267 break; |
| 271 _state = _HEADER_VALUE_FOLDING_OR_ENDING; | 268 |
| 272 } else if (byte != CharCode.SP && byte != CharCode.HT) { | 269 case _HEADER_VALUE: |
| 273 // Start of new header value. | 270 if (byte == CharCode.CR) { |
| 274 _headerValue.add(byte); | 271 _state = _HEADER_VALUE_FOLDING_OR_ENDING; |
| 275 _state = _HEADER_VALUE; | 272 } else { |
| 276 } | 273 _headerValue.add(byte); |
| 277 break; | 274 } |
| 278 | 275 break; |
| 279 case _HEADER_VALUE: | 276 |
| 280 if (byte == CharCode.CR) { | 277 case _HEADER_VALUE_FOLDING_OR_ENDING: |
| 281 _state = _HEADER_VALUE_FOLDING_OR_ENDING; | 278 _expectByteValue(byte, CharCode.LF); |
| 282 } else { | 279 _state = _HEADER_VALUE_FOLD_OR_END; |
| 283 _headerValue.add(byte); | 280 break; |
| 284 } | 281 |
| 285 break; | 282 case _HEADER_VALUE_FOLD_OR_END: |
| 286 | 283 if (byte == CharCode.SP || byte == CharCode.HT) { |
| 287 case _HEADER_VALUE_FOLDING_OR_ENDING: | 284 _state = _HEADER_VALUE_START; |
| 288 _expectByteValue(byte, CharCode.LF); | 285 } else { |
| 289 _state = _HEADER_VALUE_FOLD_OR_END; | 286 String headerField = UTF8.decode(_headerField); |
| 290 break; | 287 String headerValue = UTF8.decode(_headerValue); |
| 291 | 288 _headers[headerField.toLowerCase()] = headerValue; |
| 292 case _HEADER_VALUE_FOLD_OR_END: | 289 _headerField.clear(); |
| 293 if (byte == CharCode.SP || byte == CharCode.HT) { | 290 _headerValue.clear(); |
| 294 _state = _HEADER_VALUE_START; | 291 if (byte == CharCode.CR) { |
| 295 } else { | 292 _state = _HEADER_ENDING; |
| 296 String headerField = UTF8.decode(_headerField); | 293 } else { |
| 297 String headerValue = UTF8.decode(_headerValue); | 294 // Start of new header field. |
| 298 _headers[headerField.toLowerCase()] = headerValue; | 295 _headerField.add(_toLowerCase(byte)); |
| 299 _headerField.clear(); | 296 _state = _HEADER_FIELD; |
| 300 _headerValue.clear(); | 297 } |
| 301 if (byte == CharCode.CR) { | 298 } |
| 302 _state = _HEADER_ENDING; | 299 break; |
| 303 } else { | 300 |
| 304 // Start of new header field. | 301 case _HEADER_ENDING: |
| 305 _headerField.add(_toLowerCase(byte)); | 302 _expectByteValue(byte, CharCode.LF); |
| 306 _state = _HEADER_FIELD; | 303 _multipartController = new StreamController(sync: true, onListen: () { |
| 307 } | 304 if (_subscription.isPaused) _subscription.resume(); |
| 308 } | 305 }, onPause: _subscription.pause, onResume: _subscription.resume); |
| 309 break; | 306 _controller |
| 310 | 307 .add(new _MimeMultipart(_headers, _multipartController.stream)); |
| 311 case _HEADER_ENDING: | 308 _headers = null; |
| 312 _expectByteValue(byte, CharCode.LF); | 309 _state = _CONTENT; |
| 313 _multipartController = new StreamController( | 310 contentStartIndex = _index + 1; |
| 314 sync: true, | 311 break; |
| 315 onListen: () { | 312 |
| 316 if (_subscription.isPaused) _subscription.resume(); | 313 case _CONTENT: |
| 317 }, | 314 if (byte == _boundary[_boundaryIndex]) { |
| 318 onPause: _subscription.pause, | 315 _boundaryIndex++; |
| 319 onResume: _subscription.resume); | 316 if (_boundaryIndex == _boundary.length) { |
| 320 _controller.add( | 317 if (contentStartIndex != null) { |
| 321 new _MimeMultipart(_headers, _multipartController.stream)); | 318 _index++; |
| 322 _headers = null; | 319 reportData(); |
| 323 _state = _CONTENT; | 320 _index--; |
| 324 contentStartIndex = _index + 1; | 321 } |
| 325 break; | 322 _multipartController.close(); |
| 326 | 323 _multipartController = null; |
| 327 case _CONTENT: | 324 _tryPropagateControllerState(); |
| 328 if (byte == _boundary[_boundaryIndex]) { | 325 _boundaryIndex = 0; |
| 329 _boundaryIndex++; | 326 _state = _BOUNDARY_ENDING; |
| 330 if (_boundaryIndex == _boundary.length) { | 327 } |
| 331 if (contentStartIndex != null) { | 328 } else { |
| 332 _index++; | 329 // Restart matching of the boundary. |
| 333 reportData(); | 330 _index = _index - _boundaryIndex; |
| 334 _index--; | 331 if (contentStartIndex == null) contentStartIndex = _index; |
| 335 } | 332 _boundaryIndex = 0; |
| 336 _multipartController.close(); | 333 } |
| 337 _multipartController = null; | 334 break; |
| 338 _tryPropagateControllerState(); | 335 |
| 339 _boundaryIndex = 0; | 336 case _LAST_BOUNDARY_DASH2: |
| 340 _state = _BOUNDARY_ENDING; | 337 _expectByteValue(byte, CharCode.DASH); |
| 341 } | 338 _state = _LAST_BOUNDARY_ENDING; |
| 342 } else { | 339 break; |
| 343 // Restart matching of the boundary. | 340 |
| 344 _index = _index - _boundaryIndex; | 341 case _LAST_BOUNDARY_ENDING: |
| 345 if (contentStartIndex == null) contentStartIndex = _index; | 342 if (byte == CharCode.CR) { |
| 346 _boundaryIndex = 0; | 343 _state = _LAST_BOUNDARY_END; |
| 347 } | 344 } else { |
| 348 break; | 345 _expectWhitespace(byte); |
| 349 | 346 } |
| 350 case _LAST_BOUNDARY_DASH2: | 347 break; |
| 351 _expectByteValue(byte, CharCode.DASH); | 348 |
| 352 _state = _LAST_BOUNDARY_ENDING; | 349 case _LAST_BOUNDARY_END: |
| 353 break; | 350 _expectByteValue(byte, CharCode.LF); |
| 354 | 351 if (_multipartController != null) { |
| 355 case _LAST_BOUNDARY_ENDING: | 352 _multipartController.close(); |
| 356 if (byte == CharCode.CR) { | 353 _multipartController = null; |
| 357 _state = _LAST_BOUNDARY_END; | 354 _tryPropagateControllerState(); |
| 358 } else { | 355 } |
| 359 _expectWhitespace(byte); | 356 _state = _DONE; |
| 360 } | 357 break; |
| 361 break; | 358 |
| 362 | 359 default: |
| 363 case _LAST_BOUNDARY_END: | 360 // Should be unreachable. |
| 364 _expectByteValue(byte, CharCode.LF); | 361 assert(false); |
| 365 if (_multipartController != null) { | 362 break; |
| 366 _multipartController.close(); | 363 } |
| 367 _multipartController = null; | 364 |
| 368 _tryPropagateControllerState(); | 365 // Move to the next byte. |
| 369 } | 366 _index++; |
| 370 _state = _DONE; | 367 } |
| 371 break; | 368 |
| 372 | 369 // Report any known content. |
| 373 default: | 370 if (_state == _CONTENT && contentStartIndex != null) { |
| 374 // Should be unreachable. | 371 reportData(); |
| 375 assert(false); | 372 } |
| 376 break; | 373 |
| 377 } | 374 // Resume if at end. |
| 378 | 375 if (_index == _buffer.length) { |
| 379 // Move to the next byte. | 376 _buffer = null; |
| 380 _index++; | 377 _index = null; |
| 381 } | 378 _subscription.resume(); |
| 382 | 379 } |
| 383 // Report any known content. | 380 } |
| 384 if (_state == _CONTENT && contentStartIndex != null) { | |
| 385 reportData(); | |
| 386 } | |
| 387 | |
| 388 // Resume if at end. | |
| 389 if (_index == _buffer.length) { | |
| 390 _buffer = null; | |
| 391 _index = null; | |
| 392 _subscription.resume(); | |
| 393 } | |
| 394 } | |
| 395 } | 381 } |
| OLD | NEW |