- Revision
- 263874
- Author
- katherine_che...@apple.com
- Date
- 2020-07-02 16:15:47 -0700 (Thu, 02 Jul 2020)
Log Message
Custom URL schemes should be treated as app-bound
https://bugs.webkit.org/show_bug.cgi?id=213889
<rdar://problem/64804671>
Reviewed by Brent Fulgham.
Source/WebKit:
For applications which opt-in to App-Bound Domains, allow
specification of app-bound custom URL schemes. All content loaded
using an app-bound scheme will have access to otherwise restricted
APIs. A custom scheme is specified by a colon at the end of a string
in the WKAppBoundDomains list. Custom schemes are included in the
count of 10 app-bound domains.
* UIProcess/API/Cocoa/WKWebsiteDataStore.mm:
(-[WKWebsiteDataStore _appBoundSchemes:]):
* UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h:
SPI for testing.
* UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
(WebKit::appBoundSchemes):
(WebKit::WebsiteDataStore::initializeAppBoundDomains):
Change variable name from appBoundDomains to appBoundData now that
more than domains can be specified in the Info.plist.
(WebKit::WebsiteDataStore::ensureAppBoundDomains const):
Return both domains and schemes to avoid code duplication, at the expense
of occasionally returning an unused parameter if only domains or only
schemes are needed.
(WebKit::WebsiteDataStore::beginAppBoundDomainCheck):
(WebKit::WebsiteDataStore::getAppBoundDomains const):
(WebKit::WebsiteDataStore::getAppBoundSchemes const):
* UIProcess/WebsiteData/WebsiteDataStore.h:
Tools:
Added custom schemes to TestWebKitAPI's Info.plist for testing
duplicate values and the max of 10 domains/schemes. Added
two API tests to check that schemes are properly read from the
Info.plist and that content loaded using a specified app-bound scheme
has restricted API use.
* TestWebKitAPI/Info.plist:
* TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm:
(TEST):
Modified Paths
Diff
Modified: trunk/Source/WebKit/ChangeLog (263873 => 263874)
--- trunk/Source/WebKit/ChangeLog 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Source/WebKit/ChangeLog 2020-07-02 23:15:47 UTC (rev 263874)
@@ -1,3 +1,39 @@
+2020-07-02 Kate Cheney <katherine_che...@apple.com>
+
+ Custom URL schemes should be treated as app-bound
+ https://bugs.webkit.org/show_bug.cgi?id=213889
+ <rdar://problem/64804671>
+
+ Reviewed by Brent Fulgham.
+
+ For applications which opt-in to App-Bound Domains, allow
+ specification of app-bound custom URL schemes. All content loaded
+ using an app-bound scheme will have access to otherwise restricted
+ APIs. A custom scheme is specified by a colon at the end of a string
+ in the WKAppBoundDomains list. Custom schemes are included in the
+ count of 10 app-bound domains.
+
+ * UIProcess/API/Cocoa/WKWebsiteDataStore.mm:
+ (-[WKWebsiteDataStore _appBoundSchemes:]):
+ * UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h:
+ SPI for testing.
+
+ * UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
+ (WebKit::appBoundSchemes):
+ (WebKit::WebsiteDataStore::initializeAppBoundDomains):
+ Change variable name from appBoundDomains to appBoundData now that
+ more than domains can be specified in the Info.plist.
+
+ (WebKit::WebsiteDataStore::ensureAppBoundDomains const):
+ Return both domains and schemes to avoid code duplication, at the expense
+ of occasionally returning an unused parameter if only domains or only
+ schemes are needed.
+
+ (WebKit::WebsiteDataStore::beginAppBoundDomainCheck):
+ (WebKit::WebsiteDataStore::getAppBoundDomains const):
+ (WebKit::WebsiteDataStore::getAppBoundSchemes const):
+ * UIProcess/WebsiteData/WebsiteDataStore.h:
+
2020-07-02 Austin Blackwood <ablackwo...@apple.com>
Crash in +[UIViewController _viewControllerForFullScreenPresentationFromView:] when WKContentView is deallocated
Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm (263873 => 263874)
--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm 2020-07-02 23:15:47 UTC (rev 263874)
@@ -642,4 +642,15 @@
});
}
+- (void)_appBoundSchemes:(void (^)(NSArray<NSString *> *))completionHandler
+{
+ _websiteDataStore->getAppBoundSchemes([completionHandler = makeBlockPtr(completionHandler)](auto& schemes) mutable {
+ Vector<RefPtr<API::Object>> apiSchemes;
+ apiSchemes.reserveInitialCapacity(schemes.size());
+ for (auto& scheme : schemes)
+ apiSchemes.uncheckedAppend(API::String::create(scheme));
+ completionHandler(wrapper(API::Array::create(WTFMove(apiSchemes))));
+ });
+}
+
@end
Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h (263873 => 263874)
--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h 2020-07-02 23:15:47 UTC (rev 263874)
@@ -79,6 +79,7 @@
- (void)_statisticsDatabaseHasAllTables:(void (^)(BOOL))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
- (void)_processStatisticsAndDataRecords:(void (^)(void))completionHandler WK_API_AVAILABLE(macos(10.15), ios(13.0));
- (void)_appBoundDomains:(void (^)(NSArray<NSString *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_appBoundSchemes:(void (^)(NSArray<NSString *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
- (void)_renameOrigin:(NSURL *)oldName to:(NSURL *)newName forDataOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(void))completionHandler;
Modified: trunk/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm (263873 => 263874)
--- trunk/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm 2020-07-02 23:15:47 UTC (rev 263874)
@@ -405,6 +405,13 @@
return appBoundDomains;
}
+static HashSet<String>& appBoundSchemes()
+{
+ ASSERT(RunLoop::isMain());
+ static NeverDestroyed<HashSet<String>> appBoundSchemes;
+ return appBoundSchemes;
+}
+
void WebsiteDataStore::initializeAppBoundDomains(ForceReinitialization forceReinitialization)
{
ASSERT(RunLoop::isMain());
@@ -418,10 +425,10 @@
if (hasInitializedAppBoundDomains && forceReinitialization != ForceReinitialization::Yes)
return;
- NSArray<NSString *> *domains = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WKAppBoundDomains"];
- keyExists = domains ? true : false;
+ NSArray<NSString *> *appBoundData = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WKAppBoundDomains"];
+ keyExists = appBoundData ? true : false;
- RunLoop::main().dispatch([forceReinitialization, domains = retainPtr(domains)] {
+ RunLoop::main().dispatch([forceReinitialization, appBoundData = retainPtr(appBoundData)] {
if (hasInitializedAppBoundDomains && forceReinitialization != ForceReinitialization::Yes)
return;
@@ -428,8 +435,18 @@
if (forceReinitialization == ForceReinitialization::Yes)
appBoundDomains().clear();
- for (NSString *domain in domains.get()) {
- URL url { URL(), domain };
+ for (NSString *data in appBoundData.get()) {
+ if (appBoundDomains().size() + appBoundSchemes().size() >= maxAppBoundDomainCount)
+ break;
+ if ([data hasSuffix:@":"]) {
+ auto appBoundScheme = String([data substringToIndex:[data length] - 1]);
+ if (!appBoundScheme.isEmpty()) {
+ appBoundSchemes().add(appBoundScheme);
+ continue;
+ }
+ }
+
+ URL url { URL(), data };
if (url.protocol().isEmpty())
url.setProtocol("https");
if (!url.isValid())
@@ -438,8 +455,6 @@
if (appBoundDomain.isEmpty())
continue;
appBoundDomains().add(appBoundDomain);
- if (appBoundDomains().size() >= maxAppBoundDomainCount)
- break;
}
hasInitializedAppBoundDomains = true;
if (isAppBoundITPRelaxationEnabled)
@@ -448,13 +463,13 @@
});
}
-void WebsiteDataStore::ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&& completionHandler) const
+void WebsiteDataStore::ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&, const HashSet<String>&)>&& completionHandler) const
{
if (hasInitializedAppBoundDomains) {
if (m_isInAppBrowserPrivacyTestModeEnabled) {
WEBSITE_DATA_STORE_ADDITIONS;
}
- completionHandler(appBoundDomains());
+ completionHandler(appBoundDomains(), appBoundSchemes());
return;
}
@@ -466,16 +481,24 @@
if (m_isInAppBrowserPrivacyTestModeEnabled) {
WEBSITE_DATA_STORE_ADDITIONS;
}
- completionHandler(appBoundDomains());
+ completionHandler(appBoundDomains(), appBoundSchemes());
});
});
}
+static NavigatingToAppBoundDomain schemeOrDomainIsAppBound(const URL& requestURL, const HashSet<WebCore::RegistrableDomain>& domains, const HashSet<String>& schemes)
+{
+ auto protocol = requestURL.protocol().toString();
+ auto schemeIsAppBound = !protocol.isNull() && schemes.contains(protocol);
+ auto domainIsAppBound = domains.contains(WebCore::RegistrableDomain(requestURL));
+ return schemeIsAppBound || domainIsAppBound ? NavigatingToAppBoundDomain::Yes : NavigatingToAppBoundDomain::No;
+}
+
void WebsiteDataStore::beginAppBoundDomainCheck(const URL& requestURL, WebFramePolicyListenerProxy& listener)
{
ASSERT(RunLoop::isMain());
- ensureAppBoundDomains([&requestURL, listener = makeRef(listener)] (auto& domains) mutable {
+ ensureAppBoundDomains([&requestURL, listener = makeRef(listener)] (auto& domains, auto& schemes) mutable {
// Must check for both an empty app bound domains list and an empty key before returning nullopt
// because test cases may have app bound domains but no key.
bool hasAppBoundDomains = keyExists || !domains.isEmpty();
@@ -483,7 +506,7 @@
listener->didReceiveAppBoundDomainResult(WTF::nullopt);
return;
}
- listener->didReceiveAppBoundDomainResult(domains.contains(WebCore::RegistrableDomain(requestURL)) ? NavigatingToAppBoundDomain::Yes : NavigatingToAppBoundDomain::No);
+ listener->didReceiveAppBoundDomainResult(schemeOrDomainIsAppBound(requestURL, domains, schemes));
});
}
@@ -491,11 +514,20 @@
{
ASSERT(RunLoop::isMain());
- ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains) mutable {
+ ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains, auto& schemes) mutable {
completionHandler(domains);
});
}
+void WebsiteDataStore::getAppBoundSchemes(CompletionHandler<void(const HashSet<String>&)>&& completionHandler) const
+{
+ ASSERT(RunLoop::isMain());
+
+ ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains, auto& schemes) mutable {
+ completionHandler(schemes);
+ });
+}
+
Optional<HashSet<WebCore::RegistrableDomain>> WebsiteDataStore::appBoundDomainsIfInitialized()
{
ASSERT(RunLoop::isMain());
Modified: trunk/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h (263873 => 263874)
--- trunk/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h 2020-07-02 23:15:47 UTC (rev 263874)
@@ -301,7 +301,8 @@
void beginAppBoundDomainCheck(const URL&, WebFramePolicyListenerProxy&);
void getAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&&) const;
- void ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&&) const;
+ void getAppBoundSchemes(CompletionHandler<void(const HashSet<String>&)>&&) const;
+ void ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&, const HashSet<String>&)>&&) const;
void reinitializeAppBoundDomains();
static void setAppBoundDomainsForTesting(HashSet<WebCore::RegistrableDomain>&&, CompletionHandler<void()>&&);
void updateBundleIdentifierInNetworkProcess(const String&, CompletionHandler<void()>&&);
Modified: trunk/Tools/ChangeLog (263873 => 263874)
--- trunk/Tools/ChangeLog 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Tools/ChangeLog 2020-07-02 23:15:47 UTC (rev 263874)
@@ -1,3 +1,21 @@
+2020-07-02 Kate Cheney <katherine_che...@apple.com>
+
+ Custom URL schemes should be treated as app-bound
+ https://bugs.webkit.org/show_bug.cgi?id=213889
+ <rdar://problem/64804671>
+
+ Reviewed by Brent Fulgham.
+
+ Added custom schemes to TestWebKitAPI's Info.plist for testing
+ duplicate values and the max of 10 domains/schemes. Added
+ two API tests to check that schemes are properly read from the
+ Info.plist and that content loaded using a specified app-bound scheme
+ has restricted API use.
+
+ * TestWebKitAPI/Info.plist:
+ * TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm:
+ (TEST):
+
2020-07-02 Philippe Normand <pnorm...@igalia.com>
REGRESSION(r263625): run-minibrowser fails on mac
Modified: trunk/Tools/TestWebKitAPI/Info.plist (263873 => 263874)
--- trunk/Tools/TestWebKitAPI/Info.plist 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Tools/TestWebKitAPI/Info.plist 2020-07-02 23:15:47 UTC (rev 263874)
@@ -4,6 +4,9 @@
<dict>
<key>WKAppBoundDomains</key>
<array>
+ <string>test:</string>
+ <string>:</string>
+ <string>test:</string>
<string>testDomain1</string>
<string>apple.com</string>
<string>sub.domain.webkit.org/road/to/nowhere/</string>
@@ -21,6 +24,8 @@
<string>http://www.example.com/</string>
<string>http://bar.com/</string>
<string>http://foo.com/</string>
+ <string>app-bound-custom-scheme:</string>
+ <string>should-not-be-included:</string>
</array>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm (263873 => 263874)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm 2020-07-02 22:50:32 UTC (rev 263873)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm 2020-07-02 23:15:47 UTC (rev 263874)
@@ -28,6 +28,7 @@
#import "PlatformUtilities.h"
#import "ServiceWorkerTCPServer.h"
#import "TestNavigationDelegate.h"
+#import "TestURLSchemeHandler.h"
#import "TestWKWebView.h"
#import "WKWebViewConfigurationExtras.h"
#import <WebCore/RegistrableDomain.h>
@@ -284,6 +285,26 @@
TestWebKitAPI::Util::run(&isDone);
}
+TEST(InAppBrowserPrivacy, AppBoundSchemes)
+{
+ initializeInAppBrowserPrivacyTestSettings();
+ isDone = false;
+ [[WKWebsiteDataStore defaultDataStore] _appBoundSchemes:^(NSArray<NSString *> *schemes) {
+ NSArray *schemesToCompare = @[@"app-bound-custom-scheme", @"test"];
+
+ NSArray *sortedSchemes = [schemes sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
+
+ int length = [sortedSchemes count];
+ EXPECT_EQ(length, 2);
+ for (int i = 0; i < length; i++)
+ EXPECT_WK_STREQ([sortedSchemes objectAtIndex:i], [schemesToCompare objectAtIndex:i]);
+
+ cleanUpInAppBrowserPrivacyTestSettings();
+ isDone = true;
+ }];
+ TestWebKitAPI::Util::run(&isDone);
+}
+
TEST(InAppBrowserPrivacy, LocalFilesAreAppBound)
{
initializeInAppBrowserPrivacyTestSettings();
@@ -1272,6 +1293,51 @@
TestWebKitAPI::Util::run(&isDone);
}
+TEST(InAppBrowserPrivacy, AppBoundCustomScheme)
+{
+ initializeInAppBrowserPrivacyTestSettings();
+ isDone = false;
+
+ auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ auto schemeHandler = adoptNS([[TestURLSchemeHandler alloc] init]);
+ [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"test"];
+ [configuration setLimitsNavigationsToAppBoundDomains:YES];
+
+ [schemeHandler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
+ auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil]);
+ [task didReceiveResponse:response.get()];
+ [task didReceiveData:[NSData dataWithBytes:mainBytes length:strlen(mainBytes)]];
+ [task didFinish];
+ }];
+
+ auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+ NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"test://host/main.html"]];
+
+ [webView loadRequest:request];
+ [webView _test_waitForDidFinishNavigation];
+
+ isDone = false;
+ [webView _isNavigatingToAppBoundDomain:^(BOOL isAppBound) {
+ EXPECT_TRUE(isAppBound);
+ cleanUpInAppBrowserPrivacyTestSettings();
+ isDone = true;
+ }];
+ TestWebKitAPI::Util::run(&isDone);
+
+ // Make sure app-bound behavior works for this webview.
+ isDone = false;
+ [webView evaluateJavaScript:@"location.href;" completionHandler:^(id result, NSError *error) {
+ EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+ EXPECT_FALSE(!!error);
+ EXPECT_TRUE([result isEqualToString:@"test://host/main.html"]);
+
+ isDone = true;
+ }];
+
+ TestWebKitAPI::Util::run(&isDone);
+}
+
#endif // USE(APPLE_INTERNAL_SDK)
#endif // PLATFORM(IOS_FAMILY)