Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |