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

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

Issue 11363094: Add a multipart HTTP request class. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Add test file Created 8 years, 1 month 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
(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 library multipart_request;
6
7 import 'dart:io';
8 import 'dart:math';
9 import 'dart:uri';
10 import 'dart:utf';
11
12 import 'base_request.dart';
13 import 'multipart_file.dart';
14 import 'utils.dart';
15
16 /// A `multipart/form-data` request. Such a request has both string [fields],
17 /// which function as normal form fields, and (potentially streamed) binary
18 /// [files].
19 ///
20 /// This request automatically sets the Content-Type header to
21 /// `multipart/form-data` and the Content-Transfer-Encoding header to `binary`.
22 /// These values will override any values set by the user.
23 ///
24 /// var uri = new Uri.fromString("http://pub.dartlang.org/packages/create");
25 /// var request = new http.MultipartRequest("POST", url);
26 /// request.fields['user'] = 'nweiz@google.com';
27 /// request.files.add(new http.MultipartFile.fromFile(
28 /// 'package',
29 /// new File('build/package.tar.gz'),
30 /// contentType: new ContentType('application', 'x-tar'));
31 /// request.send().then((response) {
32 /// // ...
Bob Nystrom 2012/11/06 22:00:08 Maybe "print('Uploaded!')" or something a little m
nweiz 2012/11/06 23:15:56 Done.
33 /// });
34 class MultipartRequest extends BaseRequest {
35 /// The total length of the multipart boundaries used when building the
36 /// request body. According to RFC 1341, this can't be longer than 70.
Bob Nystrom 2012/11/06 22:00:08 Link to RFC 1341?
nweiz 2012/11/06 23:15:56 Done.
37 static final int _boundaryLength = 70;
Bob Nystrom 2012/11/06 22:00:08 static const _BOUNDARY_LENGTH = 70;
nweiz 2012/11/06 23:15:56 Done. I couldn't remember what we'd settled on for
nweiz 2012/11/06 23:15:56 Done.
38
39 /// The total length of the request body, in bytes. This is calculated from
40 /// [fields] and [files] and cannot be set manually.
41 int get contentLength {
42 var length = 0;
43
44 fields.forEach((name, value) {
45 length += 2 + _boundaryLength + 2 +
46 _headerForField(name, value).length +
47 encodeUtf8(value).length + 2;
48 });
49
50 for (var file in files) {
51 length += 2 + _boundaryLength + 2 +
52 _headerForFile(file).length +
53 file.length + 2;
54 }
55
56 return length + 2 + _boundaryLength + 4;
57 }
Bob Nystrom 2012/11/06 22:00:08 Please explain what the 2 and 4 magic numbers are
nweiz 2012/11/06 23:15:56 I've changed them to .length expressions on the ac
58
59 set contentLength(int value) {
60 throw new UnsupportedError("Cannot set the contentLength property of "
61 "multipart requests.");
62 }
63
64 /// The form fields to send for this request.
65 final Map<String, String> fields;
66
67 /// The files to upload for this request.
68 final List<MultipartFile> files;
69
70 /// Creates a new [MultipartRequest].
71 MultipartRequest(String method, Uri url)
72 : super(method, url),
73 fields = <String>{},
74 files = <MultipartFile>[];
75
76 /// Freezes all mutable fields and returns an [InputStream] that will emit the
77 /// request body.
78 InputStream finalize() {
79 // TODO(nweiz): freeze fields and files
80 var boundary = _boundaryString(_boundaryLength);
81 headers['content-type'] = 'multipart/form-data, boundary="$boundary"';
82 headers['content-transfer-encoding'] = 'binary';
83 super.finalize();
84
85 var stream = new ListInputStream();
86
87 void writeAscii(String string) {
88 assert(isPlainAscii(string));
89 stream.write(string.charCodes);
90 }
91
92 void writeUtf8(String string) => stream.write(encodeUtf8(string));
93 void writeLine() => stream.write([13, 10]); // \r\n
94
95 fields.forEach((name, value) {
96 writeAscii('--$boundary\r\n');
97 writeAscii(_headerForField(name, value));
98 writeUtf8(value);
99 writeLine();
100 });
101
102 forEachFuture(files, (file) {
103 writeAscii('--$boundary\r\n');
104 writeAscii(_headerForFile(file));
105 return writeInputToInput(file.finalize(), stream)
106 .transform((_) => writeLine());
107 }).then((_) {
108 // TODO(nweiz): pass any errors propagated through this future on to
109 // the stream. See issue 3657.
110 writeAscii('--$boundary--\r\n');
111 stream.markEndOfStream();
112 });
113
114 return stream;
115 }
116
117 /// All character codes that are valid in multipart boundaries. From
118 /// http://tools.ietf.org/html/rfc2046#section-5.1.1.
119 static final List<int> _boundaryCharacters = const <int>[
Bob Nystrom 2012/11/06 22:00:08 static const _BOUNDARY_CHARS = const <int> [ ...
nweiz 2012/11/06 23:15:56 Done.
120 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54,
121 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
122 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103,
123 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
124 119, 120, 121, 122
125 ];
126
127 /// Returns the header string for a field. The return value is guaranteed to
128 /// contain only ASCII characters.
129 String _headerForField(String name, String value) {
130 // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
131 // field names and file names, but in practice user agents seem to just
132 // URL-encode them so we do the same.
133 var header = 'content-disposition: form-data; name="${encodeUri(name)}"';
134 if (!isPlainAscii(value)) {
135 header = '$header\r\ncontent-type: text/plain; charset=UTF-8';
136 }
137 return '$header\r\n\r\n';
138 }
139
140 /// Returns the header string for a file. The return value is guaranteed to
141 /// contain only ASCII characters.
142 String _headerForFile(MultipartFile file) {
143 var header = 'content-type: ${file.contentType}\r\n'
144 'content-disposition: form-data; name="${encodeUri(file.field)}"';
145
146 if (file.filename != null) {
147 header = '$header; filename="${encodeUri(file.filename)}"';
148 }
149 return '$header\r\n\r\n';
150 }
151
152 /// Returns a randomly-generated multipart boundary string of the given
153 /// [length].
154 String _boundaryString(int length) {
155 var prefix = "dart-http-boundary-";
156 var random = new Random();
Bob Nystrom 2012/11/06 22:00:08 Why use a random string here? Creating a new RNG
nweiz 2012/11/06 23:15:56 The boundary needs to be a string that will never
157 var list = new List<int>(length - prefix.length);
158 for (var i = 0; i < list.length; i++) {
159 list[i] = _boundaryCharacters[random.nextInt(_boundaryCharacters.length)];
160 }
161 return "$prefix${new String.fromCharCodes(list)}";
162 }
163 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698