EWS should have a way to retry a patch
[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 HAVE(SAFE_BROWSING)
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 didCloseCalled;
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)webViewDidClose:(WKWebView *)webView
62 {
63     didCloseCalled = 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_TRUE([webView configuration].preferences.fraudulentWebsiteWarningEnabled);
174     [webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
175     [webView setNavigationDelegate:delegate.get()];
176     [webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
177     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
178     while (![webView _safeBrowsingWarning])
179         TestWebKitAPI::Util::spinRunLoop();
180     [webView configuration].preferences.fraudulentWebsiteWarningEnabled = NO;
181     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
182     TestWebKitAPI::Util::run(&done);
183     EXPECT_FALSE([webView configuration].preferences.fraudulentWebsiteWarningEnabled);
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.fraudulentWebsiteWarningEnabled = 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, bool mainFrame = true)
224 {
225     WKWebView *webView = (WKWebView *)view.superview;
226     auto box = view.subviews.firstObject;
227     checkTitleAndClick(box.subviews[3], "Go Back");
228     if (mainFrame)
229         EXPECT_EQ([webView _safeBrowsingWarning], nil);
230     else
231         EXPECT_NE([webView _safeBrowsingWarning], nil);
232 }
233
234 TEST(SafeBrowsing, GoBack)
235 {
236     auto webView = safeBrowsingView();
237     EXPECT_FALSE(didCloseCalled);
238     goBack([webView _safeBrowsingWarning]);
239     EXPECT_TRUE(didCloseCalled);
240 }
241
242 template<typename ViewType> void visitUnsafeSite(ViewType *view)
243 {
244     [view performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[NSURL URLWithString:@"WKVisitUnsafeWebsiteSentinel"]];
245 }
246
247 TEST(SafeBrowsing, VisitUnsafeWebsite)
248 {
249     auto webView = safeBrowsingView();
250     auto warning = [webView _safeBrowsingWarning];
251     EXPECT_EQ(warning.subviews.count, 1ull);
252 #if PLATFORM(MAC)
253     EXPECT_GT(warning.subviews.firstObject.subviews[2].frame.size.height, 0);
254 #endif
255     checkTitleAndClick(warning.subviews.firstObject.subviews[4], "Show Details");
256     EXPECT_EQ(warning.subviews.count, 2ull);
257     EXPECT_FALSE(committedNavigation);
258     visitUnsafeSite(warning);
259     TestWebKitAPI::Util::run(&committedNavigation);
260 }
261
262 TEST(SafeBrowsing, NavigationClearsWarning)
263 {
264     auto webView = safeBrowsingView();
265     EXPECT_NE([webView _safeBrowsingWarning], nil);
266     [webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]];
267     while ([webView _safeBrowsingWarning])
268         TestWebKitAPI::Util::spinRunLoop();
269 }
270
271 TEST(SafeBrowsing, ShowWarningSPI)
272 {
273     __block bool completionHandlerCalled = false;
274     __block BOOL shouldContinueValue = NO;
275     auto webView = adoptNS([WKWebView new]);
276     auto showWarning = ^{
277         completionHandlerCalled = false;
278         [webView _showSafeBrowsingWarningWithURL:nil title:@"test title" warning:@"test warning" details:[[[NSAttributedString alloc] initWithString:@"test details"] autorelease] completionHandler:^(BOOL shouldContinue) {
279             shouldContinueValue = shouldContinue;
280             completionHandlerCalled = true;
281         }];
282 #if !PLATFORM(MAC)
283         [[webView _safeBrowsingWarning] didMoveToWindow];
284 #endif
285     };
286
287     showWarning();
288     checkTitleAndClick([webView _safeBrowsingWarning].subviews.firstObject.subviews[3], "Go Back");
289     TestWebKitAPI::Util::run(&completionHandlerCalled);
290     EXPECT_FALSE(shouldContinueValue);
291
292     showWarning();
293     [[webView _safeBrowsingWarning] performSelector:NSSelectorFromString(@"clickedOnLink:") withObject:[WKWebView _visitUnsafeWebsiteSentinel]];
294     TestWebKitAPI::Util::run(&completionHandlerCalled);
295     EXPECT_TRUE(shouldContinueValue);
296 }
297
298 static Vector<URL> urls;
299
300 @interface SafeBrowsingObserver : NSObject
301 @end
302
303 @implementation SafeBrowsingObserver
304
305 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
306 {
307     urls.append((NSURL *)[change objectForKey:NSKeyValueChangeNewKey]);
308 }
309
310 @end
311
312 TEST(SafeBrowsing, URLObservation)
313 {
314     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [TestLookupContext methodForSelector:@selector(sharedLookupContext)]);
315
316     RetainPtr<NSURL> simpleURL = [[NSBundle mainBundle] URLForResource:@"simple" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
317     RetainPtr<NSURL> simple2URL = [[NSBundle mainBundle] URLForResource:@"simple2" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
318     auto observer = adoptNS([SafeBrowsingObserver new]);
319
320     auto webViewWithWarning = [&] () -> RetainPtr<WKWebView> {
321         auto webView = adoptNS([WKWebView new]);
322         [webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
323         [webView addObserver:observer.get() forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
324
325         [webView loadHTMLString:@"meaningful content to be drawn" baseURL:simpleURL.get()];
326         while (![webView _safeBrowsingWarning])
327             TestWebKitAPI::Util::spinRunLoop();
328 #if !PLATFORM(MAC)
329         [[webView _safeBrowsingWarning] didMoveToWindow];
330 #endif
331         visitUnsafeSite([webView _safeBrowsingWarning]);
332         EXPECT_TRUE(!![webView _safeBrowsingWarning]);
333         while ([webView _safeBrowsingWarning])
334             TestWebKitAPI::Util::spinRunLoop();
335         EXPECT_FALSE(!![webView _safeBrowsingWarning]);
336
337         [webView evaluateJavaScript:[NSString stringWithFormat:@"window.location='%@'", simple2URL.get()] completionHandler:nil];
338         while (![webView _safeBrowsingWarning])
339             TestWebKitAPI::Util::spinRunLoop();
340 #if !PLATFORM(MAC)
341         [[webView _safeBrowsingWarning] didMoveToWindow];
342 #endif
343         return webView;
344     };
345     
346     auto checkURLs = [&] (Vector<RetainPtr<NSURL>>&& expected) {
347         EXPECT_EQ(urls.size(), expected.size());
348         if (urls.size() != expected.size())
349             return;
350         for (size_t i = 0; i < expected.size(); ++i)
351             EXPECT_STREQ(urls[i].string().utf8().data(), [expected[i] absoluteString].UTF8String);
352     };
353
354     {
355         auto webView = webViewWithWarning();
356         checkURLs({ simpleURL, simple2URL });
357         goBack([webView _safeBrowsingWarning]);
358         checkURLs({ simpleURL, simple2URL, simpleURL });
359         [webView removeObserver:observer.get() forKeyPath:@"URL"];
360     }
361     
362     urls.clear();
363
364     {
365         auto webView = webViewWithWarning();
366         checkURLs({ simpleURL, simple2URL });
367         visitUnsafeSite([webView _safeBrowsingWarning]);
368         TestWebKitAPI::Util::spinRunLoop(5);
369         checkURLs({ simpleURL, simple2URL });
370         [webView removeObserver:observer.get() forKeyPath:@"URL"];
371     }
372 }
373
374 static RetainPtr<NSString> phishingResourceName;
375
376 @interface SimpleLookupContext : NSObject
377 @end
378
379 @implementation SimpleLookupContext
380
381 + (SimpleLookupContext *)sharedLookupContext
382 {
383     static SimpleLookupContext *context = [[SimpleLookupContext alloc] init];
384     return context;
385 }
386
387 - (void)lookUpURL:(NSURL *)URL completionHandler:(void (^)(TestLookupResult *, NSError *))completionHandler
388 {
389     BOOL phishing = NO;
390     if ([URL isEqual:resourceURL(phishingResourceName.get())])
391         phishing = YES;
392     completionHandler([TestLookupResult resultWithResults:@[[TestServiceLookupResult resultWithProvider:@"TestProvider" phishing:phishing malware:NO unwantedSoftware:NO]]], nil);
393 }
394
395 @end
396
397 static bool navigationFinished;
398
399 @interface WKWebViewGoBackNavigationDelegate : NSObject <WKNavigationDelegate>
400 @end
401
402 @implementation WKWebViewGoBackNavigationDelegate
403
404 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
405 {
406     navigationFinished = true;
407 }
408
409 @end
410
411 TEST(SafeBrowsing, WKWebViewGoBack)
412 {
413     phishingResourceName = @"simple3";
414     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
415     
416     auto delegate = adoptNS([WKWebViewGoBackNavigationDelegate new]);
417     auto webView = adoptNS([WKWebView new]);
418     [webView configuration].preferences.fraudulentWebsiteWarningEnabled = YES;
419     [webView setNavigationDelegate:delegate.get()];
420     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple")]];
421     TestWebKitAPI::Util::run(&navigationFinished);
422
423     navigationFinished = false;
424     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
425     TestWebKitAPI::Util::run(&navigationFinished);
426
427     navigationFinished = false;
428     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple3")]];
429     while (![webView _safeBrowsingWarning])
430         TestWebKitAPI::Util::spinRunLoop();
431     [webView goBack];
432     TestWebKitAPI::Util::run(&navigationFinished);
433     EXPECT_TRUE([[webView URL] isEqual:resourceURL(@"simple2")]);
434 }
435
436 TEST(SafeBrowsing, WKWebViewGoBackIFrame)
437 {
438     phishingResourceName = @"simple";
439     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [SimpleLookupContext methodForSelector:@selector(sharedLookupContext)]);
440     
441     auto delegate = adoptNS([WKWebViewGoBackNavigationDelegate new]);
442     auto webView = adoptNS([WKWebView new]);
443     [webView configuration].preferences._safeBrowsingEnabled = YES;
444     [webView setNavigationDelegate:delegate.get()];
445     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple2")]];
446     TestWebKitAPI::Util::run(&navigationFinished);
447
448     [webView loadRequest:[NSURLRequest requestWithURL:resourceURL(@"simple-iframe")]];
449     while (![webView _safeBrowsingWarning])
450         TestWebKitAPI::Util::spinRunLoop();
451 #if !PLATFORM(MAC)
452     [[webView _safeBrowsingWarning] didMoveToWindow];
453 #endif
454     navigationFinished = false;
455     goBack([webView _safeBrowsingWarning], false);
456     TestWebKitAPI::Util::run(&navigationFinished);
457     EXPECT_TRUE([[webView URL] isEqual:resourceURL(@"simple2")]);
458 }
459
460 @interface NullLookupContext : NSObject
461 @end
462 @implementation NullLookupContext
463 + (NullLookupContext *)sharedLookupContext
464 {
465     return nil;
466 }
467 @end
468
469 TEST(SafeBrowsing, MissingFramework)
470 {
471     ClassMethodSwizzler swizzler(objc_getClass("SSBLookupContext"), @selector(sharedLookupContext), [NullLookupContext methodForSelector:@selector(sharedLookupContext)]);
472     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
473     [webView synchronouslyLoadTestPageNamed:@"simple"];
474 }
475
476 #endif // HAVE(SAFE_BROWSING)