Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d46b5b3

Browse files
aamzanderso
andauthored
Provide better messaging when user attempts to use non-secure http connection. (#26226)
* Provide better message when user attempts to use non-secure http connection. Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
1 parent bbc7fe4 commit d46b5b3

File tree

6 files changed

+176
-0
lines changed

6 files changed

+176
-0
lines changed

lib/io/dart_io.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains,
3636
Dart_Handle set_domain_network_policy_result = Dart_Invoke(
3737
embedder_config_type, ToDart("_setDomainPolicies"), 1, dart_args);
3838
FML_CHECK(!LogIfError(set_domain_network_policy_result));
39+
40+
Dart_Handle ui_lib = Dart_LookupLibrary(ToDart("dart:ui"));
41+
Dart_Handle dart_validate_args[1];
42+
dart_validate_args[0] = ToDart(may_insecurely_connect_to_all_domains);
43+
Dart_Handle http_connection_hook_closure =
44+
Dart_Invoke(ui_lib, ToDart("_getHttpConnectionHookClosure"),
45+
/*number_of_arguments=*/1, dart_validate_args);
46+
FML_CHECK(!LogIfError(http_connection_hook_closure));
47+
Dart_Handle http_lib = Dart_LookupLibrary(ToDart("dart:_http"));
48+
FML_CHECK(!LogIfError(http_lib));
49+
Dart_Handle set_http_connection_hook_result = Dart_SetField(
50+
http_lib, ToDart("_httpConnectionHook"), http_connection_hook_closure);
51+
FML_CHECK(!LogIfError(set_http_connection_hook_result));
3952
}
4053

4154
} // namespace flutter

