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