Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / platform / cocoa / NetworkExtensionContentFilter.mm
1 /*
2  * Copyright (C) 2015 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 #import "NetworkExtensionContentFilter.h"
28
29 #if HAVE(NETWORK_EXTENSION)
30
31 #import "ContentFilterUnblockHandler.h"
32 #import "Logging.h"
33 #import "ResourceRequest.h"
34 #import "ResourceResponse.h"
35 #import "RuntimeApplicationChecks.h"
36 #import "SharedBuffer.h"
37 #import <objc/runtime.h>
38 #import <pal/spi/cocoa/NEFilterSourceSPI.h>
39 #import <wtf/SoftLinking.h>
40 #import <wtf/URL.h>
41 #import <wtf/threads/BinarySemaphore.h>
42
43 SOFT_LINK_FRAMEWORK_OPTIONAL(NetworkExtension);
44 SOFT_LINK_CLASS_OPTIONAL(NetworkExtension, NEFilterSource);
45
46 // FIXME: Remove this once -setSourceAppPid: is declared in an SDK used by the builders
47 @interface NEFilterSource ()
48 - (void)setSourceAppPid:(pid_t)sourceAppPid;
49 @end
50
51 static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo)
52 {
53     ASSERT_WITH_SECURITY_IMPLICATION(!decisionInfo || [decisionInfo isKindOfClass:[NSDictionary class]]);
54     return decisionInfo[NEFilterSourceOptionsPageData];
55 }
56
57 namespace WebCore {
58
59 bool NetworkExtensionContentFilter::enabled()
60 {
61     bool enabled = [getNEFilterSourceClass() filterRequired];
62     LOG(ContentFiltering, "NetworkExtensionContentFilter is %s.\n", enabled ? "enabled" : "not enabled");
63     return enabled;
64 }
65
66 std::unique_ptr<NetworkExtensionContentFilter> NetworkExtensionContentFilter::create()
67 {
68     return std::make_unique<NetworkExtensionContentFilter>();
69 }
70
71 void NetworkExtensionContentFilter::initialize(const URL* url)
72 {
73     ASSERT(!m_queue);
74     ASSERT(!m_neFilterSource);
75     m_queue = adoptOSObject(dispatch_queue_create("WebKit NetworkExtension Filtering", DISPATCH_QUEUE_SERIAL));
76     ASSERT_UNUSED(url, !url);
77     m_neFilterSource = adoptNS([allocNEFilterSourceInstance() initWithDecisionQueue:m_queue.get()]);
78 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
79     [m_neFilterSource setSourceAppIdentifier:applicationBundleIdentifier()];
80     // FIXME: Remove the -respondsToSelector: check once -setSourceAppPid: is defined in an SDK used by the builders.
81     if ([m_neFilterSource respondsToSelector:@selector(setSourceAppPid:)])
82         [m_neFilterSource setSourceAppPid:presentingApplicationPID()];
83 #endif
84 }
85
86 void NetworkExtensionContentFilter::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
87 {
88     ASSERT(!request.isNull());
89     if (!request.url().protocolIsInHTTPFamily() || !enabled()) {
90         m_state = State::Allowed;
91         return;
92     }
93
94     initialize();
95
96     if (!redirectResponse.isNull()) {
97         responseReceived(redirectResponse);
98         if (!needsMoreData())
99             return;
100     }
101
102     BinarySemaphore semaphore;
103     RetainPtr<NSString> modifiedRequestURLString;
104     [m_neFilterSource willSendRequest:request.nsURLRequest(DoNotUpdateHTTPBody) decisionHandler:[this, &modifiedRequestURLString, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
105         modifiedRequestURLString = decisionInfo[NEFilterSourceOptionsRedirectURL];
106         ASSERT(!modifiedRequestURLString || [modifiedRequestURLString isKindOfClass:[NSString class]]);
107         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
108         semaphore.signal();
109     }];
110
111     // FIXME: We have to block here since DocumentLoader expects to have a
112     // blocked/not blocked answer from the filter immediately after calling
113     // addData(). We should find a way to make this asynchronous.
114     semaphore.wait();
115
116     if (!modifiedRequestURLString)
117         return;
118
119     URL modifiedRequestURL { URL(), modifiedRequestURLString.get() };
120     if (!modifiedRequestURL.isValid()) {
121         LOG(ContentFiltering, "NetworkExtensionContentFilter failed to convert modified URL string %@ to a  URL.\n", modifiedRequestURLString.get());
122         return;
123     }
124
125     request.setURL(modifiedRequestURL);
126 }
127
128 void NetworkExtensionContentFilter::responseReceived(const ResourceResponse& response)
129 {
130     if (!response.url().protocolIsInHTTPFamily()) {
131         m_state = State::Allowed;
132         return;
133     }
134
135     BinarySemaphore semaphore;
136     [m_neFilterSource receivedResponse:response.nsURLResponse() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
137         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
138         semaphore.signal();
139     }];
140
141     // FIXME: We have to block here since DocumentLoader expects to have a
142     // blocked/not blocked answer from the filter immediately after calling
143     // addData(). We should find a way to make this asynchronous.
144     semaphore.wait();
145 }
146
147 void NetworkExtensionContentFilter::addData(const char* data, int length)
148 {
149     RetainPtr<NSData> copiedData { [NSData dataWithBytes:(void*)data length:length] };
150
151     BinarySemaphore semaphore;
152     [m_neFilterSource receivedData:copiedData.get() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
153         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
154         semaphore.signal();
155     }];
156
157     // FIXME: We have to block here since DocumentLoader expects to have a
158     // blocked/not blocked answer from the filter immediately after calling
159     // addData(). We should find a way to make this asynchronous.
160     semaphore.wait();
161 }
162
163 void NetworkExtensionContentFilter::finishedAddingData()
164 {
165     BinarySemaphore semaphore;
166     [m_neFilterSource finishedLoadingWithDecisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
167         handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
168         semaphore.signal();
169     }];
170
171     // FIXME: We have to block here since DocumentLoader expects to have a
172     // blocked/not blocked answer from the filter immediately after calling
173     // finishedAddingData(). We should find a way to make this asynchronous.
174     semaphore.wait();
175 }
176
177 Ref<SharedBuffer> NetworkExtensionContentFilter::replacementData() const
178 {
179     ASSERT(didBlockData());
180     return SharedBuffer::create(m_replacementData.get());
181 }
182
183 #if ENABLE(CONTENT_FILTERING)
184 ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() const
185 {
186     using DecisionHandlerFunction = ContentFilterUnblockHandler::DecisionHandlerFunction;
187
188     RetainPtr<NEFilterSource> neFilterSource { m_neFilterSource };
189     return ContentFilterUnblockHandler {
190         "nefilter-unblock"_s, [neFilterSource](DecisionHandlerFunction decisionHandler) {
191             [neFilterSource remediateWithDecisionHandler:[decisionHandler](NEFilterSourceStatus status, NSDictionary *) {
192                 LOG(ContentFiltering, "NEFilterSource %s the unblock request.\n", status == NEFilterSourceStatusPass ? "allowed" : "did not allow");
193                 decisionHandler(status == NEFilterSourceStatusPass);
194             }];
195         }
196     };
197 }
198 #endif
199
200 void NetworkExtensionContentFilter::handleDecision(NEFilterSourceStatus status, NSData *replacementData)
201 {
202     ASSERT_WITH_SECURITY_IMPLICATION(!replacementData || [replacementData isKindOfClass:[NSData class]]);
203
204     switch (status) {
205     case NEFilterSourceStatusPass:
206     case NEFilterSourceStatusError:
207     case NEFilterSourceStatusWhitelisted:
208     case NEFilterSourceStatusBlacklisted:
209         m_state = State::Allowed;
210         break;
211     case NEFilterSourceStatusBlock:
212         m_state = State::Blocked;
213         break;
214     case NEFilterSourceStatusNeedsMoreData:
215         m_state = State::Filtering;
216         break;
217     }
218
219     if (didBlockData())
220         m_replacementData = replacementData;
221 #if !LOG_DISABLED
222     if (!needsMoreData())
223         LOG(ContentFiltering, "NetworkExtensionContentFilter stopped buffering with status %zd and replacement data length %zu.\n", status, replacementData.length);
224 #endif
225 }
226
227 } // namespace WebCore
228
229 #endif // HAVE(NETWORK_EXTENSION)