1/*
2 * Copyright (C) 2013-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#include "config.h"
27#include "ContentFilter.h"
28
29#if ENABLE(CONTENT_FILTERING)
30
31#include "CachedRawResource.h"
32#include "ContentFilterUnblockHandler.h"
33#include "DocumentLoader.h"
34#include "Frame.h"
35#include "FrameLoadRequest.h"
36#include "FrameLoader.h"
37#include "FrameLoaderClient.h"
38#include "Logging.h"
39#include "NetworkExtensionContentFilter.h"
40#include "ParentalControlsContentFilter.h"
41#include "ScriptController.h"
42#include "SharedBuffer.h"
43#include <wtf/NeverDestroyed.h>
44#include <wtf/Ref.h>
45#include <wtf/SetForScope.h>
46#include <wtf/Vector.h>
47
48#if !LOG_DISABLED
49#include <wtf/text/CString.h>
50#endif
51
52namespace WebCore {
53
54Vector<ContentFilter::Type>& ContentFilter::types()
55{
56 static NeverDestroyed<Vector<ContentFilter::Type>> types {
57 Vector<ContentFilter::Type> {
58#if HAVE(PARENTAL_CONTROLS)
59 type<ParentalControlsContentFilter>(),
60#endif
61#if HAVE(NETWORK_EXTENSION)
62 type<NetworkExtensionContentFilter>()
63#endif
64 }
65 };
66 return types;
67}
68
69std::unique_ptr<ContentFilter> ContentFilter::create(DocumentLoader& documentLoader)
70{
71 Container filters;
72 for (auto& type : types()) {
73 auto filter = type.create();
74 ASSERT(filter);
75 filters.append(WTFMove(filter));
76 }
77
78 if (filters.isEmpty())
79 return nullptr;
80
81 return std::make_unique<ContentFilter>(WTFMove(filters), documentLoader);
82}
83
84ContentFilter::ContentFilter(Container&& contentFilters, DocumentLoader& documentLoader)
85 : m_contentFilters { WTFMove(contentFilters) }
86 , m_documentLoader { documentLoader }
87{
88 LOG(ContentFiltering, "Creating ContentFilter with %zu platform content filter(s).\n", m_contentFilters.size());
89 ASSERT(!m_contentFilters.isEmpty());
90}
91
92ContentFilter::~ContentFilter()
93{
94 LOG(ContentFiltering, "Destroying ContentFilter.\n");
95}
96
97bool ContentFilter::continueAfterWillSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
98{
99 Ref<DocumentLoader> protectedDocumentLoader { m_documentLoader };
100
101 LOG(ContentFiltering, "ContentFilter received request for <%s> with redirect response from <%s>.\n", request.url().string().ascii().data(), redirectResponse.url().string().ascii().data());
102#if !LOG_DISABLED
103 ResourceRequest originalRequest { request };
104#endif
105 ASSERT(m_state == State::Stopped || m_state == State::Filtering);
106 forEachContentFilterUntilBlocked([&request, &redirectResponse](PlatformContentFilter& contentFilter) {
107 contentFilter.willSendRequest(request, redirectResponse);
108 });
109 if (m_state == State::Blocked)
110 request = ResourceRequest();
111#if !LOG_DISABLED
112 if (request != originalRequest)
113 LOG(ContentFiltering, "ContentFilter changed request url to <%s>.\n", originalRequest.url().string().ascii().data());
114#endif
115 return !request.isNull();
116}
117
118void ContentFilter::startFilteringMainResource(CachedRawResource& resource)
119{
120 if (m_state != State::Stopped)
121 return;
122
123 LOG(ContentFiltering, "ContentFilter will start filtering main resource at <%s>.\n", resource.url().string().ascii().data());
124 m_state = State::Filtering;
125 ASSERT(!m_mainResource);
126 m_mainResource = &resource;
127}
128
129void ContentFilter::stopFilteringMainResource()
130{
131 if (m_state != State::Blocked)
132 m_state = State::Stopped;
133 m_mainResource = nullptr;
134}
135
136bool ContentFilter::continueAfterResponseReceived(const ResourceResponse& response)
137{
138 Ref<DocumentLoader> protectedDocumentLoader { m_documentLoader };
139
140 if (m_state == State::Filtering) {
141 LOG(ContentFiltering, "ContentFilter received response from <%s>.\n", response.url().string().ascii().data());
142 forEachContentFilterUntilBlocked([&response](PlatformContentFilter& contentFilter) {
143 contentFilter.responseReceived(response);
144 });
145 }
146
147 return m_state != State::Blocked;
148}
149
150bool ContentFilter::continueAfterDataReceived(const char* data, int length)
151{
152 Ref<DocumentLoader> protectedDocumentLoader { m_documentLoader };
153
154 if (m_state == State::Filtering) {
155 LOG(ContentFiltering, "ContentFilter received %d bytes of data from <%s>.\n", length, m_mainResource->url().string().ascii().data());
156 forEachContentFilterUntilBlocked([data, length](PlatformContentFilter& contentFilter) {
157 contentFilter.addData(data, length);
158 });
159
160 if (m_state == State::Allowed)
161 deliverResourceData(*m_mainResource);
162 return false;
163 }
164
165 return m_state != State::Blocked;
166}
167
168bool ContentFilter::continueAfterNotifyFinished(CachedResource& resource)
169{
170 ASSERT_UNUSED(resource, &resource == m_mainResource);
171 Ref<DocumentLoader> protectedDocumentLoader { m_documentLoader };
172
173 if (m_mainResource->errorOccurred())
174 return true;
175
176 if (m_state == State::Filtering) {
177 LOG(ContentFiltering, "ContentFilter will finish filtering main resource at <%s>.\n", m_mainResource->url().string().ascii().data());
178 forEachContentFilterUntilBlocked([](PlatformContentFilter& contentFilter) {
179 contentFilter.finishedAddingData();
180 });
181
182 if (m_state != State::Blocked) {
183 m_state = State::Allowed;
184 deliverResourceData(*m_mainResource);
185 }
186
187 if (m_state == State::Stopped)
188 return false;
189 }
190
191 return m_state != State::Blocked;
192}
193
194template <typename Function>
195inline void ContentFilter::forEachContentFilterUntilBlocked(Function&& function)
196{
197 bool allFiltersAllowedLoad { true };
198 for (auto& contentFilter : m_contentFilters) {
199 if (!contentFilter->needsMoreData()) {
200 ASSERT(!contentFilter->didBlockData());
201 continue;
202 }
203
204 function(*contentFilter);
205
206 if (contentFilter->didBlockData()) {
207 ASSERT(!m_blockingContentFilter);
208 m_blockingContentFilter = contentFilter.get();
209 didDecide(State::Blocked);
210 return;
211 } else if (contentFilter->needsMoreData())
212 allFiltersAllowedLoad = false;
213 }
214
215 if (allFiltersAllowedLoad)
216 didDecide(State::Allowed);
217}
218
219void ContentFilter::didDecide(State state)
220{
221 ASSERT(m_state != State::Allowed);
222 ASSERT(m_state != State::Blocked);
223 ASSERT(state == State::Allowed || state == State::Blocked);
224 LOG(ContentFiltering, "ContentFilter decided load should be %s for main resource at <%s>.\n", state == State::Allowed ? "allowed" : "blocked", m_mainResource ? m_mainResource->url().string().ascii().data() : "");
225 m_state = state;
226 if (m_state != State::Blocked)
227 return;
228
229 ContentFilterUnblockHandler unblockHandler { m_blockingContentFilter->unblockHandler() };
230 unblockHandler.setUnreachableURL(m_documentLoader.documentURL());
231 auto frame { m_documentLoader.frame() };
232 String unblockRequestDeniedScript { m_blockingContentFilter->unblockRequestDeniedScript() };
233 if (!unblockRequestDeniedScript.isEmpty() && frame) {
234 unblockHandler.wrapWithDecisionHandler([scriptController = makeWeakPtr(frame->script()), script = unblockRequestDeniedScript.isolatedCopy()](bool unblocked) {
235 if (!unblocked && scriptController)
236 scriptController->executeScript(script);
237 });
238 }
239 m_documentLoader.frameLoader()->client().contentFilterDidBlockLoad(WTFMove(unblockHandler));
240
241 m_blockedError = m_documentLoader.frameLoader()->blockedByContentFilterError(m_documentLoader.request());
242 m_documentLoader.cancelMainResourceLoad(m_blockedError);
243}
244
245void ContentFilter::deliverResourceData(CachedResource& resource)
246{
247 ASSERT(m_state == State::Allowed);
248 ASSERT(resource.dataBufferingPolicy() == DataBufferingPolicy::BufferData);
249 if (auto* resourceBuffer = resource.resourceBuffer())
250 m_documentLoader.dataReceived(resource, resourceBuffer->data(), resourceBuffer->size());
251}
252
253static const URL& blockedPageURL()
254{
255 static const auto blockedPageURL = makeNeverDestroyed([] () -> URL {
256 auto webCoreBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebCore"));
257 return adoptCF(CFBundleCopyResourceURL(webCoreBundle, CFSTR("ContentFilterBlockedPage"), CFSTR("html"), nullptr)).get();
258 }());
259 return blockedPageURL;
260}
261
262bool ContentFilter::continueAfterSubstituteDataRequest(const DocumentLoader& activeLoader, const SubstituteData& substituteData)
263{
264 if (auto contentFilter = activeLoader.contentFilter()) {
265 if (contentFilter->m_state == State::Blocked && !contentFilter->m_isLoadingBlockedPage)
266 return contentFilter->m_blockedError.failingURL() != substituteData.failingURL();
267 }
268
269 if (activeLoader.request().url() == blockedPageURL()) {
270 ASSERT(activeLoader.substituteData().isValid());
271 return activeLoader.substituteData().failingURL() != substituteData.failingURL();
272 }
273
274 return true;
275}
276
277void ContentFilter::handleProvisionalLoadFailure(const ResourceError& error)
278{
279 if (m_state != State::Blocked)
280 return;
281
282 if (m_blockedError.errorCode() != error.errorCode() || m_blockedError.domain() != error.domain())
283 return;
284
285 ASSERT(m_blockedError.failingURL() == error.failingURL());
286
287 RefPtr<SharedBuffer> replacementData { m_blockingContentFilter->replacementData() };
288 ResourceResponse response { URL(), "text/html"_s, static_cast<long long>(replacementData->size()), "UTF-8"_s };
289 SubstituteData substituteData { WTFMove(replacementData), error.failingURL(), response, SubstituteData::SessionHistoryVisibility::Hidden };
290 SetForScope<bool> loadingBlockedPage { m_isLoadingBlockedPage, true };
291 m_documentLoader.frameLoader()->load(FrameLoadRequest(*m_documentLoader.frame(), blockedPageURL(), ShouldOpenExternalURLsPolicy::ShouldNotAllow, substituteData));
292}
293
294} // namespace WebCore
295
296#endif // ENABLE(CONTENT_FILTERING)
297