OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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 | 4 |
5 import "package:http_server/http_server.dart"; | 5 import "package:http_server/http_server.dart"; |
6 import "package:mime/mime.dart"; | 6 import "package:mime/mime.dart"; |
7 import "package:unittest/unittest.dart"; | 7 import "package:unittest/unittest.dart"; |
8 import 'dart:async'; | 8 import 'dart:async'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 import 'dart:convert'; | 10 import 'dart:convert'; |
11 | 11 |
| 12 // Representation of a form field from a multipart/form-data form POST body. |
12 class FormField { | 13 class FormField { |
| 14 // Name of the form field specified in Content-Disposition. |
13 final String name; | 15 final String name; |
| 16 // Value of the form field. This is either a String or a List<int> depending |
| 17 // on the Content-Type. |
14 final value; | 18 final value; |
| 19 // Content-Type of the form field. |
15 final String contentType; | 20 final String contentType; |
| 21 // Filename if specified in Content-Disposition. |
16 final String filename; | 22 final String filename; |
17 | 23 |
18 FormField(String this.name, | 24 FormField(String this.name, |
19 this.value, | 25 this.value, |
20 {String this.contentType, | 26 {String this.contentType, |
21 String this.filename}); | 27 String this.filename}); |
22 | 28 |
23 bool operator==(other) { | 29 bool operator==(other) { |
24 if (value.length != other.value.length) return false; | 30 if (value.length != other.value.length) return false; |
25 for (int i = 0; i < value.length; i++) { | 31 for (int i = 0; i < value.length; i++) { |
26 if (value[i] != other.value[i]) { | 32 if (value[i] != other.value[i]) { |
27 return false; | 33 return false; |
28 } | 34 } |
29 } | 35 } |
30 return name == other.name && | 36 return name == other.name && |
31 contentType == other.contentType && | 37 contentType == other.contentType && |
32 filename == other.filename; | 38 filename == other.filename; |
33 } | 39 } |
34 | 40 |
| 41 int get hashCode => name.hashCode; |
| 42 |
35 String toString() { | 43 String toString() { |
36 return "FormField('$name', '$value', '$contentType', '$filename')"; | 44 return "FormField('$name', '$value', '$contentType', '$filename')"; |
37 } | 45 } |
38 } | 46 } |
39 | 47 |
40 void postDataTest(List<int> message, | 48 void postDataTest(List<int> message, |
41 String contentType, | 49 String contentType, |
42 String boundary, | 50 String boundary, |
43 List<FormField> expectedFields) { | 51 List<FormField> expectedFields, |
| 52 {defaultEncoding: LATIN1}) { |
44 HttpServer.bind("127.0.0.1", 0).then((server) { | 53 HttpServer.bind("127.0.0.1", 0).then((server) { |
45 server.listen((request) { | 54 server.listen((request) { |
46 String boundary = request.headers.contentType.parameters['boundary']; | 55 String boundary = request.headers.contentType.parameters['boundary']; |
47 request | 56 request |
48 .transform(new MimeMultipartTransformer(boundary)) | 57 .transform(new MimeMultipartTransformer(boundary)) |
49 .map((part) => HttpMultipartFormData.parse( | 58 .map((part) => HttpMultipartFormData.parse( |
50 part, defaultEncoding: LATIN1)) | 59 part, defaultEncoding: defaultEncoding)) |
51 .map((multipart) { | 60 .map((multipart) { |
52 var future; | 61 var future; |
53 if (multipart.isText) { | 62 if (multipart.isText) { |
54 future = multipart | 63 future = multipart.join(); |
55 .fold(new StringBuffer(), (b, s) => b..write(s)) | |
56 .then((b) => b.toString()); | |
57 } else { | 64 } else { |
58 future = multipart | 65 future = multipart.fold([], (b, s) => b..addAll(s)); |
59 .fold([], (b, s) => b..addAll(s)); | |
60 } | 66 } |
61 return future | 67 return future |
62 .then((data) { | 68 .then((data) { |
63 String contentType; | 69 String contentType; |
64 if (multipart.contentType != null) { | 70 if (multipart.contentType != null) { |
65 contentType = multipart.contentType.mimeType; | 71 contentType = multipart.contentType.mimeType; |
66 } | 72 } |
67 return new FormField( | 73 return new FormField( |
68 multipart.contentDisposition.parameters['name'], | 74 multipart.contentDisposition.parameters['name'], |
69 data, | 75 data, |
70 contentType: contentType, | 76 contentType: contentType, |
71 filename: | 77 filename: |
72 multipart.contentDisposition.parameters['filename']); | 78 multipart.contentDisposition.parameters['filename']); |
73 }); | 79 }); |
74 }) | 80 }) |
75 .fold([], (l, f) => l..add(f)) | 81 .toList() |
76 .then(Future.wait) | 82 .then(Future.wait) |
77 .then((fields) { | 83 .then((fields) { |
78 expect(fields, equals(expectedFields)); | 84 expect(fields, equals(expectedFields)); |
79 request.response.close().then((_) => server.close()); | 85 request.response.close().then((_) => server.close()); |
80 }); | 86 }); |
81 }); | 87 }); |
82 var client = new HttpClient(); | 88 var client = new HttpClient(); |
83 client.post('127.0.0.1', server.port, '/') | 89 client.post('127.0.0.1', server.port, '/') |
84 .then((request) { | 90 .then((request) { |
85 request.headers.set('content-type', | 91 request.headers.set('content-type', |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
179 | 185 |
180 postDataTest(message, | 186 postDataTest(message, |
181 'multipart/form-data', | 187 'multipart/form-data', |
182 '----WebKitFormBoundaryQSql8kDALM7tAkC1', | 188 '----WebKitFormBoundaryQSql8kDALM7tAkC1', |
183 [new FormField('submit-name', 'Test'), | 189 [new FormField('submit-name', 'Test'), |
184 new FormField('files', | 190 new FormField('files', |
185 data, | 191 data, |
186 contentType: 'application/octet-stream', | 192 contentType: 'application/octet-stream', |
187 filename: 'VERSION')]); | 193 filename: 'VERSION')]); |
188 | 194 |
| 195 // In Chrome, Safari and Firefox HTML entity encoding might be used for |
| 196 // values in form fields. The HTML entity encoding for ひらがな is |
| 197 // ひらがな |
189 message = [ | 198 message = [ |
190 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, | 199 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, |
191 111, 117, 110, 100, 97, 114, 121, 118, 65, 86, 122, 117, 103, 75, 77, 116, | 200 111, 117, 110, 100, 97, 114, 121, 118, 65, 86, 122, 117, 103, 75, 77, 116, |
192 90, 98, 121, 87, 111, 66, 71, 13, 10, 67, 111, 110, 116, 101, 110, 116, | 201 90, 98, 121, 87, 111, 66, 71, 13, 10, 67, 111, 110, 116, 101, 110, 116, |
193 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, | 202 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, |
194 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, | 203 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, |
195 110, 97, 109, 101, 34, 13, 10, 13, 10, 38, 35, 49, 50, 52, 48, 50, 59, 38, | 204 110, 97, 109, 101, 34, 13, 10, 13, 10, 38, 35, 49, 50, 52, 48, 50, 59, 38, |
196 35, 49, 50, 52, 50, 53, 59, 38, 35, 49, 50, 51, 54, 52, 59, 38, 35, 49, | 205 35, 49, 50, 52, 50, 53, 59, 38, 35, 49, 50, 51, 54, 52, 59, 38, 35, 49, |
197 50, 51, 57, 52, 59, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, | 206 50, 51, 57, 52, 59, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, |
198 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 118, 65, 86, | 207 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 118, 65, 86, |
199 122, 117, 103, 75, 77, 116, 90, 98, 121, 87, 111, 66, 71, 45, 45, 13, 10]; | 208 122, 117, 103, 75, 77, 116, 90, 98, 121, 87, 111, 66, 71, 45, 45, 13, 10]; |
200 | 209 |
201 postDataTest(message, | 210 postDataTest(message, |
202 'multipart/form-data', | 211 'multipart/form-data', |
203 '----WebKitFormBoundaryvAVzugKMtZbyWoBG', | 212 '----WebKitFormBoundaryvAVzugKMtZbyWoBG', |
204 [new FormField('name', 'ひらがな')]); | 213 [new FormField('name', 'ひらがな')], |
| 214 defaultEncoding: UTF8); |
| 215 |
| 216 // The UTF-8 encoding of ひらがな is |
| 217 // [227, 129, 178, 227, 130, 137, 227, 129, 140, 227, 129, 170]. |
| 218 message = [ |
| 219 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, |
| 220 111, 117, 110, 100, 97, 114, 121, 71, 88, 116, 66, 114, 99, 106, 120, 104, |
| 221 101, 75, 101, 78, 54, 105, 48, 13, 10, 67, 111, 110, 116, 101, 110, 116, |
| 222 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, |
| 223 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, |
| 224 116, 101, 115, 116, 34, 13, 10, 13, 10, 227, 129, 178, 227, 130, 137, 227, |
| 225 129, 140, 227, 129, 170, 13, 10, 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, |
| 226 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, 100, 97, 114, 121, 71, 88, |
| 227 116, 66, 114, 99, 106, 120, 104, 101, 75, 101, 78, 54, 105, 48, 45, 45, |
| 228 13, 10]; |
| 229 |
| 230 postDataTest(message, |
| 231 'multipart/form-data', |
| 232 '----WebKitFormBoundaryGXtBrcjxheKeN6i0', |
| 233 [new FormField('test', 'ひらがな')], |
| 234 defaultEncoding: UTF8); |
205 | 235 |
206 message = [ | 236 message = [ |
207 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, | 237 45, 45, 45, 45, 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, |
208 111, 117, 110, 100, 97, 114, 121, 102, 101, 48, 69, 122, 86, 49, 97, 78, | 238 111, 117, 110, 100, 97, 114, 121, 102, 101, 48, 69, 122, 86, 49, 97, 78, |
209 121, 115, 68, 49, 98, 80, 104, 13, 10, 67, 111, 110, 116, 101, 110, 116, | 239 121, 115, 68, 49, 98, 80, 104, 13, 10, 67, 111, 110, 116, 101, 110, 116, |
210 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, | 240 45, 68, 105, 115, 112, 111, 115, 105, 116, 105, 111, 110, 58, 32, 102, |
211 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, | 241 111, 114, 109, 45, 100, 97, 116, 97, 59, 32, 110, 97, 109, 101, 61, 34, |
212 110, 97, 109, 101, 34, 13, 10, 13, 10, 248, 118, 13, 10, 45, 45, 45, 45, | 242 110, 97, 109, 101, 34, 13, 10, 13, 10, 248, 118, 13, 10, 45, 45, 45, 45, |
213 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, | 243 45, 45, 87, 101, 98, 75, 105, 116, 70, 111, 114, 109, 66, 111, 117, 110, |
214 100, 97, 114, 121, 102, 101, 48, 69, 122, 86, 49, 97, 78, 121, 115, 68, | 244 100, 97, 114, 121, 102, 101, 48, 69, 122, 86, 49, 97, 78, 121, 115, 68, |
215 49, 98, 80, 104, 45, 45, 13, 10]; | 245 49, 98, 80, 104, 45, 45, 13, 10]; |
216 | 246 |
217 postDataTest(message, | 247 postDataTest(message, |
218 'multipart/form-data', | 248 'multipart/form-data', |
219 '----WebKitFormBoundaryfe0EzV1aNysD1bPh', | 249 '----WebKitFormBoundaryfe0EzV1aNysD1bPh', |
220 [new FormField('name', 'øv')]); | 250 [new FormField('name', 'øv')]); |
221 } | 251 } |
222 | 252 |
223 | 253 |
224 void main() { | 254 void main() { |
225 testEmptyPostData(); | 255 testEmptyPostData(); |
226 testPostData(); | 256 testPostData(); |
227 } | 257 } |
OLD | NEW |