Reland "[dart:_http] Allow the embedder to prohibit HTTP traffic."

This is a reland of 6b44c631ad

Original change's description:
> [dart:_http] Allow the embedder to prohibit HTTP traffic.
> 
> This can be configured by embedders by setting the `_embedderAllowsHttp`
> variable. It can also be overridden by client apps by introducing a
> `Zone` with the variable `#dart.library.io.allow_http` set to `true` or
> `false`.
> 
> The default behavior in SDK is unchanged.
> 
> Bug: https://github.com/dart-lang/sdk/issues/40548
> Change-Id: Ifec0ad2d759de4bbb836644840d8c312e560f285
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/138911
> Commit-Queue: Martin Kustermann <kustermann@google.com>
> Reviewed-by: Jonas Termansen <sortie@google.com>

Bug: https://github.com/dart-lang/sdk/issues/40548
Change-Id: I6ced6c1248b3b6687f6c7d998e5206b2b385f00b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/142446
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Mehmet Fidanboylu 2020-04-06 17:46:44 +00:00 committed by commit-bot@chromium.org
parent f44b5324e3
commit 74a20b7bfd
20 changed files with 328 additions and 2 deletions

View file

@ -0,0 +1,13 @@
// Copyright (c) 2020, 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.
// @dart = 2.6
part of dart._http;
/// Embedder-specific `dart:_http` configuration.
/// [HttpClient] will disallow HTTP URLs if this value is set to `false`.
@pragma("vm:entry-point")
bool _embedderAllowsHttp = true;

View file

@ -24,6 +24,7 @@ import 'dart:io';
import 'dart:typed_data';
part 'crypto.dart';
part 'embedder_config.dart';
part 'http_date.dart';
part 'http_headers.dart';
part 'http_impl.dart';

View file

