⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 263874 in webkit


Ignore:
Timestamp:
Jul 2, 2020, 4:15:47 PM (6 years ago)
Author:
Kate Cheney
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):

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r263869 r263874  
     12020-07-02  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        Custom URL schemes should be treated as app-bound
     4        https://bugs.webkit.org/show_bug.cgi?id=213889
     5        <rdar://problem/64804671>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        For applications which opt-in to App-Bound Domains, allow
     10        specification of app-bound custom URL schemes. All content loaded
     11        using an app-bound scheme will have access to otherwise restricted
     12        APIs. A custom scheme is specified by a colon at the end of a string
     13        in the WKAppBoundDomains list. Custom schemes are included in the
     14        count of 10 app-bound domains.
     15
     16        * UIProcess/API/Cocoa/WKWebsiteDataStore.mm:
     17        (-[WKWebsiteDataStore _appBoundSchemes:]):
     18        * UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h:
     19        SPI for testing.
     20
     21        * UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm:
     22        (WebKit::appBoundSchemes):
     23        (WebKit::WebsiteDataStore::initializeAppBoundDomains):
     24        Change variable name from appBoundDomains to appBoundData now that
     25        more than domains can be specified in the Info.plist.
     26
     27        (WebKit::WebsiteDataStore::ensureAppBoundDomains const):
     28        Return both domains and schemes to avoid code duplication, at the expense
     29        of occasionally returning an unused parameter if only domains or only
     30        schemes are needed.
     31
     32        (WebKit::WebsiteDataStore::beginAppBoundDomainCheck):
     33        (WebKit::WebsiteDataStore::getAppBoundDomains const):
     34        (WebKit::WebsiteDataStore::getAppBoundSchemes const):
     35        * UIProcess/WebsiteData/WebsiteDataStore.h:
     36
    1372020-07-02  Austin Blackwood  <ablackwoood@apple.com>
    238
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStore.mm

    r263570 r263874  
    643643}
    644644
     645- (void)_appBoundSchemes:(void (^)(NSArray<NSString *> *))completionHandler
     646{
     647    _websiteDataStore->getAppBoundSchemes([completionHandler = makeBlockPtr(completionHandler)](auto& schemes) mutable {
     648        Vector<RefPtr<API::Object>> apiSchemes;
     649        apiSchemes.reserveInitialCapacity(schemes.size());
     650        for (auto& scheme : schemes)
     651            apiSchemes.uncheckedAppend(API::String::create(scheme));
     652        completionHandler(wrapper(API::Array::create(WTFMove(apiSchemes))));
     653    });
     654}
     655
    645656@end
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataStorePrivate.h

    r261038 r263874  
    8080- (void)_processStatisticsAndDataRecords:(void (^)(void))completionHandler WK_API_AVAILABLE(macos(10.15), ios(13.0));
    8181- (void)_appBoundDomains:(void (^)(NSArray<NSString *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
     82- (void)_appBoundSchemes:(void (^)(NSArray<NSString *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    8283
    8384- (void)_renameOrigin:(NSURL *)oldName to:(NSURL *)newName forDataOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(void))completionHandler;
  • trunk/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm

    r263441 r263874  
    406406}
    407407
     408static HashSet<String>& appBoundSchemes()
     409{
     410    ASSERT(RunLoop::isMain());
     411    static NeverDestroyed<HashSet<String>> appBoundSchemes;
     412    return appBoundSchemes;
     413}
     414
    408415void WebsiteDataStore::initializeAppBoundDomains(ForceReinitialization forceReinitialization)
    409416{
    … …  
    419426            return;
    420427       
    421         NSArray<NSString *> *domains = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WKAppBoundDomains"];
    422         keyExists = domains ? true : false;
     428        NSArray<NSString *> *appBoundData = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"WKAppBoundDomains"];
     429        keyExists = appBoundData ? true : false;
    423430       
    424         RunLoop::main().dispatch([forceReinitialization, domains = retainPtr(domains)] {
     431        RunLoop::main().dispatch([forceReinitialization, appBoundData = retainPtr(appBoundData)] {
    425432            if (hasInitializedAppBoundDomains && forceReinitialization != ForceReinitialization::Yes)
    426433                return;
    … …  
    429436                appBoundDomains().clear();
    430437
    431             for (NSString *domain in domains.get()) {
    432                 URL url { URL(), domain };
     438            for (NSString *data in appBoundData.get()) {
     439                if (appBoundDomains().size() + appBoundSchemes().size() >= maxAppBoundDomainCount)
     440                    break;
     441                if ([data hasSuffix:@":"]) {
     442                    auto appBoundScheme = String([data substringToIndex:[data length] - 1]);
     443                    if (!appBoundScheme.isEmpty()) {
     444                        appBoundSchemes().add(appBoundScheme);
     445                        continue;
     446                    }
     447                }
     448
     449                URL url { URL(), data };
    433450                if (url.protocol().isEmpty())
    434451                    url.setProtocol("https");
    … …  
    439456                    continue;
    440457                appBoundDomains().add(appBoundDomain);
    441                 if (appBoundDomains().size() >= maxAppBoundDomainCount)
    442                     break;
    443458            }
    444459            hasInitializedAppBoundDomains = true;
    … …  
    449464}
    450465
    451 void WebsiteDataStore::ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&& completionHandler) const
     466void WebsiteDataStore::ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&, const HashSet<String>&)>&& completionHandler) const
    452467{
    453468    if (hasInitializedAppBoundDomains) {
    … …  
    455470            WEBSITE_DATA_STORE_ADDITIONS;
    456471        }
    457         completionHandler(appBoundDomains());
     472        completionHandler(appBoundDomains(), appBoundSchemes());
    458473        return;
    459474    }
    … …  
    467482                WEBSITE_DATA_STORE_ADDITIONS;
    468483            }
    469             completionHandler(appBoundDomains());
     484            completionHandler(appBoundDomains(), appBoundSchemes());
    470485        });
    471486    });
    472487}
    473488
     489static NavigatingToAppBoundDomain schemeOrDomainIsAppBound(const URL& requestURL, const HashSet<WebCore::RegistrableDomain>& domains, const HashSet<String>& schemes)
     490{
     491    auto protocol = requestURL.protocol().toString();
     492    auto schemeIsAppBound = !protocol.isNull() && schemes.contains(protocol);
     493    auto domainIsAppBound = domains.contains(WebCore::RegistrableDomain(requestURL));
     494    return schemeIsAppBound || domainIsAppBound ? NavigatingToAppBoundDomain::Yes : NavigatingToAppBoundDomain::No;
     495}
     496
    474497void WebsiteDataStore::beginAppBoundDomainCheck(const URL& requestURL, WebFramePolicyListenerProxy& listener)
    475498{
    476499    ASSERT(RunLoop::isMain());
    477500
    478     ensureAppBoundDomains([&requestURL, listener = makeRef(listener)] (auto& domains) mutable {
     501    ensureAppBoundDomains([&requestURL, listener = makeRef(listener)] (auto& domains, auto& schemes) mutable {
    479502        // Must check for both an empty app bound domains list and an empty key before returning nullopt
    480503        // because test cases may have app bound domains but no key.
    … …  
    484507            return;
    485508        }
    486         listener->didReceiveAppBoundDomainResult(domains.contains(WebCore::RegistrableDomain(requestURL)) ? NavigatingToAppBoundDomain::Yes : NavigatingToAppBoundDomain::No);
     509        listener->didReceiveAppBoundDomainResult(schemeOrDomainIsAppBound(requestURL, domains, schemes));
    487510    });
    488511}
    … …  
    492515    ASSERT(RunLoop::isMain());
    493516
    494     ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains) mutable {
     517    ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains, auto& schemes) mutable {
    495518        completionHandler(domains);
     519    });
     520}
     521
     522void WebsiteDataStore::getAppBoundSchemes(CompletionHandler<void(const HashSet<String>&)>&& completionHandler) const
     523{
     524    ASSERT(RunLoop::isMain());
     525
     526    ensureAppBoundDomains([completionHandler = WTFMove(completionHandler)] (auto& domains, auto& schemes) mutable {
     527        completionHandler(schemes);
    496528    });
    497529}
  • trunk/Source/WebKit/UIProcess/WebsiteData/WebsiteDataStore.h

    r263736 r263874  
    302302    void beginAppBoundDomainCheck(const URL&, WebFramePolicyListenerProxy&);
    303303    void getAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&&) const;
    304     void ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&)>&&) const;
     304    void getAppBoundSchemes(CompletionHandler<void(const HashSet<String>&)>&&) const;
     305    void ensureAppBoundDomains(CompletionHandler<void(const HashSet<WebCore::RegistrableDomain>&, const HashSet<String>&)>&&) const;
    305306    void reinitializeAppBoundDomains();
    306307    static void setAppBoundDomainsForTesting(HashSet<WebCore::RegistrableDomain>&&, CompletionHandler<void()>&&);
  • trunk/Tools/ChangeLog

    r263848 r263874  
     12020-07-02  Kate Cheney  <katherine_cheney@apple.com>
     2
     3        Custom URL schemes should be treated as app-bound
     4        https://bugs.webkit.org/show_bug.cgi?id=213889
     5        <rdar://problem/64804671>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Added custom schemes to TestWebKitAPI's Info.plist for testing
     10        duplicate values and the max of 10 domains/schemes. Added
     11        two API tests to check that schemes are properly read from the
     12        Info.plist and that content loaded using a specified app-bound scheme
     13        has restricted API use.
     14
     15        * TestWebKitAPI/Info.plist:
     16        * TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm:
     17        (TEST):
     18
    1192020-07-02  Philippe Normand  <pnormand@igalia.com>
    220
  • trunk/Tools/TestWebKitAPI/Info.plist

    r259322 r263874  
    55        <key>WKAppBoundDomains</key>
    66        <array>
     7                <string>test:</string>
     8                <string>:</string>
     9                <string>test:</string>
    710                <string>testDomain1</string>
    811                <string>apple.com</string>
    … …  
    2225                <string>http://bar.com/</string>
    2326                <string>http://foo.com/</string>
     27                <string>app-bound-custom-scheme:</string>
     28                <string>should-not-be-included:</string>
    2429        </array>
    2530        <key>CFBundleName</key>
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/InAppBrowserPrivacy.mm

    r263806 r263874  
    2929#import "ServiceWorkerTCPServer.h"
    3030#import "TestNavigationDelegate.h"
     31#import "TestURLSchemeHandler.h"
    3132#import "TestWKWebView.h"
    3233#import "WKWebViewConfigurationExtras.h"
    … …  
    285286}
    286287
     288TEST(InAppBrowserPrivacy, AppBoundSchemes)
     289{
     290    initializeInAppBrowserPrivacyTestSettings();
     291    isDone = false;
     292    [[WKWebsiteDataStore defaultDataStore] _appBoundSchemes:^(NSArray<NSString *> *schemes) {
     293        NSArray *schemesToCompare = @[@"app-bound-custom-scheme", @"test"];
     294
     295        NSArray *sortedSchemes = [schemes sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
     296
     297        int length = [sortedSchemes count];
     298        EXPECT_EQ(length, 2);
     299        for (int i = 0; i < length; i++)
     300            EXPECT_WK_STREQ([sortedSchemes objectAtIndex:i], [schemesToCompare objectAtIndex:i]);
     301
     302        cleanUpInAppBrowserPrivacyTestSettings();
     303        isDone = true;
     304    }];
     305    TestWebKitAPI::Util::run(&isDone);
     306}
     307
    287308TEST(InAppBrowserPrivacy, LocalFilesAreAppBound)
    288309{
    … …  
    12731294}
    12741295
     1296TEST(InAppBrowserPrivacy, AppBoundCustomScheme)
     1297{
     1298    initializeInAppBrowserPrivacyTestSettings();
     1299    isDone = false;
     1300
     1301    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     1302    auto schemeHandler = adoptNS([[TestURLSchemeHandler alloc] init]);
     1303    [configuration setURLSchemeHandler:schemeHandler.get() forURLScheme:@"test"];
     1304    [configuration setLimitsNavigationsToAppBoundDomains:YES];
     1305
     1306    [schemeHandler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
     1307        auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil]);
     1308        [task didReceiveResponse:response.get()];
     1309        [task didReceiveData:[NSData dataWithBytes:mainBytes length:strlen(mainBytes)]];
     1310        [task didFinish];
     1311    }];
     1312
     1313    auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
     1314   
     1315    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"test://host/main.html"]];
     1316
     1317    [webView loadRequest:request];
     1318    [webView _test_waitForDidFinishNavigation];
     1319
     1320    isDone = false;
     1321    [webView _isNavigatingToAppBoundDomain:^(BOOL isAppBound) {
     1322        EXPECT_TRUE(isAppBound);
     1323        cleanUpInAppBrowserPrivacyTestSettings();
     1324        isDone = true;
     1325    }];
     1326    TestWebKitAPI::Util::run(&isDone);
     1327   
     1328    // Make sure app-bound behavior works for this webview.
     1329    isDone = false;
     1330    [webView evaluateJavaScript:@"location.href;" completionHandler:^(id result, NSError *error) {
     1331        EXPECT_TRUE([result isKindOfClass:[NSString class]]);
     1332        EXPECT_FALSE(!!error);
     1333        EXPECT_TRUE([result isEqualToString:@"test://host/main.html"]);
     1334
     1335        isDone = true;
     1336    }];
     1337   
     1338    TestWebKitAPI::Util::run(&isDone);
     1339}
     1340
    12751341#endif // USE(APPLE_INTERNAL_SDK)
    12761342
Note: See TracChangeset for help on using the changeset viewer.