Fix API test after r241728
[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 "TestNavigationDelegate.h"
33 #import "TestWKWebView.h"
34 #import <WebKit/WKNavigationDelegate.h>
35 #import <WebKit/WKPreferencesPrivate.h>
36 #import <WebKit/WKUIDelegatePrivate.h>
37 #import <WebKit/WKWebViewPrivate.h>
38 #import <wtf/RetainPtr.h>
39 #import <wtf/URL.h>
40 #import <wtf/Vector.h>
41
42 static bool committedNavigation;
43 static bool warningShown;
44 static bool goBackClicked;
45
46 @interface SafeBrowsingNavigationDelegate : NSObject <WKNavigationDelegate, WKUIDelegatePrivate>
47 @end
48
49 @implementation SafeBrowsingNavigationDelegate
50
51 - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation
52 {
53     committedNavigation = true;
54 }
55
56 - (void)_webViewDidShowSafeBrowsingWarning:(WKWebView *)webView
57 {
58     warningShown = true;
59 }
60
61 - (void)_webViewDidClickGoBackFromSafeBrowsingWarning:(WKWebView *)webView
62 {
63     goBackClicked = true;
64 }
65
66 @end
67
68 @interface TestServiceLookupResult : NSObject {
69     RetainPtr<NSString> _provider;
70     BOOL _isPhishing;
71     BOOL _isMalware;
72     BOOL _isUnwantedSoftware;
73 }
74 @end
75
76 @implementation TestServiceLookupResult
77
78 + (instancetype)resultWithProvider:(RetainPtr<NSString>&&)provider phishing:(BOOL)phishing malware:(BOOL)malware unwantedSoftware:(BOOL)unwantedSoftware
79 {
80     TestServiceLookupResult *result = [[TestServiceLookupResult alloc] init];
81     if (!result)
82         return nil;
83
84     result->_provider = WTFMove(provider);
85     result->_isPhishing = phishing;
86     result->_isMalware = malware;
87     result->_isUnwantedSoftware = unwantedSoftware;
88
89     return [result autorelease];
90 }
91
92 - (NSString *)provider
93 {
94     return _provider.get();
95 }
96
97 - (BOOL)isPhishing
98 {
99     return _isPhishing;
100 }
101
102 - (BOOL)isMalware
103 {
104     return _isMalware;
105 }
106
107 - (BOOL)isUnwantedSoftware
108 {
109     return _isUnwantedSoftware;
110 }
111
112 @end
113
114 @interface TestLookupResult : NSObject {
115     RetainPtr<NSArray> _results;
116 }
117 @end
118
119 @implementation TestLookupResult
120
121 + (instancetype)resultWithResults:(RetainPtr<NSArray<TestServiceLookupResult *>>&&)results
122 {
123     TestLookupResult *result = [[TestLookupResult alloc] init];
124     if (!result)
125         return nil;
126     
127     result->_results = WTFMove(results);
128     
129     return [result autorelease];
130 }
131
132 - (NSArray<TestServiceLookupResult *> *)serviceLookupResults
133 {
134     return _results.get();
135 }
136
137 @end
138
139 @interface TestLookupContext : NSObject
140 @end
141
142 @implementation TestLookupContext
143
144 + (TestLookupContext *)sharedLookupContext
145 {
146     static TestLookupContext *context = [[TestLookupContext alloc] init];
147     return context;
148 }
149
150 - (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
151 {
152     completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"TestProvider" phishing:YES malware:NO unwantedSoftware:NO]]], nil);
153 }
154
155 @end
156
157 static NSURL *resourceURL(NSString *resource)
158 {
159     return [[NSBundle mainBundle] URLForResource:resource withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
160 }
161
162 TEST(SafeBrowsing, Preference)
163 {
164     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
165
166     __block bool done = false;
167     auto delegate = adoptNS([TestNavigationDelegate new]);
168     delegate.get().didStartProvisionalNavigation = ^(WKWebView *, WKNavigation *) {
169         done = true;
170     };
171
172     auto webView = adoptNS([WKWebView new]);
173     EXPECT_FALSE([webView configuration].preferences._safeBrowsingEnabled);
174     [webView configuration].preferences._safeBrowsingEnabled = YES;
175     [webView setNavigationDelegate:delegate.get()];
176     [webView configuration].preferences._safeBrowsingEnabled = YES;
177     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
178     while (![webView _safeBrowsingWarning])
179         TestWebKitAPI::Util::spinRunLoop();
180     [webView configuration].preferences._safeBrowsingEnabled = NO;
181     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
182     TestWebKitAPI::Util::run(&done);
183     EXPECT_FALSE([webView configuration].preferences._safeBrowsingEnabled);
184     EXPECT_FALSE([webView _safeBrowsingWarning]);
185 }
186
187 static RetainPtr<WKWebView> safeBrowsingView()
188 {
189     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
190
191     static auto delegate = adoptNS([SafeBrowsingNavigationDelegate new]);
192     auto webView = adoptNS([WKWebView new]);
193     [webView configuration].preferences._safeBrowsingEnabled = YES;
194     [webView setNavigationDelegate:delegate.get()];
195     [webView setUIDelegate:delegate.get()];
196     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
197     EXPECT_FALSE(warningShown);
198     while (![webView _safeBrowsingWarning])
199         TestWebKitAPI::Util::spinRunLoop();
200     EXPECT_TRUE(warningShown);
201 #if !PLATFORM(MAC)
202     [[webView _safeBrowsingWarning] didMoveToWindow];
203 #endif
204     return webView;
205 }
206
207 #if PLATFORM(MAC)
208 static void checkTitleAndClick(NSButton *button, const char* expectedTitle)
209 {
210     EXPECT_STREQ(button.title.UTF8String, expectedTitle);
211     [button performClick:nil];
212 }
213 #else
214 static void checkTitleAndClick(UIButton *button, const char* expectedTitle)
215 {
216     EXPECT_STREQ([button attributedTitleForState:UIControlStateNormal].string.UTF8String, expectedTitle);
217     UIView *target = button.superview.superview;
218     SEL selector = NSSelectorFromString(strcmp(expectedTitle, "Go Back") ? @"showDetailsClicked" : @"goBackClicked");
219     [target performSelector:selector];
220 }
221 #endif
222
223 template<typename ViewType> void goBack(ViewType *view)
224 {
225     WKWebView *webView = (WKWebView *)view.superview;
226     auto box = view.subviews.firstObject;
227     checkTitleAndClick(box.subviews[3], "Go Back");
228     EXPECT_EQ([webView _safeBrowsingWarning], nil);
229 }
230
231 TEST(SafeBrowsing, GoBack)
232 {
233     auto webView = safeBrowsingView();
234     EXPECT_FALSE(goBackClicked);
235     goBack([webView _safeBrowsingWarning]);
236     EXPECT_TRUE(goBackClicked);
237 }
238
239 template<typename ViewType> void visitUnsafeSite(ViewType *view)
240 {
241     [view performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[NSURL URLWithString:@"WKVisitUnsafeWebsiteSentinel"]];
242 }
243
244 TEST(SafeBrowsing, VisitUnsafeWebsite)
245 {
246     auto webView = safeBrowsingView();
247     auto warning = [webView _safeBrowsingWarning];
248     EXPECT_EQ(warning.subviews.count, 1ull);
249 #if PLATFORM(MAC)
250     EXPECT_GT(warning.subviews.firstObject.subviews[2].frame.size.height, 0);
251 #endif
252     checkTitleAndClick(warning.subviews.firstObject.subviews[4], "Show Details");
253     EXPECT_EQ(warning.subviews.count, 2ull);
254     EXPECT_FALSE(committedNavigation);
255     visitUnsafeSite(warning);
256     TestWebKitAPI::Util::run(&committedNavigation);
257 }
258
259 TEST(SafeBrowsing, NavigationClearsWarning)
260 {
261     auto webView = safeBrowsingView();
262     EXPECT_NE([webView _safeBrowsingWarning], nil);
263     [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]];
264     while ([webView _safeBrowsingWarning])
265         TestWebKitAPI::Util::spinRunLoop();
266 }
267
268 TEST(SafeBrowsing, ShowWarningSPI)
269 {
270     __block bool completionHandlerCalled = false;
271     __block BOOL shouldContinueValue = NO;
272     auto webView = adoptNS([WKWebView new]);
273     auto showWarning = ^{
274         completionHandlerCalled = false;
275         [webView _showSafeBrowsingWarningWithURL:nil title:@"test title" warning:@"test warning" details:[[[NSAttributedString alloc] initWithString:@"test details"] autorelease] completionHandler:^(BOOL shouldContinue) {
276             shouldContinueValue = shouldContinue;
277             completionHandlerCalled = true;
278         }];
279 #if !PLATFORM(MAC)
280         [[webView _safeBrowsingWarning] didMoveToWindow];
281 #endif
282     };
283
284     showWarning();
285     checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
286     TestWebKitAPI::Util::run(&completionHandlerCalled);
287     EXPECT_FALSE(shouldContinueValue);
288
289     showWarning();
290     [[webView _safeBrowsingWarning] performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[WKWebView _visitUnsafeWebsiteSentinel]];
291     TestWebKitAPI::Util::run(&completionHandlerCalled);
292     EXPECT_TRUE(shouldContinueValue);
293 }
294
295 static Vector<URL> urls;
296
297 @interface SafeBrowsingObserver : NSObject
298 @end
299
300 @implementation SafeBrowsingObserver
301
302 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
303 {
304     urls.append((NSURL *)[change objectForKey:NSKeyValueChangeNewKey]);
305 }
306
307 @end
308
309 TEST(SafeBrowsing, URLObservation)
310 {
311     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
312
313     RetainPtr<NSURL> simpleURL = [[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
314     RetainPtr<NSURL> simple2URL = [[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
315     auto observer = adoptNS([SafeBrowsingObserver new]);
316
317     auto webViewWithWarning = [&] () -> RetainPtr<WKWebView> {
318         auto webView = adoptNS([WKWebView new]);
319         [webView configuration].preferences._safeBrowsingEnabled = YES;
320         [webView addObserver:observer.get() forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
321
322         [webView loadHTMLString:@"meaningful content to be drawn" baseURL:simpleURL.get()];
323         while (![webView _safeBrowsingWarning])
324             TestWebKitAPI::Util::spinRunLoop();
325 #if !PLATFORM(MAC)
326         [[webView _safeBrowsingWarning] didMoveToWindow];
327 #endif
328         visitUnsafeSite([webView _safeBrowsingWarning]);
329         EXPECT_TRUE(!![webView _safeBrowsingWarning]);
330         while ([webView _safeBrowsingWarning])
331             TestWebKitAPI::Util::spinRunLoop();
332         EXPECT_FALSE(!![webView _safeBrowsingWarning]);
333
334         [webView evaluateJavaScript:[NSString stringWithFormat:@"window.location='%@'", simple2URL.get()] completionHandler:nil];
335         while (![webView _safeBrowsingWarning])
336             TestWebKitAPI::Util::spinRunLoop();
337 #if !PLATFORM(MAC)
338         [[webView _safeBrowsingWarning] didMoveToWindow];
339 #endif
340         return webView;
341     };
342     
343     auto checkURLs = [&] (Vector<RetainPtr<NSURL>>&& expected) {
344         EXPECT_EQ(urls.size(), expected.size());
345         if (urls.size() != expected.size())
346             return;
347         for (size_t i = 0; i < expected.size(); ++i)
348             EXPECT_STREQ(urls[i].string().utf8().data(), [expected[i] absoluteString].UTF8String);
349     };
350
351     {
352         auto webView = webViewWithWarning();
353         checkURLs({ simpleURL, simple2URL });
354         goBack([webView _safeBrowsingWarning]);
355         checkURLs({ simpleURL, simple2URL, simpleURL });
356         [webView removeObserver:observer.get() forKeyPath:@"URL"];
357     }
358     
359     urls.clear();
360
361     {
362         auto webView = webViewWithWarning();
363         checkURLs({ simpleURL, simple2URL });
364         visitUnsafeSite([webView _safeBrowsingWarning]);
365         TestWebKitAPI::Util::spinRunLoop(5);
366         checkURLs({ simpleURL, simple2URL });
367         [webView removeObserver:observer.get() forKeyPath:@"URL"];
368     }
369 }
370
371 @interface Simple3LookupContext : NSObject
372 @end
373
374 @implementation Simple3LookupContext
375
376 + (Simple3LookupContext *)sharedLookupContext
377 {
378     static Simple3LookupContext *context = [[Simple3LookupContext alloc] init];
379     return context;
380 }
381
382 - (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
383 {
384     BOOL phishing = NO;
385     if ([URL isEqual:resourceURL(@"simple3")])
386         phishing = YES;
387     completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"TestProvider" phishing:phishing malware:NO unwantedSoftware:NO]]], nil);
388 }
389
390 @end
391
392 static bool navigationFinished;
393
394 @interface WKWebViewGoBackNavigationDelegate : NSObject <WKNavigationDelegate>
395 @end
396
397 @implementation WKWebViewGoBackNavigationDelegate
398
399 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
400 {
401     navigationFinished = true;
402 }
403
404 @end
405
406 TEST(SafeBrowsing, WKWebViewGoBack)
407 {
408     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [Simple3LookupContext methodForSelector:@selector(sharedLookupContext)]);
409     
410     auto delegate = adoptNS([WKWebViewGoBackNavigationDelegate new]);
411     auto webView = adoptNS([WKWebView new]);
412     [webView configuration].preferences._safeBrowsingEnabled = YES;
413     [webView setNavigationDelegate:delegate.get()];
414     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
415     TestWebKitAPI::Util::run(&navigationFinished);
416
417     navigationFinished = false;
418     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
419     TestWebKitAPI::Util::run(&navigationFinished);
420
421     navigationFinished = false;
422     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple3")]];
423     while (![webView _safeBrowsingWarning])
424         TestWebKitAPI::Util::spinRunLoop();
425     [webView goBack];
426     TestWebKitAPI::Util::run(&navigationFinished);
427     EXPECT_TRUE([[webView URL] isEqual:resourceURL(@"simple2")]);
428 }
429
430 @interface NullLookupContext : NSObject
431 @end
432 @implementation NullLookupContext
433 + (NullLookupContext *)sharedLookupContext
434 {
435     return nil;
436 }
437 @end
438
439 TEST(SafeBrowsing, MissingFramework)
440 {
441     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [NullLookupContext methodForSelector:@selector(sharedLookupContext)]);
442     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
443     [webView synchronouslyLoadTestPageNamed:@"simple"];
444 }
445
446 #endif // WK_API_ENABLED