diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index fa4e5b64b1849..67a4c1794c5cb 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -920,6 +920,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryM FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 32c3fbf234a36..327dfdd1605ca 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -208,6 +208,7 @@ shared_library("ios_test_flutter") { ] sources = [ "framework/Source/FlutterBinaryMessengerRelayTest.mm", + "framework/Source/FlutterDartProjectTest.mm", "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterPluginAppLifeCycleDelegateTest.m", "framework/Source/FlutterTextInputPluginTest.m", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 3fb7c350febd0..4f6a2acdf7b76 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -26,42 +26,6 @@ static const char* kApplicationKernelSnapshotFileName = "kernel_blob.bin"; -// TODO(mehmetf): Announce this since it is breaking change then enable it. -// static NSString* DomainNetworkPolicy(NSDictionary* appTransportSecurity) { -// if (appTransportSecurity == nil) { -// return @""; -// } -// // -// https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsexceptiondomains -// NSDictionary* exceptionDomains = [appTransportSecurity objectForKey:@"NSExceptionDomains"]; -// if (exceptionDomains == nil) { -// return @""; -// } -// NSMutableArray* networkConfigArray = [[NSMutableArray alloc] init]; -// for (NSString* domain in exceptionDomains) { -// NSDictionary* domainConfiguration = [exceptionDomains objectForKey:domain]; -// BOOL includesSubDomains = -// [[domainConfiguration objectForKey:@"NSIncludesSubdomains"] boolValue]; -// BOOL allowsCleartextCommunication = -// [[domainConfiguration objectForKey:@"NSExceptionAllowsInsecureHTTPLoads"] boolValue]; -// [networkConfigArray addObject:[NSArray arrayWithObjects:domain, includesSubDomains, -// allowsCleartextCommunication, nil]]; -// } -// NSData* jsonData = [NSJSONSerialization dataWithJSONObject:networkConfigArray -// options:0 -// error:NULL]; -// return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; -// } - -// TODO(mehmetf): Announce this since it is breaking change then enable it. -// static bool AllowsArbitraryLoads(NSDictionary* appTransportSecurity) { -// if (appTransportSecurity != nil) { -// return [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue]; -// } else { -// return false; -// } -// } - static flutter::Settings DefaultSettingsForProcess(NSBundle* bundle = nil) { auto command_line = flutter::CommandLineFromNSProcessInfo(); @@ -168,12 +132,19 @@ } } - // TODO(mehmetf): Announce this since it is breaking change then enable it. // Domain network configuration - // NSDictionary* appTransportSecurity = - // [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; - // settings.may_insecurely_connect_to_all_domains = AllowsArbitraryLoads(appTransportSecurity); - // settings.domain_network_policy = DomainNetworkPolicy(appTransportSecurity).UTF8String; + NSDictionary* appTransportSecurity = + [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; + settings.may_insecurely_connect_to_all_domains = + [FlutterDartProject allowsArbitraryLoads:appTransportSecurity]; + settings.domain_network_policy = + [FlutterDartProject domainNetworkPolicy:appTransportSecurity].UTF8String; + + // TODO(mehmetf): We need to announce this change since it is breaking. + // Remove these two lines after we announce and we know which release this is + // going to be part of. + settings.may_insecurely_connect_to_all_domains = true; + settings.domain_network_policy = ""; #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG // There are no ownership concerns here as all mappings are owned by the @@ -262,6 +233,34 @@ + (NSString*)flutterAssetsName:(NSBundle*)bundle { return flutterAssetsName; } ++ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity { + // https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity/nsexceptiondomains + NSDictionary* exceptionDomains = [appTransportSecurity objectForKey:@"NSExceptionDomains"]; + if (exceptionDomains == nil) { + return @""; + } + NSMutableArray* networkConfigArray = [[NSMutableArray alloc] init]; + for (NSString* domain in exceptionDomains) { + NSDictionary* domainConfiguration = [exceptionDomains objectForKey:domain]; + // Default value is false. + bool includesSubDomains = + [[domainConfiguration objectForKey:@"NSIncludesSubdomains"] boolValue]; + bool allowsCleartextCommunication = + [[domainConfiguration objectForKey:@"NSExceptionAllowsInsecureHTTPLoads"] boolValue]; + [networkConfigArray addObject:@[ + domain, includesSubDomains ? @YES : @NO, allowsCleartextCommunication ? @YES : @NO + ]]; + } + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:networkConfigArray + options:0 + error:NULL]; + return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +} + ++ (bool)allowsArbitraryLoads:(NSDictionary*)appTransportSecurity { + return [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue]; +} + + (NSString*)lookupKeyForAsset:(NSString*)asset { return [self lookupKeyForAsset:asset fromBundle:nil]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm new file mode 100644 index 0000000000000..eed1bd9cc8969 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -0,0 +1,85 @@ +// Copyright 2013 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. + +#import +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h" + +FLUTTER_ASSERT_ARC + +@interface FlutterDartProjectTest : XCTestCase +@end + +@implementation FlutterDartProjectTest + +- (void)setUp { +} + +- (void)tearDown { +} + +- (void)testMainBundleSettingsAreCorrectlyParsed { + NSBundle* mainBundle = [NSBundle mainBundle]; + NSDictionary* appTransportSecurity = + [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; + XCTAssertTrue([FlutterDartProject allowsArbitraryLoads:appTransportSecurity]); + XCTAssertEqualObjects( + @"[[\"invalid-site.com\",true,false],[\"sub.invalid-site.com\",false,false]]", + [FlutterDartProject domainNetworkPolicy:appTransportSecurity]); +} + +- (void)testEmptySettingsAreCorrect { + XCTAssertFalse([FlutterDartProject allowsArbitraryLoads:[[NSDictionary alloc] init]]); + XCTAssertEqualObjects(@"", [FlutterDartProject domainNetworkPolicy:[[NSDictionary alloc] init]]); +} + +- (void)testAllowsArbitraryLoads { + XCTAssertFalse([FlutterDartProject allowsArbitraryLoads:@{@"NSAllowsArbitraryLoads" : @false}]); + XCTAssertTrue([FlutterDartProject allowsArbitraryLoads:@{@"NSAllowsArbitraryLoads" : @true}]); +} + +- (void)testProperlyFormedExceptionDomains { + NSDictionary* domainInfoOne = @{ + @"NSIncludesSubdomains" : @false, + @"NSExceptionAllowsInsecureHTTPLoads" : @true, + @"NSExceptionMinimumTLSVersion" : @"4.0" + }; + NSDictionary* domainInfoTwo = @{ + @"NSIncludesSubdomains" : @true, + @"NSExceptionAllowsInsecureHTTPLoads" : @false, + @"NSExceptionMinimumTLSVersion" : @"4.0" + }; + NSDictionary* domainInfoThree = @{ + @"NSIncludesSubdomains" : @false, + @"NSExceptionAllowsInsecureHTTPLoads" : @true, + @"NSExceptionMinimumTLSVersion" : @"4.0" + }; + NSDictionary* exceptionDomains = @{ + @"domain.name" : domainInfoOne, + @"sub.domain.name" : domainInfoTwo, + @"sub.two.domain.name" : domainInfoThree + }; + NSDictionary* appTransportSecurity = @{@"NSExceptionDomains" : exceptionDomains}; + XCTAssertEqualObjects(@"[[\"domain.name\",false,true],[\"sub.domain.name\",true,false]," + @"[\"sub.two.domain.name\",false,true]]", + [FlutterDartProject domainNetworkPolicy:appTransportSecurity]); +} + +- (void)testExceptionDomainsWithMissingInfo { + NSDictionary* domainInfoOne = @{@"NSExceptionMinimumTLSVersion" : @"4.0"}; + NSDictionary* domainInfoTwo = @{ + @"NSIncludesSubdomains" : @true, + }; + NSDictionary* domainInfoThree = @{}; + NSDictionary* exceptionDomains = @{ + @"domain.name" : domainInfoOne, + @"sub.domain.name" : domainInfoTwo, + @"sub.two.domain.name" : domainInfoThree + }; + NSDictionary* appTransportSecurity = @{@"NSExceptionDomains" : exceptionDomains}; + XCTAssertEqualObjects(@"[[\"domain.name\",false,false],[\"sub.domain.name\",true,false]," + @"[\"sub.two.domain.name\",false,false]]", + [FlutterDartProject domainNetworkPolicy:appTransportSecurity]); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h index 9ac366f5fcc0c..daac68e663786 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN libraryOrNil:(nullable NSString*)dartLibraryOrNil; + (NSString*)flutterAssetsName:(NSBundle*)bundle; ++ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity; ++ (bool)allowsArbitraryLoads:(NSDictionary*)appTransportSecurity; /** * The embedder can specify data that the isolate can request synchronously on launch. Engines diff --git a/testing/ios/IosUnitTests/App/Info.plist b/testing/ios/IosUnitTests/App/Info.plist index 16be3b681122d..52b6c2050105b 100644 --- a/testing/ios/IosUnitTests/App/Info.plist +++ b/testing/ios/IosUnitTests/App/Info.plist @@ -24,6 +24,26 @@ LaunchScreen UIMainStoryboardFile Main + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + invalid-site.com + + NSIncludesSubdomains + + NSThirdPartyExceptionAllowsInsecureHTTPLoads + + + sub.invalid-site.com + + NSThirdPartyExceptionAllowsInsecureHTTPLoads + + + + UIRequiredDeviceCapabilities armv7