lib/ui/hooks.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,44 @@ void _invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone zon
224224
});
225225
}
226226
}
227+
228+
bool _isLoopback(String host) {
229+
if (host.isEmpty) {
230+
return false;
231+
}
232+
if ('localhost' == host) {
233+
return true;
234+
}
235+
try {
236+
return InternetAddress(host).isLoopback;
237+
} on ArgumentError {
238+
return false;
239+
}
240+
}
241+
242+
/// Loopback connections are always allowed.
243+
/// Zone override with 'flutter.io.allow_http' takes first priority.
244+
/// If zone override is not provided, engine setting is checked.
245+
@pragma('vm:entry-point')
246+
// ignore: unused_element
247+
void Function(Uri) _getHttpConnectionHookClosure(bool mayInsecurelyConnectToAllDomains) {
248+
return (Uri uri) {
249+
if (_isLoopback(uri.host)) {
250+
return;
251+
}
252+
final dynamic zoneOverride = Zone.current[#flutter.io.allow_http];
253+
if (zoneOverride == true) {
254+
return;
255+
}
256+
if (zoneOverride == false && uri.isScheme('http')) {
257+
// Going to throw
258+
} else if (mayInsecurelyConnectToAllDomains || uri.isScheme('https')) {
259+
// In absence of zone override, if engine setting allows the connection
260+
// or if connection is to `https`, allow the connection.
261+
return;
262+
}
263+
throw UnsupportedError(
264+
'Non-https connection "$uri" is not supported by the platform. '
265+
'Refer to https://flutter.dev/docs/release/breaking-changes/network-policy-ios-android.');
266+
};
267+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2021 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.9
6+
7+
import 'dart:async';
8+
import 'dart:io';
9+
10+
import 'package:litetest/litetest.dart';
11+
12+
import 'http_disallow_http_connections_test.dart';
13+
14+
void main() {
15+
test('Normal HTTP request succeeds', () async {
16+
final String host = await getLocalHostIP();
17+
await bindServerAndTest(host, (HttpClient httpClient, Uri uri) async {
18+
await httpClient.getUrl(uri);
19+
});
20+
});
21+
22+
test('We can ban HTTP explicitly.', () async {
23+
final String host = await getLocalHostIP();
24+
await bindServerAndTest(host, (HttpClient httpClient, Uri uri) async {
25+
asyncExpectThrows<UnsupportedError>(
26+
() async => runZoned(() => httpClient.getUrl(uri),
27+
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: false}));
28+
});
29+
});
30+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2021 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.9
6+
7+
// FlutterTesterOptions=--disallow-insecure-connections
8+
9+
import 'dart:async';
10+
import 'dart:io';
11+
12+
import 'package:litetest/litetest.dart';
13+
14+
/// Asserts that `callback` throws an exception of type `T`.
15+
Future<void> asyncExpectThrows<T>(Function callback) async {
16+
bool threw = false;
17+
try {
18+
await callback();
19+
} catch (e) {
20+
expect(e is T, true);
21+
threw = true;
22+
}
23+
expect(threw, true);
24+
}
25+
26+
Future<String> getLocalHostIP() async {
27+
final List<NetworkInterface> interfaces = await NetworkInterface.list(
28+
includeLoopback: false, type: InternetAddressType.IPv4);
29+
return interfaces.first.addresses.first.address;
30+
}
31+
32+
Future<void> bindServerAndTest(String serverHost,
33+
Future<void> Function(HttpClient client, Uri uri) testCode) async {
34+
final HttpClient httpClient = HttpClient();
35+
final HttpServer server = await HttpServer.bind(serverHost, 0);
36+
final Uri uri = Uri(scheme: 'http', host: serverHost, port: server.port);
37+
try {
38+
await testCode(httpClient, uri);
39+
} finally {
40+
httpClient.close(force: true);
41+
await server.close();
42+
}
43+
}
44+
45+
/// Answers the question whether this computer supports binding to IPv6 addresses.
46+
Future<bool> _supportsIPv6() async {
47+
try {
48+
final ServerSocket socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
49+
await socket.close();
50+
return true;
51+
} on SocketException catch (_) {
52+
return false;
53+
}
54+
}
55+
56+
void main() {
57+
test('testWithHostname', () async {
58+
await bindServerAndTest(await getLocalHostIP(), (HttpClient httpClient, Uri httpUri) async {
59+
asyncExpectThrows<UnsupportedError>(
60+
() async => httpClient.getUrl(httpUri));
61+
asyncExpectThrows<UnsupportedError>(
62+
() async => runZoned(() => httpClient.getUrl(httpUri),
63+
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: 'foo'}));
64+
asyncExpectThrows<UnsupportedError>(
65+
() async => runZoned(() => httpClient.getUrl(httpUri),
66+
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: false}));
67+
await runZoned(() => httpClient.getUrl(httpUri),
68+
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: true});
69+
});
70+
});
71+
72+
test('testWithLoopback', () async {
73+
await bindServerAndTest('127.0.0.1', (HttpClient httpClient, Uri uri) async {
74+
await httpClient.getUrl(Uri.parse('http://localhost:${uri.port}'));
75+
await httpClient.getUrl(Uri.parse('http://127.0.0.1:${uri.port}'));
76+
});
77+
});
78+
79+
test('testWithIPV6', () async {
80+
if (await _supportsIPv6()) {
81+
await bindServerAndTest('::1', (HttpClient httpClient, Uri uri) async {
82+
await httpClient.getUrl(uri);
83+
});
84+
}
85+
});
86+
}

testing/dart/window_hooks_integration_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'dart:collection' as collection;
1313
import 'dart:convert';
1414
import 'dart:developer' as developer;
1515
import 'dart:math' as math;
16+
import 'dart:io' show InternetAddress;
1617
import 'dart:nativewrappers';
1718
import 'dart:typed_data';
1819

testing/run_tests.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ def RunDartTest(build_dir, test_packages, dart_file, verbose_dart_snapshot, mult
259259
if not enable_observatory:
260260
command_args.append('--disable-observatory')
261261

262+
dart_file_contents = open(dart_file, 'r')
263+
custom_options = re.findall("// FlutterTesterOptions=(.*)", dart_file_contents.read())
264+
dart_file_contents.close()
265+
command_args.extend(custom_options)
266+
262267
command_args += [
263268
'--use-test-fonts',
264269
kernel_file_output

0 commit comments

Comments
 (0)