diff --git a/engine/src/flutter/lib/ui/hooks.dart b/engine/src/flutter/lib/ui/hooks.dart index c0b78e7a291..8f7602791e7 100644 --- a/engine/src/flutter/lib/ui/hooks.dart +++ b/engine/src/flutter/lib/ui/hooks.dart @@ -249,20 +249,22 @@ bool _isLoopback(String host) { // 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 + // Going to _isLoopback check before throwing } 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; } + // Loopback connections are always allowed + // Check at last resort to avoid debug annoyance of try/on ArgumentError + if (_isLoopback(uri.host)) { + 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.'); diff --git a/engine/src/flutter/testing/dart/http_disallow_http_connections_test.dart b/engine/src/flutter/testing/dart/http_disallow_http_connections_test.dart index f9b8e4ebd50..f2839b9e96d 100644 --- a/engine/src/flutter/testing/dart/http_disallow_http_connections_test.dart +++ b/engine/src/flutter/testing/dart/http_disallow_http_connections_test.dart @@ -52,7 +52,7 @@ Future _supportsIPv6() async { } void main() { - test('testWithHostname', () async { + test('testWithLocalIP', () async { await bindServerAndTest(await getLocalHostIP(), (HttpClient httpClient, Uri httpUri) async { asyncExpectThrows( () async => httpClient.getUrl(httpUri)); @@ -67,6 +67,30 @@ void main() { }); }); + test('testWithHostname', () async { + await bindServerAndTest(Platform.localHostname, (HttpClient httpClient, Uri httpUri) async { + asyncExpectThrows( + () async => httpClient.getUrl(httpUri)); + + final _MockZoneValue mockFoo = _MockZoneValue('foo'); + asyncExpectThrows( + () async => runZoned(() => httpClient.getUrl(httpUri), + zoneValues: {#flutter.io.allow_http: mockFoo})); + expect(mockFoo.checked, isTrue); + + final _MockZoneValue mockFalse = _MockZoneValue(false); + asyncExpectThrows( + () async => runZoned(() => httpClient.getUrl(httpUri), + zoneValues: {#flutter.io.allow_http: mockFalse})); + expect(mockFalse.checked, isTrue); + + final _MockZoneValue mockTrue = _MockZoneValue(true); + await runZoned(() => httpClient.getUrl(httpUri), + zoneValues: {#flutter.io.allow_http: mockTrue}); + expect(mockFalse.checked, isTrue); + }); + }); + test('testWithLoopback', () async { await bindServerAndTest('127.0.0.1', (HttpClient httpClient, Uri uri) async { await httpClient.getUrl(Uri.parse('http://localhost:${uri.port}')); @@ -82,3 +106,27 @@ void main() { } }); } + +class _MockZoneValue { + _MockZoneValue(this._value); + + Object? _value; + bool _falseChecked = false; + bool _trueChecked = false; + + @override + bool operator ==(Object o) { + if(o == true) { + _trueChecked = true; + } + if(o == false) { + _falseChecked = true; + } + return _value == o; + } + + bool get checked => _falseChecked && _trueChecked; + + @override + int get hashCode => _value.hashCode; +}