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

Side by Side Diff: pkg/http/lib/src/multipart_request.dart

Issue 218993016: Make MultipartRequest more closely adhere to browsers' behavior. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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
OLDNEW
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 library multipart_request; 5 library multipart_request;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:convert'; 8 import 'dart:convert';
9 import 'dart:math'; 9 import 'dart:math';
10 10
11 import 'base_request.dart'; 11 import 'base_request.dart';
12 import 'byte_stream.dart'; 12 import 'byte_stream.dart';
13 import 'multipart_file.dart'; 13 import 'multipart_file.dart';
14 import 'utils.dart'; 14 import 'utils.dart';
15 15
16 final _newlineRegExp = new RegExp(r"\r\n|\r|\n");
17
16 /// A `multipart/form-data` request. Such a request has both string [fields], 18 /// A `multipart/form-data` request. Such a request has both string [fields],
17 /// which function as normal form fields, and (potentially streamed) binary 19 /// which function as normal form fields, and (potentially streamed) binary
18 /// [files]. 20 /// [files].
19 /// 21 ///
20 /// This request automatically sets the Content-Type header to 22 /// This request automatically sets the Content-Type header to
21 /// `multipart/form-data` and the Content-Transfer-Encoding header to `binary`. 23 /// `multipart/form-data` and the Content-Transfer-Encoding header to `binary`.
22 /// These values will override any values set by the user. 24 /// These values will override any values set by the user.
23 /// 25 ///
24 /// var uri = Uri.parse("http://pub.dartlang.org/packages/create"); 26 /// var uri = Uri.parse("http://pub.dartlang.org/packages/create");
25 /// var request = new http.MultipartRequest("POST", url); 27 /// var request = new http.MultipartRequest("POST", url);
(...skipping 28 matching lines...) Expand all
54 /// The list of files to upload for this request. 56 /// The list of files to upload for this request.
55 List<MultipartFile> get files => _files; 57 List<MultipartFile> get files => _files;
56 58
57 /// The total length of the request body, in bytes. This is calculated from 59 /// The total length of the request body, in bytes. This is calculated from
58 /// [fields] and [files] and cannot be set manually. 60 /// [fields] and [files] and cannot be set manually.
59 int get contentLength { 61 int get contentLength {
60 var length = 0; 62 var length = 0;
61 63
62 fields.forEach((name, value) { 64 fields.forEach((name, value) {
63 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length + 65 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
64 _headerForField(name, value).length + 66 UTF8.encode(_headerForField(name, value)).length +
65 UTF8.encode(value).length + "\r\n".length; 67 UTF8.encode(value).length + "\r\n".length;
66 }); 68 });
67 69
68 for (var file in _files) { 70 for (var file in _files) {
69 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length + 71 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
70 _headerForFile(file).length + 72 UTF8.encode(_headerForFile(file)).length +
71 file.length + "\r\n".length; 73 file.length + "\r\n".length;
72 } 74 }
73 75
74 return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length; 76 return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
75 } 77 }
76 78
77 void set contentLength(int value) { 79 void set contentLength(int value) {
78 throw new UnsupportedError("Cannot set the contentLength property of " 80 throw new UnsupportedError("Cannot set the contentLength property of "
79 "multipart requests."); 81 "multipart requests.");
80 } 82 }
81 83
82 /// Freezes all mutable fields and returns a single-subscription [ByteStream] 84 /// Freezes all mutable fields and returns a single-subscription [ByteStream]
83 /// that will emit the request body. 85 /// that will emit the request body.
84 ByteStream finalize() { 86 ByteStream finalize() {
85 // TODO(nweiz): freeze fields and files 87 // TODO(nweiz): freeze fields and files
86 var boundary = _boundaryString(); 88 var boundary = _boundaryString();
87 headers['content-type'] = 'multipart/form-data; boundary="$boundary"'; 89 headers['content-type'] = 'multipart/form-data; boundary="$boundary"';
88 headers['content-transfer-encoding'] = 'binary'; 90 headers['content-transfer-encoding'] = 'binary';
89 super.finalize(); 91 super.finalize();
90 92
91 var controller = new StreamController<List<int>>(sync: true); 93 var controller = new StreamController<List<int>>(sync: true);
92 94
93 void writeAscii(String string) { 95 void writeAscii(String string) {
94 assert(isPlainAscii(string)); 96 controller.add(UTF8.encode(string));
95 controller.add(string.codeUnits);
96 } 97 }
97 98
98 writeUtf8(String string) => controller.add(UTF8.encode(string)); 99 writeUtf8(String string) => controller.add(UTF8.encode(string));
99 writeLine() => controller.add([13, 10]); // \r\n 100 writeLine() => controller.add([13, 10]); // \r\n
100 101
101 fields.forEach((name, value) { 102 fields.forEach((name, value) {
102 writeAscii('--$boundary\r\n'); 103 writeAscii('--$boundary\r\n');
103 writeAscii(_headerForField(name, value)); 104 writeAscii(_headerForField(name, value));
104 writeUtf8(value); 105 writeUtf8(value);
105 writeLine(); 106 writeLine();
(...skipping 20 matching lines...) Expand all
126 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54, 127 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54,
127 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 128 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
128 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 129 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103,
129 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 130 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
130 119, 120, 121, 122 131 119, 120, 121, 122
131 ]; 132 ];
132 133
133 /// Returns the header string for a field. The return value is guaranteed to 134 /// Returns the header string for a field. The return value is guaranteed to
134 /// contain only ASCII characters. 135 /// contain only ASCII characters.
135 String _headerForField(String name, String value) { 136 String _headerForField(String name, String value) {
136 // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
137 // field names and file names, but in practice user agents seem to just
138 // URL-encode them so we do the same.
139 var header = 137 var header =
140 'content-disposition: form-data; name="${Uri.encodeFull(name)}"'; 138 'content-disposition: form-data; name="${_browserEncode(name)}"';
141 if (!isPlainAscii(value)) { 139 if (!isPlainAscii(value)) {
142 header = '$header\r\ncontent-type: text/plain; charset=utf-8'; 140 header = '$header\r\ncontent-type: text/plain; charset=utf-8';
143 } 141 }
144 return '$header\r\n\r\n'; 142 return '$header\r\n\r\n';
145 } 143 }
146 144
147 /// Returns the header string for a file. The return value is guaranteed to 145 /// Returns the header string for a file. The return value is guaranteed to
148 /// contain only ASCII characters. 146 /// contain only ASCII characters.
149 String _headerForFile(MultipartFile file) { 147 String _headerForFile(MultipartFile file) {
150 var header = 'content-type: ${file.contentType}\r\n' 148 var header = 'content-type: ${file.contentType}\r\n'
151 'content-disposition: form-data; name="${Uri.encodeFull(file.field)}"'; 149 'content-disposition: form-data; name="${_browserEncode(file.field)}"';
152 150
153 if (file.filename != null) { 151 if (file.filename != null) {
154 header = '$header; filename="${Uri.encodeFull(file.filename)}"'; 152 header = '$header; filename="${_browserEncode(file.filename)}"';
155 } 153 }
156 return '$header\r\n\r\n'; 154 return '$header\r\n\r\n';
157 } 155 }
158 156
157 /// Encode [value] in the same way browsers do.
158 String _browserEncode(String value) {
159 // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
160 // field names and file names, but in practice user agents seem not to
161 // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
162 // `\r\n`; URL-encode `"`; and do thing else (even for `%` or non-ASCII
Bob Nystrom 2014/04/01 19:45:26 thing -> nothing.
nweiz 2014/04/02 01:09:44 Done.
163 // characters). We follow their behavior.
164 return value.replaceAll(_newlineRegExp, "%0D%0A").replaceAll('"', "%22");
165 }
166
159 /// Returns a randomly-generated multipart boundary string 167 /// Returns a randomly-generated multipart boundary string
160 String _boundaryString() { 168 String _boundaryString() {
161 var prefix = "dart-http-boundary-"; 169 var prefix = "dart-http-boundary-";
162 var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length, 170 var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length,
163 (index) => 171 (index) =>
164 _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)], 172 _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)],
165 growable: false); 173 growable: false);
166 return "$prefix${new String.fromCharCodes(list)}"; 174 return "$prefix${new String.fromCharCodes(list)}";
167 } 175 }
168 } 176 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698