Skip to content

Commit

Permalink
Reland "Provide better messaging when user attempts to use non-secure…
Browse files Browse the repository at this point in the history
… http connection. (flutter#26226)" (flutter#26400)

This reverts commit c7ef259 as dart sdk with corresponding changes has landed in internal repo.
  • Loading branch information
aam authored May 25, 2021
1 parent 3e44ba2 commit 90235a8
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/io/dart_io.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains,
Dart_Handle set_domain_network_policy_result = Dart_Invoke(
embedder_config_type, ToDart("_setDomainPolicies"), 1, dart_args);
FML_CHECK(!LogIfError(set_domain_network_policy_result));

Dart_Handle ui_lib = Dart_LookupLibrary(ToDart("dart:ui"));
Dart_Handle dart_validate_args[1];
dart_validate_args[0] = ToDart(may_insecurely_connect_to_all_domains);
Dart_Handle http_connection_hook_closure =
Dart_Invoke(ui_lib, ToDart("_getHttpConnectionHookClosure"),
/*number_of_arguments=*/1, dart_validate_args);
FML_CHECK(!LogIfError(http_connection_hook_closure));
Dart_Handle http_lib = Dart_LookupLibrary(ToDart("dart:_http"));
FML_CHECK(!LogIfError(http_lib));
Dart_Handle set_http_connection_hook_result = Dart_SetField(
http_lib, ToDart("_httpConnectionHook"), http_connection_hook_closure);
FML_CHECK(!LogIfError(set_http_connection_hook_result));
}

} // namespace flutter
41 changes: 41 additions & 0 deletions lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,44 @@ void _invoke3<A1, A2, A3>(void Function(A1 a1, A2 a2, A3 a3)? callback, Zone zon
});
}
}

bool _isLoopback(String host) {
if (host.isEmpty) {
return false;
}
if ('localhost' == host) {
return true;
}
try {
return InternetAddress(host).isLoopback;
} on ArgumentError {
return false;
}
}

/// Loopback connections are always allowed.
/// Zone override with 'flutter.io.allow_http' takes first priority.
/// If zone override is not provided, engine setting is checked.
@pragma('vm:entry-point')
// ignore: unused_element
void Function(Uri) _getHttpConnectionHookClosure(bool mayInsecurelyConnectToAllDomains) {
return (Uri uri) {
if (_isLoopback(uri.host)) {
return;
}
final dynamic zoneOverride = Zone.current[#flutter.io.allow_http];
if (zoneOverride == true) {
return;
}
if (zoneOverride == false && uri.isScheme('http')) {
// Going to throw
} else if (mayInsecurelyConnectToAllDomains || uri.isScheme('https')) {
// In absence of zone override, if engine setting allows the connection
// or if connection is to `https`, allow the connection.
return;
}
throw UnsupportedError(
'Non-https connection "$uri" is not supported by the platform. '
'Refer to https://flutter.dev/docs/release/breaking-changes/network-policy-ios-android.');
};
}
30 changes: 30 additions & 0 deletions testing/dart/http_allow_http_connections_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2021 The Flutter Authors. 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.9

import 'dart:async';
import 'dart:io';

import 'package:litetest/litetest.dart';

import 'http_disallow_http_connections_test.dart';

void main() {
test('Normal HTTP request succeeds', () async {
final String host = await getLocalHostIP();
await bindServerAndTest(host, (HttpClient httpClient, Uri uri) async {
await httpClient.getUrl(uri);
});
});

test('We can ban HTTP explicitly.', () async {
final String host = await getLocalHostIP();
await bindServerAndTest(host, (HttpClient httpClient, Uri uri) async {
asyncExpectThrows<UnsupportedError>(
() async => runZoned(() => httpClient.getUrl(uri),
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: false}));
});
});
}
86 changes: 86 additions & 0 deletions testing/dart/http_disallow_http_connections_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2021 The Flutter Authors. 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.9

// FlutterTesterOptions=--disallow-insecure-connections

import 'dart:async';
import 'dart:io';

import 'package:litetest/litetest.dart';

/// Asserts that `callback` throws an exception of type `T`.
Future<void> asyncExpectThrows<T>(Function callback) async {
bool threw = false;
try {
await callback();
} catch (e) {
expect(e is T, true);
threw = true;
}
expect(threw, true);
}

Future<String> getLocalHostIP() async {
final List<NetworkInterface> interfaces = await NetworkInterface.list(
includeLoopback: false, type: InternetAddressType.IPv4);
return interfaces.first.addresses.first.address;
}

Future<void> bindServerAndTest(String serverHost,
Future<void> Function(HttpClient client, Uri uri) testCode) async {
final HttpClient httpClient = HttpClient();
final HttpServer server = await HttpServer.bind(serverHost, 0);
final Uri uri = Uri(scheme: 'http', host: serverHost, port: server.port);
try {
await testCode(httpClient, uri);
} finally {
httpClient.close(force: true);
await server.close();
}
}

/// Answers the question whether this computer supports binding to IPv6 addresses.
Future<bool> _supportsIPv6() async {
try {
final ServerSocket socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
await socket.close();
return true;
} on SocketException catch (_) {
return false;
}
}

void main() {
test('testWithHostname', () async {
await bindServerAndTest(await getLocalHostIP(), (HttpClient httpClient, Uri httpUri) async {
asyncExpectThrows<UnsupportedError>(
() async => httpClient.getUrl(httpUri));
asyncExpectThrows<UnsupportedError>(
() async => runZoned(() => httpClient.getUrl(httpUri),
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: 'foo'}));
asyncExpectThrows<UnsupportedError>(
() async => runZoned(() => httpClient.getUrl(httpUri),
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: false}));
await runZoned(() => httpClient.getUrl(httpUri),
zoneValues: <dynamic, dynamic>{#flutter.io.allow_http: true});
});
});

test('testWithLoopback', () async {
await bindServerAndTest('127.0.0.1', (HttpClient httpClient, Uri uri) async {
await httpClient.getUrl(Uri.parse('http://localhost:${uri.port}'));
await httpClient.getUrl(Uri.parse('http://127.0.0.1:${uri.port}'));
});
});

test('testWithIPV6', () async {
if (await _supportsIPv6()) {
await bindServerAndTest('::1', (HttpClient httpClient, Uri uri) async {
await httpClient.getUrl(uri);
});
}
});
}
1 change: 1 addition & 0 deletions testing/dart/window_hooks_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'dart:collection' as collection;
import 'dart:convert';
import 'dart:developer' as developer;
import 'dart:math' as math;
import 'dart:io' show InternetAddress;
import 'dart:nativewrappers';
import 'dart:typed_data';

Expand Down
5 changes: 5 additions & 0 deletions testing/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ def RunDartTest(build_dir, test_packages, dart_file, verbose_dart_snapshot, mult
if not enable_observatory:
command_args.append('--disable-observatory')

dart_file_contents = open(dart_file, 'r')
custom_options = re.findall("// FlutterTesterOptions=(.*)", dart_file_contents.read())
dart_file_contents.close()
command_args.extend(custom_options)

command_args += [
'--use-test-fonts',
kernel_file_output
Expand Down

0 comments on commit 90235a8

Please sign in to comment.