@ -2266,6 +2266,32 @@ class _HttpClient implements HttpClient {
});
}
/// Whether HTTP requests are currently allowed.
///
/// If the [Zone] variable `#dart.library.io.allow_http` is set to a boolean,
/// it determines whether the HTTP protocol is allowed. If the zone variable
/// is set to any other non-null value, HTTP is not allowed.
/// Otherwise, if the `dart.library.io.allow_http` environment flag
/// is set to `false`, HTTP is not allowed.
/// Otherwise, [_embedderAllowsHTTP] determines the result.
bool get _isHttpAllowed {
final zoneOverride = Zone.current[#dart.library.io.allow_http];
if (zoneOverride != null) return true == zoneOverride;
bool envOverride =
bool.fromEnvironment("dart.library.io.allow_http", defaultValue: true);
return envOverride && _embedderAllowsHttp;
}
bool _isLoopback(String host) {
if (host.isEmpty) return false;
if ("localhost" == host) return true;
try {
return InternetAddress(host).isLoopback;
} on ArgumentError {
return false;
}
}
Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
if (_closing) {
throw new StateError("Client is closed");
@ -2286,7 +2312,12 @@ class _HttpClient implements HttpClient {
}
}
bool isSecure = (uri.scheme == "https");
bool isSecure = uri.isScheme("https");
if (!_isHttpAllowed && !isSecure && !_isLoopback(uri.host)) {
throw new StateError(
"Insecure HTTP is not allowed by the current platform: $uri");
}
int port = uri.port;
if (port == 0) {
port =

View file

@ -0,0 +1,11 @@
// Copyright (c) 2020, 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.
part of dart._http;
/// Embedder-specific `dart:_http` configuration.
/// [HttpClient] will disallow HTTP URLs if this value is set to `false`.
@pragma("vm:entry-point")
bool _embedderAllowsHttp = true;

View file

@ -23,6 +23,7 @@ import 'dart:io';
import 'dart:typed_data';
part 'crypto.dart';
part 'embedder_config.dart';
part 'http_date.dart';
part 'http_headers.dart';
part 'http_impl.dart';

View file

@ -2267,6 +2267,32 @@ class _HttpClient implements HttpClient {
});
}
/// Whether HTTP requests are currently allowed.
///
/// If the [Zone] variable `#dart.library.io.allow_http` is set to a boolean,
/// it determines whether the HTTP protocol is allowed. If the zone variable
/// is set to any other non-null value, HTTP is not allowed.
/// Otherwise, if the `dart.library.io.allow_http` environment flag
/// is set to `false`, HTTP is not allowed.
/// Otherwise, [_embedderAllowsHttp] determines the result.
bool get _isHttpAllowed {
final zoneOverride = Zone.current[#dart.library.io.allow_http];
if (zoneOverride != null) return true == zoneOverride;
bool envOverride =
bool.fromEnvironment("dart.library.io.allow_http", defaultValue: true);
return envOverride && _embedderAllowsHttp;
}
bool _isLoopback(String host) {
if (host.isEmpty) return false;
if ("localhost" == host) return true;
try {
return InternetAddress(host).isLoopback;
} on ArgumentError {
return false;
}
}
Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
if (_closing) {
throw new StateError("Client is closed");
@ -2287,7 +2313,12 @@ class _HttpClient implements HttpClient {
}
}
bool isSecure = (uri.scheme == "https");
bool isSecure = uri.isScheme("https");
if (!_isHttpAllowed && !isSecure && !_isLoopback(uri.host)) {
throw new StateError(
"Insecure HTTP is not allowed by the current platform: $uri");
}
int port = uri.port;
if (port == 0) {
port =

View file

@ -0,0 +1,69 @@
// Copyright (c) 2020, 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.
// SharedOptions=-Ddart.library.io.allow_http=false
import 'dart:async';
import 'dart:io';
import "package:async_helper/async_helper.dart";
import 'http_ban_http_normal_test.dart';
import 'http_bind_test.dart';
Future<void> testWithHostname() async {
await testBanHttp(await getLocalHostIP(), (httpClient, httpUri) async {
asyncExpectThrows(
() async => await httpClient.getUrl(httpUri), (e) => e is StateError);
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: 'foo'}),
(e) => e is StateError);
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: false}),
(e) => e is StateError);
await asyncTest(() => runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: true}));
});
}
Future<void> testWithLoopback() async {
await testBanHttp("127.0.0.1", (httpClient, uri) async {
await asyncTest(
() => httpClient.getUrl(Uri.parse('http://localhost:${uri.port}')));
await asyncTest(
() => httpClient.getUrl(Uri.parse('http://127.0.0.1:${uri.port}')));
});
}
Future<void> testWithIPv6() async {
if (await supportsIPV6()) {
await testBanHttp("::1", (httpClient, uri) async {
await asyncTest(() => httpClient.getUrl(uri));
});
}
}
Future<void> testWithHTTPS() async {
await testBanHttp(await getLocalHostIP(), (httpClient, uri) async {
asyncExpectThrows(
() => httpClient.getUrl(Uri(
scheme: 'https',
host: uri.host,
port: uri.port,
)),
(e) => e is SocketException || e is HandshakeException);
});
}
main() {
asyncStart();
Future.wait(<Future>[
testWithHostname(),
testWithLoopback(),
testWithIPv6(),
testWithHTTPS(),
]).then((_) => asyncEnd());
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2020, 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_helper/async_helper.dart";
Future<String> getLocalHostIP() async {
final interfaces = await NetworkInterface.list(
includeLoopback: false, type: InternetAddressType.IPv4);
return interfaces.first.addresses.first.address;
}
Future<void> testBanHttp(String serverHost,
Future<void> testCode(HttpClient client, Uri uri)) async {
final httpClient = new HttpClient();
final server = await HttpServer.bind(serverHost, 0);
final uri = Uri(scheme: 'http', host: serverHost, port: server.port);
try {
await testCode(httpClient, uri);
} finally {
httpClient.close(force: true);
await server.close();
}
}
main() async {
await asyncTest(() async {
final host = await getLocalHostIP();
// Normal HTTP request succeeds.
await testBanHttp(host, (httpClient, uri) async {
await asyncTest(() => httpClient.getUrl(uri));
});
// We can ban HTTP explicitly.
await testBanHttp(host, (httpClient, uri) async {
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(uri),
zoneValues: {#dart.library.io.allow_http: false}),
(e) => e is StateError);
});
});
}

View file

@ -16,6 +16,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -17,6 +17,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -17,6 +17,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -18,6 +18,7 @@ import "dart:typed_data";
import "dart:isolate";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -13,6 +13,8 @@ no_lazy_dispatchers_test: SkipByDesign # KBC interpreter doesn't support --no_la
[ $system == android ]
entrypoints_verification_test: Skip # Requires shared objects which the test script doesn't "adb push".
io/http_ban_http_embedder_test: Skip # Requires http server bound to non-loopback; not provided by system.
io/http_ban_http_normal_test: Skip # Requires http server bound to non-loopback; not provided by system.
[ $arch == ia32 && $builder_tag == optimization_counter_threshold ]
io/file_lock_test: SkipSlow # Timeout

View file

@ -0,0 +1,69 @@
// Copyright (c) 2020, 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.
// SharedOptions=-Ddart.library.io.allow_http=false
import 'dart:async';
import 'dart:io';
import "package:async_helper/async_helper.dart";
import 'http_ban_http_normal_test.dart';
import 'http_bind_test.dart';
Future<void> testWithHostname() async {
await testBanHttp(await getLocalHostIP(), (httpClient, httpUri) async {
asyncExpectThrows(
() async => await httpClient.getUrl(httpUri), (e) => e is StateError);
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: 'foo'}),
(e) => e is StateError);
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: false}),
(e) => e is StateError);
await asyncTest(() => runZoned(() => httpClient.getUrl(httpUri),
zoneValues: {#dart.library.io.allow_http: true}));
});
}
Future<void> testWithLoopback() async {
await testBanHttp("127.0.0.1", (httpClient, uri) async {
await asyncTest(
() => httpClient.getUrl(Uri.parse('http://localhost:${uri.port}')));
await asyncTest(
() => httpClient.getUrl(Uri.parse('http://127.0.0.1:${uri.port}')));
});
}
Future<void> testWithIPv6() async {
if (await supportsIPV6()) {
await testBanHttp("::1", (httpClient, uri) async {
await asyncTest(() => httpClient.getUrl(uri));
});
}
}
Future<void> testWithHTTPS() async {
await testBanHttp(await getLocalHostIP(), (httpClient, uri) async {
asyncExpectThrows(
() => httpClient.getUrl(Uri(
scheme: 'https',
host: uri.host,
port: uri.port,
)),
(e) => e is SocketException || e is HandshakeException);
});
}
main() {
asyncStart();
Future.wait(<Future>[
testWithHostname(),
testWithLoopback(),
testWithIPv6(),
testWithHTTPS(),
]).then((_) => asyncEnd());
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2020, 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_helper/async_helper.dart";
Future<String> getLocalHostIP() async {
final interfaces = await NetworkInterface.list(
includeLoopback: false, type: InternetAddressType.IPv4);
return interfaces.first.addresses.first.address;
}
Future<void> testBanHttp(String serverHost,
Future<void> testCode(HttpClient client, Uri uri)) async {
final httpClient = new HttpClient();
final server = await HttpServer.bind(serverHost, 0);
final uri = Uri(scheme: 'http', host: serverHost, port: server.port);
try {
await testCode(httpClient, uri);
} finally {
httpClient.close(force: true);
await server.close();
}
}
main() async {
await asyncTest(() async {
final host = await getLocalHostIP();
// Normal HTTP request succeeds.
await testBanHttp(host, (httpClient, uri) async {
await asyncTest(() => httpClient.getUrl(uri));
});
// We can ban HTTP explicitly.
await testBanHttp(host, (httpClient, uri) async {
asyncExpectThrows(
() async => await runZoned(() => httpClient.getUrl(uri),
zoneValues: {#dart.library.io.allow_http: false}),
(e) => e is StateError);
});
});
}

View file

@ -16,6 +16,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -17,6 +17,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -17,6 +17,7 @@ import "dart:typed_data";
import "package:expect/expect.dart";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -18,6 +18,7 @@ import "dart:typed_data";
import "dart:isolate";
part "../../../sdk/lib/_http/crypto.dart";
part "../../../sdk/lib/_http/embedder_config.dart";
part "../../../sdk/lib/_http/http_impl.dart";
part "../../../sdk/lib/_http/http_date.dart";
part "../../../sdk/lib/_http/http_parser.dart";

View file

@ -13,6 +13,8 @@ no_lazy_dispatchers_test: SkipByDesign # KBC interpreter doesn't support --no_la
[ $system == android ]
entrypoints_verification_test: Skip # Requires shared objects which the test script doesn't "adb push".
io/http_ban_http_embedder_test: Skip # Requires http server bound to non-loopback; not provided by system.
io/http_ban_http_normal_test: Skip # Requires http server bound to non-loopback; not provided by system.
[ $arch == ia32 && $builder_tag == optimization_counter_threshold ]
io/file_lock_test: SkipSlow # Timeout