301dba5a46f92b15caaf159b75bc5253c7d88c40
[WebKit.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / ContentFiltering.mm
1 /*
2  * Copyright (C) 2015-2016 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 #import "ContentFiltering.h"
29 #import "MockContentFilterSettings.h"
30 #import "PlatformUtilities.h"
31 #import "TestProtocol.h"
32 #import "WKWebViewConfigurationExtras.h"
33 #import <WebKit/WKErrorRef.h>
34 #import <WebKit/WKNavigationDelegatePrivate.h>
35 #import <WebKit/WKProcessPoolPrivate.h>
36 #import <WebKit/WKWebView.h>
37 #import <WebKit/WKWebViewPrivate.h>
38 #import <WebKit/_WKDownloadDelegate.h>
39 #import <WebKit/_WKRemoteObjectInterface.h>
40 #import <WebKit/_WKRemoteObjectRegistry.h>
41 #import <pal/spi/cocoa/NEFilterSourceSPI.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/SoftLinking.h>
44
45 SOFT_LINK_FRAMEWORK_OPTIONAL(NetworkExtension);
46 SOFT_LINK_CLASS_OPTIONAL(NetworkExtension, NEFilterSource);
47
48 using Decision = WebCore::MockContentFilterSettings::Decision;
49 using DecisionPoint = WebCore::MockContentFilterSettings::DecisionPoint;
50
51 static bool isDone;
52
53 @interface MockContentFilterEnabler : NSObject <NSCopying, NSSecureCoding>
54 - (instancetype)initWithDecision:(Decision)decision decisionPoint:(DecisionPoint)decisionPoint;
55 @end
56
57 @implementation MockContentFilterEnabler {
58     Decision _decision;
59     DecisionPoint _decisionPoint;
60 }
61
62 + (BOOL)supportsSecureCoding
63 {
64     return YES;
65 }
66
67 - (id)copyWithZone:(NSZone *)zone
68 {
69     return [self retain];
70 }
71
72 - (instancetype)initWithCoder:(NSCoder *)decoder
73 {
74     return [super init];
75 }
76
77 - (instancetype)initWithDecision:(Decision)decision decisionPoint:(DecisionPoint)decisionPoint
78 {
79     if (!(self = [super init]))
80         return nil;
81
82     _decision = decision;
83     _decisionPoint = decisionPoint;
84     return self;
85 }
86
87 - (void)encodeWithCoder:(NSCoder *)coder
88 {
89     [coder encodeInt:static_cast<int>(_decision) forKey:@"Decision"];
90     [coder encodeInt:static_cast<int>(_decisionPoint) forKey:@"DecisionPoint"];
91 }
92
93 @end
94
95 static RetainPtr<WKWebViewConfiguration> configurationWithContentFilterSettings(Decision decision, DecisionPoint decisionPoint)
96 {
97     auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"ContentFilteringPlugIn"]);
98     auto contentFilterEnabler = adoptNS([[MockContentFilterEnabler alloc] initWithDecision:decision decisionPoint:decisionPoint]);
99     [[configuration processPool] _setObject:contentFilterEnabler.get() forBundleParameter:NSStringFromClass([MockContentFilterEnabler class])];
100     return configuration;
101 }
102
103 @interface ServerRedirectNavigationDelegate : NSObject <WKNavigationDelegate>
104 @end
105
106 @implementation ServerRedirectNavigationDelegate
107
108 - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
109 {
110     EXPECT_WK_STREQ(webView.URL.absoluteString, @"http://redirect/?pass");
111 }
112
113 - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation
114 {
115     EXPECT_WK_STREQ(webView.URL.absoluteString, @"http://pass/");
116 }
117
118 - (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
119 {
120     isDone = true;
121 }
122
123 @end
124
125 TEST(ContentFiltering, URLAfterServerRedirect)
126 {
127     @autoreleasepool {
128         [TestProtocol registerWithScheme:@"http"];
129
130         auto configuration = configurationWithContentFilterSettings(Decision::Allow, DecisionPoint::AfterAddData);
131         auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
132         auto navigationDelegate = adoptNS([[ServerRedirectNavigationDelegate alloc] init]);
133         [webView setNavigationDelegate:navigationDelegate.get()];
134         [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://redirect?pass"]]];
135         TestWebKitAPI::Util::run(&isDone);
136
137         [TestProtocol unregister];
138     }
139 }
140
141 @interface BecomeDownloadDelegate : NSObject <WKNavigationDelegate>
142 @end
143
144 @implementation BecomeDownloadDelegate
145
146 - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
147 {
148     decisionHandler(_WKNavigationResponsePolicyBecomeDownload);
149 }
150
151 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
152 {
153     isDone = true;
154 }
155
156 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
157 {
158     isDone = true;
159 }
160
161 @end
162
163 static bool downloadDidStart;
164
165 @interface ContentFilteringDownloadDelegate : NSObject <_WKDownloadDelegate>
166 @end
167
168 @implementation ContentFilteringDownloadDelegate
169
170 - (void)_downloadDidStart:(_WKDownload *)download
171 {
172     downloadDidStart = true;
173 }
174
175 @end
176
177 static void downloadTest(Decision decision, DecisionPoint decisionPoint)
178 {
179     @autoreleasepool {
180         [TestProtocol registerWithScheme:@"http"];
181
182         auto configuration = configurationWithContentFilterSettings(decision, decisionPoint);
183         auto downloadDelegate = adoptNS([[ContentFilteringDownloadDelegate alloc] init]);
184         [[configuration processPool] _setDownloadDelegate:downloadDelegate.get()];
185         auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
186         auto navigationDelegate = adoptNS([[BecomeDownloadDelegate alloc] init]);
187         [webView setNavigationDelegate:navigationDelegate.get()];
188         [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://redirect/?download"]]];
189
190         isDone = false;
191         downloadDidStart = false;
192         const bool downloadShouldStart = decision == Decision::Allow || decisionPoint > DecisionPoint::AfterResponse;
193         if (downloadShouldStart)
194             TestWebKitAPI::Util::run(&downloadDidStart);
195         else
196             TestWebKitAPI::Util::run(&isDone);
197
198         EXPECT_EQ(downloadShouldStart, downloadDidStart);
199
200         [TestProtocol unregister];
201     }
202 }
203
204 TEST(ContentFiltering, AllowDownloadAfterWillSendRequest)
205 {
206     downloadTest(Decision::Allow, DecisionPoint::AfterWillSendRequest);
207 }
208
209 TEST(ContentFiltering, BlockDownloadAfterWillSendRequest)
210 {
211     downloadTest(Decision::Block, DecisionPoint::AfterWillSendRequest);
212 }
213
214 TEST(ContentFiltering, AllowDownloadAfterRedirect)
215 {
216     downloadTest(Decision::Allow, DecisionPoint::AfterRedirect);
217 }
218
219 TEST(ContentFiltering, BlockDownloadAfterRedirect)
220 {
221     downloadTest(Decision::Block, DecisionPoint::AfterRedirect);
222 }
223
224 TEST(ContentFiltering, AllowDownloadAfterResponse)
225 {
226     downloadTest(Decision::Allow, DecisionPoint::AfterResponse);
227 }
228
229 TEST(ContentFiltering, BlockDownloadAfterResponse)
230 {
231     downloadTest(Decision::Block, DecisionPoint::AfterResponse);
232 }
233
234 TEST(ContentFiltering, AllowDownloadAfterAddData)
235 {
236     downloadTest(Decision::Allow, DecisionPoint::AfterAddData);
237 }
238
239 TEST(ContentFiltering, BlockDownloadAfterAddData)
240 {
241     downloadTest(Decision::Block, DecisionPoint::AfterAddData);
242 }
243
244 TEST(ContentFiltering, AllowDownloadAfterFinishedAddingData)
245 {
246     downloadTest(Decision::Allow, DecisionPoint::AfterFinishedAddingData);
247 }
248
249 TEST(ContentFiltering, BlockDownloadAfterFinishedAddingData)
250 {
251     downloadTest(Decision::Block, DecisionPoint::AfterFinishedAddingData);
252 }
253
254 TEST(ContentFiltering, AllowDownloadNever)
255 {
256     downloadTest(Decision::Allow, DecisionPoint::Never);
257 }
258
259 TEST(ContentFiltering, BlockDownloadNever)
260 {
261     downloadTest(Decision::Block, DecisionPoint::Never);
262 }
263
264 @interface LoadAlternateNavigationDelegate : NSObject <WKNavigationDelegate>
265 @end
266
267 @implementation LoadAlternateNavigationDelegate
268
269 - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
270 {
271     EXPECT_WK_STREQ(WebKitErrorDomain, error.domain);
272     EXPECT_EQ(kWKErrorCodeFrameLoadBlockedByContentFilter, error.code);
273     [webView _loadAlternateHTMLString:@"FAIL" baseURL:nil forUnreachableURL:[error.userInfo objectForKey:NSURLErrorFailingURLErrorKey]];
274 }
275
276 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
277 {
278     [webView evaluateJavaScript:@"document.body.innerText" completionHandler:^ (id result, NSError *error) {
279         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
280         EXPECT_WK_STREQ(@"blocked", result);
281         isDone = true;
282     }];
283 }
284
285 @end
286
287 static void loadAlternateTest(Decision decision, DecisionPoint decisionPoint)
288 {
289     @autoreleasepool {
290         [TestProtocol registerWithScheme:@"http"];
291
292         auto configuration = configurationWithContentFilterSettings(decision, decisionPoint);
293         auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
294         auto navigationDelegate = adoptNS([[LoadAlternateNavigationDelegate alloc] init]);
295         [webView setNavigationDelegate:navigationDelegate.get()];
296         [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://redirect/?result"]]];
297
298         isDone = false;
299         TestWebKitAPI::Util::run(&isDone);
300
301         [TestProtocol unregister];
302     }
303 }
304
305 TEST(ContentFiltering, LoadAlternateAfterWillSendRequestWK2)
306 {
307     loadAlternateTest(Decision::Block, DecisionPoint::AfterWillSendRequest);
308 }
309
310 TEST(ContentFiltering, LoadAlternateAfterRedirectWK2)
311 {
312     loadAlternateTest(Decision::Block, DecisionPoint::AfterRedirect);
313 }
314
315 TEST(ContentFiltering, LoadAlternateAfterResponseWK2)
316 {
317     loadAlternateTest(Decision::Block, DecisionPoint::AfterResponse);
318 }
319
320 TEST(ContentFiltering, LoadAlternateAfterAddDataWK2)
321 {
322     loadAlternateTest(Decision::Block, DecisionPoint::AfterAddData);
323 }
324
325 TEST(ContentFiltering, LoadAlternateAfterFinishedAddingDataWK2)
326 {
327     loadAlternateTest(Decision::Block, DecisionPoint::AfterFinishedAddingData);
328 }
329
330
331 @interface LazilyLoadPlatformFrameworksController : NSObject <WKNavigationDelegate>
332 @property (nonatomic, readonly) WKWebView *webView;
333 - (void)expectParentalControlsLoaded:(BOOL)parentalControlsShouldBeLoaded networkExtensionLoaded:(BOOL)networkExtensionShouldBeLoaded;
334 @end
335
336 @implementation LazilyLoadPlatformFrameworksController {
337     RetainPtr<WKWebView> _webView;
338     RetainPtr<id <ContentFilteringProtocol>> _remoteObjectProxy;
339 }
340
341 - (instancetype)init
342 {
343     if (!(self = [super init]))
344         return nil;
345
346     WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"ContentFilteringPlugIn"];
347     _webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
348     [_webView setNavigationDelegate:self];
349
350     _WKRemoteObjectInterface *interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(ContentFilteringProtocol)];
351     _remoteObjectProxy = [[_webView _remoteObjectRegistry] remoteObjectProxyWithInterface:interface];
352
353     return self;
354 }
355
356 - (WKWebView *)webView
357 {
358     return _webView.get();
359 }
360
361 - (void)expectParentalControlsLoaded:(BOOL)parentalControlsShouldBeLoaded networkExtensionLoaded:(BOOL)networkExtensionShouldBeLoaded
362 {
363     isDone = false;
364     [_remoteObjectProxy checkIfPlatformFrameworksAreLoaded:^(BOOL parentalControlsLoaded, BOOL networkExtensionLoaded) {
365 #if HAVE(PARENTAL_CONTROLS)
366         EXPECT_EQ(static_cast<bool>(parentalControlsShouldBeLoaded), static_cast<bool>(parentalControlsLoaded));
367 #endif
368 #if HAVE(NETWORK_EXTENSION)
369         EXPECT_EQ(static_cast<bool>(networkExtensionShouldBeLoaded), static_cast<bool>(networkExtensionLoaded));
370 #endif // HAVE(NETWORK_EXTENSION)
371         isDone = true;
372     }];
373     TestWebKitAPI::Util::run(&isDone);
374 }
375
376 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
377 {
378     isDone = true;
379 }
380
381 @end
382
383 static BOOL filterRequired(id self, SEL _cmd)
384 {
385     return YES;
386 }
387
388 TEST(ContentFiltering, LazilyLoadPlatformFrameworks)
389 {
390     // Swizzle [NEFilterSource filterRequired] to return YES in the UI process since NetworkExtension will not be loaded otherwise.
391     Method method = class_getClassMethod(getNEFilterSourceClass(), @selector(filterRequired));
392     method_setImplementation(method, reinterpret_cast<IMP>(filterRequired));
393
394     @autoreleasepool {
395         auto controller = adoptNS([[LazilyLoadPlatformFrameworksController alloc] init]);
396         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:NO];
397
398         isDone = false;
399         [[controller webView] loadHTMLString:@"PASS" baseURL:[NSURL URLWithString:@"about:blank"]];
400         TestWebKitAPI::Util::run(&isDone);
401         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:NO];
402
403         isDone = false;
404         [[controller webView] loadData:[NSData dataWithBytes:"PASS" length:4] MIMEType:@"text/html" characterEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"about:blank"]];
405         TestWebKitAPI::Util::run(&isDone);
406         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:NO];
407
408         isDone = false;
409         NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"ContentFiltering" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
410         [[controller webView] loadFileURL:fileURL allowingReadAccessToURL:fileURL];
411         TestWebKitAPI::Util::run(&isDone);
412         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:NO];
413
414         isDone = false;
415         [TestProtocol registerWithScheme:@"custom"];
416         [[controller webView] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"custom://test"]]];
417         TestWebKitAPI::Util::run(&isDone);
418         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:NO];
419         [TestProtocol unregister];
420
421         isDone = false;
422         [TestProtocol registerWithScheme:@"http"];
423         [[controller webView] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://test"]]];
424         TestWebKitAPI::Util::run(&isDone);
425 #if PLATFORM(MAC)
426         [controller expectParentalControlsLoaded:NO networkExtensionLoaded:YES];
427 #else
428         [controller expectParentalControlsLoaded:YES networkExtensionLoaded:YES];
429 #endif
430         [TestProtocol unregister];
431
432 #if PLATFORM(MAC)
433         isDone = false;
434         [TestProtocol registerWithScheme:@"https"];
435         [[controller webView] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://test"]]];
436         TestWebKitAPI::Util::run(&isDone);
437         [controller expectParentalControlsLoaded:YES networkExtensionLoaded:YES];
438         [TestProtocol unregister];
439 #endif
440     }
441 }