[iOS] Implement safe browsing in WebKit
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / SafeBrowsing.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27
28 #if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)) && !defined(__i386__) && !PLATFORM(IOSMAC)
29
30 #import "ClassMethodSwizzler.h"
31 #import "PlatformUtilities.h"
32 #import "TestWKWebView.h"
33 #import <WebKit/WKNavigationDelegate.h>
34 #import <WebKit/WKWebViewPrivate.h>
35 #import <wtf/RetainPtr.h>
36
37 static bool committedNavigation;
38
39 @interface SafeBrowsingNavigationDelegate : NSObject <WKNavigationDelegate>
40 @end
41
42 @implementation SafeBrowsingNavigationDelegate
43
44 - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
45 {
46     committedNavigation = true;
47 }
48
49 @end
50
51 @interface TestServiceLookupResult : NSObject {
52     RetainPtr<NSString> _provider;
53     BOOL _isPhishing;
54     BOOL _isMalware;
55     BOOL _isUnwantedSoftware;
56 }
57 @end
58
59 @implementation TestServiceLookupResult
60
61 + (instancetype)resultWithProvider:(RetainPtr<NSString>&&)provider phishing:(BOOL)phishing malware:(BOOL)malware unwantedSoftware:(BOOL)unwantedSoftware
62 {
63     TestServiceLookupResult *result = [[TestServiceLookupResult alloc] init];
64     if (!result)
65         return nil;
66
67     result->_provider = WTFMove(provider);
68     result->_isPhishing = phishing;
69     result->_isMalware = malware;
70     result->_isUnwantedSoftware = unwantedSoftware;
71
72     return [result autorelease];
73 }
74
75 - (NSString *)provider
76 {
77     return _provider.get();
78 }
79
80 - (BOOL)isPhishing
81 {
82     return _isPhishing;
83 }
84
85 - (BOOL)isMalware
86 {
87     return _isMalware;
88 }
89
90 - (BOOL)isUnwantedSoftware
91 {
92     return _isUnwantedSoftware;
93 }
94
95 @end
96
97 @interface TestLookupResult : NSObject {
98     RetainPtr<NSArray> _results;
99 }
100 @end
101
102 @implementation TestLookupResult
103
104 + (instancetype)resultWithResults:(RetainPtr<NSArray<TestServiceLookupResult *>>&&)results
105 {
106     TestLookupResult *result = [[TestLookupResult alloc] init];
107     if (!result)
108         return nil;
109     
110     result->_results = WTFMove(results);
111     
112     return [result autorelease];
113 }
114
115 - (NSArray<TestServiceLookupResult *> *)serviceLookupResults
116 {
117     return _results.get();
118 }
119
120 @end
121
122 @interface TestLookupContext : NSObject
123 @end
124
125 @implementation TestLookupContext
126
127 + (TestLookupContext *)sharedLookupContext
128 {
129     static TestLookupContext *context = [[TestLookupContext alloc] init];
130     return context;
131 }
132
133 - (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
134 {
135     completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"TestProvider" phishing:YES malware:NO unwantedSoftware:NO]]], nil);
136 }
137
138 @end
139
140 static NSURL *simpleURL()
141 {
142     return [[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
143 }
144
145 static RetainPtr<WKWebView> safeBrowsingView()
146 {
147     TestWebKitAPI::ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
148
149     static auto delegate = adoptNS([SafeBrowsingNavigationDelegate new]);
150     auto webView = adoptNS([WKWebView new]);
151     [webView setNavigationDelegate:delegate.get()];
152     [webView loadRequest:[NSURLRequest requestWithURL:simpleURL()]];
153     while (![webView _safeBrowsingWarningForTesting])
154         TestWebKitAPI::Util::spinRunLoop();
155 #if !PLATFORM(MAC)
156     [[webView _safeBrowsingWarningForTesting] didMoveToWindow];
157 #endif
158     return webView;
159 }
160
161 #if PLATFORM(MAC)
162 static void checkTitleAndClick(NSButton *button, const char* expectedTitle)
163 {
164     EXPECT_STREQ(button.title.UTF8String, expectedTitle);
165     [button performClick:nil];
166 }
167 #else
168 static void checkTitleAndClick(UIButton *button, const char* expectedTitle)
169 {
170     EXPECT_STREQ([button attributedTitleForState:UIControlStateNormal].string.UTF8String, expectedTitle);
171     UIView *target = button.superview.superview;
172     SEL selector = NSSelectorFromString(strcmp(expectedTitle, "Go Back") ? @"showDetailsClicked" : @"goBackClicked");
173     [target performSelector:selector];
174 }
175 #endif
176
177 TEST(SafeBrowsing, GoBack)
178 {
179     auto webView = safeBrowsingView();
180     auto warning = [webView _safeBrowsingWarningForTesting];
181     auto box = warning.subviews.firstObject;
182     checkTitleAndClick(box.subviews[3], "Go Back");
183     EXPECT_EQ([webView _safeBrowsingWarningForTesting], nil);
184 }
185
186 TEST(SafeBrowsing, VisitUnsafeWebsite)
187 {
188     auto webView = safeBrowsingView();
189     auto warning = [webView _safeBrowsingWarningForTesting];
190     EXPECT_EQ(warning.subviews.count, 1ull);
191     checkTitleAndClick(warning.subviews.firstObject.subviews[4], "Show Details");
192     EXPECT_EQ(warning.subviews.count, 2ull);
193     EXPECT_FALSE(committedNavigation);
194     [warning performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[NSURL URLWithString:@"WKVisitUnsafeWebsiteSentinel"]];
195     TestWebKitAPI::Util::run(&committedNavigation);
196 }
197
198 TEST(SafeBrowsing, NavigationClearsWarning)
199 {
200     auto webView = safeBrowsingView();
201     EXPECT_NE([webView _safeBrowsingWarningForTesting], nil);
202     [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]];
203     while ([webView _safeBrowsingWarningForTesting])
204         TestWebKitAPI::Util::spinRunLoop();
205 }
206
207 @interface NullLookupContext : NSObject
208 @end
209 @implementation NullLookupContext
210 + (NullLookupContext *)sharedLookupContext
211 {
212     return nil;
213 }
214 @end
215
216 TEST(SafeBrowsing, MissingFramework)
217 {
218     TestWebKitAPI::ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [NullLookupContext methodForSelector:@selector(sharedLookupContext)]);
219     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
220     [webView synchronouslyLoadTestPageNamed:@"simple"];
221 }
222
223 #endif // WK_API_ENABLED