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

Side by Side Diff: sdk/lib/io/mime_multipart_parser.dart

Issue 18438005: Move MimeMultipartTransformer and HttpBodyHandler to mime and http_server packages. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Remove import. Created 7 years, 5 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 | « sdk/lib/io/iolib_sources.gypi ('k') | tests/standalone/io/http_body_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of dart.io;
6
7
8 /**
9 * A Mime Multipart class representing each part parsed by
10 * [MimeMultipartTransformer]. The data is streamed in as it become available.
11 */
12 abstract class MimeMultipart extends Stream<List<int>> {
13 Map<String, String> get headers;
14 }
15
16 class _MimeMultipart extends MimeMultipart {
17 final Map<String, String> headers;
18 final Stream<List<int>> _stream;
19
20 _MimeMultipart(this.headers, this._stream);
21
22 StreamSubscription<List<int>> listen(void onData(List<int> data),
23 {void onDone(),
24 void onError(error),
25 bool cancelOnError}) {
26 return _stream.listen(onData,
27 onDone: onDone,
28 onError: onError,
29 cancelOnError: cancelOnError);
30 }
31 }
32
33 /**
34 * Parser for MIME multipart types of data as described in RFC 2046
35 * section 5.1.1. The data is transformed into [MimeMultipart] objects, each
36 * of them streaming the multipart data.
37 */
38 class MimeMultipartTransformer
39 implements StreamTransformer<List<int>, MimeMultipart> {
40 static const int _START = 0;
41 static const int _FIRST_BOUNDARY_ENDING = 111;
42 static const int _FIRST_BOUNDARY_END = 112;
43 static const int _BOUNDARY_ENDING = 1;
44 static const int _BOUNDARY_END = 2;
45 static const int _HEADER_START = 3;
46 static const int _HEADER_FIELD = 4;
47 static const int _HEADER_VALUE_START = 5;
48 static const int _HEADER_VALUE = 6;
49 static const int _HEADER_VALUE_FOLDING_OR_ENDING = 7;
50 static const int _HEADER_VALUE_FOLD_OR_END = 8;
51 static const int _HEADER_ENDING = 9;
52 static const int _CONTENT = 10;
53 static const int _LAST_BOUNDARY_DASH2 = 11;
54 static const int _LAST_BOUNDARY_ENDING = 12;
55 static const int _LAST_BOUNDARY_END = 13;
56 static const int _DONE = 14;
57 static const int _FAILURE = 15;
58
59 StreamController _controller;
60 StreamSubscription _subscription;
61
62 StreamController _multipartController;
63 Map<String, String> _headers;
64
65 List<int> _boundary;
66 int _state = _START;
67 int _boundaryIndex = 2;
68
69 // Current index in the data buffer. If index is negative then it
70 // is the index into the artificial prefix of the boundary string.
71 int _index;
72 List<int> _buffer;
73
74 StringBuffer _headerField = new StringBuffer();
75 StringBuffer _headerValue = new StringBuffer();
76
77 /**
78 * Construct a new MIME multipart parser with the boundary
79 * [boundary]. The boundary should be as specified in the content
80 * type parameter, that is without the -- prefix.
81 */
82 MimeMultipartTransformer(String boundary) {
83 List<int> charCodes = boundary.codeUnits;
84 _boundary = new Uint8List(4 + charCodes.length);
85 // Set-up the matching boundary preceding it with CRLF and two
86 // dashes.
87 _boundary[0] = _CharCode.CR;
88 _boundary[1] = _CharCode.LF;
89 _boundary[2] = _CharCode.DASH;
90 _boundary[3] = _CharCode.DASH;
91 _boundary.setRange(4, 4 + charCodes.length, charCodes);
92 }
93
94 void _resumeStream() {
95 _subscription.resume();
96 }
97
98 void _pauseStream() {
99 _subscription.pause();
100 }
101
102 Stream<MimeMultipart> bind(Stream<List<int>> stream) {
103 _controller = new StreamController(
104 sync: true,
105 onPause: _pauseStream,
106 onResume:_resumeStream,
107 onCancel: () {
108 _subscription.cancel();
109 },
110 onListen: () {
111 _subscription = stream.listen(
112 (data) {
113 assert(_buffer == null);
114 _pauseStream();
115 _buffer = data;
116 _index = 0;
117 _parse();
118 },
119 onDone: () {
120 if (_state != _DONE) {
121 _controller.addError(
122 new MimeMultipartException("Bad multipart ending"));
123 }
124 _controller.close();
125 },
126 onError: (error) {
127 _controller.addError(error);
128 });
129 });
130 return _controller.stream;
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 != _FAILURE && _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 _expectWS(byte);
205 }
206 break;
207
208 case _FIRST_BOUNDARY_END:
209 _expect(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 _expectWS(byte);
220 }
221 break;
222
223 case _BOUNDARY_END:
224 _expect(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.writeCharCode(_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.writeCharCode(_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.writeCharCode(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.writeCharCode(byte);
267 }
268 break;
269
270 case _HEADER_VALUE_FOLDING_OR_ENDING:
271 _expect(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 = _headerField.toString();
280 String headerValue =_headerValue.toString();
281 _headers[headerField.toLowerCase()] = headerValue;
282 _headerField = new StringBuffer();
283 _headerValue = new StringBuffer();
284 if (byte == _CharCode.CR) {
285 _state = _HEADER_ENDING;
286 } else {
287 // Start of new header field.
288 _headerField.writeCharCode(_toLowerCase(byte));
289 _state = _HEADER_FIELD;
290 }
291 }
292 break;
293
294 case _HEADER_ENDING:
295 _expect(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 _expect(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 _expectWS(byte);
343 }
344 break;
345
346 case _LAST_BOUNDARY_END:
347 _expect(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
376 bool _isTokenChar(int byte) {
377 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1;
378 }
379
380 int _toLowerCase(int byte) {
381 final int aCode = "A".codeUnitAt(0);
382 final int zCode = "Z".codeUnitAt(0);
383 final int delta = "a".codeUnitAt(0) - aCode;
384 return (aCode <= byte && byte <= zCode) ? byte + delta : byte;
385 }
386
387 void _expect(int val1, int val2) {
388 if (val1 != val2) {
389 throw new MimeMultipartException("Failed to parse multipart mime 1");
390 }
391 }
392
393 void _expectWS(int byte) {
394 if (byte != _CharCode.SP && byte != _CharCode.HT) {
395 throw new MimeMultipartException("Failed to parse multipart mime 2");
396 }
397 }
398 }
399
400
401 class MimeMultipartException implements IOException {
402 const MimeMultipartException([String this.message = ""]);
403 String toString() => "MimeMultipartException: $message";
404 final String message;
405 }
OLDNEW
« no previous file with comments | « sdk/lib/io/iolib_sources.gypi ('k') | tests/standalone/io/http_body_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698