2 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "NetworkExtensionContentFilter.h"
29 #if HAVE(NETWORK_EXTENSION)
31 #import "ContentFilterUnblockHandler.h"
33 #import "ResourceRequest.h"
34 #import "ResourceResponse.h"
35 #import "RuntimeApplicationChecks.h"
36 #import "SharedBuffer.h"
38 #import <objc/runtime.h>
39 #import <pal/spi/cocoa/NEFilterSourceSPI.h>
40 #import <wtf/SoftLinking.h>
41 #import <wtf/threads/BinarySemaphore.h>
43 SOFT_LINK_FRAMEWORK_OPTIONAL(NetworkExtension);
44 SOFT_LINK_CLASS_OPTIONAL(NetworkExtension, NEFilterSource);
46 // FIXME: Remove this once -setSourceAppPid: is declared in an SDK used by the builders
47 @interface NEFilterSource ()
48 - (void)setSourceAppPid:(pid_t)sourceAppPid;
51 static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo)
53 ASSERT_WITH_SECURITY_IMPLICATION(!decisionInfo || [decisionInfo isKindOfClass:[NSDictionary class]]);
54 return decisionInfo[NEFilterSourceOptionsPageData];
59 bool NetworkExtensionContentFilter::enabled()
61 bool enabled = [getNEFilterSourceClass() filterRequired];
62 LOG(ContentFiltering, "NetworkExtensionContentFilter is %s.\n", enabled ? "enabled" : "not enabled");
66 std::unique_ptr<NetworkExtensionContentFilter> NetworkExtensionContentFilter::create()
68 return std::make_unique<NetworkExtensionContentFilter>();
71 void NetworkExtensionContentFilter::initialize(const URL* url)
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) && __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()];
86 void NetworkExtensionContentFilter::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
88 ASSERT(!request.isNull());
89 if (!request.url().protocolIsInHTTPFamily() || !enabled()) {
90 m_state = State::Allowed;
96 if (!redirectResponse.isNull()) {
97 responseReceived(redirectResponse);
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));
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.
116 if (!modifiedRequestURLString)
119 URL modifiedRequestURL { URL(), modifiedRequestURLString.get() };
120 if (!modifiedRequestURL.isValid()) {
121 LOG(ContentFiltering, "NetworkExtensionContentFilter failed to convert modified URL string %@ to a WebCore::URL.\n", modifiedRequestURLString.get());
125 request.setURL(modifiedRequestURL);
128 void NetworkExtensionContentFilter::responseReceived(const ResourceResponse& response)
130 if (!response.url().protocolIsInHTTPFamily()) {
131 m_state = State::Allowed;
135 BinarySemaphore semaphore;
136 [m_neFilterSource receivedResponse:response.nsURLResponse() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
137 handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
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.
147 void NetworkExtensionContentFilter::addData(const char* data, int length)
149 RetainPtr<NSData> copiedData { [NSData dataWithBytes:(void*)data length:length] };
151 BinarySemaphore semaphore;
152 [m_neFilterSource receivedData:copiedData.get() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
153 handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
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.
163 void NetworkExtensionContentFilter::finishedAddingData()
165 BinarySemaphore semaphore;
166 [m_neFilterSource finishedLoadingWithDecisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
167 handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
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.
177 Ref<SharedBuffer> NetworkExtensionContentFilter::replacementData() const
179 ASSERT(didBlockData());
180 return SharedBuffer::create(m_replacementData.get());
183 #if ENABLE(CONTENT_FILTERING)
184 ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() const
186 using DecisionHandlerFunction = ContentFilterUnblockHandler::DecisionHandlerFunction;
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);
200 void NetworkExtensionContentFilter::handleDecision(NEFilterSourceStatus status, NSData *replacementData)
202 ASSERT_WITH_SECURITY_IMPLICATION(!replacementData || [replacementData isKindOfClass:[NSData class]]);
205 case NEFilterSourceStatusPass:
206 case NEFilterSourceStatusError:
207 case NEFilterSourceStatusWhitelisted:
208 case NEFilterSourceStatusBlacklisted:
209 m_state = State::Allowed;
211 case NEFilterSourceStatusBlock:
212 m_state = State::Blocked;
214 case NEFilterSourceStatusNeedsMoreData:
215 m_state = State::Filtering;
220 m_replacementData = replacementData;
222 if (!needsMoreData())
223 LOG(ContentFiltering, "NetworkExtensionContentFilter stopped buffering with status %zd and replacement data length %zu.\n", status, replacementData.length);
227 } // namespace WebCore
229 #endif // HAVE(NETWORK_EXTENSION)