From a502efd29b1eda1d47149a5e8e9c456f66c5a0c3 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Feb 2017 15:42:06 -0800 Subject: [PATCH 1/7] Stop using dart:mirrors. (#55) --- CHANGELOG.md | 4 +++ lib/browser_client.dart | 4 +-- lib/src/client.dart | 6 +--- lib/src/io.dart | 55 ------------------------------------- lib/src/io_client.dart | 24 ++++------------ lib/src/multipart_file.dart | 8 +++--- pubspec.yaml | 4 +-- 7 files changed, 17 insertions(+), 88 deletions(-) delete mode 100644 lib/src/io.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index bb02985362..3e4ebd8ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.3+10 + +* Stop using `dart:mirrors`. + ## 0.11.3+9 * Remove an extra newline in multipart chunks. diff --git a/lib/browser_client.dart b/lib/browser_client.dart index 883b2b184a..309b3ac7e1 100644 --- a/lib/browser_client.dart +++ b/lib/browser_client.dart @@ -15,9 +15,7 @@ import 'src/exception.dart'; import 'src/streamed_response.dart'; // TODO(nweiz): Move this under src/, re-export from lib/http.dart, and use this -// automatically from [new Client] once we can create an HttpRequest using -// mirrors on dart2js (issue 18541) and dart2js doesn't crash on pkg/collection -// (issue 18535). +// automatically from [new Client] once sdk#24581 is fixed. /// A `dart:html`-based HTTP client that runs in the browser and is backed by /// XMLHttpRequests. diff --git a/lib/src/client.dart b/lib/src/client.dart index 70d317e34d..cf1ff784a0 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -8,7 +8,6 @@ import 'dart:typed_data'; import 'base_client.dart'; import 'base_request.dart'; -import 'io.dart' as io; import 'io_client.dart'; import 'response.dart'; import 'streamed_response.dart'; @@ -28,10 +27,7 @@ abstract class Client { /// Currently this will create an [IOClient] if `dart:io` is available and /// throw an [UnsupportedError] otherwise. In the future, it will create a /// [BrowserClient] if `dart:html` is available. - factory Client() { - io.assertSupported("IOClient"); - return new IOClient(); - } + factory Client() => new IOClient(); /// Sends an HTTP HEAD request with the given headers to the given URL, which /// can be a [Uri] or a [String]. diff --git a/lib/src/io.dart b/lib/src/io.dart deleted file mode 100644 index 7c41f99290..0000000000 --- a/lib/src/io.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -@MirrorsUsed(targets: const ['dart.io.HttpClient', 'dart.io.HttpException', - 'dart.io.File']) -import 'dart:mirrors'; - -/// Whether `dart:io` is supported on this platform. -bool get supported => _library != null; - -/// The `dart:io` library mirror, or `null` if it couldn't be loaded. -final _library = _getLibrary(); - -/// The `dart:io` HttpClient class mirror. -final ClassMirror _httpClient = - _library.declarations[const Symbol('HttpClient')]; - -/// The `dart:io` HttpException class mirror. -final ClassMirror _httpException = - _library.declarations[const Symbol('HttpException')]; - -/// The `dart:io` File class mirror. -final ClassMirror _file = _library.declarations[const Symbol('File')]; - -/// Asserts that the [name]d `dart:io` feature is supported on this platform. -/// -/// If `dart:io` doesn't work on this platform, this throws an -/// [UnsupportedError]. -void assertSupported(String name) { - if (supported) return; - throw new UnsupportedError("$name isn't supported on this platform."); -} - -/// Creates a new `dart:io` HttpClient instance. -newHttpClient() => _httpClient.newInstance(const Symbol(''), []).reflectee; - -/// Creates a new `dart:io` File instance with the given [path]. -newFile(String path) => _file.newInstance(const Symbol(''), [path]).reflectee; - -/// Returns whether [error] is a `dart:io` HttpException. -bool isHttpException(error) => reflect(error).type.isSubtypeOf(_httpException); - -/// Returns whether [client] is a `dart:io` HttpClient. -bool isHttpClient(client) => reflect(client).type.isSubtypeOf(_httpClient); - -/// Tries to load `dart:io` and returns `null` if it fails. -LibraryMirror _getLibrary() { - try { - return currentMirrorSystem().findLibrary(const Symbol('dart.io')); - } catch (_) { - // TODO(nweiz): narrow the catch clause when issue 18532 is fixed. - return null; - } -} diff --git a/lib/src/io_client.dart b/lib/src/io_client.dart index 7387a30edb..03950ad6fa 100644 --- a/lib/src/io_client.dart +++ b/lib/src/io_client.dart @@ -3,13 +3,13 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:async/async.dart'; import 'base_client.dart'; import 'base_request.dart'; import 'exception.dart'; -import 'io.dart' as io; import 'streamed_response.dart'; /// A `dart:io`-based HTTP client. @@ -17,23 +17,10 @@ import 'streamed_response.dart'; /// This is the default client when running on the command line. class IOClient extends BaseClient { /// The underlying `dart:io` HTTP client. - var _inner; + HttpClient _inner; /// Creates a new HTTP client. - /// - /// [innerClient] must be a `dart:io` HTTP client. If it's not passed, a - /// default one will be instantiated. - IOClient([innerClient]) { - io.assertSupported("IOClient"); - if (innerClient != null) { - // TODO(nweiz): remove this assert when we can type [innerClient] - // properly. - assert(io.isHttpClient(innerClient)); - _inner = innerClient; - } else { - _inner = io.newHttpClient(); - } - } + IOClient([HttpClient inner]) : _inner = inner ?? new HttpClient(); /// Sends an HTTP request and asynchronously returns the response. Future send(BaseRequest request) async { @@ -63,7 +50,7 @@ class IOClient extends BaseClient { return new StreamedResponse( DelegatingStream.typed/*>*/(response).handleError((error) => throw new ClientException(error.message, error.uri), - test: (error) => io.isHttpException(error)), + test: (error) => error is HttpException), response.statusCode, contentLength: response.contentLength == -1 ? null @@ -73,8 +60,7 @@ class IOClient extends BaseClient { isRedirect: response.isRedirect, persistentConnection: response.persistentConnection, reasonPhrase: response.reasonPhrase); - } catch (error) { - if (!io.isHttpException(error)) rethrow; + } on HttpException catch (error) { throw new ClientException(error.message, error.uri); } } diff --git a/lib/src/multipart_file.dart b/lib/src/multipart_file.dart index 3597c6ef59..da4bface78 100644 --- a/lib/src/multipart_file.dart +++ b/lib/src/multipart_file.dart @@ -4,13 +4,13 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:async/async.dart'; import 'package:http_parser/http_parser.dart'; import 'package:path/path.dart' as path; import 'byte_stream.dart'; -import 'io.dart' as io; import 'utils.dart'; /// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to @@ -85,12 +85,12 @@ class MultipartFile { /// defaults to `application/octet-stream`, but in the future may be inferred /// from [filename]. /// - /// This can only be used in an environment that supports "dart:io". + /// Throws an [UnsupportedError] if `dart:io` isn't supported in this + /// environment. static Future fromPath(String field, String filePath, {String filename, MediaType contentType}) async { - io.assertSupported("MultipartFile.fromPath"); if (filename == null) filename = path.basename(filePath); - var file = io.newFile(filePath); + var file = new File(filePath); var length = await file.length(); var stream = new ByteStream(DelegatingStream.typed(file.openRead())); return new MultipartFile(field, stream, length, diff --git a/pubspec.yaml b/pubspec.yaml index 4aa69c1b06..4ae55d8068 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: http -version: 0.11.3+9 +version: 0.11.3+10 author: "Dart Team " homepage: https://github.com/dart-lang/http description: A composable, Future-based API for making HTTP requests. @@ -12,4 +12,4 @@ dependencies: dev_dependencies: unittest: ">=0.9.0 <0.12.0" environment: - sdk: ">=1.9.0 <2.0.0" + sdk: ">=1.22.0 <2.0.0" From 632deeba259b8a454526832e33763767ec9f3a5b Mon Sep 17 00:00:00 2001 From: Alexandre Ardhuin Date: Wed, 22 Feb 2017 21:57:26 +0100 Subject: [PATCH 2/7] Remove comment-based syntax for generic methods (#59) --- lib/http.dart | 2 +- lib/src/io_client.dart | 2 +- lib/src/utils.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/http.dart b/lib/http.dart index 9ccd77a6f8..86bcefbc4d 100644 --- a/lib/http.dart +++ b/lib/http.dart @@ -161,7 +161,7 @@ Future read(url, {Map headers}) => Future readBytes(url, {Map headers}) => _withClient((client) => client.readBytes(url, headers: headers)); -Future/**/ _withClient/**/(Future/**/ fn(Client client)) async { +Future _withClient(Future fn(Client client)) async { var client = new Client(); try { return await fn(client); diff --git a/lib/src/io_client.dart b/lib/src/io_client.dart index 03950ad6fa..8a7fe6da18 100644 --- a/lib/src/io_client.dart +++ b/lib/src/io_client.dart @@ -48,7 +48,7 @@ class IOClient extends BaseClient { }); return new StreamedResponse( - DelegatingStream.typed/*>*/(response).handleError((error) => + DelegatingStream.typed>(response).handleError((error) => throw new ClientException(error.message, error.uri), test: (error) => error is HttpException), response.statusCode, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 86a6690fb5..789c2d964f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -86,7 +86,7 @@ ByteStream toByteStream(Stream> stream) { /// Calls [onDone] once [stream] (a single-subscription [Stream]) is finished. /// The return value, also a single-subscription [Stream] should be used in /// place of [stream] after calling this method. -Stream/**/ onDone/**/(Stream/**/ stream, void onDone()) => +Stream onDone(Stream stream, void onDone()) => stream.transform(new StreamTransformer.fromHandlers(handleDone: (sink) { sink.close(); onDone(); From 6374d62f53767df9fcaade5f242e89670fab35e9 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 6 Mar 2017 12:36:13 -0800 Subject: [PATCH 3/7] Correct the pubspec.yaml, we require 1.23.0 (#58) Closes https://github.com/dart-lang/http/issues/57. --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4ae55d8068..8f419adbb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: http -version: 0.11.3+10 +version: 0.11.3+11 author: "Dart Team " homepage: https://github.com/dart-lang/http description: A composable, Future-based API for making HTTP requests. @@ -12,4 +12,4 @@ dependencies: dev_dependencies: unittest: ">=0.9.0 <0.12.0" environment: - sdk: ">=1.22.0 <2.0.0" + sdk: ">=1.23.0-dev.0.0 <2.0.0" From 9076857998d27fca722bc48f7bc9ad17789cf91f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 7 Mar 2017 12:56:58 -0800 Subject: [PATCH 4/7] Don't quote the multipart boundary header. (#63) This is be more broadly compatible than the quoted version, although the quoted version is what's described by the spec. curl and browsers send unquoted boundaries, so we can safely assume that any server will support them. Closes #61 --- CHANGELOG.md | 10 ++++++++++ lib/src/multipart_request.dart | 21 +++++++++++++-------- pubspec.yaml | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4ebd8ffe..4a5cb769c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.11.3+12 + +* Don't quote the boundary header for `MultipartRequest`. This is more + compatible with server quirks. + +## 0.11.3+11 + +* Fix the SDK constraint to only include SDK versions that support importing + `dart:io` everywhere. + ## 0.11.3+10 * Stop using `dart:mirrors`. diff --git a/lib/src/multipart_request.dart b/lib/src/multipart_request.dart index 2d3b318c9d..7250def98f 100644 --- a/lib/src/multipart_request.dart +++ b/lib/src/multipart_request.dart @@ -83,7 +83,7 @@ class MultipartRequest extends BaseRequest { ByteStream finalize() { // TODO(nweiz): freeze fields and files var boundary = _boundaryString(); - headers['content-type'] = 'multipart/form-data; boundary="$boundary"'; + headers['content-type'] = 'multipart/form-data; boundary=$boundary'; super.finalize(); var controller = new StreamController>(sync: true); @@ -117,14 +117,19 @@ class MultipartRequest extends BaseRequest { return new ByteStream(controller.stream); } - /// All character codes that are valid in multipart boundaries. From - /// http://tools.ietf.org/html/rfc2046#section-5.1.1. + /// All character codes that are valid in multipart boundaries. This is the + /// intersection of the characters allowed in the `bcharsnospace` production + /// defined in [RFC 2046][] and those allowed in the `token` production + /// defined in [RFC 1521][]. + /// + /// [RFC 2046]: http://tools.ietf.org/html/rfc2046#section-5.1.1. + /// [RFC 1521]: https://tools.ietf.org/html/rfc1521#section-4 static const List _BOUNDARY_CHARACTERS = const [ - 39, 40, 41, 43, 95, 44, 45, 46, 47, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54, - 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 122 + 39, 43, 95, 44, 45, 46, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122 ]; /// Returns the header string for a field. The return value is guaranteed to diff --git a/pubspec.yaml b/pubspec.yaml index 8f419adbb6..b922a51742 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: http -version: 0.11.3+11 +version: 0.11.3+12 author: "Dart Team " homepage: https://github.com/dart-lang/http description: A composable, Future-based API for making HTTP requests. From f5adb791f390f474d815ba08c4449fc124184b9a Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 29 Mar 2017 17:29:45 -0700 Subject: [PATCH 5/7] remove boundary characters that pkg:http_parser cannot parse (#72) --- lib/src/boundary_characters.dart | 18 ++++++++++++++++++ lib/src/multipart_request.dart | 18 ++---------------- test/io/utils.dart | 2 +- test/multipart_test.dart | 8 ++++++++ 4 files changed, 29 insertions(+), 17 deletions(-) create mode 100644 lib/src/boundary_characters.dart diff --git a/lib/src/boundary_characters.dart b/lib/src/boundary_characters.dart new file mode 100644 index 0000000000..03b7ac2d5e --- /dev/null +++ b/lib/src/boundary_characters.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// All character codes that are valid in multipart boundaries. This is the +/// intersection of the characters allowed in the `bcharsnospace` production +/// defined in [RFC 2046][] and those allowed in the `token` production +/// defined in [RFC 1521][]. +/// +/// [RFC 2046]: http://tools.ietf.org/html/rfc2046#section-5.1.1. +/// [RFC 1521]: https://tools.ietf.org/html/rfc1521#section-4 +const List BOUNDARY_CHARACTERS = const [ + 39, 43, 95, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122 +]; diff --git a/lib/src/multipart_request.dart b/lib/src/multipart_request.dart index 7250def98f..8132f80924 100644 --- a/lib/src/multipart_request.dart +++ b/lib/src/multipart_request.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:math'; import 'base_request.dart'; +import 'boundary_characters.dart'; import 'byte_stream.dart'; import 'multipart_file.dart'; import 'utils.dart'; @@ -117,21 +118,6 @@ class MultipartRequest extends BaseRequest { return new ByteStream(controller.stream); } - /// All character codes that are valid in multipart boundaries. This is the - /// intersection of the characters allowed in the `bcharsnospace` production - /// defined in [RFC 2046][] and those allowed in the `token` production - /// defined in [RFC 1521][]. - /// - /// [RFC 2046]: http://tools.ietf.org/html/rfc2046#section-5.1.1. - /// [RFC 1521]: https://tools.ietf.org/html/rfc1521#section-4 - static const List _BOUNDARY_CHARACTERS = const [ - 39, 43, 95, 44, 45, 46, 58, 61, 63, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, - 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, - 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, - 122 - ]; - /// Returns the header string for a field. The return value is guaranteed to /// contain only ASCII characters. String _headerForField(String name, String value) { @@ -172,7 +158,7 @@ class MultipartRequest extends BaseRequest { var prefix = "dart-http-boundary-"; var list = new List.generate(_BOUNDARY_LENGTH - prefix.length, (index) => - _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)], + BOUNDARY_CHARACTERS[_random.nextInt(BOUNDARY_CHARACTERS.length)], growable: false); return "$prefix${new String.fromCharCodes(list)}"; } diff --git a/test/io/utils.dart b/test/io/utils.dart index 4594b6e5a8..24276faff8 100644 --- a/test/io/utils.dart +++ b/test/io/utils.dart @@ -85,7 +85,7 @@ Future startServer() { requestBody = requestBodyBytes; } - var content = { + var content = { 'method': request.method, 'path': request.uri.path, 'headers': {} diff --git a/test/multipart_test.dart b/test/multipart_test.dart index 8f908f938b..ae3c4b320f 100644 --- a/test/multipart_test.dart +++ b/test/multipart_test.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http/src/boundary_characters.dart'; import 'package:http_parser/http_parser.dart'; import 'package:unittest/unittest.dart'; @@ -18,6 +19,13 @@ void main() { ''')); }); + test('boundary characters', () { + var testBoundary = new String.fromCharCodes(BOUNDARY_CHARACTERS); + var contentType = new MediaType.parse('text/plain; boundary=${testBoundary}'); + var boundary = contentType.parameters['boundary']; + expect(boundary, testBoundary); + }); + test('with fields and files', () { var request = new http.MultipartRequest('POST', dummyUrl); request.fields['field1'] = 'value1'; From ac6fd5ec911aba9ecda13c11f14eb1d62d543cad Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 29 Mar 2017 17:41:29 -0700 Subject: [PATCH 6/7] version bump; changelog about boundary characters (#73) --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5cb769c3..8545e484a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.11.3+13 + +* remove boundary characters that package:http_parser cannot parse. + ## 0.11.3+12 * Don't quote the boundary header for `MultipartRequest`. This is more diff --git a/pubspec.yaml b/pubspec.yaml index b922a51742..7bad9f065c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: http -version: 0.11.3+12 +version: 0.11.3+13 author: "Dart Team " homepage: https://github.com/dart-lang/http description: A composable, Future-based API for making HTTP requests. From 995979629e7fbc99b52ea744fc80bcfb2854466c Mon Sep 17 00:00:00 2001 From: Yegor Date: Fri, 7 Apr 2017 10:04:53 -0700 Subject: [PATCH 7/7] remove unused import in mock_client_test.dart (#76) --- test/mock_client_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mock_client_test.dart b/test/mock_client_test.dart index a19adcad24..48e635fee3 100644 --- a/test/mock_client_test.dart +++ b/test/mock_client_test.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:http/src/utils.dart'; import 'package:http/testing.dart'; import 'package:unittest/unittest.dart';