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 |