Add http package

Change-Id: I88b88e8c32c462bef1115a8dc8b0978831e528e3
diff --git a/importer/importer.py b/importer/importer.py
index 6c5642e..6f729ef 100755
--- a/importer/importer.py
+++ b/importer/importer.py
@@ -26,7 +26,6 @@
 LOCAL_PACKAGES = {
   'analyzer': '//dart/pkg/analyzer',
   'flutter': '//lib/flutter/packages/flutter',
-  'http': '//apps/modules/packages/flutter-http',
   'linter': '//dart/third_party/pkg/linter',
   'typed_mock': '//dart/pkg/typed_mock',
 }
diff --git a/pub/_discoveryapis_commons/BUILD.gn b/pub/_discoveryapis_commons/BUILD.gn
index 335cceb..1439d62 100644
--- a/pub/_discoveryapis_commons/BUILD.gn
+++ b/pub/_discoveryapis_commons/BUILD.gn
@@ -10,6 +10,6 @@
   disable_analysis = true
 
   deps = [
-    "//apps/modules/packages/flutter-http",
+    "//third_party/dart-pkg/pub/http",
   ]
 }
diff --git a/pub/googleapis/BUILD.gn b/pub/googleapis/BUILD.gn
index 1a7b13c..6729abe 100644
--- a/pub/googleapis/BUILD.gn
+++ b/pub/googleapis/BUILD.gn
@@ -10,7 +10,7 @@
   disable_analysis = true
 
   deps = [
-    "//apps/modules/packages/flutter-http",
+    "//third_party/dart-pkg/pub/http",
     "//third_party/dart-pkg/pub/_discoveryapis_commons",
   ]
 }
diff --git a/pub/googleapis_auth/BUILD.gn b/pub/googleapis_auth/BUILD.gn
index 9ce54b2..a0c8170 100644
--- a/pub/googleapis_auth/BUILD.gn
+++ b/pub/googleapis_auth/BUILD.gn
@@ -10,7 +10,7 @@
   disable_analysis = true
 
   deps = [
-    "//apps/modules/packages/flutter-http",
+    "//third_party/dart-pkg/pub/http",
     "//third_party/dart-pkg/pub/crypto",
   ]
 }
