Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(33)

Side by Side Diff: mojo/public/dart/third_party/mime/lib/src/bound_multipart_stream.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « mojo/public/dart/third_party/mime/lib/mime.dart ('k') | mojo/public/dart/third_party/mime/lib/src/char_code.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698