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