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

Side by Side Diff: pkg/mime/lib/src/mime_multipart_transformer.dart

Issue 307963004: pkg/mime: Removed mutable state from MimeMultipartTransformer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 6 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 | Annotate | Revision Log
« no previous file with comments | « pkg/mime/lib/src/char_code.dart ('k') | pkg/mime/lib/src/mime_shared.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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
5 library mime.multipart_transformer; 4 library mime.multipart_transformer;
6 5
7 import 'dart:async'; 6 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:typed_data'; 7 import 'dart:typed_data';
10 8
11 /** 9 import 'bound_multipart_stream.dart';
12 * A Mime Multipart class representing each part parsed by 10 import 'mime_shared.dart';
13 * [MimeMultipartTransformer]. The data is streamed in as it become available. 11 import 'char_code.dart';
14 */
15 abstract class MimeMultipart extends Stream<List<int>> {
16 Map<String, String> get headers;
17 }
18 12
19 class _MimeMultipart extends MimeMultipart {
20 final Map<String, String> headers;
21 final Stream<List<int>> _stream;
22 13
23 _MimeMultipart(this.headers, this._stream); 14 Uint8List _getBoundary(String boundary) {
15 var charCodes = boundary.codeUnits;
24 16
25 StreamSubscription<List<int>> listen(void onData(List<int> data), 17 var boundaryList = new Uint8List(4 + charCodes.length);
26 {void onDone(), 18 // Set-up the matching boundary preceding it with CRLF and two
27 Function onError, 19 // dashes.
28 bool cancelOnError}) { 20 boundaryList[0] = CharCode.CR;
29 return _stream.listen(onData, 21 boundaryList[1] = CharCode.LF;
30 onDone: onDone, 22 boundaryList[2] = CharCode.DASH;
31 onError: onError, 23 boundaryList[3] = CharCode.DASH;
32 cancelOnError: cancelOnError); 24 boundaryList.setRange(4, 4 + charCodes.length, charCodes);
33 } 25 return boundaryList;
34 }
35
36 class _CharCode {
37 static const int HT = 9;
38 static const int LF = 10;
39 static const int CR = 13;
40 static const int SP = 32;
41 static const int DASH = 45;
42 static const int COLON = 58;
43 } 26 }
44 27
45 /** 28 /**
46 * Parser for MIME multipart types of data as described in RFC 2046 29 * Parser for MIME multipart types of data as described in RFC 2046
47 * section 5.1.1. The data is transformed into [MimeMultipart] objects, each 30 * section 5.1.1. The data is transformed into [MimeMultipart] objects, each
48 * of them streaming the multipart data. 31 * of them streaming the multipart data.
49 */ 32 */
50 class MimeMultipartTransformer 33 class MimeMultipartTransformer
51 implements StreamTransformer<List<int>, MimeMultipart> { 34 implements StreamTransformer<List<int>, MimeMultipart> {
52 static const int _START = 0;
53 static const int _FIRST_BOUNDARY_ENDING = 111;
54 static const int _FIRST_BOUNDARY_END = 112;
55 static const int _BOUNDARY_ENDING = 1;
56 static const int _BOUNDARY_END = 2;
57 static const int _HEADER_START = 3;
58 static const int _HEADER_FIELD = 4;
59 static const int _HEADER_VALUE_START = 5;
60 static const int _HEADER_VALUE = 6;
61 static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7;
62 static const int _HEADER_VALUE_FOLD_OR_END = 8;
63 static const int _HEADER_ENDING = 9;
64 static const int _CONTENT = 10;
65 static const int _LAST_BOUNDARY_DASH2 = 11;
66 static const int _LAST_BOUNDARY_ENDING = 12;
67 static const int _LAST_BOUNDARY_END = 13;
68 static const int _DONE = 14;
69 static const int _FAILURE = 15;
70 35
71 // Bytes for '()<>@,;:\\"/[]?={} \t'. 36 final List<int> _boundary;
72 static const _SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47,
73 91, 93, 63, 61, 123, 125, 32, 9];
74
75 StreamController _controller;
76 StreamSubscription _subscription;
77
78 StreamController _multipartController;
79 Map<String, String> _headers;
80
81 List<int> _boundary;
82 int _state = _START;
83 int _boundaryIndex = 2;
84
85 // Current index in the data buffer. If index is negative then it
86 // is the index into the artificial prefix of the boundary string.
87 int _index;
88 List<int> _buffer;
89
90 List<int> _headerField = [];
91 List<int> _headerValue = [];
92 37
93 /** 38 /**
94 * Construct a new MIME multipart parser with the boundary 39 * Construct a new MIME multipart parser with the boundary
95 * [boundary]. The boundary should be as specified in the content 40 * [boundary]. The boundary should be as specified in the content
96 * type parameter, that is without the -- prefix. 41 * type parameter, that is without the -- prefix.
97 */ 42 */
98 MimeMultipartTransformer(String boundary) { 43 MimeMultipartTransformer(String boundary)
99 List<int> charCodes = boundary.codeUnits; 44 : _boundary = _getBoundary(boundary);
100 _boundary = new Uint8List(4 + charCodes.length);
101 // Set-up the matching boundary preceding it with CRLF and two
102 // dashes.
103 _boundary[0] = _CharCode.CR;
104 _boundary[1] = _CharCode.LF;
105 _boundary[2] = _CharCode.DASH;
106 _boundary[3] = _CharCode.DASH;
107 _boundary.setRange(4, 4 + charCodes.length, charCodes);
108 }
109
110 void _resumeStream() {
111 _subscription.resume();
112 }
113
114 void _pauseStream() {
115 _subscription.pause();
116 }
117 45
118 Stream<MimeMultipart> bind(Stream<List<int>> stream) { 46 Stream<MimeMultipart> bind(Stream<List<int>> stream) {
119 _controller = new StreamController( 47 return new BoundMultipartStream(_boundary, stream).stream;
120 sync: true,
121 onPause: _pauseStream,
122 onResume:_resumeStream,
123 onCancel: () {
124 _subscription.cancel();
125 },
126 onListen: () {
127 _subscription = stream.listen(
128 (data) {
129 assert(_buffer == null);
130 _pauseStream();
131 _buffer = data;
132 _index = 0;
133 _parse();
134 },
135 onDone: () {
136 if (_state != _DONE) {
137 _controller.addError(
138 new MimeMultipartException("Bad multipart ending"));
139 }
140 _controller.close();
141 },
142 onError: _controller.addError);
143 });
144 return _controller.stream;
145 }
146
147 void _parse() {
148 // Number of boundary bytes to artificially place before the supplied data.
149 int boundaryPrefix = 0;
150 // Position where content starts. Will be null if no known content
151 // start exists. Will be negative of the content starts in the
152 // boundary prefix. Will be zero or position if the content starts
153 // in the current buffer.
154 int contentStartIndex;
155
156 // Function to report content data for the current part. The data
157 // reported is from the current content start index up til the
158 // current index. As the data can be artificially prefixed with a
159 // prefix of the boundary both the content start index and index
160 // can be negative.
161 void reportData() {
162 if (contentStartIndex < 0) {
163 var contentLength = boundaryPrefix + _index - _boundaryIndex;
164 if (contentLength <= boundaryPrefix) {
165 _multipartController.add(
166 _boundary.sublist(0, contentLength));
167 } else {
168 _multipartController.add(
169 _boundary.sublist(0, boundaryPrefix));
170 _multipartController.add(
171 _buffer.sublist(0, contentLength - boundaryPrefix));
172 }
173 } else {
174 var contentEndIndex = _index - _boundaryIndex;
175 _multipartController.add(
176 _buffer.sublist(contentStartIndex, contentEndIndex));
177 }
178 }
179
180 if (_state == _CONTENT && _boundaryIndex == 0) {
181 contentStartIndex = 0;
182 } else {
183 contentStartIndex = null;
184 }
185 // The data to parse might be "artificially" prefixed with a
186 // partial match of the boundary.
187 boundaryPrefix = _boundaryIndex;
188
189 while ((_index < _buffer.length) && _state != _FAILURE && _state != _DONE) {
190 if (_multipartController != null && _multipartController.isPaused) {
191 return;
192 }
193 int byte;
194 if (_index < 0) {
195 byte = _boundary[boundaryPrefix + _index];
196 } else {
197 byte = _buffer[_index];
198 }
199 switch (_state) {
200 case _START:
201 if (byte == _boundary[_boundaryIndex]) {
202 _boundaryIndex++;
203 if (_boundaryIndex == _boundary.length) {
204 _state = _FIRST_BOUNDARY_ENDING;
205 _boundaryIndex = 0;
206 }
207 } else {
208 // Restart matching of the boundary.
209 _index = _index - _boundaryIndex;
210 _boundaryIndex = 0;
211 }
212 break;
213
214 case _FIRST_BOUNDARY_ENDING:
215 if (byte == _CharCode.CR) {
216 _state = _FIRST_BOUNDARY_END;
217 } else {
218 _expectWS(byte);
219 }
220 break;
221
222 case _FIRST_BOUNDARY_END:
223 _expect(byte, _CharCode.LF);
224 _state = _HEADER_START;
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 _expectWS(byte);
234 }
235 break;
236
237 case _BOUNDARY_END:
238 _expect(byte, _CharCode.LF);
239 _multipartController.close();
240 _multipartController = null;
241 _state = _HEADER_START;
242 break;
243
244 case _HEADER_START:
245 _headers = new Map<String, String>();
246 if (byte == _CharCode.CR) {
247 _state = _HEADER_ENDING;
248 } else {
249 // Start of new header field.
250 _headerField.add(_toLowerCase(byte));
251 _state = _HEADER_FIELD;
252 }
253 break;
254
255 case _HEADER_FIELD:
256 if (byte == _CharCode.COLON) {
257 _state = _HEADER_VALUE_START;
258 } else {
259 if (!_isTokenChar(byte)) {
260 throw new MimeMultipartException("Invalid header field name");
261 }
262 _headerField.add(_toLowerCase(byte));
263 }
264 break;
265
266 case _HEADER_VALUE_START:
267 if (byte == _CharCode.CR) {
268 _state = _HEADER_VALUE_FOLDING_OR_ENDING;
269 } else if (byte != _CharCode.SP && byte != _CharCode.HT) {
270 // Start of new header value.
271 _headerValue.add(byte);
272 _state = _HEADER_VALUE;
273 }
274 break;
275
276 case _HEADER_VALUE:
277 if (byte == _CharCode.CR) {
278 _state = _HEADER_VALUE_FOLDING_OR_ENDING;
279 } else {
280 _headerValue.add(byte);
281 }
282 break;
283
284 case _HEADER_VALUE_FOLDING_OR_ENDING:
285 _expect(byte, _CharCode.LF);
286 _state = _HEADER_VALUE_FOLD_OR_END;
287 break;
288
289 case _HEADER_VALUE_FOLD_OR_END:
290 if (byte == _CharCode.SP || byte == _CharCode.HT) {
291 _state = _HEADER_VALUE_START;
292 } else {
293 String headerField = UTF8.decode(_headerField);
294 String headerValue = UTF8.decode(_headerValue);
295 _headers[headerField.toLowerCase()] = headerValue;
296 _headerField = [];
297 _headerValue = [];
298 if (byte == _CharCode.CR) {
299 _state = _HEADER_ENDING;
300 } else {
301 // Start of new header field.
302 _headerField.add(_toLowerCase(byte));
303 _state = _HEADER_FIELD;
304 }
305 }
306 break;
307
308 case _HEADER_ENDING:
309 _expect(byte, _CharCode.LF);
310 _multipartController = new StreamController(
311 sync: true,
312 onPause: () {
313 _pauseStream();
314 },
315 onResume: () {
316 _resumeStream();
317 _parse();
318 });
319 _controller.add(
320 new _MimeMultipart(_headers, _multipartController.stream));
321 _headers = null;
322 _state = _CONTENT;
323 contentStartIndex = _index + 1;
324 break;
325
326 case _CONTENT:
327 if (byte == _boundary[_boundaryIndex]) {
328 _boundaryIndex++;
329 if (_boundaryIndex == _boundary.length) {
330 if (contentStartIndex != null) {
331 _index++;
332 reportData();
333 _index--;
334 }
335 _multipartController.close();
336 _boundaryIndex = 0;
337 _state = _BOUNDARY_ENDING;
338 }
339 } else {
340 // Restart matching of the boundary.
341 _index = _index - _boundaryIndex;
342 if (contentStartIndex == null) contentStartIndex = _index;
343 _boundaryIndex = 0;
344 }
345 break;
346
347 case _LAST_BOUNDARY_DASH2:
348 _expect(byte, _CharCode.DASH);
349 _state = _LAST_BOUNDARY_ENDING;
350 break;
351
352 case _LAST_BOUNDARY_ENDING:
353 if (byte == _CharCode.CR) {
354 _state = _LAST_BOUNDARY_END;
355 } else {
356 _expectWS(byte);
357 }
358 break;
359
360 case _LAST_BOUNDARY_END:
361 _expect(byte, _CharCode.LF);
362 _multipartController.close();
363 _multipartController = null;
364 _state = _DONE;
365 break;
366
367 default:
368 // Should be unreachable.
369 assert(false);
370 break;
371 }
372
373 // Move to the next byte.
374 _index++;
375 }
376
377 // Report any known content.
378 if (_state == _CONTENT && contentStartIndex != null) {
379 reportData();
380 }
381
382 // Resume if at end.
383 if (_index == _buffer.length) {
384 _buffer = null;
385 _index = null;
386 _resumeStream();
387 }
388 }
389
390 bool _isTokenChar(int byte) {
391 return byte > 31 && byte < 128 && _SEPARATORS.indexOf(byte) == -1;
392 }
393
394 int _toLowerCase(int byte) {
395 final int aCode = "A".codeUnitAt(0);
396 final int zCode = "Z".codeUnitAt(0);
397 final int delta = "a".codeUnitAt(0) - aCode;
398 return (aCode <= byte && byte <= zCode) ? byte + delta : byte;
399 }
400
401 void _expect(int val1, int val2) {
402 if (val1 != val2) {
403 throw new MimeMultipartException("Failed to parse multipart mime 1");
404 }
405 }
406
407 void _expectWS(int byte) {
408 if (byte != _CharCode.SP && byte != _CharCode.HT) {
409 throw new MimeMultipartException("Failed to parse multipart mime 2");
410 }
411 } 48 }
412 } 49 }
413
414
415 class MimeMultipartException implements Exception {
416 final String message;
417
418 const MimeMultipartException([String this.message = ""]);
419
420 String toString() => "MimeMultipartException: $message";
421 }
OLDNEW
« no previous file with comments | « pkg/mime/lib/src/char_code.dart ('k') | pkg/mime/lib/src/mime_shared.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698