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

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: Code review changes 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 /// if (response.statusCode == 200) print("Uploaded!");
33 /// });
34 class MultipartRequest extends BaseRequest {
35 /// The total length of the multipart boundaries used when building the
36 /// request body. According to http://tools.ietf.org/html/rfc1341.html, this
37 /// can't be longer than 70.
38 static final int _BOUNDARY_LENGTH = 70;
39
40 static final Random _random = new Random();
41
42 /// The total length of the request body, in bytes. This is calculated from
43 /// [fields] and [files] and cannot be set manually.
44 int get contentLength {
45 var length = 0;
46
47 fields.forEach((name, value) {
48 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
49 _headerForField(name, value).length +
50 encodeUtf8(value).length + "\r\n".length;
51 });
52
53 for (var file in files) {
54 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
55 _headerForFile(file).length +
56 file.length + "\r\n".length;
57 }
58
59 return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
60 }
61
62 set contentLength(int value) {
63 throw new UnsupportedError("Cannot set the contentLength property of "
64 "multipart requests.");
65 }
66
67 /// The form fields to send for this request.
68 final Map<String, String> fields;
69
70 /// The files to upload for this request.
71 final List<MultipartFile> files;
72
73 /// Creates a new [MultipartRequest].
74 MultipartRequest(String method, Uri url)
75 : super(method, url),
76 fields = <String>{},
77 files = <MultipartFile>[];
78
79 /// Freezes all mutable fields and returns an [InputStream] that will emit the
80 /// request body.
81 InputStream finalize() {
82 // TODO(nweiz): freeze fields and files
83 var boundary = _boundaryString(_BOUNDARY_LENGTH);
84 headers['content-type'] = 'multipart/form-data, boundary="$boundary"';
85 headers['content-transfer-encoding'] = 'binary';
86 super.finalize();
87
88 var stream = new ListInputStream();
89
90 void writeAscii(String string) {
91 assert(isPlainAscii(string));
92 stream.write(string.charCodes);
93 }
94
95 void writeUtf8(String string) => stream.write(encodeUtf8(string));
96 void writeLine() => stream.write([13, 10]); // \r\n
97
98 fields.forEach((name, value) {
99 writeAscii('--$boundary\r\n');
100 writeAscii(_headerForField(name, value));
101 writeUtf8(value);
102 writeLine();
103 });
104
105 forEachFuture(files, (file) {
106 writeAscii('--$boundary\r\n');
107 writeAscii(_headerForFile(file));
108 return writeInputToInput(file.finalize(), stream)
109 .transform((_) => writeLine());
110 }).then((_) {
111 // TODO(nweiz): pass any errors propagated through this future on to
112 // the stream. See issue 3657.
113 writeAscii('--$boundary--\r\n');
114 stream.markEndOfStream();
115 });
116
117 return stream;
118 }
119
120 /// All character codes that are valid in multipart boundaries. From
121 /// http://tools.ietf.org/html/rfc2046#section-5.1.1.
122 static final List<int> _BOUNDARY_CHARACTERS = const <int>[
123 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54,
124 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
125 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103,
126 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
127 119, 120, 121, 122
128 ];
129
130 /// Returns the header string for a field. The return value is guaranteed to
131 /// contain only ASCII characters.
132 String _headerForField(String name, String value) {
133 // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
134 // field names and file names, but in practice user agents seem to just
135 // URL-encode them so we do the same.
136 var header = 'content-disposition: form-data; name="${encodeUri(name)}"';
137 if (!isPlainAscii(value)) {
138 header = '$header\r\ncontent-type: text/plain; charset=UTF-8';
139 }
140 return '$header\r\n\r\n';
141 }
142
143 /// Returns the header string for a file. The return value is guaranteed to
144 /// contain only ASCII characters.
145 String _headerForFile(MultipartFile file) {
146 var header = 'content-type: ${file.contentType}\r\n'
147 'content-disposition: form-data; name="${encodeUri(file.field)}"';
148
149 if (file.filename != null) {
150 header = '$header; filename="${encodeUri(file.filename)}"';
151 }
152 return '$header\r\n\r\n';
153 }
154
155 /// Returns a randomly-generated multipart boundary string of the given
156 /// [length].
157 String _boundaryString(int length) {
158 var prefix = "dart-http-boundary-";
159 var list = new List<int>(length - prefix.length);
160 for (var i = 0; i < list.length; i++) {
161 list[i] = _BOUNDARY_CHARACTERS[
162 _random.nextInt(_BOUNDARY_CHARACTERS.length)];
163 }
164 return "$prefix${new String.fromCharCodes(list)}";
165 }
166 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698