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

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

Issue 810223002: Remove the http package from the repo. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years 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) 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 library multipart_request;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:math';
10
11 import 'base_request.dart';
12 import 'byte_stream.dart';
13 import 'multipart_file.dart';
14 import 'utils.dart';
15
16 final _newlineRegExp = new RegExp(r"\r\n|\r|\n");
17
18 /// A `multipart/form-data` request. Such a request has both string [fields],
19 /// which function as normal form fields, and (potentially streamed) binary
20 /// [files].
21 ///
22 /// This request automatically sets the Content-Type header to
23 /// `multipart/form-data` and the Content-Transfer-Encoding header to `binary`.
24 /// These values will override any values set by the user.
25 ///
26 /// var uri = Uri.parse("http://pub.dartlang.org/packages/create");
27 /// var request = new http.MultipartRequest("POST", url);
28 /// request.fields['user'] = 'nweiz@google.com';
29 /// request.files.add(new http.MultipartFile.fromFile(
30 /// 'package',
31 /// new File('build/package.tar.gz'),
32 /// contentType: new MediaType('application', 'x-tar'));
33 /// request.send().then((response) {
34 /// if (response.statusCode == 200) print("Uploaded!");
35 /// });
36 class MultipartRequest extends BaseRequest {
37 /// The total length of the multipart boundaries used when building the
38 /// request body. According to http://tools.ietf.org/html/rfc1341.html, this
39 /// can't be longer than 70.
40 static const int _BOUNDARY_LENGTH = 70;
41
42 static final Random _random = new Random();
43
44 /// The form fields to send for this request.
45 final Map<String, String> fields;
46
47 /// The private version of [files].
48 final List<MultipartFile> _files;
49
50 /// Creates a new [MultipartRequest].
51 MultipartRequest(String method, Uri url)
52 : super(method, url),
53 fields = {},
54 _files = <MultipartFile>[];
55
56 /// The list of files to upload for this request.
57 List<MultipartFile> get files => _files;
58
59 /// The total length of the request body, in bytes. This is calculated from
60 /// [fields] and [files] and cannot be set manually.
61 int get contentLength {
62 var length = 0;
63
64 fields.forEach((name, value) {
65 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
66 UTF8.encode(_headerForField(name, value)).length +
67 UTF8.encode(value).length + "\r\n".length;
68 });
69
70 for (var file in _files) {
71 length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
72 UTF8.encode(_headerForFile(file)).length +
73 file.length + "\r\n".length;
74 }
75
76 return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
77 }
78
79 void set contentLength(int value) {
80 throw new UnsupportedError("Cannot set the contentLength property of "
81 "multipart requests.");
82 }
83
84 /// Freezes all mutable fields and returns a single-subscription [ByteStream]
85 /// that will emit the request body.
86 ByteStream finalize() {
87 // TODO(nweiz): freeze fields and files
88 var boundary = _boundaryString();
89 headers['content-type'] = 'multipart/form-data; boundary="$boundary"';
90 headers['content-transfer-encoding'] = 'binary';
91 super.finalize();
92
93 var controller = new StreamController<List<int>>(sync: true);
94
95 void writeAscii(String string) {
96 controller.add(UTF8.encode(string));
97 }
98
99 writeUtf8(String string) => controller.add(UTF8.encode(string));
100 writeLine() => controller.add([13, 10]); // \r\n
101
102 fields.forEach((name, value) {
103 writeAscii('--$boundary\r\n');
104 writeAscii(_headerForField(name, value));
105 writeUtf8(value);
106 writeLine();
107 });
108
109 Future.forEach(_files, (file) {
110 writeAscii('--$boundary\r\n');
111 writeAscii(_headerForFile(file));
112 return writeStreamToSink(file.finalize(), controller)
113 .then((_) => writeLine());
114 }).then((_) {
115 // TODO(nweiz): pass any errors propagated through this future on to
116 // the stream. See issue 3657.
117 writeAscii('--$boundary--\r\n');
118 controller.close();
119 });
120
121 return new ByteStream(controller.stream);
122 }
123
124 /// All character codes that are valid in multipart boundaries. From
125 /// http://tools.ietf.org/html/rfc2046#section-5.1.1.
126 static const List<int> _BOUNDARY_CHARACTERS = const <int>[
127 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54,
128 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
129 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103,
130 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
131 119, 120, 121, 122
132 ];
133
134 /// Returns the header string for a field. The return value is guaranteed to
135 /// contain only ASCII characters.
136 String _headerForField(String name, String value) {
137 var header =
138 'content-disposition: form-data; name="${_browserEncode(name)}"';
139 if (!isPlainAscii(value)) {
140 header = '$header\r\ncontent-type: text/plain; charset=utf-8';
141 }
142 return '$header\r\n\r\n';
143 }
144
145 /// Returns the header string for a file. The return value is guaranteed to
146 /// contain only ASCII characters.
147 String _headerForFile(MultipartFile file) {
148 var header = 'content-type: ${file.contentType}\r\n'
149 'content-disposition: form-data; name="${_browserEncode(file.field)}"';
150
151 if (file.filename != null) {
152 header = '$header; filename="${_browserEncode(file.filename)}"';
153 }
154 return '$header\r\n\r\n';
155 }
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 nothing else (even for `%` or non-ASCII
163 // characters). We follow their behavior.
164 return value.replaceAll(_newlineRegExp, "%0D%0A").replaceAll('"', "%22");
165 }
166
167 /// Returns a randomly-generated multipart boundary string
168 String _boundaryString() {
169 var prefix = "dart-http-boundary-";
170 var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length,
171 (index) =>
172 _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)],
173 growable: false);
174 return "$prefix${new String.fromCharCodes(list)}";
175 }
176 }
OLDNEW
« no previous file with comments | « pkg/http/lib/src/multipart_file.dart ('k') | pkg/http/lib/src/request.dart » ('j') | pkg/pkg.gyp » ('J')

Powered by Google App Engine
This is Rietveld 408576698