diff --git a/pub/http/.analysis_options b/pub/http/.analysis_options
new file mode 100644
index 0000000..a10d4c5
--- /dev/null
+++ b/pub/http/.analysis_options
@@ -0,0 +1,2 @@
+analyzer:
+  strong-mode: true
diff --git a/pub/http/.gitignore b/pub/http/.gitignore
new file mode 100644
index 0000000..7dbf035
--- /dev/null
+++ b/pub/http/.gitignore
@@ -0,0 +1,15 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
\ No newline at end of file
diff --git a/pub/http/.status b/pub/http/.status
new file mode 100644
index 0000000..8e10eb2
--- /dev/null
+++ b/pub/http/.status
@@ -0,0 +1,25 @@
+# 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.
+
+# Skip non-test files ending with "_test".
+packages/*: Skip
+*/packages/*: Skip
+*/*/packages/*: Skip
+*/*/*/packages/*: Skip
+*/*/*/*packages/*: Skip
+*/*/*/*/*packages/*: Skip
+
+# Only run tests from the build directory, since we don't care about the
+# difference between transformed an untransformed code.
+test/*: Skip
+
+[ $browser ]
+build/test/io/*: Fail, OK # Uses dart:io.
+
+[ $runtime == vm ]
+build/test/html/*: Skip # Uses dart:html.
+
+[ $runtime == drt ]
+build/test/html/client_test: Skip # Issue 18566
+
diff --git a/pub/http/BUILD.gn b/pub/http/BUILD.gn
new file mode 100644
index 0000000..492b1b2
--- /dev/null
+++ b/pub/http/BUILD.gn
@@ -0,0 +1,19 @@
+# This file is generated by importer.py for http-0.11.3+12
+
+import("//build/dart/dart_package.gni")
+
+dart_package("http") {
+  package_name = "http"
+
+  source_dir = "lib"
+
+  disable_analysis = true
+
+  deps = [
+    "//third_party/dart-pkg/pub/async",
+    "//third_party/dart-pkg/pub/path",
+    "//third_party/dart-pkg/pub/http_parser",
+    "//third_party/dart-pkg/pub/collection",
+    "//third_party/dart-pkg/pub/stack_trace",
+  ]
+}
diff --git a/pub/http/CHANGELOG.md b/pub/http/CHANGELOG.md
new file mode 100644
index 0000000..4a5cb76
--- /dev/null
+++ b/pub/http/CHANGELOG.md
@@ -0,0 +1,104 @@
+## 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`.
+
+## 0.11.3+9
+
+* Remove an extra newline in multipart chunks.
+
+## 0.11.3+8
+
+* Properly specify `Content-Transfer-Encoding` for multipart chunks.
+
+## 0.11.3+7
+
+* Declare compatibility with `http_parser` 3.0.0.
+
+## 0.11.3+6
+
+* Fix one more strong mode warning in `http/testing.dart`.
+
+## 0.11.3+5
+
+* Fix some lingering strong mode warnings.
+
+## 0.11.3+4
+
+* Fix all strong mode warnings.
+
+## 0.11.3+3
+
+* Support `http_parser` 2.0.0.
+
+## 0.11.3+2
+
+* Require Dart SDK >= 1.9.0
+
+* Eliminate many uses of `Chain.track` from the `stack_trace` package.
+
+## 0.11.3+1
+
+* Support `http_parser` 1.0.0.
+
+## 0.11.3
+
+* Add a `Client.patch` shortcut method and a matching top-level `patch` method.
+
+## 0.11.2
+
+* Add a `BrowserClient.withCredentials` property.
+
+## 0.11.1+3
+
+* Properly namespace an internal library name.
+
+## 0.11.1+2
+
+* Widen the version constraint on `unittest`.
+
+## 0.11.1+1
+
+* Widen the version constraint for `stack_trace`.
+
+## 0.11.1
+
+* Expose the `IOClient` class which wraps a `dart:io` `HttpClient`.
+
+## 0.11.0+1
+
+* Fix a bug in handling errors in decoding XMLHttpRequest responses for
+  `BrowserClient`.
+
+## 0.11.0
+
+* The package no longer depends on `dart:io`. The `BrowserClient` class in
+  `package:http/browser_client.dart` can now be used to make requests on the
+  browser.
+
+* Change `MultipartFile.contentType` from `dart:io`'s `ContentType` type to
+  `http_parser`'s `MediaType` type.
+
+* Exceptions are now of type `ClientException` rather than `dart:io`'s
+  `HttpException`.
+
+## 0.10.0
+
+* Make `BaseRequest.contentLength` and `BaseResponse.contentLength` use `null`
+  to indicate an unknown content length rather than -1.
+
+* The `contentLength` parameter to `new BaseResponse` is now named rather than
+  positional.
+
+* Make request headers case-insensitive.
+
+* Make `MultipartRequest` more closely adhere to browsers' encoding conventions.
diff --git a/pub/http/LICENSE b/pub/http/LICENSE
new file mode 100644
index 0000000..5c60afe
--- /dev/null
+++ b/pub/http/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2014, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pub/http/README.md b/pub/http/README.md
new file mode 100644
index 0000000..506bbad
--- /dev/null
+++ b/pub/http/README.md
@@ -0,0 +1,100 @@
+# http
+
+A composable, Future-based library for making HTTP requests.
+
+This package contains a set of high-level functions and classes that make it
+easy to consume HTTP resources. It's platform-independent, and can be used on
+both the command-line and the browser. Currently the global utility functions
+are unsupported on the browser; see "Using on the Browser" below.
+
+## Using
+
+The easiest way to use this library is via the top-level functions, although
+they currently only work on platforms where `dart:io` is available. They allow
+you to make individual HTTP requests with minimal hassle:
+
+```dart
+import 'package:http/http.dart' as http;
+
+var url = "http://example.com/whatsit/create";
+http.post(url, body: {"name": "doodle", "color": "blue"})
+    .then((response) {
+  print("Response status: ${response.statusCode}");
+  print("Response body: ${response.body}");
+});
+
+http.read("http://example.com/foobar.txt").then(print);
+```
+
+If you're making multiple requests to the same server, you can keep open a
+persistent connection by using a [Client][] rather than making one-off requests.
+If you do this, make sure to close the client when you're done:
+
+```dart
+var client = new http.Client();
+client.post(
+    "http://example.com/whatsit/create",
+    body: {"name": "doodle", "color": "blue"})
+  .then((response) => client.get(response.bodyFields['uri']))
+  .then((response) => print(response.body))
+  .whenComplete(client.close);
+```
+
+You can also exert more fine-grained control over your requests and responses by
+creating [Request][] or [StreamedRequest][] objects yourself and passing them to
+[Client.send][].
+
+[Request]: https://www.dartdocs.org/documentation/http/latest/http/Request-class.html
+[StreamedRequest]: https://www.dartdocs.org/documentation/http/latest/http/StreamedRequest-class.html
+[Client.send]: https://www.dartdocs.org/documentation/http/latest/http/Client/send.html
+
+This package is designed to be composable. This makes it easy for external
+libraries to work with one another to add behavior to it. Libraries wishing to
+add behavior should create a subclass of [BaseClient][] that wraps another
+[Client][] and adds the desired behavior:
+
+[BaseClient]: https://www.dartdocs.org/documentation/http/latest/http/BaseClient-class.html
+[Client]: https://www.dartdocs.org/documentation/http/latest/http/Client-class.html
+
+```dart
+class UserAgentClient extends http.BaseClient {
+  final String userAgent;
+  final http.Client _inner;
+
+  UserAgentClient(this.userAgent, this._inner);
+
+  Future<StreamedResponse> send(BaseRequest request) {
+    request.headers['user-agent'] = userAgent;
+    return _inner.send(request);
+  }
+}
+```
+
+## Using on the Browser
+
+The HTTP library can be used on the browser via the [BrowserClient][] class in
+`package:http/browser_client.dart`. This client translates requests into
+XMLHttpRequests. For example:
+
+[BrowserClient]: https://www.dartdocs.org/documentation/http/latest/http.browser_client/BrowserClient-class.html
+
+```dart
+import 'dart:async';
+import 'package:http/browser_client.dart';
+
+main() async {
+  var client = new BrowserClient();
+  var url = '/whatsit/create';
+  var response =
+      await client.post(url, body: {'name': 'doodle', 'color': 'blue'});
+  print('Response status: ${response.statusCode}');
+  print('Response body: ${response.body}');
+}
+```
+
+## Filing issues
+
+Please file issues for the http package at [http://dartbug.com/new][bugs].
+
+[bugs]: http://dartbug.com/new
+[docs]: https://api.dartlang.org/docs/channels/dev/latest/http.html
diff --git a/pub/http/codereview.settings b/pub/http/codereview.settings
new file mode 100644
index 0000000..f980465
--- /dev/null
+++ b/pub/http/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/http/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/pub/http/lib/browser_client.dart b/pub/http/lib/browser_client.dart
new file mode 100644
index 0000000..309b3ac
--- /dev/null
+++ b/pub/http/lib/browser_client.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:html';
+import 'dart:typed_data';
+
+import 'package:stack_trace/stack_trace.dart';
+
+import 'src/base_client.dart';
+import 'src/base_request.dart';
+import 'src/byte_stream.dart';
+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 sdk#24581 is fixed.
+
+/// A `dart:html`-based HTTP client that runs in the browser and is backed by
+/// XMLHttpRequests.
+///
+/// This client inherits some of the limitations of XMLHttpRequest. It ignores
+/// the [BaseRequest.contentLength], [BaseRequest.persistentConnection],
+/// [BaseRequest.followRedirects], and [BaseRequest.maxRedirects] fields. It is
+/// also unable to stream requests or responses; a request will only be sent and
+/// a response will only be returned once all the data is available.
+class BrowserClient extends BaseClient {
+  /// The currently active XHRs.
+  ///
+  /// These are aborted if the client is closed.
+  final _xhrs = new Set<HttpRequest>();
+
+  /// Creates a new HTTP client.
+  BrowserClient();
+
+  /// Whether to send credentials such as cookies or authorization headers for
+  /// cross-site requests.
+  ///
+  /// Defaults to `false`.
+  bool withCredentials = false;
+
+  /// Sends an HTTP request and asynchronously returns the response.
+  Future<StreamedResponse> send(BaseRequest request) async {
+    var bytes = await request.finalize().toBytes();
+    var xhr = new HttpRequest();
+    _xhrs.add(xhr);
+    _openHttpRequest(xhr, request.method, request.url.toString(), asynch: true);
+    xhr.responseType = 'blob';
+    xhr.withCredentials = withCredentials;
+    request.headers.forEach(xhr.setRequestHeader);
+
+    var completer = new Completer<StreamedResponse>();
+    xhr.onLoad.first.then((_) {
+      // TODO(nweiz): Set the response type to "arraybuffer" when issue 18542
+      // is fixed.
+      var blob = xhr.response == null ? new Blob([]) : xhr.response;
+      var reader = new FileReader();
+
+      reader.onLoad.first.then((_) {
+        var body = reader.result as Uint8List;
+        completer.complete(new StreamedResponse(
+            new ByteStream.fromBytes(body),
+            xhr.status,
+            contentLength: body.length,
+            request: request,
+            headers: xhr.responseHeaders,
+            reasonPhrase: xhr.statusText));
+      });
+
+      reader.onError.first.then((error) {
+        completer.completeError(
+            new ClientException(error.toString(), request.url),
+            new Chain.current());
+      });
+
+      reader.readAsArrayBuffer(blob);
+    });
+
+    xhr.onError.first.then((_) {
+      // Unfortunately, the underlying XMLHttpRequest API doesn't expose any
+      // specific information about the error itself.
+      completer.completeError(
+          new ClientException("XMLHttpRequest error.", request.url),
+          new Chain.current());
+    });
+
+    xhr.send(bytes);
+
+    try {
+      return await completer.future;
+    } finally {
+      _xhrs.remove(xhr);
+    }
+  }
+
+  // TODO(nweiz): Remove this when sdk#24637 is fixed.
+  void _openHttpRequest(HttpRequest request, String method, String url,
+      {bool asynch, String user, String password}) {
+    request.open(method, url, async: asynch, user: user, password: password);
+  }
+
+  /// Closes the client.
+  ///
+  /// This terminates all active requests.
+  void close() {
+    for (var xhr in _xhrs) {
+      xhr.abort();
+    }
+  }
+}
diff --git a/pub/http/lib/http.dart b/pub/http/lib/http.dart
new file mode 100644
index 0000000..86bcefb
--- /dev/null
+++ b/pub/http/lib/http.dart
@@ -0,0 +1,171 @@
+// 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.
+
+/// A composable, [Future]-based library for making HTTP requests.
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'src/client.dart';
+import 'src/response.dart';
+
+export 'src/base_client.dart';
+export 'src/base_request.dart';
+export 'src/base_response.dart';
+export 'src/byte_stream.dart';
+export 'src/client.dart';
+export 'src/exception.dart';
+export 'src/io_client.dart';
+export 'src/multipart_file.dart';
+export 'src/multipart_request.dart';
+export 'src/request.dart';
+export 'src/response.dart';
+export 'src/streamed_request.dart';
+export 'src/streamed_response.dart';
+
+/// Sends an HTTP HEAD request with the given headers to the given URL, which
+/// can be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> head(url, {Map<String, String> headers}) =>
+  _withClient((client) => client.head(url, headers: headers));
+
+/// Sends an HTTP GET request with the given headers to the given URL, which can
+/// be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> get(url, {Map<String, String> headers}) =>
+  _withClient((client) => client.get(url, headers: headers));
+
+/// Sends an HTTP POST request with the given headers and body to the given URL,
+/// which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> post(url, {Map<String, String> headers, body,
+    Encoding encoding}) =>
+  _withClient((client) => client.post(url,
+      headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP PUT request with the given headers and body to the given URL,
+/// which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> put(url, {Map<String, String> headers, body,
+    Encoding encoding}) =>
+  _withClient((client) => client.put(url,
+      headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP PATCH request with the given headers and body to the given
+/// URL, which can be a [Uri] or a [String].
+///
+/// [body] sets the body of the request. It can be a [String], a [List<int>] or
+/// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
+/// used as the body of the request. The content-type of the request will
+/// default to "text/plain".
+///
+/// If [body] is a List, it's used as a list of bytes for the body of the
+/// request.
+///
+/// If [body] is a Map, it's encoded as form fields using [encoding]. The
+/// content-type of the request will be set to
+/// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+///
+/// [encoding] defaults to [UTF8].
+///
+/// For more fine-grained control over the request, use [Request] or
+/// [StreamedRequest] instead.
+Future<Response> patch(url, {Map<String, String> headers, body,
+    Encoding encoding}) =>
+  _withClient((client) => client.patch(url,
+      headers: headers, body: body, encoding: encoding));
+
+/// Sends an HTTP DELETE request with the given headers to the given URL, which
+/// can be a [Uri] or a [String].
+///
+/// This automatically initializes a new [Client] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [Client] for all of those requests.
+///
+/// For more fine-grained control over the request, use [Request] instead.
+Future<Response> delete(url, {Map<String, String> headers}) =>
+  _withClient((client) => client.delete(url, headers: headers));
+
+/// Sends an HTTP GET request with the given headers to the given URL, which can
+/// be a [Uri] or a [String], and returns a Future that completes to the body of
+/// the response as a [String].
+///
+/// The Future will emit a [ClientException] if the response doesn't have a
+/// success status code.
+///
+/// This automatically initializes a new [Client] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [Client] for all of those requests.
+///
+/// For more fine-grained control over the request and response, use [Request]
+/// instead.
+Future<String> read(url, {Map<String, String> headers}) =>
+  _withClient((client) => client.read(url, headers: headers));
+
+/// Sends an HTTP GET request with the given headers to the given URL, which can
+/// be a [Uri] or a [String], and returns a Future that completes to the body of
+/// the response as a list of bytes.
+///
+/// The Future will emit a [ClientException] if the response doesn't have a
+/// success status code.
+///
+/// This automatically initializes a new [Client] and closes that client once
+/// the request is complete. If you're planning on making multiple requests to
+/// the same server, you should use a single [Client] for all of those requests.
+///
+/// For more fine-grained control over the request and response, use [Request]
+/// instead.
+Future<Uint8List> readBytes(url, {Map<String, String> headers}) =>
+  _withClient((client) => client.readBytes(url, headers: headers));
+
+Future<T> _withClient<T>(Future<T> fn(Client client)) async {
+  var client = new Client();
+  try {
+    return await fn(client);
+  } finally {
+    client.close();
+  }
+}
diff --git a/pub/http/lib/src/base_client.dart b/pub/http/lib/src/base_client.dart
new file mode 100644
index 0000000..7b3fbfa
--- /dev/null
+++ b/pub/http/lib/src/base_client.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:collection/collection.dart';
+
+import 'base_request.dart';
+import 'client.dart';
+import 'exception.dart';
+import 'request.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+/// The abstract base class for an HTTP client. This is a mixin-style class;
+/// subclasses only need to implement [send] and maybe [close], and then they
+/// get various convenience methods for free.
+abstract class BaseClient implements Client {
+  /// Sends an HTTP HEAD request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> head(url, {Map<String, String> headers}) =>
+    _sendUnstreamed("HEAD", url, headers);
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> get(url, {Map<String, String> headers}) =>
+    _sendUnstreamed("GET", url, headers);
+
+  /// Sends an HTTP POST request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to UTF-8.
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> post(url, {Map<String, String> headers, body,
+      Encoding encoding}) =>
+    _sendUnstreamed("POST", url, headers, body, encoding);
+
+  /// Sends an HTTP PUT request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to UTF-8.
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> put(url, {Map<String, String> headers, body,
+      Encoding encoding}) =>
+    _sendUnstreamed("PUT", url, headers, body, encoding);
+
+  /// Sends an HTTP PATCH request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to UTF-8.
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> patch(url, {Map<String, String> headers, body,
+      Encoding encoding}) =>
+    _sendUnstreamed("PATCH", url, headers, body, encoding);
+
+  /// Sends an HTTP DELETE request with the given headers to the given URL,
+  /// which can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> delete(url, {Map<String, String> headers}) =>
+    _sendUnstreamed("DELETE", url, headers);
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String], and returns a Future that completes to the
+  /// body of the response as a String.
+  ///
+  /// The Future will emit a [ClientException] if the response doesn't have a
+  /// success status code.
+  ///
+  /// For more fine-grained control over the request and response, use [send] or
+  /// [get] instead.
+  Future<String> read(url, {Map<String, String> headers}) {
+    return get(url, headers: headers).then((response) {
+      _checkResponseSuccess(url, response);
+      return response.body;
+    });
+  }
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String], and returns a Future that completes to the
+  /// body of the response as a list of bytes.
+  ///
+  /// The Future will emit an [ClientException] if the response doesn't have a
+  /// success status code.
+  ///
+  /// For more fine-grained control over the request and response, use [send] or
+  /// [get] instead.
+  Future<Uint8List> readBytes(url, {Map<String, String> headers}) {
+    return get(url, headers: headers).then((response) {
+      _checkResponseSuccess(url, response);
+      return response.bodyBytes;
+    });
+  }
+
+  /// Sends an HTTP request and asynchronously returns the response.
+  ///
+  /// Implementers should call [BaseRequest.finalize] to get the body of the
+  /// request as a [ByteStream]. They shouldn't make any assumptions about the
+  /// state of the stream; it could have data written to it asynchronously at a
+  /// later point, or it could already be closed when it's returned. Any
+  /// internal HTTP errors should be wrapped as [ClientException]s.
+  Future<StreamedResponse> send(BaseRequest request);
+
+  /// Sends a non-streaming [Request] and returns a non-streaming [Response].
+  Future<Response> _sendUnstreamed(String method, url,
+      Map<String, String> headers, [body, Encoding encoding]) async {
+
+    if (url is String) url = Uri.parse(url);
+    var request = new Request(method, url);
+
+    if (headers != null) request.headers.addAll(headers);
+    if (encoding != null) request.encoding = encoding;
+    if (body != null) {
+      if (body is String) {
+        request.body = body;
+      } else if (body is List) {
+        request.bodyBytes = DelegatingList.typed(body);
+      } else if (body is Map) {
+        request.bodyFields = DelegatingMap.typed(body);
+      } else {
+        throw new ArgumentError('Invalid request body "$body".');
+      }
+    }
+
+    return Response.fromStream(await send(request));
+  }
+
+  /// Throws an error if [response] is not successful.
+  void _checkResponseSuccess(url, Response response) {
+    if (response.statusCode < 400) return;
+    var message = "Request to $url failed with status ${response.statusCode}";
+    if (response.reasonPhrase != null) {
+      message = "$message: ${response.reasonPhrase}";
+    }
+    if (url is String) url = Uri.parse(url);
+    throw new ClientException("$message.", url);
+  }
+
+  /// Closes the client and cleans up any resources associated with it. It's
+  /// important to close each client when it's done being used; failing to do so
+  /// can cause the Dart process to hang.
+  void close() {}
+}
diff --git a/pub/http/lib/src/base_request.dart b/pub/http/lib/src/base_request.dart
new file mode 100644
index 0000000..b11ef05
--- /dev/null
+++ b/pub/http/lib/src/base_request.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'byte_stream.dart';
+import 'client.dart';
+import 'streamed_response.dart';
+import 'utils.dart';
+
+/// The base class for HTTP requests.
+///
+/// Subclasses of [BaseRequest] can be constructed manually and passed to
+/// [BaseClient.send], which allows the user to provide fine-grained control
+/// over the request properties. However, usually it's easier to use convenience
+/// methods like [get] or [BaseClient.get].
+abstract class BaseRequest {
+  /// The HTTP method of the request. Most commonly "GET" or "POST", less
+  /// commonly "HEAD", "PUT", or "DELETE". Non-standard method names are also
+  /// supported.
+  final String method;
+
+  /// The URL to which the request will be sent.
+  final Uri url;
+
+  /// The size of the request body, in bytes.
+  ///
+  /// This defaults to `null`, which indicates that the size of the request is
+  /// not known in advance.
+  int get contentLength => _contentLength;
+  int _contentLength;
+
+  set contentLength(int value) {
+    if (value != null && value < 0) {
+      throw new ArgumentError("Invalid content length $value.");
+    }
+    _checkFinalized();
+    _contentLength = value;
+  }
+
+  /// Whether a persistent connection should be maintained with the server.
+  /// Defaults to true.
+  bool get persistentConnection => _persistentConnection;
+  bool _persistentConnection = true;
+
+  set persistentConnection(bool value) {
+    _checkFinalized();
+    _persistentConnection = value;
+  }
+
+  /// Whether the client should follow redirects while resolving this request.
+  /// Defaults to true.
+  bool get followRedirects => _followRedirects;
+  bool _followRedirects = true;
+
+  set followRedirects(bool value) {
+    _checkFinalized();
+    _followRedirects = value;
+  }
+
+  /// The maximum number of redirects to follow when [followRedirects] is true.
+  /// If this number is exceeded the [BaseResponse] future will signal a
+  /// [RedirectException]. Defaults to 5.
+  int get maxRedirects => _maxRedirects;
+  int _maxRedirects = 5;
+
+  set maxRedirects(int value) {
+    _checkFinalized();
+    _maxRedirects = value;
+  }
+
+  // TODO(nweiz): automatically parse cookies from headers
+
+  // TODO(nweiz): make this a HttpHeaders object
+  /// The headers for this request.
+  final Map<String, String> headers;
+
+  /// Whether the request has been finalized.
+  bool get finalized => _finalized;
+  bool _finalized = false;
+
+  /// Creates a new HTTP request.
+  BaseRequest(this.method, this.url)
+    : headers = new LinkedHashMap(
+        equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
+        hashCode: (key) => key.toLowerCase().hashCode);
+
+  /// Finalizes the HTTP request in preparation for it being sent. This freezes
+  /// all mutable fields and returns a single-subscription [ByteStream] that
+  /// emits the body of the request.
+  ///
+  /// The base implementation of this returns null rather than a [ByteStream];
+  /// subclasses are responsible for creating the return value, which should be
+  /// single-subscription to ensure that no data is dropped. They should also
+  /// freeze any additional mutable fields they add that don't make sense to
+  /// change after the request headers are sent.
+  ByteStream finalize() {
+    // TODO(nweiz): freeze headers
+    if (finalized) throw new StateError("Can't finalize a finalized Request.");
+    _finalized = true;
+    return null;
+  }
+
+  /// Sends this request.
+  ///
+  /// This automatically initializes a new [Client] and closes that client once
+  /// the request is complete. If you're planning on making multiple requests to
+  /// the same server, you should use a single [Client] for all of those
+  /// requests.
+  Future<StreamedResponse> send() async {
+    var client = new Client();
+
+    try {
+      var response = await client.send(this);
+      var stream = onDone(response.stream, client.close);
+      return new StreamedResponse(
+          new ByteStream(stream),
+          response.statusCode,
+          contentLength: response.contentLength,
+          request: response.request,
+          headers: response.headers,
+          isRedirect: response.isRedirect,
+          persistentConnection: response.persistentConnection,
+          reasonPhrase: response.reasonPhrase);
+    } catch (_) {
+      client.close();
+      rethrow;
+    }
+  }
+
+  // Throws an error if this request has been finalized.
+  void _checkFinalized() {
+    if (!finalized) return;
+    throw new StateError("Can't modify a finalized Request.");
+  }
+
+  String toString() => "$method $url";
+}
diff --git a/pub/http/lib/src/base_response.dart b/pub/http/lib/src/base_response.dart
new file mode 100644
index 0000000..26427f8
--- /dev/null
+++ b/pub/http/lib/src/base_response.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2012, 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.
+
+import 'base_request.dart';
+
+/// The base class for HTTP responses.
+///
+/// Subclasses of [BaseResponse] are usually not constructed manually; instead,
+/// they're returned by [BaseClient.send] or other HTTP client methods.
+abstract class BaseResponse {
+  /// The (frozen) request that triggered this response.
+  final BaseRequest request;
+
+  /// The status code of the response.
+  final int statusCode;
+
+  /// The reason phrase associated with the status code.
+  final String reasonPhrase;
+
+  /// The size of the response body, in bytes.
+  ///
+  /// If the size of the request is not known in advance, this is `null`.
+  final int contentLength;
+
+  // TODO(nweiz): automatically parse cookies from headers
+
+  // TODO(nweiz): make this a HttpHeaders object.
+  /// The headers for this response.
+  final Map<String, String> headers;
+
+  /// Whether this response is a redirect.
+  final bool isRedirect;
+
+  /// Whether the server requested that a persistent connection be maintained.
+  final bool persistentConnection;
+
+  /// Creates a new HTTP response.
+  BaseResponse(
+      this.statusCode,
+      {this.contentLength,
+       this.request,
+       this.headers: const {},
+       this.isRedirect: false,
+       this.persistentConnection: true,
+       this.reasonPhrase}) {
+    if (statusCode < 100) {
+      throw new ArgumentError("Invalid status code $statusCode.");
+    } else if (contentLength != null && contentLength < 0) {
+      throw new ArgumentError("Invalid content length $contentLength.");
+    }
+  }
+}
diff --git a/pub/http/lib/src/byte_stream.dart b/pub/http/lib/src/byte_stream.dart
new file mode 100644
index 0000000..a9d47b0
--- /dev/null
+++ b/pub/http/lib/src/byte_stream.dart
@@ -0,0 +1,36 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+/// A stream of chunks of bytes representing a single piece of data.
+class ByteStream extends StreamView<List<int>> {
+  ByteStream(Stream<List<int>> stream)
+      : super(stream);
+
+  /// Returns a single-subscription byte stream that will emit the given bytes
+  /// in a single chunk.
+  factory ByteStream.fromBytes(List<int> bytes) =>
+      new ByteStream(new Stream.fromIterable([bytes]));
+
+  /// Collects the data of this stream in a [Uint8List].
+  Future<Uint8List> toBytes() {
+    var completer = new Completer<Uint8List>();
+    var sink = new ByteConversionSink.withCallback((bytes) =>
+        completer.complete(new Uint8List.fromList(bytes)));
+    listen(sink.add, onError: completer.completeError, onDone: sink.close,
+        cancelOnError: true);
+    return completer.future;
+  }
+
+  /// Collect the data of this stream in a [String], decoded according to
+  /// [encoding], which defaults to `UTF8`.
+  Future<String> bytesToString([Encoding encoding=UTF8]) =>
+      encoding.decodeStream(this);
+
+  Stream<String> toStringStream([Encoding encoding=UTF8]) =>
+      encoding.decoder.bind(this);
+}
diff --git a/pub/http/lib/src/client.dart b/pub/http/lib/src/client.dart
new file mode 100644
index 0000000..cf1ff78
--- /dev/null
+++ b/pub/http/lib/src/client.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'base_client.dart';
+import 'base_request.dart';
+import 'io_client.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+/// The interface for HTTP clients that take care of maintaining persistent
+/// connections across multiple requests to the same server. If you only need to
+/// send a single request, it's usually easier to use [head], [get], [post],
+/// [put], [patch], or [delete] instead.
+///
+/// When creating an HTTP client class with additional functionality, you must
+/// extend [BaseClient] rather than [Client]. In most cases, you can wrap
+/// another instance of [Client] and add functionality on top of that. This
+/// allows all classes implementing [Client] to be mutually composable.
+abstract class Client {
+  /// Creates a new 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() => new IOClient();
+
+  /// Sends an HTTP HEAD request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> head(url, {Map<String, String> headers});
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> get(url, {Map<String, String> headers});
+
+  /// Sends an HTTP POST request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to [UTF8].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> post(url, {Map<String, String> headers, body,
+      Encoding encoding});
+
+  /// Sends an HTTP PUT request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to [UTF8].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> put(url, {Map<String, String> headers, body,
+      Encoding encoding});
+
+  /// Sends an HTTP PATCH request with the given headers and body to the given
+  /// URL, which can be a [Uri] or a [String].
+  ///
+  /// [body] sets the body of the request. It can be a [String], a [List<int>]
+  /// or a [Map<String, String>]. If it's a String, it's encoded using
+  /// [encoding] and used as the body of the request. The content-type of the
+  /// request will default to "text/plain".
+  ///
+  /// If [body] is a List, it's used as a list of bytes for the body of the
+  /// request.
+  ///
+  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
+  /// content-type of the request will be set to
+  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
+  ///
+  /// [encoding] defaults to [UTF8].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> patch(url, {Map<String, String> headers, body,
+      Encoding encoding});
+
+  /// Sends an HTTP DELETE request with the given headers to the given URL,
+  /// which can be a [Uri] or a [String].
+  ///
+  /// For more fine-grained control over the request, use [send] instead.
+  Future<Response> delete(url, {Map<String, String> headers});
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String], and returns a Future that completes to the
+  /// body of the response as a String.
+  ///
+  /// The Future will emit a [ClientException] if the response doesn't have a
+  /// success status code.
+  ///
+  /// For more fine-grained control over the request and response, use [send] or
+  /// [get] instead.
+  Future<String> read(url, {Map<String, String> headers});
+
+  /// Sends an HTTP GET request with the given headers to the given URL, which
+  /// can be a [Uri] or a [String], and returns a Future that completes to the
+  /// body of the response as a list of bytes.
+  ///
+  /// The Future will emit a [ClientException] if the response doesn't have a
+  /// success status code.
+  ///
+  /// For more fine-grained control over the request and response, use [send] or
+  /// [get] instead.
+  Future<Uint8List> readBytes(url, {Map<String, String> headers});
+
+  /// Sends an HTTP request and asynchronously returns the response.
+  Future<StreamedResponse> send(BaseRequest request);
+
+  /// Closes the client and cleans up any resources associated with it. It's
+  /// important to close each client when it's done being used; failing to do so
+  /// can cause the Dart process to hang.
+  void close();
+}
diff --git a/pub/http/lib/src/exception.dart b/pub/http/lib/src/exception.dart
new file mode 100644
index 0000000..db2c224
--- /dev/null
+++ b/pub/http/lib/src/exception.dart
@@ -0,0 +1,15 @@
+// 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.
+
+/// An exception caused by an error in a pkg/http client.
+class ClientException implements Exception {
+  final String message;
+
+  /// The URL of the HTTP request or response that failed.
+  final Uri uri;
+
+  ClientException(this.message, [this.uri]);
+
+  String toString() => message;
+}
diff --git a/pub/http/lib/src/io_client.dart b/pub/http/lib/src/io_client.dart
new file mode 100644
index 0000000..8a7fe6d
--- /dev/null
+++ b/pub/http/lib/src/io_client.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+
+import 'base_client.dart';
+import 'base_request.dart';
+import 'exception.dart';
+import 'streamed_response.dart';
+
+/// A `dart:io`-based HTTP client.
+///
+/// This is the default client when running on the command line.
+class IOClient extends BaseClient {
+  /// The underlying `dart:io` HTTP client.
+  HttpClient _inner;
+
+  /// Creates a new HTTP client.
+  IOClient([HttpClient inner]) : _inner = inner ?? new HttpClient();
+
+  /// Sends an HTTP request and asynchronously returns the response.
+  Future<StreamedResponse> send(BaseRequest request) async {
+    var stream = request.finalize();
+
+    try {
+      var ioRequest = await _inner.openUrl(request.method, request.url);
+
+      ioRequest
+          ..followRedirects = request.followRedirects
+          ..maxRedirects = request.maxRedirects
+          ..contentLength = request.contentLength == null
+              ? -1
+              : request.contentLength
+          ..persistentConnection = request.persistentConnection;
+      request.headers.forEach((name, value) {
+        ioRequest.headers.set(name, value);
+      });
+
+      var response = await stream.pipe(
+          DelegatingStreamConsumer.typed(ioRequest));
+      var headers = <String, String>{};
+      response.headers.forEach((key, values) {
+        headers[key] = values.join(',');
+      });
+
+      return new StreamedResponse(
+          DelegatingStream.typed<List<int>>(response).handleError((error) =>
+              throw new ClientException(error.message, error.uri),
+              test: (error) => error is HttpException),
+          response.statusCode,
+          contentLength: response.contentLength == -1
+              ? null
+              : response.contentLength,
+          request: request,
+          headers: headers,
+          isRedirect: response.isRedirect,
+          persistentConnection: response.persistentConnection,
+          reasonPhrase: response.reasonPhrase);
+    } on HttpException catch (error) {
+      throw new ClientException(error.message, error.uri);
+    }
+  }
+
+  /// Closes the client. This terminates all active connections. If a client
+  /// remains unclosed, the Dart process may not terminate.
+  void close() {
+    if (_inner != null) _inner.close(force: true);
+    _inner = null;
+  }
+}
diff --git a/pub/http/lib/src/mock_client.dart b/pub/http/lib/src/mock_client.dart
new file mode 100644
index 0000000..acda5fd
--- /dev/null
+++ b/pub/http/lib/src/mock_client.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+
+import 'base_client.dart';
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'request.dart';
+import 'response.dart';
+import 'streamed_response.dart';
+
+// TODO(nweiz): once Dart has some sort of Rack- or WSGI-like standard for
+// server APIs, MockClient should conform to it.
+
+/// A mock HTTP client designed for use when testing code that uses
+/// [BaseClient]. This client allows you to define a handler callback for all
+/// requests that are made through it so that you can mock a server without
+/// having to send real HTTP requests.
+class MockClient extends BaseClient {
+  /// The handler for receiving [StreamedRequest]s and sending
+  /// [StreamedResponse]s.
+  final MockClientStreamHandler _handler;
+
+  MockClient._(this._handler);
+
+  /// Creates a [MockClient] with a handler that receives [Request]s and sends
+  /// [Response]s.
+  MockClient(MockClientHandler fn)
+    : this._((baseRequest, bodyStream) {
+      return bodyStream.toBytes().then((bodyBytes) {
+        var request = new Request(baseRequest.method, baseRequest.url)
+            ..persistentConnection = baseRequest.persistentConnection
+            ..followRedirects = baseRequest.followRedirects
+            ..maxRedirects = baseRequest.maxRedirects
+            ..headers.addAll(baseRequest.headers)
+            ..bodyBytes = bodyBytes
+            ..finalize();
+
+        return fn(request);
+      }).then((response) {
+        return new StreamedResponse(
+            new ByteStream.fromBytes(response.bodyBytes),
+            response.statusCode,
+            contentLength: response.contentLength,
+            request: baseRequest,
+            headers: response.headers,
+            isRedirect: response.isRedirect,
+            persistentConnection: response.persistentConnection,
+            reasonPhrase: response.reasonPhrase);
+      });
+    });
+
+  /// Creates a [MockClient] with a handler that receives [StreamedRequest]s and
+  /// sends [StreamedResponse]s.
+  MockClient.streaming(MockClientStreamHandler fn)
+    : this._((request, bodyStream) {
+      return fn(request, bodyStream).then((response) {
+        return new StreamedResponse(
+            response.stream,
+            response.statusCode,
+            contentLength: response.contentLength,
+            request: request,
+            headers: response.headers,
+            isRedirect: response.isRedirect,
+            persistentConnection: response.persistentConnection,
+            reasonPhrase: response.reasonPhrase);
+      });
+    });
+
+  /// Sends a request.
+  Future<StreamedResponse> send(BaseRequest request) async {
+    var bodyStream = request.finalize();
+    return await _handler(request, bodyStream);
+  }
+}
+
+/// A handler function that receives [StreamedRequest]s and sends
+/// [StreamedResponse]s. Note that [request] will be finalized.
+typedef Future<StreamedResponse> MockClientStreamHandler(
+    BaseRequest request, ByteStream bodyStream);
+
+/// A handler function that receives [Request]s and sends [Response]s. Note that
+/// [request] will be finalized.
+typedef Future<Response> MockClientHandler(Request request);
diff --git a/pub/http/lib/src/multipart_file.dart b/pub/http/lib/src/multipart_file.dart
new file mode 100644
index 0000000..da4bfac
--- /dev/null
+++ b/pub/http/lib/src/multipart_file.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, 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.
+
+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 'utils.dart';
+
+/// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to
+/// correspond to a physical file.
+class MultipartFile {
+  /// The name of the form field for the file.
+  final String field;
+
+  /// The size of the file in bytes. This must be known in advance, even if this
+  /// file is created from a [ByteStream].
+  final int length;
+
+  /// The basename of the file. May be null.
+  final String filename;
+
+  /// The content-type of the file. Defaults to `application/octet-stream`.
+  final MediaType contentType;
+
+  /// The stream that will emit the file's contents.
+  final ByteStream _stream;
+
+  /// Whether [finalize] has been called.
+  bool get isFinalized => _isFinalized;
+  bool _isFinalized = false;
+
+  /// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length
+  /// of the file in bytes must be known in advance. If it's not, read the data
+  /// from the stream and use [MultipartFile.fromBytes] instead.
+  ///
+  /// [contentType] currently defaults to `application/octet-stream`, but in the
+  /// future may be inferred from [filename].
+  MultipartFile(this.field, Stream<List<int>> stream, this.length,
+      {this.filename, MediaType contentType})
+    : this._stream = toByteStream(stream),
+      this.contentType = contentType != null ? contentType :
+          new MediaType("application", "octet-stream");
+
+  /// Creates a new [MultipartFile] from a byte array.
+  ///
+  /// [contentType] currently defaults to `application/octet-stream`, but in the
+  /// future may be inferred from [filename].
+  factory MultipartFile.fromBytes(String field, List<int> value,
+      {String filename, MediaType contentType}) {
+    var stream = new ByteStream.fromBytes(value);
+    return new MultipartFile(field, stream, value.length,
+        filename: filename,
+        contentType: contentType);
+  }
+
+  /// Creates a new [MultipartFile] from a string.
+  ///
+  /// The encoding to use when translating [value] into bytes is taken from
+  /// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8.
+  /// [contentType] currently defaults to `text/plain; charset=utf-8`, but in
+  /// the future may be inferred from [filename].
+  factory MultipartFile.fromString(String field, String value,
+      {String filename, MediaType contentType}) {
+    contentType = contentType == null ? new MediaType("text", "plain")
+                                      : contentType;
+    var encoding = encodingForCharset(contentType.parameters['charset'], UTF8);
+    contentType = contentType.change(parameters: {'charset': encoding.name});
+
+    return new MultipartFile.fromBytes(field, encoding.encode(value),
+        filename: filename,
+        contentType: contentType);
+  }
+
+  // TODO(nweiz): Infer the content-type from the filename.
+  /// Creates a new [MultipartFile] from a path to a file on disk.
+  ///
+  /// [filename] defaults to the basename of [filePath]. [contentType] currently
+  /// defaults to `application/octet-stream`, but in the future may be inferred
+  /// from [filename].
+  ///
+  /// Throws an [UnsupportedError] if `dart:io` isn't supported in this
+  /// environment.
+  static Future<MultipartFile> fromPath(String field, String filePath,
+      {String filename, MediaType contentType}) async {
+    if (filename == null) filename = path.basename(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,
+        filename: filename,
+        contentType: contentType);
+  }
+
+  // Finalizes the file in preparation for it being sent as part of a
+  // [MultipartRequest]. This returns a [ByteStream] that should emit the body
+  // of the file. The stream may be closed to indicate an empty file.
+  ByteStream finalize() {
+    if (isFinalized) {
+      throw new StateError("Can't finalize a finalized MultipartFile.");
+    }
+    _isFinalized = true;
+    return _stream;
+  }
+}
diff --git a/pub/http/lib/src/multipart_request.dart b/pub/http/lib/src/multipart_request.dart
new file mode 100644
index 0000000..7250def
--- /dev/null
+++ b/pub/http/lib/src/multipart_request.dart
@@ -0,0 +1,179 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:math';
+
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'multipart_file.dart';
+import 'utils.dart';
+
+final _newlineRegExp = new RegExp(r"\r\n|\r|\n");
+
+/// A `multipart/form-data` request. Such a request has both string [fields],
+/// which function as normal form fields, and (potentially streamed) binary
+/// [files].
+///
+/// This request automatically sets the Content-Type header to
+/// `multipart/form-data`. This value will override any value set by the user.
+///
+///     var uri = Uri.parse("http://pub.dartlang.org/packages/create");
+///     var request = new http.MultipartRequest("POST", url);
+///     request.fields['user'] = 'nweiz@google.com';
+///     request.files.add(new http.MultipartFile.fromFile(
+///         'package',
+///         new File('build/package.tar.gz'),
+///         contentType: new MediaType('application', 'x-tar'));
+///     request.send().then((response) {
+///       if (response.statusCode == 200) print("Uploaded!");
+///     });
+class MultipartRequest extends BaseRequest {
+  /// The total length of the multipart boundaries used when building the
+  /// request body. According to http://tools.ietf.org/html/rfc1341.html, this
+  /// can't be longer than 70.
+  static const int _BOUNDARY_LENGTH = 70;
+
+  static final Random _random = new Random();
+
+  /// The form fields to send for this request.
+  final Map<String, String> fields;
+
+  /// The private version of [files].
+  final List<MultipartFile> _files;
+
+  /// Creates a new [MultipartRequest].
+  MultipartRequest(String method, Uri url)
+    : fields = {},
+      _files = <MultipartFile>[],
+      super(method, url);
+
+  /// The list of files to upload for this request.
+  List<MultipartFile> get files => _files;
+
+  /// The total length of the request body, in bytes. This is calculated from
+  /// [fields] and [files] and cannot be set manually.
+  int get contentLength {
+    var length = 0;
+
+    fields.forEach((name, value) {
+      length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
+          UTF8.encode(_headerForField(name, value)).length +
+          UTF8.encode(value).length + "\r\n".length;
+    });
+
+    for (var file in _files) {
+      length += "--".length + _BOUNDARY_LENGTH + "\r\n".length +
+          UTF8.encode(_headerForFile(file)).length +
+          file.length + "\r\n".length;
+    }
+
+    return length + "--".length + _BOUNDARY_LENGTH + "--\r\n".length;
+  }
+
+  void set contentLength(int value) {
+    throw new UnsupportedError("Cannot set the contentLength property of "
+        "multipart requests.");
+  }
+
+  /// Freezes all mutable fields and returns a single-subscription [ByteStream]
+  /// that will emit the request body.
+  ByteStream finalize() {
+    // TODO(nweiz): freeze fields and files
+    var boundary = _boundaryString();
+    headers['content-type'] = 'multipart/form-data; boundary=$boundary';
+    super.finalize();
+
+    var controller = new StreamController<List<int>>(sync: true);
+
+    void writeAscii(String string) {
+      controller.add(UTF8.encode(string));
+    }
+
+    writeUtf8(String string) => controller.add(UTF8.encode(string));
+    writeLine() => controller.add([13, 10]); // \r\n
+
+    fields.forEach((name, value) {
+      writeAscii('--$boundary\r\n');
+      writeAscii(_headerForField(name, value));
+      writeUtf8(value);
+      writeLine();
+    });
+
+    Future.forEach(_files, (file) {
+      writeAscii('--$boundary\r\n');
+      writeAscii(_headerForFile(file));
+      return writeStreamToSink(file.finalize(), controller)
+        .then((_) => writeLine());
+    }).then((_) {
+      // TODO(nweiz): pass any errors propagated through this future on to
+      // the stream. See issue 3657.
+      writeAscii('--$boundary--\r\n');
+      controller.close();
+    });
+
+    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<int> _BOUNDARY_CHARACTERS = const <int>[
+    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) {
+    var header =
+        'content-disposition: form-data; name="${_browserEncode(name)}"';
+    if (!isPlainAscii(value)) {
+      header = '$header\r\n'
+          'content-type: text/plain; charset=utf-8\r\n'
+          'content-transfer-encoding: binary';
+    }
+    return '$header\r\n\r\n';
+  }
+
+  /// Returns the header string for a file. The return value is guaranteed to
+  /// contain only ASCII characters.
+  String _headerForFile(MultipartFile file) {
+    var header = 'content-type: ${file.contentType}\r\n'
+      'content-disposition: form-data; name="${_browserEncode(file.field)}"';
+
+    if (file.filename != null) {
+      header = '$header; filename="${_browserEncode(file.filename)}"';
+    }
+    return '$header\r\n\r\n';
+  }
+
+  /// Encode [value] in the same way browsers do.
+  String _browserEncode(String value) {
+    // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
+    // field names and file names, but in practice user agents seem not to
+    // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
+    // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
+    // characters). We follow their behavior.
+    return value.replaceAll(_newlineRegExp, "%0D%0A").replaceAll('"', "%22");
+  }
+
+  /// Returns a randomly-generated multipart boundary string
+  String _boundaryString() {
+    var prefix = "dart-http-boundary-";
+    var list = new List<int>.generate(_BOUNDARY_LENGTH - prefix.length,
+        (index) =>
+            _BOUNDARY_CHARACTERS[_random.nextInt(_BOUNDARY_CHARACTERS.length)],
+        growable: false);
+    return "$prefix${new String.fromCharCodes(list)}";
+  }
+}
diff --git a/pub/http/lib/src/request.dart b/pub/http/lib/src/request.dart
new file mode 100644
index 0000000..67b664c
--- /dev/null
+++ b/pub/http/lib/src/request.dart
@@ -0,0 +1,161 @@
+// 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.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:http_parser/http_parser.dart';
+
+import 'base_request.dart';
+import 'byte_stream.dart';
+import 'utils.dart';
+
+/// An HTTP request where the entire request body is known in advance.
+class Request extends BaseRequest {
+  /// The size of the request body, in bytes. This is calculated from
+  /// [bodyBytes].
+  ///
+  /// The content length cannot be set for [Request], since it's automatically
+  /// calculated from [bodyBytes].
+  int get contentLength => bodyBytes.length;
+
+  set contentLength(int value) {
+    throw new UnsupportedError("Cannot set the contentLength property of "
+        "non-streaming Request objects.");
+  }
+
+  /// The default encoding to use when converting between [bodyBytes] and
+  /// [body]. This is only used if [encoding] hasn't been manually set and if
+  /// the content-type header has no encoding information.
+  Encoding _defaultEncoding;
+
+  /// The encoding used for the request. This encoding is used when converting
+  /// between [bodyBytes] and [body].
+  ///
+  /// If the request has a `Content-Type` header and that header has a `charset`
+  /// parameter, that parameter's value is used as the encoding. Otherwise, if
+  /// [encoding] has been set manually, that encoding is used. If that hasn't
+  /// been set either, this defaults to [UTF8].
+  ///
+  /// If the `charset` parameter's value is not a known [Encoding], reading this
+  /// will throw a [FormatException].
+  ///
+  /// If the request has a `Content-Type` header, setting this will set the
+  /// charset parameter on that header.
+  Encoding get encoding {
+    if (_contentType == null ||
+        !_contentType.parameters.containsKey('charset')) {
+      return _defaultEncoding;
+    }
+    return requiredEncodingForCharset(_contentType.parameters['charset']);
+  }
+
+  set encoding(Encoding value) {
+    _checkFinalized();
+    _defaultEncoding = value;
+    var contentType = _contentType;
+    if (contentType == null) return;
+    _contentType = contentType.change(parameters: {'charset': value.name});
+  }
+
+  // TODO(nweiz): make this return a read-only view
+  /// The bytes comprising the body of the request. This is converted to and
+  /// from [body] using [encoding].
+  ///
+  /// This list should only be set, not be modified in place.
+  Uint8List get bodyBytes => _bodyBytes;
+  Uint8List _bodyBytes;
+
+  set bodyBytes(List<int> value) {
+    _checkFinalized();
+    _bodyBytes = toUint8List(value);
+  }
+
+  /// The body of the request as a string. This is converted to and from
+  /// [bodyBytes] using [encoding].
+  ///
+  /// When this is set, if the request does not yet have a `Content-Type`
+  /// header, one will be added with the type `text/plain`. Then the `charset`
+  /// parameter of the `Content-Type` header (whether new or pre-existing) will
+  /// be set to [encoding] if it wasn't already set.
+  String get body => encoding.decode(bodyBytes);
+
+  set body(String value) {
+    bodyBytes = encoding.encode(value);
+    var contentType = _contentType;
+    if (contentType == null) {
+      _contentType = new MediaType("text", "plain", {'charset': encoding.name});
+    } else if (!contentType.parameters.containsKey('charset')) {
+      _contentType = contentType.change(parameters: {'charset': encoding.name});
+    }
+  }
+
+  /// The form-encoded fields in the body of the request as a map from field
+  /// names to values. The form-encoded body is converted to and from
+  /// [bodyBytes] using [encoding] (in the same way as [body]).
+  ///
+  /// If the request doesn't have a `Content-Type` header of
+  /// `application/x-www-form-urlencoded`, reading this will throw a
+  /// [StateError].
+  ///
+  /// If the request has a `Content-Type` header with a type other than
+  /// `application/x-www-form-urlencoded`, setting this will throw a
+  /// [StateError]. Otherwise, the content type will be set to
+  /// `application/x-www-form-urlencoded`.
+  ///
+  /// This map should only be set, not modified in place.
+  Map<String, String> get bodyFields {
+    var contentType = _contentType;
+    if (contentType == null ||
+        contentType.mimeType != "application/x-www-form-urlencoded") {
+      throw new StateError('Cannot access the body fields of a Request without '
+          'content-type "application/x-www-form-urlencoded".');
+    }
+
+    return Uri.splitQueryString(body, encoding: encoding);
+  }
+
+  set bodyFields(Map<String, String> fields) {
+    var contentType = _contentType;
+    if (contentType == null) {
+      _contentType = new MediaType("application", "x-www-form-urlencoded");
+    } else if (contentType.mimeType != "application/x-www-form-urlencoded") {
+      throw new StateError('Cannot set the body fields of a Request with '
+          'content-type "${contentType.mimeType}".');
+    }
+
+    this.body = mapToQuery(fields, encoding: encoding);
+  }
+
+  /// Creates a new HTTP request.
+  Request(String method, Uri url)
+    : _defaultEncoding = UTF8,
+      _bodyBytes = new Uint8List(0),
+      super(method, url);
+
+  /// Freezes all mutable fields and returns a single-subscription [ByteStream]
+  /// containing the request body.
+  ByteStream finalize() {
+    super.finalize();
+    return new ByteStream.fromBytes(bodyBytes);
+  }
+
+  /// The `Content-Type` header of the request (if it exists) as a
+  /// [MediaType].
+  MediaType get _contentType {
+    var contentType = headers['content-type'];
+    if (contentType == null) return null;
+    return new MediaType.parse(contentType);
+  }
+
+  set _contentType(MediaType value) {
+    headers['content-type'] = value.toString();
+  }
+
+  /// Throw an error if this request has been finalized.
+  void _checkFinalized() {
+    if (!finalized) return;
+    throw new StateError("Can't modify a finalized Request.");
+  }
+}
diff --git a/pub/http/lib/src/response.dart b/pub/http/lib/src/response.dart
new file mode 100644
index 0000000..9fa06ee
--- /dev/null
+++ b/pub/http/lib/src/response.dart
@@ -0,0 +1,95 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:http_parser/http_parser.dart';
+
+import 'base_request.dart';
+import 'base_response.dart';
+import 'streamed_response.dart';
+import 'utils.dart';
+
+/// An HTTP response where the entire response body is known in advance.
+class Response extends BaseResponse {
+  /// The bytes comprising the body of this response.
+  final Uint8List bodyBytes;
+
+  /// The body of the response as a string. This is converted from [bodyBytes]
+  /// using the `charset` parameter of the `Content-Type` header field, if
+  /// available. If it's unavailable or if the encoding name is unknown,
+  /// [LATIN1] is used by default, as per [RFC 2616][].
+  ///
+  /// [RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
+  String get body => _encodingForHeaders(headers).decode(bodyBytes);
+
+  /// Creates a new HTTP response with a string body.
+  Response(
+      String body,
+      int statusCode,
+      {BaseRequest request,
+       Map<String, String> headers: const {},
+       bool isRedirect: false,
+       bool persistentConnection: true,
+       String reasonPhrase})
+    : this.bytes(
+        _encodingForHeaders(headers).encode(body),
+        statusCode,
+        request: request,
+        headers: headers,
+        isRedirect: isRedirect,
+        persistentConnection: persistentConnection,
+        reasonPhrase: reasonPhrase);
+
+  /// Create a new HTTP response with a byte array body.
+  Response.bytes(
+      List<int> bodyBytes,
+      int statusCode,
+      {BaseRequest request,
+       Map<String, String> headers: const {},
+       bool isRedirect: false,
+       bool persistentConnection: true,
+       String reasonPhrase})
+    : bodyBytes = toUint8List(bodyBytes),
+      super(
+        statusCode,
+        contentLength: bodyBytes.length,
+        request: request,
+        headers: headers,
+        isRedirect: isRedirect,
+        persistentConnection: persistentConnection,
+        reasonPhrase: reasonPhrase);
+
+  /// Creates a new HTTP response by waiting for the full body to become
+  /// available from a [StreamedResponse].
+  static Future<Response> fromStream(StreamedResponse response) {
+    return response.stream.toBytes().then((body) {
+      return new Response.bytes(
+          body,
+          response.statusCode,
+          request: response.request,
+          headers: response.headers,
+          isRedirect: response.isRedirect,
+          persistentConnection: response.persistentConnection,
+          reasonPhrase: response.reasonPhrase);
+    });
+  }
+}
+
+/// Returns the encoding to use for a response with the given headers. This
+/// defaults to [LATIN1] if the headers don't specify a charset or
+/// if that charset is unknown.
+Encoding _encodingForHeaders(Map<String, String> headers) =>
+  encodingForCharset(_contentTypeForHeaders(headers).parameters['charset']);
+
+/// Returns the [MediaType] object for the given headers's content-type.
+///
+/// Defaults to `application/octet-stream`.
+MediaType _contentTypeForHeaders(Map<String, String> headers) {
+  var contentType = headers['content-type'];
+  if (contentType != null) return new MediaType.parse(contentType);
+  return new MediaType("application", "octet-stream");
+}
diff --git a/pub/http/lib/src/streamed_request.dart b/pub/http/lib/src/streamed_request.dart
new file mode 100644
index 0000000..6a020bd
--- /dev/null
+++ b/pub/http/lib/src/streamed_request.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+
+import 'byte_stream.dart';
+import 'base_request.dart';
+
+/// An HTTP request where the request body is sent asynchronously after the
+/// connection has been established and the headers have been sent.
+///
+/// When the request is sent via [BaseClient.send], only the headers and
+/// whatever data has already been written to [StreamedRequest.stream] will be
+/// sent immediately. More data will be sent as soon as it's written to
+/// [StreamedRequest.sink], and when the sink is closed the request will end.
+class StreamedRequest extends BaseRequest {
+  /// The sink to which to write data that will be sent as the request body.
+  /// This may be safely written to before the request is sent; the data will be
+  /// buffered.
+  ///
+  /// Closing this signals the end of the request.
+  EventSink<List<int>> get sink => _controller.sink;
+
+  /// The controller for [sink], from which [BaseRequest] will read data for
+  /// [finalize].
+  final StreamController<List<int>> _controller;
+
+  /// Creates a new streaming request.
+  StreamedRequest(String method, Uri url)
+    : _controller = new StreamController<List<int>>(sync: true),
+      super(method, url);
+
+  /// Freezes all mutable fields other than [stream] and returns a
+  /// single-subscription [ByteStream] that emits the data being written to
+  /// [sink].
+  ByteStream finalize() {
+    super.finalize();
+    return new ByteStream(_controller.stream);
+  }
+}
diff --git a/pub/http/lib/src/streamed_response.dart b/pub/http/lib/src/streamed_response.dart
new file mode 100644
index 0000000..69d8356
--- /dev/null
+++ b/pub/http/lib/src/streamed_response.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+
+import 'byte_stream.dart';
+import 'base_response.dart';
+import 'base_request.dart';
+import 'utils.dart';
+
+/// An HTTP response where the response body is received asynchronously after
+/// the headers have been received.
+class StreamedResponse extends BaseResponse {
+  /// The stream from which the response body data can be read. This should
+  /// always be a single-subscription stream.
+  final ByteStream stream;
+
+  /// Creates a new streaming response. [stream] should be a single-subscription
+  /// stream.
+  StreamedResponse(
+      Stream<List<int>> stream,
+      int statusCode,
+      {int contentLength,
+       BaseRequest request,
+       Map<String, String> headers: const {},
+       bool isRedirect: false,
+       bool persistentConnection: true,
+       String reasonPhrase})
+    : this.stream = toByteStream(stream),
+      super(
+          statusCode,
+          contentLength: contentLength,
+          request: request,
+          headers: headers,
+          isRedirect: isRedirect,
+          persistentConnection: persistentConnection,
+          reasonPhrase: reasonPhrase);
+}
diff --git a/pub/http/lib/src/utils.dart b/pub/http/lib/src/utils.dart
new file mode 100644
index 0000000..789c2d9
--- /dev/null
+++ b/pub/http/lib/src/utils.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2012, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'byte_stream.dart';
+
+/// Converts a [Map] from parameter names to values to a URL query string.
+///
+///     mapToQuery({"foo": "bar", "baz": "bang"});
+///     //=> "foo=bar&baz=bang"
+String mapToQuery(Map<String, String> map, {Encoding encoding}) {
+  var pairs = <List<String>>[];
+  map.forEach((key, value) =>
+      pairs.add([Uri.encodeQueryComponent(key, encoding: encoding),
+                 Uri.encodeQueryComponent(value, encoding: encoding)]));
+  return pairs.map((pair) => "${pair[0]}=${pair[1]}").join("&");
+}
+
+/// Like [String.split], but only splits on the first occurrence of the pattern.
+/// This will always return an array of two elements or fewer.
+///
+///     split1("foo,bar,baz", ","); //=> ["foo", "bar,baz"]
+///     split1("foo", ","); //=> ["foo"]
+///     split1("", ","); //=> []
+List<String> split1(String toSplit, String pattern) {
+  if (toSplit.isEmpty) return <String>[];
+
+  var index = toSplit.indexOf(pattern);
+  if (index == -1) return [toSplit];
+  return [
+    toSplit.substring(0, index),
+    toSplit.substring(index + pattern.length)
+  ];
+}
+
+/// Returns the [Encoding] that corresponds to [charset]. Returns [fallback] if
+/// [charset] is null or if no [Encoding] was found that corresponds to
+/// [charset].
+Encoding encodingForCharset(String charset, [Encoding fallback = LATIN1]) {
+  if (charset == null) return fallback;
+  var encoding = Encoding.getByName(charset);
+  return encoding == null ? fallback : encoding;
+}
+
+
+/// Returns the [Encoding] that corresponds to [charset]. Throws a
+/// [FormatException] if no [Encoding] was found that corresponds to [charset].
+/// [charset] may not be null.
+Encoding requiredEncodingForCharset(String charset) {
+  var encoding = Encoding.getByName(charset);
+  if (encoding != null) return encoding;
+  throw new FormatException('Unsupported encoding "$charset".');
+}
+
+/// A regular expression that matches strings that are composed entirely of
+/// ASCII-compatible characters.
+final RegExp _ASCII_ONLY = new RegExp(r"^[\x00-\x7F]+$");
+
+/// Returns whether [string] is composed entirely of ASCII-compatible
+/// characters.
+bool isPlainAscii(String string) => _ASCII_ONLY.hasMatch(string);
+
+/// Converts [input] into a [Uint8List].
+///
+/// If [input] is a [TypedData], this just returns a view on [input].
+Uint8List toUint8List(List<int> input) {
+  if (input is Uint8List) return input;
+  if (input is TypedData) {
+    // TODO(nweiz): remove "as" when issue 11080 is fixed.
+    return new Uint8List.view((input as TypedData).buffer);
+  }
+  return new Uint8List.fromList(input);
+}
+
+/// If [stream] is already a [ByteStream], returns it. Otherwise, wraps it in a
+/// [ByteStream].
+ByteStream toByteStream(Stream<List<int>> stream) {
+  if (stream is ByteStream) return stream;
+  return new ByteStream(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<T> onDone<T>(Stream<T> stream, void onDone()) =>
+    stream.transform(new StreamTransformer.fromHandlers(handleDone: (sink) {
+      sink.close();
+      onDone();
+    }));
+
+// TODO(nweiz): remove this when issue 7786 is fixed.
+/// Pipes all data and errors from [stream] into [sink]. When [stream] is done,
+/// [sink] is closed and the returned [Future] is completed.
+Future store(Stream stream, EventSink sink) {
+  var completer = new Completer();
+  stream.listen(sink.add,
+      onError: sink.addError,
+      onDone: () {
+        sink.close();
+        completer.complete();
+      });
+  return completer.future;
+}
+
+/// Pipes all data and errors from [stream] into [sink]. Completes [Future] once
+/// [stream] is done. Unlike [store], [sink] remains open after [stream] is
+/// done.
+Future writeStreamToSink(Stream stream, EventSink sink) {
+  var completer = new Completer();
+  stream.listen(sink.add,
+      onError: sink.addError,
+      onDone: () => completer.complete());
+  return completer.future;
+}
+
+/// A pair of values.
+class Pair<E, F> {
+  E first;
+  F last;
+
+  Pair(this.first, this.last);
+
+  String toString() => '($first, $last)';
+
+  bool operator==(other) {
+    if (other is! Pair) return false;
+    return other.first == first && other.last == last;
+  }
+
+  int get hashCode => first.hashCode ^ last.hashCode;
+}
+
+/// Configures [future] so that its result (success or exception) is passed on
+/// to [completer].
+void chainToCompleter(Future future, Completer completer) {
+  future.then(completer.complete, onError: completer.completeError);
+}
diff --git a/pub/http/lib/testing.dart b/pub/http/lib/testing.dart
new file mode 100644
index 0000000..d5a7874
--- /dev/null
+++ b/pub/http/lib/testing.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2012, 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.
+
+/// This library contains testing classes for the HTTP library.
+///
+/// The [MockClient] class is a drop-in replacement for `http.Client` that
+/// allows test code to set up a local request handler in order to fake a server
+/// that responds to HTTP requests:
+///
+///     import 'dart:convert';
+///     import 'package:http/testing.dart';
+///
+///     var client = new MockClient((request) async {
+///       if (request.url.path != "/data.json") {
+///         return new Response("", 404);
+///       }
+///       return new Response(
+///           JSON.encode({
+///             'numbers': [1, 4, 15, 19, 214]
+///           }),
+///           200,
+///           headers: {'content-type': 'application/json'});
+///     });
+export 'src/mock_client.dart';
diff --git a/pub/http/pubspec.yaml b/pub/http/pubspec.yaml
new file mode 100644
index 0000000..b922a51
--- /dev/null
+++ b/pub/http/pubspec.yaml
@@ -0,0 +1,15 @@
+name: http
+version: 0.11.3+12
+author: "Dart Team <misc@dartlang.org>"
+homepage: https://github.com/dart-lang/http
+description: A composable, Future-based API for making HTTP requests.
+dependencies:
+  async: "^1.10.0"
+  collection: "^1.5.0"
+  http_parser: ">=0.0.1 <4.0.0"
+  path: ">=0.9.0 <2.0.0"
+  stack_trace: ">=0.9.1 <2.0.0"
+dev_dependencies:
+  unittest: ">=0.9.0 <0.12.0"
+environment:
+  sdk: ">=1.23.0-dev.0.0 <2.0.0"
diff --git a/pub/package_resolver/BUILD.gn b/pub/package_resolver/BUILD.gn
index 29af794..fce5ff2 100644
--- a/pub/package_resolver/BUILD.gn
+++ b/pub/package_resolver/BUILD.gn
@@ -12,7 +12,7 @@
   deps = [
     "//third_party/dart-pkg/pub/path",
     "//third_party/dart-pkg/pub/package_config",
-    "//apps/modules/packages/flutter-http",
+    "//third_party/dart-pkg/pub/http",
     "//third_party/dart-pkg/pub/collection",
   ]
 }