mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:48:25 +00:00
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:
parent
f44b5324e3
commit
74a20b7bfd
13
sdk/lib/_http/embedder_config.dart
Normal file
13
sdk/lib/_http/embedder_config.dart
Normal 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;
|
|
@ -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';
|
||||
|
|
|
@ -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 =
|
||||
|
|
11
sdk_nnbd/lib/_http/embedder_config.dart
Normal file
11
sdk_nnbd/lib/_http/embedder_config.dart
Normal 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;
|
|
@ -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';
|
||||
|
|
|
@ -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 =
|
||||
|
|
69
tests/standalone/io/http_ban_http_embedder_test.dart
Normal file
69
tests/standalone/io/http_ban_http_embedder_test.dart
Normal 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());
|
||||
}
|
44
tests/standalone/io/http_ban_http_normal_test.dart
Normal file
44
tests/standalone/io/http_ban_http_normal_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
69
tests/standalone_2/io/http_ban_http_embedder_test.dart
Normal file
69
tests/standalone_2/io/http_ban_http_embedder_test.dart
Normal 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());
|
||||
}
|
44
tests/standalone_2/io/http_ban_http_normal_test.dart
Normal file
44
tests/standalone_2/io/http_ban_http_normal_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue