blob: c24fc750668ca771d80e12d159db8e7aa976abd7 [file] [log] [blame] [edit]
/*
* Copyright (C) 2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#if HAVE(SAFE_BROWSING)
#import "ClassMethodSwizzler.h"
#import "HTTPServer.h"
#import "PlatformUtilities.h"
#import "Test.h"
#import "TestNavigationDelegate.h"
#import "TestResourceLoadDelegate.h"
#import "TestURLSchemeHandler.h"
#import "TestWKWebView.h"
#import "WKWebViewConfigurationExtras.h"
#import <Foundation/NSURLError.h>
#import <WebKit/WKNavigationDelegate.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/_WKFeature.h>
#import <WebKit/_WKResourceLoadDelegate.h>
#import <WebKit/_WKResourceLoadInfo.h>
#import <wtf/RetainPtr.h>
#import <wtf/RunLoop.h>
#import <wtf/SoftLinking.h>
#import <wtf/URL.h>
#import <wtf/Vector.h>
SOFT_LINK_PRIVATE_FRAMEWORK(SafariSafeBrowsing);
SOFT_LINK_CLASS(SafariSafeBrowsing, SSBLookupContext);
SOFT_LINK_CLASS(SafariSafeBrowsing, SSBLookupResult);
SOFT_LINK_CLASS(SafariSafeBrowsing, SSBServiceLookupResult);
static bool committedNavigation;
static bool warningShown;
static bool didCloseCalled;
@interface SafeBrowsingNavigationDelegate : NSObject <WKNavigationDelegate, WKUIDelegatePrivate>
@end
@implementation SafeBrowsingNavigationDelegate
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
committedNavigation = true;
}
- (void)_webViewDidShowSafeBrowsingWarning:(WKWebView *)webView
{
warningShown = true;
}
- (void)webViewDidClose:(WKWebView *)webView
{
didCloseCalled = true;
}
@end
@interface TestServiceLookupResult : NSObject {
RetainPtr<NSString> _provider;
BOOL _isPhishing;
BOOL _isMalware;
BOOL _isUnwantedSoftware;
}
@end
@implementation TestServiceLookupResult
+ (instancetype)resultWithProvider:(RetainPtr<NSString>&&)provider phishing:(BOOL)phishing malware:(BOOL)malware unwantedSoftware:(BOOL)unwantedSoftware
{
auto result = adoptNS([[TestServiceLookupResult alloc] init]);
if (!result)
return nil;
result->_provider = WTF::move(provider);
result->_isPhishing = phishing;
result->_isMalware = malware;
result->_isUnwantedSoftware = unwantedSoftware;
return result.autorelease();
}
- (NSString *)provider
{
return _provider.get();
}
- (BOOL)isPhishing
{
return _isPhishing;
}
- (BOOL)isMalware
{
return _isMalware;
}
- (BOOL)isUnwantedSoftware
{
return _isUnwantedSoftware;
}
- (NSString *)malwareDetailsBaseURLString
{
return @"test://";
}
- (NSURL *)learnMoreURL
{
return [NSURL URLWithString:@"test://"];
}
- (NSString *)reportAnErrorBaseURLString
{
return @"test://";
}
- (NSString *)localizedProviderDisplayName
{
return @"test display name";
}
@end
@interface TestLookupResult : NSObject {
RetainPtr<NSArray> _results;
}
@end
@implementation TestLookupResult
+ (instancetype)resultWithResults:(RetainPtr<NSArray<TestServiceLookupResult *>>&&)results
{
auto result = adoptNS([[TestLookupResult alloc] init]);
if (!result)
return nil;
result->_results = WTF::move(results);
return result.autorelease();
}
- (NSArray<TestServiceLookupResult *> *)serviceLookupResults
{
return _results.get();
}
@end
@interface TestLookupContext : NSObject
@end
@implementation TestLookupContext
+ (TestLookupContext *)sharedLookupContext
{
static TestLookupContext *context = [[TestLookupContext alloc] init];
return context;
}
- (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
{
completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"SSBProviderApple" phishing:YES malware:NO unwantedSoftware:NO]]], nil);
}
@end
static NSURL *resourceURL(NSString *resource)
{
return [NSBundle.test_resourcesBundle URLForResource:resource withExtension:@"html"];
}
TEST(SafeBrowsing, Preference)
{
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
__block bool done = false;
auto delegate = adoptNS([TestNavigationDelegate new]);
delegate.get().didStartProvisionalNavigation = ^(WKWebView *, WKNavigation *) {
done = true;
};
auto webView = adoptNS([WKWebView new]);
EXPECT_TRUE([webView configuration].preferences.fraudulentWebsiteWarningEnabled);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView setNavigationDelegate:delegate.get()];
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = NO;
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
TestWebKitAPI::Util::run(&done);
EXPECT_FALSE([webView configuration].preferences.fraudulentWebsiteWarningEnabled);
EXPECT_FALSE([webView _safeBrowsingWarning]);
}
static RetainPtr<WKWebView> safeBrowsingView()
{
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
static auto delegate = adoptNS([SafeBrowsingNavigationDelegate new]);
auto webView = adoptNS([WKWebView new]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView setNavigationDelegate:delegate.get()];
[webView setUIDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
EXPECT_FALSE(warningShown);
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_TRUE(warningShown);
#if !PLATFORM(MAC)
[[webView _safeBrowsingWarning] didMoveToWindow];
#endif
return webView;
}
#if PLATFORM(MAC)
static void checkTitleAndClick(NSButton *button, const char* expectedTitle)
{
EXPECT_STREQ(button.title.UTF8String, expectedTitle);
[button performClick:nil];
}
#else
static void checkTitleAndClick(UIButton *button, const char* expectedTitle)
{
EXPECT_STREQ([button attributedTitleForState:UIControlStateNormal].string.UTF8String, expectedTitle);
UIView *target = button.superview.superview;
SEL selector = NSSelectorFromString(strcmp(expectedTitle, "Go Back") ? @"showDetailsClicked" : @"goBackClicked");
[target performSelector:selector];
}
#endif
template<typename ViewType> void goBack(ViewType *view, bool mainFrame = true)
{
WKWebView *webView = (WKWebView *)view.superview;
auto box = view.subviews.firstObject;
checkTitleAndClick(box.subviews[3], "Go Back");
if (mainFrame)
EXPECT_EQ([webView _safeBrowsingWarning], nil);
else
EXPECT_NE([webView _safeBrowsingWarning], nil);
}
TEST(SafeBrowsing, GoBack)
{
auto webView = safeBrowsingView();
EXPECT_FALSE(didCloseCalled);
goBack([webView _safeBrowsingWarning]);
EXPECT_TRUE(didCloseCalled);
}
TEST(SafeBrowsing, GoBackAfterRestoreFromSessionState)
{
auto webView1 = adoptNS([WKWebView new]);
[webView1 loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]]];
[webView1 _test_waitForDidFinishNavigation];
_WKSessionState *state = [webView1 _sessionState];
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([SafeBrowsingNavigationDelegate new]);
auto webView2 = adoptNS([WKWebView new]);
[webView2 configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView2 setNavigationDelegate:delegate.get()];
[webView2 setUIDelegate:delegate.get()];
[webView2 _restoreSessionState:state andNavigate:YES];
EXPECT_FALSE(warningShown);
while (![webView2 _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_TRUE(warningShown);
#if !PLATFORM(MAC)
[[webView2 _safeBrowsingWarning] didMoveToWindow];
#endif
EXPECT_FALSE(didCloseCalled);
goBack([webView2 _safeBrowsingWarning]);
EXPECT_TRUE(didCloseCalled);
WKBackForwardList *list = [webView2 backForwardList];
EXPECT_FALSE(!!list.backItem);
EXPECT_FALSE(!!list.forwardItem);
EXPECT_TRUE([list.currentItem.URL.path hasSuffix:@"/simple.html"]);
}
template<typename ViewType> void visitUnsafeSite(ViewType *view)
{
[view performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[NSURL URLWithString:@"WKVisitUnsafeWebsiteSentinel"]];
}
TEST(SafeBrowsing, VisitUnsafeWebsite)
{
auto webView = safeBrowsingView();
auto warning = [webView _safeBrowsingWarning];
EXPECT_EQ(warning.subviews.count, 1ull);
#if PLATFORM(MAC)
EXPECT_GT(warning.subviews.firstObject.subviews[2].frame.size.height, 0);
#endif
EXPECT_WK_STREQ([webView title], "Deceptive Website Warning");
checkTitleAndClick(warning.subviews.firstObject.subviews[4], "Show Details");
EXPECT_EQ(warning.subviews.count, 2ull);
EXPECT_FALSE(committedNavigation);
visitUnsafeSite(warning);
EXPECT_WK_STREQ([webView title], "");
TestWebKitAPI::Util::run(&committedNavigation);
}
TEST(SafeBrowsing, NavigationClearsWarning)
{
auto webView = safeBrowsingView();
EXPECT_NE([webView _safeBrowsingWarning], nil);
[webView loadRequest:[NSURLRequest requestWithURL:[NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]]];
while ([webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, ShowWarningSPI)
{
__block bool completionHandlerCalled = false;
__block BOOL shouldContinueValue = NO;
auto webView = adoptNS([WKWebView new]);
auto showWarning = ^{
completionHandlerCalled = false;
[webView _showSafeBrowsingWarningWithURL:nil title:@"test title" warning:@"test warning" details:adoptNS([[NSAttributedString alloc] initWithString:@"test details"]).get() completionHandler:^(BOOL shouldContinue) {
shouldContinueValue = shouldContinue;
completionHandlerCalled = true;
}];
#if !PLATFORM(MAC)
[[webView _safeBrowsingWarning] didMoveToWindow];
#endif
};
showWarning();
checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
TestWebKitAPI::Util::run(&completionHandlerCalled);
EXPECT_FALSE(shouldContinueValue);
showWarning();
[[webView _safeBrowsingWarning] performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[WKWebView _visitUnsafeWebsiteSentinel]];
TestWebKitAPI::Util::run(&completionHandlerCalled);
EXPECT_TRUE(shouldContinueValue);
}
static Vector<URL> urls;
@interface SafeBrowsingObserver : NSObject
@end
@implementation SafeBrowsingObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
{
urls.append((NSURL *)[change objectForKey:NSKeyValueChangeNewKey]);
}
@end
TEST(SafeBrowsing, URLObservation)
{
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
RetainPtr<NSURL> simpleURL = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"];
RetainPtr<NSURL> simple2URL = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"];
auto observer = adoptNS([SafeBrowsingObserver new]);
auto webViewWithWarning = [&] () -> RetainPtr<WKWebView> {
auto webView = adoptNS([WKWebView new]);
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
navigationDelegate.get().decidePolicyForNavigationAction = ^(WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
TestWebKitAPI::Util::runFor(0.01_s);
decisionHandler(WKNavigationActionPolicyAllow);
};
[webView setNavigationDelegate:navigationDelegate.get()];
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView addObserver:observer.get() forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
[webView loadHTMLString:@"meaningful content to be drawn" baseURL:simpleURL.get()];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
#if !PLATFORM(MAC)
[[webView _safeBrowsingWarning] didMoveToWindow];
#endif
visitUnsafeSite([webView _safeBrowsingWarning]);
EXPECT_TRUE(!![webView _safeBrowsingWarning]);
while ([webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
EXPECT_FALSE(!![webView _safeBrowsingWarning]);
[webView evaluateJavaScript:[NSString stringWithFormat:@"window.location='%@'", simple2URL.get()] completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
#if !PLATFORM(MAC)
[[webView _safeBrowsingWarning] didMoveToWindow];
#endif
return webView;
};
auto checkURLs = [&] (Vector<RetainPtr<NSURL>>&& expected) {
EXPECT_EQ(urls.size(), expected.size());
if (urls.size() != expected.size())
return;
for (size_t i = 0; i < expected.size(); ++i)
EXPECT_STREQ(urls[i].string().utf8().data(), [expected[i] absoluteString].UTF8String);
};
{
auto webView = webViewWithWarning();
checkURLs({ simpleURL, simple2URL });
goBack([webView _safeBrowsingWarning]);
checkURLs({ simpleURL, simple2URL, simpleURL });
[webView removeObserver:observer.get() forKeyPath:@"URL"];
}
urls.clear();
{
auto webView = webViewWithWarning();
checkURLs({ simpleURL, simple2URL });
visitUnsafeSite([webView _safeBrowsingWarning]);
TestWebKitAPI::Util::spinRunLoop(5);
checkURLs({ simpleURL, simple2URL });
[webView removeObserver:observer.get() forKeyPath:@"URL"];
}
}
static RetainPtr<NSString> phishingResourceName;
@interface SimpleLookupContext : NSObject
@end
@implementation SimpleLookupContext
+ (SimpleLookupContext *)sharedLookupContext
{
static SimpleLookupContext *context = [[SimpleLookupContext alloc] init];
return context;
}
- (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
{
BOOL phishing = NO;
if ([URL isEqual:resourceURL(phishingResourceName.get())] || [[URL path] hasSuffix:phishingResourceName.get()])
phishing = YES;
completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"SSBProviderApple" phishing:phishing malware:NO unwantedSoftware:NO]]], nil);
}
@end
static bool navigationFinished;
@interface WKWebViewGoBackNavigationDelegate : NSObject <WKNavigationDelegate>
@end
@implementation WKWebViewGoBackNavigationDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
navigationFinished = true;
}
@end
TEST(SafeBrowsing, WKWebViewGoBack)
{
phishingResourceName = @"simple3";
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([WKWebViewGoBackNavigationDelegate new]);
auto webView = adoptNS([WKWebView new]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
TestWebKitAPI::Util::run(&navigationFinished);
navigationFinished = false;
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
TestWebKitAPI::Util::run(&navigationFinished);
navigationFinished = false;
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple3")]];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
[webView goBack];
TestWebKitAPI::Util::run(&navigationFinished);
EXPECT_TRUE([[webView URL] isEqual:resourceURL(@"simple2")]);
}
TEST(SafeBrowsing, WKWebViewGoBackIFrame)
{
phishingResourceName = @"simple";
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([TestNavigationDelegate new]);
auto webView = adoptNS([WKWebView new]);
[webView configuration].preferences._safeBrowsingEnabled = YES;
__block bool navigationFailed = false;
__block bool navigationFinished = false;
delegate.get().didFailProvisionalLoadInSubframeWithError = ^(WKWebView *, WKFrameInfo *frame, NSError *error) {
EXPECT_NOT_NULL(error);
auto failingURL = (NSURL *)[error.userInfo valueForKey:NSURLErrorFailingURLErrorKey];
EXPECT_TRUE([failingURL.lastPathComponent isEqualToString:@"simple.html"]);
#if USE(NSURL_ERROR_FAILING_URL_STRING_KEY)
auto failingURLString = (NSString *)[error.userInfo valueForKey:NSURLErrorFailingURLStringErrorKey];
EXPECT_TRUE([failingURLString hasSuffix:@"/simple.html"]);
#endif
navigationFailed = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
navigationFinished = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
TestWebKitAPI::Util::run(&navigationFinished);
EXPECT_FALSE(navigationFailed);
navigationFinished = false;
navigationFailed = false;
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple-iframe")]];
TestWebKitAPI::Util::run(&navigationFinished);
TestWebKitAPI::Util::run(&navigationFailed);
}
@interface NullLookupContext : NSObject
@end
@implementation NullLookupContext
+ (NullLookupContext *)sharedLookupContext
{
return nil;
}
@end
TEST(SafeBrowsing, MissingFramework)
{
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [NullLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[webView synchronouslyLoadTestPageNamed:@"simple"];
}
static Seconds delayDuration;
@interface DelayedLookupContext : NSObject
@end
@implementation DelayedLookupContext
+ (DelayedLookupContext *)sharedLookupContext
{
static DelayedLookupContext *context = [[DelayedLookupContext alloc] init];
return context;
}
- (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
{
BOOL phishing = ![URL isEqual:resourceURL(@"simple2")] && ![[URL path] isEqual:@"/safe"];
RunLoop::mainSingleton().dispatchAfter(delayDuration, [completionHandler = makeBlockPtr(completionHandler), phishing] {
completionHandler.get()([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"SSBProviderApple" phishing:phishing malware:NO unwantedSoftware:NO]]], nil);
});
}
@end
TEST(SafeBrowsing, HangTimeout)
{
delayDuration = 1000_s;
TestWebKitAPI::HTTPServer server({
{ "/test"_s, { "test"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/test'" completionHandler:nil];
[delegate waitForDidFinishNavigation];
}
TEST(SafeBrowsing, PostResponse)
{
delayDuration = 25_ms;
TestWebKitAPI::HTTPServer server({
{ "/test"_s, { "test"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/test'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, PostResponseIframe)
{
delayDuration = 25_ms;
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([TestNavigationDelegate new]);
auto webView = adoptNS([WKWebView new]);
[webView configuration].preferences._safeBrowsingEnabled = YES;
__block bool navigationFailed = false;
__block bool navigationFinished = false;
delegate.get().didFailProvisionalLoadInSubframeWithError = ^(WKWebView *, WKFrameInfo *frame, NSError *error) {
EXPECT_NOT_NULL(error);
auto failingURL = (NSURL *)[error.userInfo valueForKey:NSURLErrorFailingURLErrorKey];
EXPECT_TRUE([failingURL.lastPathComponent isEqualToString:@"simple.html"]);
#if USE(NSURL_ERROR_FAILING_URL_STRING_KEY)
auto failingURLString = (NSString *)[error.userInfo valueForKey:NSURLErrorFailingURLStringErrorKey];
EXPECT_TRUE([failingURLString hasSuffix:@"/simple.html"]);
#endif
navigationFailed = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
navigationFinished = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
TestWebKitAPI::Util::run(&navigationFinished);
EXPECT_FALSE(navigationFailed);
}
static const char* mainResource = "";
TEST(SafeBrowsing, PreresponseSafeBrowsingWarning)
{
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
auto handler = adoptNS([[TestURLSchemeHandler alloc] init]);
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setURLSchemeHandler:handler.get() forURLScheme:@"sb"];
configuration.get().preferences.fraudulentWebsiteWarningEnabled = YES;
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]);
[webView setNavigationDelegate:delegate.get()];
[handler setStartURLSchemeTaskHandler:^(WKWebView *, id<WKURLSchemeTask> task) {
RunLoop::mainSingleton().dispatchAfter(1000_s, [task = retainPtr(task)] {
auto response = adoptNS([[NSURLResponse alloc] initWithURL:task.get().request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil]);
[task didReceiveResponse:response.get()];
[task didReceiveData:[NSData dataWithBytes:mainResource length:strlen(mainResource)]];
[task didFinish];
});
}];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"sb://host1/main.html"]]];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, PostResponseServerSideRedirect)
{
delayDuration = 2_ms;
TestWebKitAPI::HTTPServer server({
{ "/safe"_s, { 301, { { "Location"_s, "/redirectTarget"_s } } } },
{ "/redirectTarget"_s, { "hi"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/safe'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, MultipleRedirectsFirstPhishing)
{
phishingResourceName = @"safe";
TestWebKitAPI::HTTPServer server({
{ "/safe"_s, { 301, { { "Location"_s, "/redirectTarget1"_s } } } },
{ "/redirectTarget1"_s, { 301, { { "Location"_s, "/redirectTarget2"_s } } } },
{ "/redirectTarget2"_s, { "hi"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/safe'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, MultipleRedirectsMiddlePhishing)
{
phishingResourceName = @"redirectTarget1";
TestWebKitAPI::HTTPServer server({
{ "/safe"_s, { 301, { { "Location"_s, "/redirectTarget1"_s } } } },
{ "/redirectTarget1"_s, { 301, { { "Location"_s, "/redirectTarget2"_s } } } },
{ "/redirectTarget2"_s, { "hi"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/safe'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, MultipleRedirectsLastPhishing)
{
phishingResourceName = @"redirectTarget2";
TestWebKitAPI::HTTPServer server({
{ "/safe"_s, { 301, { { "Location"_s, "/redirectTarget1"_s } } } },
{ "/redirectTarget1"_s, { 301, { { "Location"_s, "/redirectTarget2"_s } } } },
{ "/redirectTarget2"_s, { "hi"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/safe'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, PostResponseInjectedBundleSkipsDecidePolicyForResponse)
{
delayDuration = 25_ms;
TestWebKitAPI::HTTPServer server({
{ "/test"_s, { "test"_s } },
});
WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"SkipDecidePolicyForResponsePlugIn"];
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
navigationFinished = false;
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/test'" completionHandler:nil];
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, PostTimeout)
{
delayDuration = 100_ms;
TestWebKitAPI::HTTPServer server({
{ "/test"_s, { "test"_s } },
}, TestWebKitAPI::HTTPServer::Protocol::HttpsProxy);
auto configuration = server.httpsProxyConfiguration();
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [DelayedLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
[webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
auto delegate = adoptNS([TestNavigationDelegate new]);
[delegate allowAnyTLSCertificate];
[webView setNavigationDelegate:delegate.get()];
[webView evaluateJavaScript:@"window.location = 'https://example2.com/test'" completionHandler:nil];
EXPECT_WK_STREQ([webView title], "");
while (![webView _safeBrowsingWarning])
TestWebKitAPI::Util::spinRunLoop();
}
TEST(SafeBrowsing, PhishingInFrame)
{
phishingResourceName = @"simple";
ClassMethodSwizzler swizzler(getSSBLookupContextClassSingleton(), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
auto delegate = adoptNS([TestNavigationDelegate new]);
auto webView = adoptNS([WKWebView new]);
auto configuration = webView.get().configuration;
auto preferences = configuration.preferences;
preferences._safeBrowsingEnabled = YES;
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"SiteIsolationEnabled"]) {
[preferences _setEnabled:YES forFeature:feature];
break;
}
}
__block bool navigationFailed = false;
__block bool navigationFinished = false;
delegate.get().didFailProvisionalLoadInSubframeWithError = ^(WKWebView *, WKFrameInfo *frame, NSError *error) {
EXPECT_NOT_NULL(error);
auto failingURL = (NSURL *)[error.userInfo valueForKey:NSURLErrorFailingURLErrorKey];
EXPECT_TRUE([failingURL.lastPathComponent isEqualToString:@"simple.html"]);
#if USE(NSURL_ERROR_FAILING_URL_STRING_KEY)
auto failingURLString = (NSString *)[error.userInfo valueForKey:NSURLErrorFailingURLStringErrorKey];
EXPECT_TRUE([failingURLString hasSuffix:@"/simple.html"]);
#endif
navigationFailed = true;
};
delegate.get().didFinishNavigation = ^(WKWebView *, WKNavigation *navigation) {
navigationFinished = true;
};
[webView setNavigationDelegate:delegate.get()];
[webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple-iframe")]];
TestWebKitAPI::Util::run(&navigationFinished);
TestWebKitAPI::Util::run(&navigationFailed);
EXPECT_TRUE(navigationFailed);
EXPECT_TRUE(navigationFinished);
}
#endif // HAVE(SAFE_BROWSING)