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