OLD | NEW |
| (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 /** | |
6 * Parser for MIME multipart types of data as described in RFC 2046 | |
7 * section 5.1.1. The data to parse is supplied through the [:update:] | |
8 * method. As the data is parsed the following callbacks are called: | |
9 * | |
10 * [:partStart; | |
11 * [:headerReceived; | |
12 * [:headersComplete; | |
13 * [:partDataReceived; | |
14 * [:partEnd; | |
15 * [:error:] | |
16 */ | |
17 | |
18 class _MimeMultipartParser { | |
19 const int _START = 0; | |
20 const int _FIRST_BOUNDARY_ENDING = 111; | |
21 const int _FIRST_BOUNDARY_END = 112; | |
22 const int _BOUNDARY_ENDING = 1; | |
23 const int _BOUNDARY_END = 2; | |
24 const int _HEADER_START = 3; | |
25 const int _HEADER_FIELD = 4; | |
26 const int _HEADER_VALUE_START = 5; | |
27 const int _HEADER_VALUE = 6; | |
28 const int _HEADER_VALUE_FOLDING_OR_ENDING = 7; | |
29 const int _HEADER_VALUE_FOLD_OR_END = 8; | |
30 const int _HEADER_ENDING = 9; | |
31 const int _CONTENT = 10; | |
32 const int _LAST_BOUNDARY_DASH2 = 11; | |
33 const int _LAST_BOUNDARY_ENDING = 12; | |
34 const int _LAST_BOUNDARY_END = 13; | |
35 const int _DONE = 14; | |
36 const int _FAILURE = 15; | |
37 | |
38 // Construct a new MIME multipart parser with the boundary | |
39 // [boundary]. The boundary should be as specified in the content | |
40 // type parameter, that is without the -- prefix. | |
41 _MimeMultipartParser(String boundary) { | |
42 List<int> charCodes = boundary.charCodes; | |
43 _boundary = new List<int>(4 + charCodes.length); | |
44 // Set-up the matching boundary preceding it with CRLF and two | |
45 // dashes. | |
46 _boundary[0] = _CharCode.CR; | |
47 _boundary[1] = _CharCode.LF; | |
48 _boundary[2] = _CharCode.DASH; | |
49 _boundary[3] = _CharCode.DASH; | |
50 _boundary.setRange(4, charCodes.length, charCodes); | |
51 _state = _START; | |
52 _headerField = new StringBuffer(); | |
53 _headerValue = new StringBuffer(); | |
54 } | |
55 | |
56 int update(List<int> buffer, int offset, int count) { | |
57 // Current index in the data buffer. If index is negative then it | |
58 // is the index into the artificial prefix of the boundary string. | |
59 int index; | |
60 // Number of boundary bytes to artificially place before the supplied data. | |
61 int boundaryPrefix = 0; | |
62 // Position where content starts. Will be null if no known content | |
63 // start exists. Will be negative of the content starts in the | |
64 // boundary prefix. Will be zero or position if the content starts | |
65 // in the current buffer. | |
66 int contentStartIndex; | |
67 | |
68 // Function to report content data for the current part. The data | |
69 // reported is from the current content start index up til the | |
70 // current index. As the data can be artificially prefixed with a | |
71 // prefix of the boundary both the content start index and index | |
72 // can be negative. | |
73 void reportData() { | |
74 if (partDataReceived == null) return; | |
75 | |
76 if (contentStartIndex < 0) { | |
77 var contentLength = boundaryPrefix + index - _boundaryIndex; | |
78 if (contentLength <= boundaryPrefix) { | |
79 partDataReceived( | |
80 _boundary.getRange(0, contentLength)); | |
81 } else { | |
82 partDataReceived( | |
83 _boundary.getRange(0, boundaryPrefix)); | |
84 partDataReceived( | |
85 buffer.getRange(0, contentLength - boundaryPrefix)); | |
86 } | |
87 } else { | |
88 var contentLength = index - contentStartIndex - _boundaryIndex; | |
89 partDataReceived( | |
90 buffer.getRange(contentStartIndex, contentLength)); | |
91 } | |
92 } | |
93 | |
94 // Prepare for processing the buffer. | |
95 index = offset; | |
96 int lastIndex = offset + count; | |
97 if (_state == _CONTENT && _boundaryIndex == 0) { | |
98 contentStartIndex = 0; | |
99 } else { | |
100 contentStartIndex = null; | |
101 } | |
102 // The data to parse might be "artificially" prefixed with a | |
103 // partial match of the boundary. | |
104 boundaryPrefix = _boundaryIndex; | |
105 | |
106 while ((index < lastIndex) && _state != _FAILURE && _state != _DONE) { | |
107 int byte; | |
108 if (index < 0) { | |
109 byte = _boundary[boundaryPrefix + index]; | |
110 } else { | |
111 byte = buffer[index]; | |
112 } | |
113 switch (_state) { | |
114 case _START: | |
115 if (_toLowerCase(byte) == _toLowerCase(_boundary[_boundaryIndex])) { | |
116 _boundaryIndex++; | |
117 if (_boundaryIndex == _boundary.length) { | |
118 _state = _FIRST_BOUNDARY_ENDING; | |
119 _boundaryIndex = 0; | |
120 } | |
121 } else { | |
122 // Restart matching of the boundary. | |
123 index = index - _boundaryIndex; | |
124 _boundaryIndex = 0; | |
125 } | |
126 break; | |
127 | |
128 case _FIRST_BOUNDARY_ENDING: | |
129 if (byte == _CharCode.CR) { | |
130 _state = _FIRST_BOUNDARY_END; | |
131 } else { | |
132 _expectWS(byte); | |
133 } | |
134 break; | |
135 | |
136 case _FIRST_BOUNDARY_END: | |
137 _expect(byte, _CharCode.LF); | |
138 _state = _HEADER_START; | |
139 break; | |
140 | |
141 case _BOUNDARY_ENDING: | |
142 if (byte == _CharCode.CR) { | |
143 _state = _BOUNDARY_END; | |
144 } else if (byte == _CharCode.DASH) { | |
145 _state = _LAST_BOUNDARY_DASH2; | |
146 } else { | |
147 _expectWS(byte); | |
148 } | |
149 break; | |
150 | |
151 case _BOUNDARY_END: | |
152 _expect(byte, _CharCode.LF); | |
153 if (partEnd != null) { | |
154 partEnd(false); | |
155 } | |
156 _state = _HEADER_START; | |
157 break; | |
158 | |
159 case _HEADER_START: | |
160 if (byte == _CharCode.CR) { | |
161 _state = _HEADER_ENDING; | |
162 } else { | |
163 // Start of new header field. | |
164 _headerField.addCharCode(_toLowerCase(byte)); | |
165 _state = _HEADER_FIELD; | |
166 } | |
167 break; | |
168 | |
169 case _HEADER_FIELD: | |
170 if (byte == _CharCode.COLON) { | |
171 _state = _HEADER_VALUE_START; | |
172 } else { | |
173 if (!_isTokenChar(byte)) { | |
174 throw new MimeParserException("Invalid header field name"); | |
175 } | |
176 _headerField.addCharCode(_toLowerCase(byte)); | |
177 } | |
178 break; | |
179 | |
180 case _HEADER_VALUE_START: | |
181 if (byte == _CharCode.CR) { | |
182 _state = _HEADER_VALUE_FOLDING_OR_ENDING; | |
183 } else if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
184 // Start of new header value. | |
185 _headerValue.addCharCode(byte); | |
186 _state = _HEADER_VALUE; | |
187 } | |
188 break; | |
189 | |
190 case _HEADER_VALUE: | |
191 if (byte == _CharCode.CR) { | |
192 _state = _HEADER_VALUE_FOLDING_OR_ENDING; | |
193 } else { | |
194 _headerValue.addCharCode(byte); | |
195 } | |
196 break; | |
197 | |
198 case _HEADER_VALUE_FOLDING_OR_ENDING: | |
199 _expect(byte, _CharCode.LF); | |
200 _state = _HEADER_VALUE_FOLD_OR_END; | |
201 break; | |
202 | |
203 case _HEADER_VALUE_FOLD_OR_END: | |
204 if (byte == _CharCode.SP || byte == _CharCode.HT) { | |
205 _state = _HEADER_VALUE_START; | |
206 } else { | |
207 String headerField = _headerField.toString(); | |
208 String headerValue =_headerValue.toString(); | |
209 if (headerReceived != null) { | |
210 headerReceived(headerField, headerValue); | |
211 } | |
212 _headerField.clear(); | |
213 _headerValue.clear(); | |
214 if (byte == _CharCode.CR) { | |
215 _state = _HEADER_ENDING; | |
216 } else { | |
217 // Start of new header field. | |
218 _headerField.addCharCode(_toLowerCase(byte)); | |
219 _state = _HEADER_FIELD; | |
220 } | |
221 } | |
222 break; | |
223 | |
224 case _HEADER_ENDING: | |
225 _expect(byte, _CharCode.LF); | |
226 if (headersComplete != null) headersComplete(); | |
227 _state = _CONTENT; | |
228 contentStartIndex = index + 1; | |
229 break; | |
230 | |
231 case _CONTENT: | |
232 if (_toLowerCase(byte) == _toLowerCase(_boundary[_boundaryIndex])) { | |
233 _boundaryIndex++; | |
234 if (_boundaryIndex == _boundary.length) { | |
235 if (contentStartIndex != null) { | |
236 index++; | |
237 reportData(); | |
238 index--; | |
239 } | |
240 _boundaryIndex = 0; | |
241 _state = _BOUNDARY_ENDING; | |
242 } | |
243 } else { | |
244 // Restart matching of the boundary. | |
245 index = index - _boundaryIndex; | |
246 if (contentStartIndex == null) contentStartIndex = index; | |
247 _boundaryIndex = 0; | |
248 } | |
249 break; | |
250 | |
251 case _LAST_BOUNDARY_DASH2: | |
252 _expect(byte, _CharCode.DASH); | |
253 _state = _LAST_BOUNDARY_ENDING; | |
254 break; | |
255 | |
256 case _LAST_BOUNDARY_ENDING: | |
257 if (byte == _CharCode.CR) { | |
258 _state = _LAST_BOUNDARY_END; | |
259 } else { | |
260 _expectWS(byte); | |
261 } | |
262 break; | |
263 | |
264 case _LAST_BOUNDARY_END: | |
265 _expect(byte, _CharCode.LF); | |
266 if (partEnd != null) { | |
267 partEnd(true); | |
268 } | |
269 _state = _DONE; | |
270 break; | |
271 | |
272 default: | |
273 // Should be unreachable. | |
274 assert(false); | |
275 break; | |
276 } | |
277 | |
278 // Move to the next byte. | |
279 index++; | |
280 } | |
281 | |
282 // Report any known content. | |
283 if (_state == _CONTENT && contentStartIndex != null) { | |
284 reportData(); | |
285 } | |
286 return index - offset; | |
287 } | |
288 | |
289 bool _isTokenChar(int byte) { | |
290 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | |
291 } | |
292 | |
293 int _toLowerCase(int byte) { | |
294 final int aCode = "A".charCodeAt(0); | |
295 final int zCode = "Z".charCodeAt(0); | |
296 final int delta = "a".charCodeAt(0) - aCode; | |
297 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; | |
298 } | |
299 | |
300 void _expect(int val1, int val2) { | |
301 if (val1 != val2) { | |
302 throw new MimeParserException("Failed to parse multipart mime 1"); | |
303 } | |
304 } | |
305 | |
306 void _expectWS(int byte) { | |
307 if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
308 throw new MimeParserException("Failed to parse multipart mime 2"); | |
309 } | |
310 } | |
311 | |
312 List<int> _boundary; | |
313 int _state; | |
314 int _boundaryIndex = 0; | |
315 | |
316 StringBuffer _headerField; | |
317 StringBuffer _headerValue; | |
318 | |
319 Function partStart; | |
320 Function headerReceived; | |
321 Function headersComplete; | |
322 Function partDataReceived; | |
323 Function partEnd; | |
324 } | |
325 | |
326 | |
327 class MimeParserException implements Exception { | |
328 const MimeParserException([String this.message = ""]); | |
329 String toString() => "MimeParserException: $message"; | |
330 final String message; | |
331 } | |
OLD | NEW |