OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 import "package:expect/expect.dart"; | |
6 import 'dart:async'; | |
7 import 'dart:math'; | |
8 import 'dart:io'; | |
9 import 'dart:isolate'; | |
10 | |
11 void testParse(String message, | |
12 String boundary, | |
13 [List<Map> expectedHeaders, | |
14 List expectedParts, | |
15 bool expectError = false]) { | |
16 void testWrite(List<int> data, [int chunkSize = -1]) { | |
17 StreamController controller = new StreamController(sync: true); | |
18 | |
19 var stream = controller.stream.transform( | |
20 new MimeMultipartTransformer(boundary)); | |
21 int i = 0; | |
22 var port = new ReceivePort(); | |
23 stream.listen((multipart) { | |
24 int part = i++; | |
25 if (expectedHeaders != null) { | |
26 Expect.mapEquals(expectedHeaders[part], multipart.headers); | |
27 } | |
28 var partPort = new ReceivePort(); | |
29 multipart.fold([], (buffer, data) => buffer..addAll(data)) | |
30 .then((data) { | |
31 if (expectedParts[part] != null) { | |
32 Expect.listEquals(expectedParts[part].codeUnits, data); | |
33 } | |
34 partPort.close(); | |
35 }); | |
36 }, onError: (error) { | |
37 if (!expectError) throw error; | |
38 }, onDone: () { | |
39 if (expectedParts != null) { | |
40 Expect.equals(expectedParts.length, i); | |
41 } | |
42 port.close(); | |
43 }); | |
44 | |
45 if (chunkSize == -1) chunkSize = data.length; | |
46 | |
47 int written = 0; | |
48 for (int pos = 0; pos < data.length; pos += chunkSize) { | |
49 int remaining = data.length - pos; | |
50 int writeLength = min(chunkSize, remaining); | |
51 controller.add(data.sublist(pos, pos + writeLength)); | |
52 written += writeLength; | |
53 } | |
54 controller.close(); | |
55 } | |
56 | |
57 // Test parsing the data three times delivering the data in | |
58 // different chunks. | |
59 List<int> data = message.codeUnits; | |
60 testWrite(data); | |
61 testWrite(data, 10); | |
62 testWrite(data, 2); | |
63 testWrite(data, 1); | |
64 } | |
65 | |
66 void testParseValid() { | |
67 String message; | |
68 Map headers; | |
69 Map headers1; | |
70 Map headers2; | |
71 Map headers3; | |
72 Map headers4; | |
73 String body1; | |
74 String body2; | |
75 String body3; | |
76 String body4; | |
77 | |
78 // Sample from Wikipedia. | |
79 message = """ | |
80 This is a message with multiple parts in MIME format.\r | |
81 --frontier\r | |
82 Content-Type: text/plain\r | |
83 \r | |
84 This is the body of the message.\r | |
85 --frontier\r | |
86 Content-Type: application/octet-stream\r | |
87 Content-Transfer-Encoding: base64\r | |
88 \r | |
89 PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg | |
90 Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\r | |
91 --frontier--\r\n"""; | |
92 headers1 = <String, String>{"content-type": "text/plain"}; | |
93 headers2 = <String, String>{"content-type": "application/octet-stream", | |
94 "content-transfer-encoding": "base64"}; | |
95 body1 = "This is the body of the message."; | |
96 body2 = """ | |
97 PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg | |
98 Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg="""; | |
99 testParse(message, "frontier", [headers1, headers2], [body1, body2]); | |
100 | |
101 // Sample from HTML 4.01 Specification. | |
102 message = """ | |
103 \r\n--AaB03x\r | |
104 Content-Disposition: form-data; name=\"submit-name\"\r | |
105 \r | |
106 Larry\r | |
107 --AaB03x\r | |
108 Content-Disposition: form-data; name=\"files\"; filename=\"file1.txt\"\r | |
109 Content-Type: text/plain\r | |
110 \r | |
111 ... contents of file1.txt ...\r | |
112 --AaB03x--\r\n"""; | |
113 headers1 = <String, String>{ | |
114 "content-disposition": "form-data; name=\"submit-name\""}; | |
115 headers2 = <String, String>{ | |
116 "content-type": "text/plain", | |
117 "content-disposition": "form-data; name=\"files\"; filename=\"file1.txt\"" | |
118 }; | |
119 body1 = "Larry"; | |
120 body2 = "... contents of file1.txt ..."; | |
121 testParse(message, "AaB03x", [headers1, headers2], [body1, body2]); | |
122 | |
123 // Longer form from submitting the following from Chrome. | |
124 // | |
125 // <html> | |
126 // <body> | |
127 // <FORM action="http://127.0.0.1:1234/" | |
128 // enctype="multipart/form-data" | |
129 // method="post"> | |
130 // <P> | |
131 // Text: <INPUT type="text" name="text_input"> | |
132 // Password: <INPUT type="password" name="password_input"> | |
133 // Checkbox: <INPUT type="checkbox" name="checkbox_input"> | |
134 // Radio: <INPUT type="radio" name="radio_input"> | |
135 // Send <INPUT type="submit"> | |
136 // </P> | |
137 // </FORM> | |
138 // </body> | |
139 // </html> | |
140 | |
141 message = """ | |
142 \r\n------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r | |
143 Content-Disposition: form-data; name=\"text_input\"\r | |
144 \r | |
145 text\r | |
146 ------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r | |
147 Content-Disposition: form-data; name=\"password_input\"\r | |
148 \r | |
149 password\r | |
150 ------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r | |
151 Content-Disposition: form-data; name=\"checkbox_input\"\r | |
152 \r | |
153 on\r | |
154 ------WebKitFormBoundaryQ3cgYAmGRF8yOeYB\r | |
155 Content-Disposition: form-data; name=\"radio_input\"\r | |
156 \r | |
157 on\r | |
158 ------WebKitFormBoundaryQ3cgYAmGRF8yOeYB--\r\n"""; | |
159 headers1 = <String, String>{ | |
160 "content-disposition": "form-data; name=\"text_input\""}; | |
161 headers2 = <String, String>{ | |
162 "content-disposition": "form-data; name=\"password_input\""}; | |
163 headers3 = <String, String>{ | |
164 "content-disposition": "form-data; name=\"checkbox_input\""}; | |
165 headers4 = <String, String>{ | |
166 "content-disposition": "form-data; name=\"radio_input\""}; | |
167 body1 = "text"; | |
168 body2 = "password"; | |
169 body3 = "on"; | |
170 body4 = "on"; | |
171 testParse(message, | |
172 "----WebKitFormBoundaryQ3cgYAmGRF8yOeYB", | |
173 [headers1, headers2, headers3, headers4], | |
174 [body1, body2, body3, body4]); | |
175 | |
176 // Same form from Firefox. | |
177 message = """ | |
178 \r\n-----------------------------52284550912143824192005403738\r | |
179 Content-Disposition: form-data; name=\"text_input\"\r | |
180 \r | |
181 text\r | |
182 -----------------------------52284550912143824192005403738\r | |
183 Content-Disposition: form-data; name=\"password_input\"\r | |
184 \r | |
185 password\r | |
186 -----------------------------52284550912143824192005403738\r | |
187 Content-Disposition: form-data; name=\"checkbox_input\"\r | |
188 \r | |
189 on\r | |
190 -----------------------------52284550912143824192005403738\r | |
191 Content-Disposition: form-data; name=\"radio_input\"\r | |
192 \r | |
193 on\r | |
194 -----------------------------52284550912143824192005403738--\r\n"""; | |
195 testParse(message, | |
196 "---------------------------52284550912143824192005403738", | |
197 [headers1, headers2, headers3, headers4], | |
198 [body1, body2, body3, body4]); | |
199 | |
200 // And Internet Explorer | |
201 message = """ | |
202 \r\n-----------------------------7dc8f38c60326\r | |
203 Content-Disposition: form-data; name=\"text_input\"\r | |
204 \r | |
205 text\r | |
206 -----------------------------7dc8f38c60326\r | |
207 Content-Disposition: form-data; name=\"password_input\"\r | |
208 \r | |
209 password\r | |
210 -----------------------------7dc8f38c60326\r | |
211 Content-Disposition: form-data; name=\"checkbox_input\"\r | |
212 \r | |
213 on\r | |
214 -----------------------------7dc8f38c60326\r | |
215 Content-Disposition: form-data; name=\"radio_input\"\r | |
216 \r | |
217 on\r | |
218 -----------------------------7dc8f38c60326--\r\n"""; | |
219 testParse(message, | |
220 "---------------------------7dc8f38c60326", | |
221 [headers1, headers2, headers3, headers4], | |
222 [body1, body2, body3, body4]); | |
223 | |
224 // Test boundary prefix inside prefix and content. | |
225 message = """ | |
226 -\r | |
227 --\r | |
228 --b\r | |
229 --bo\r | |
230 --bou\r | |
231 --boun\r | |
232 --bound\r | |
233 --bounda\r | |
234 --boundar\r | |
235 --boundary\r | |
236 Content-Type: text/plain\r | |
237 \r | |
238 -\r | |
239 --\r | |
240 --b\r | |
241 --bo\r | |
242 --bou\r | |
243 --boun\r | |
244 --bound\r\r | |
245 --bounda\r\r\r | |
246 --boundar\r\r\r\r | |
247 --boundary\r | |
248 Content-Type: text/plain\r | |
249 \r | |
250 --boundar\r | |
251 --bounda\r | |
252 --bound\r | |
253 --boun\r | |
254 --bou\r | |
255 --bo\r | |
256 --b\r\r\r\r | |
257 --\r\r\r | |
258 -\r\r | |
259 --boundary--\r\n"""; | |
260 headers = <String, String>{"content-type": "text/plain"}; | |
261 body1 = """ | |
262 -\r | |
263 --\r | |
264 --b\r | |
265 --bo\r | |
266 --bou\r | |
267 --boun\r | |
268 --bound\r\r | |
269 --bounda\r\r\r | |
270 --boundar\r\r\r"""; | |
271 body2 = """ | |
272 --boundar\r | |
273 --bounda\r | |
274 --bound\r | |
275 --boun\r | |
276 --bou\r | |
277 --bo\r | |
278 --b\r\r\r\r | |
279 --\r\r\r | |
280 -\r"""; | |
281 testParse(message, "boundary", [headers, headers], [body1, body2]); | |
282 | |
283 // Without initial CRLF. | |
284 message = """ | |
285 --xxx\r | |
286 \r | |
287 \r | |
288 Body 1\r | |
289 --xxx\r | |
290 \r | |
291 \r | |
292 Body2\r | |
293 --xxx--\r\n"""; | |
294 testParse(message, "xxx", null, ["\r\nBody 1", "\r\nBody2"]); | |
295 } | |
296 | |
297 void testParseInvalid() { | |
298 String message; | |
299 | |
300 // Missing end boundary. | |
301 message = """ | |
302 \r | |
303 --xxx\r | |
304 \r | |
305 \r | |
306 Body 1\r | |
307 --xxx\r | |
308 \r | |
309 \r | |
310 Body2\r | |
311 --xxx\r\n"""; | |
312 testParse(message, "xxx", null, [null, null], true); | |
313 } | |
314 | |
315 void main() { | |
316 testParseValid(); | |
317 testParseInvalid(); | |
318 } | |
OLD | NEW |