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'; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
67 static const int _LAST_BOUNDARY_DASH2 = 11; | 67 static const int _LAST_BOUNDARY_DASH2 = 11; |
68 static const int _LAST_BOUNDARY_ENDING = 12; | 68 static const int _LAST_BOUNDARY_ENDING = 12; |
69 static const int _LAST_BOUNDARY_END = 13; | 69 static const int _LAST_BOUNDARY_END = 13; |
70 static const int _DONE = 14; | 70 static const int _DONE = 14; |
71 static const int _FAIL = 15; | 71 static const int _FAIL = 15; |
72 | 72 |
73 final List<int> _boundary; | 73 final List<int> _boundary; |
74 final List<int> _headerField = []; | 74 final List<int> _headerField = []; |
75 final List<int> _headerValue = []; | 75 final List<int> _headerValue = []; |
76 | 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_CANCELLED = 3; | |
Lasse Reichstein Nielsen
2015/02/12 13:40:04
The US version is CANCELED with one L (as I have r
kustermann
2015/02/12 13:51:24
Good to know :)
Done.
| |
84 | |
85 int _controllerState = _CONTROLLER_STATE_IDLE; | |
86 | |
77 StreamController _controller; | 87 StreamController _controller; |
78 | 88 |
79 Stream<MimeMultipart> get stream => _controller.stream; | 89 Stream<MimeMultipart> get stream => _controller.stream; |
80 | 90 |
81 StreamSubscription _subscription; | 91 StreamSubscription _subscription; |
82 | 92 |
83 StreamController _multipartController; | 93 StreamController _multipartController; |
84 Map<String, String> _headers; | 94 Map<String, String> _headers; |
85 | 95 |
86 int _state = _START; | 96 int _state = _START; |
87 int _boundaryIndex = 2; | 97 int _boundaryIndex = 2; |
88 | 98 |
89 // Current index in the data buffer. If index is negative then it | 99 // Current index in the data buffer. If index is negative then it |
90 // is the index into the artificial prefix of the boundary string. | 100 // is the index into the artificial prefix of the boundary string. |
91 int _index; | 101 int _index; |
92 List<int> _buffer; | 102 List<int> _buffer; |
93 | 103 |
94 BoundMultipartStream(this._boundary, Stream<List<int>> stream) { | 104 BoundMultipartStream(this._boundary, Stream<List<int>> stream) { |
95 _controller = new StreamController( | 105 _controller = new StreamController( |
96 sync: true, | 106 sync: true, |
97 onPause: _pauseStream, | 107 onPause: _pauseStream, |
98 onResume:_resumeStream, | 108 onResume:_resumeStream, |
99 onCancel: () { | 109 onCancel: () { |
100 _subscription.cancel(); | 110 _controllerState = _CONTROLLER_STATE_CANCELLED; |
111 _tryPropagateControllerState(); | |
101 }, | 112 }, |
102 onListen: () { | 113 onListen: () { |
114 _controllerState = _CONTROLLER_STATE_ACTIVE; | |
103 _subscription = stream.listen( | 115 _subscription = stream.listen( |
104 (data) { | 116 (data) { |
105 assert(_buffer == null); | 117 assert(_buffer == null); |
106 _pauseStream(); | 118 _subscription.pause(); |
107 _buffer = data; | 119 _buffer = data; |
108 _index = 0; | 120 _index = 0; |
109 _parse(); | 121 _parse(); |
110 }, | 122 }, |
111 onDone: () { | 123 onDone: () { |
112 if (_state != _DONE) { | 124 if (_state != _DONE) { |
113 _controller.addError( | 125 _controller.addError( |
114 new MimeMultipartException("Bad multipart ending")); | 126 new MimeMultipartException("Bad multipart ending")); |
115 } | 127 } |
116 _controller.close(); | 128 _controller.close(); |
117 }, | 129 }, |
118 onError: _controller.addError); | 130 onError: _controller.addError); |
119 }); | 131 }); |
120 } | 132 } |
121 | 133 |
122 void _resumeStream() { | 134 void _resumeStream() { |
123 _subscription.resume(); | 135 assert (_controllerState == _CONTROLLER_STATE_PAUSED); |
136 _controllerState = _CONTROLLER_STATE_ACTIVE; | |
137 _tryPropagateControllerState(); | |
124 } | 138 } |
125 | 139 |
126 void _pauseStream() { | 140 void _pauseStream() { |
127 _subscription.pause(); | 141 _controllerState = _CONTROLLER_STATE_PAUSED; |
142 _tryPropagateControllerState(); | |
128 } | 143 } |
129 | 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_CANCELLED: | |
155 _subscription.cancel(); | |
156 break; | |
157 default: | |
158 throw new Exception("This code should never be reached."); | |
Lasse Reichstein Nielsen
2015/02/12 13:40:04
This should be an Error, not an Exception.
I sugge
kustermann
2015/02/12 13:51:24
Done.
I was looking for something like `throw new
| |
159 } | |
160 } | |
161 } | |
130 | 162 |
131 void _parse() { | 163 void _parse() { |
132 // Number of boundary bytes to artificially place before the supplied data. | 164 // Number of boundary bytes to artificially place before the supplied data. |
133 int boundaryPrefix = 0; | 165 int boundaryPrefix = 0; |
134 // Position where content starts. Will be null if no known content | 166 // Position where content starts. Will be null if no known content |
135 // start exists. Will be negative of the content starts in the | 167 // start exists. Will be negative of the content starts in the |
136 // boundary prefix. Will be zero or position if the content starts | 168 // boundary prefix. Will be zero or position if the content starts |
137 // in the current buffer. | 169 // in the current buffer. |
138 int contentStartIndex; | 170 int contentStartIndex; |
139 | 171 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
203 } else { | 235 } else { |
204 _expectWhitespace(byte); | 236 _expectWhitespace(byte); |
205 } | 237 } |
206 break; | 238 break; |
207 | 239 |
208 case _BOUNDARY_END: | 240 case _BOUNDARY_END: |
209 _expectByteValue(byte, CharCode.LF); | 241 _expectByteValue(byte, CharCode.LF); |
210 if (_multipartController != null) { | 242 if (_multipartController != null) { |
211 _multipartController.close(); | 243 _multipartController.close(); |
212 _multipartController = null; | 244 _multipartController = null; |
245 _tryPropagateControllerState(); | |
213 } | 246 } |
214 _state = _HEADER_START; | 247 _state = _HEADER_START; |
215 break; | 248 break; |
216 | 249 |
217 case _HEADER_START: | 250 case _HEADER_START: |
218 _headers = new Map<String, String>(); | 251 _headers = new Map<String, String>(); |
219 if (byte == CharCode.CR) { | 252 if (byte == CharCode.CR) { |
220 _state = _HEADER_ENDING; | 253 _state = _HEADER_ENDING; |
221 } else { | 254 } else { |
222 // Start of new header field. | 255 // Start of new header field. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
275 _headerField.add(_toLowerCase(byte)); | 308 _headerField.add(_toLowerCase(byte)); |
276 _state = _HEADER_FIELD; | 309 _state = _HEADER_FIELD; |
277 } | 310 } |
278 } | 311 } |
279 break; | 312 break; |
280 | 313 |
281 case _HEADER_ENDING: | 314 case _HEADER_ENDING: |
282 _expectByteValue(byte, CharCode.LF); | 315 _expectByteValue(byte, CharCode.LF); |
283 _multipartController = new StreamController( | 316 _multipartController = new StreamController( |
284 sync: true, | 317 sync: true, |
285 onPause: () { | 318 onPause: _subscription.pause, |
286 _pauseStream(); | |
287 }, | |
288 onResume: () { | 319 onResume: () { |
289 _resumeStream(); | 320 _subscription.resume(); |
290 _parse(); | 321 _parse(); |
291 }); | 322 }); |
292 _controller.add( | 323 _controller.add( |
293 new _MimeMultipart(_headers, _multipartController.stream)); | 324 new _MimeMultipart(_headers, _multipartController.stream)); |
294 _headers = null; | 325 _headers = null; |
295 _state = _CONTENT; | 326 _state = _CONTENT; |
296 contentStartIndex = _index + 1; | 327 contentStartIndex = _index + 1; |
297 break; | 328 break; |
298 | 329 |
299 case _CONTENT: | 330 case _CONTENT: |
300 if (byte == _boundary[_boundaryIndex]) { | 331 if (byte == _boundary[_boundaryIndex]) { |
301 _boundaryIndex++; | 332 _boundaryIndex++; |
302 if (_boundaryIndex == _boundary.length) { | 333 if (_boundaryIndex == _boundary.length) { |
303 if (contentStartIndex != null) { | 334 if (contentStartIndex != null) { |
304 _index++; | 335 _index++; |
305 reportData(); | 336 reportData(); |
306 _index--; | 337 _index--; |
307 } | 338 } |
308 _multipartController.close(); | 339 _multipartController.close(); |
340 _multipartController = null; | |
341 _tryPropagateControllerState(); | |
309 _boundaryIndex = 0; | 342 _boundaryIndex = 0; |
310 _state = _BOUNDARY_ENDING; | 343 _state = _BOUNDARY_ENDING; |
311 } | 344 } |
312 } else { | 345 } else { |
313 // Restart matching of the boundary. | 346 // Restart matching of the boundary. |
314 _index = _index - _boundaryIndex; | 347 _index = _index - _boundaryIndex; |
315 if (contentStartIndex == null) contentStartIndex = _index; | 348 if (contentStartIndex == null) contentStartIndex = _index; |
316 _boundaryIndex = 0; | 349 _boundaryIndex = 0; |
317 } | 350 } |
318 break; | 351 break; |
319 | 352 |
320 case _LAST_BOUNDARY_DASH2: | 353 case _LAST_BOUNDARY_DASH2: |
321 _expectByteValue(byte, CharCode.DASH); | 354 _expectByteValue(byte, CharCode.DASH); |
322 _state = _LAST_BOUNDARY_ENDING; | 355 _state = _LAST_BOUNDARY_ENDING; |
323 break; | 356 break; |
324 | 357 |
325 case _LAST_BOUNDARY_ENDING: | 358 case _LAST_BOUNDARY_ENDING: |
326 if (byte == CharCode.CR) { | 359 if (byte == CharCode.CR) { |
327 _state = _LAST_BOUNDARY_END; | 360 _state = _LAST_BOUNDARY_END; |
328 } else { | 361 } else { |
329 _expectWhitespace(byte); | 362 _expectWhitespace(byte); |
330 } | 363 } |
331 break; | 364 break; |
332 | 365 |
333 case _LAST_BOUNDARY_END: | 366 case _LAST_BOUNDARY_END: |
334 _expectByteValue(byte, CharCode.LF); | 367 _expectByteValue(byte, CharCode.LF); |
335 if (_multipartController != null) { | 368 if (_multipartController != null) { |
336 _multipartController.close(); | 369 _multipartController.close(); |
337 _multipartController = null; | 370 _multipartController = null; |
371 _tryPropagateControllerState(); | |
338 } | 372 } |
339 _state = _DONE; | 373 _state = _DONE; |
340 break; | 374 break; |
341 | 375 |
342 default: | 376 default: |
343 // Should be unreachable. | 377 // Should be unreachable. |
344 assert(false); | 378 assert(false); |
345 break; | 379 break; |
346 } | 380 } |
347 | 381 |
348 // Move to the next byte. | 382 // Move to the next byte. |
349 _index++; | 383 _index++; |
350 } | 384 } |
351 | 385 |
352 // Report any known content. | 386 // Report any known content. |
353 if (_state == _CONTENT && contentStartIndex != null) { | 387 if (_state == _CONTENT && contentStartIndex != null) { |
354 reportData(); | 388 reportData(); |
355 } | 389 } |
356 | 390 |
357 // Resume if at end. | 391 // Resume if at end. |
358 if (_index == _buffer.length) { | 392 if (_index == _buffer.length) { |
359 _buffer = null; | 393 _buffer = null; |
360 _index = null; | 394 _index = null; |
361 _resumeStream(); | 395 _subscription.resume(); |
362 } | 396 } |
363 } | 397 } |
364 } | 398 } |
OLD | NEW |