Title: [263874] trunk
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)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to