Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / loader / ContentFilter.cpp
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
52 namespace WebCore {
53
54 Vector<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
69 std::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
84 ContentFilter::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
92 ContentFilter::~ContentFilter()
93 {
94     LOG(ContentFiltering, "Destroying ContentFilter.\n");
95 }
96
97 bool 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
118 void 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
129 void ContentFilter::stopFilteringMainResource()
130 {
131     if (m_state != State::Blocked)
132         m_state = State::Stopped;
133     m_mainResource = nullptr;
134 }
135
136 bool 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
150 bool 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
168 bool 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
194 template <typename Function>
195 inline 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
219 void 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     RefPtr<Frame> frame { m_documentLoader.frame() };
232     String unblockRequestDeniedScript { m_blockingContentFilter->unblockRequestDeniedScript() };
233     if (!unblockRequestDeniedScript.isEmpty() && frame) {
234         static_assert(std::is_base_of<ThreadSafeRefCounted<Frame>, Frame>::value, "Frame must be ThreadSafeRefCounted.");
235         unblockHandler.wrapWithDecisionHandler([frame = WTFMove(frame), script = unblockRequestDeniedScript.isolatedCopy()](bool unblocked) {
236             if (!unblocked)
237                 frame->script().executeScript(script);
238         });
239     }
240     m_documentLoader.frameLoader()->client().contentFilterDidBlockLoad(WTFMove(unblockHandler));
241
242     m_blockedError = m_documentLoader.frameLoader()->blockedByContentFilterError(m_documentLoader.request());
243     m_documentLoader.cancelMainResourceLoad(m_blockedError);
244 }
245
246 void ContentFilter::deliverResourceData(CachedResource& resource)
247 {
248     ASSERT(m_state == State::Allowed);
249     ASSERT(resource.dataBufferingPolicy() == BufferData);
250     if (auto* resourceBuffer = resource.resourceBuffer())
251         m_documentLoader.dataReceived(resource, resourceBuffer->data(), resourceBuffer->size());
252 }
253
254 static const URL& blockedPageURL()
255 {
256     static const auto blockedPageURL = makeNeverDestroyed([] () -> URL {
257         auto webCoreBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebCore"));
258         return adoptCF(CFBundleCopyResourceURL(webCoreBundle, CFSTR("ContentFilterBlockedPage"), CFSTR("html"), nullptr)).get();
259     }());
260     return blockedPageURL;
261 }
262
263 bool ContentFilter::continueAfterSubstituteDataRequest(const DocumentLoader& activeLoader, const SubstituteData& substituteData)
264 {
265     if (auto contentFilter = activeLoader.contentFilter()) {
266         if (contentFilter->m_state == State::Blocked && !contentFilter->m_isLoadingBlockedPage)
267             return contentFilter->m_blockedError.failingURL() != substituteData.failingURL();
268     }
269
270     if (activeLoader.request().url() == blockedPageURL()) {
271         ASSERT(activeLoader.substituteData().isValid());
272         return activeLoader.substituteData().failingURL() != substituteData.failingURL();
273     }
274
275     return true;
276 }
277
278 void ContentFilter::handleProvisionalLoadFailure(const ResourceError& error)
279 {
280     if (m_state != State::Blocked)
281         return;
282
283     if (m_blockedError.errorCode() != error.errorCode() || m_blockedError.domain() != error.domain())
284         return;
285
286     ASSERT(m_blockedError.failingURL() == error.failingURL());
287
288     RefPtr<SharedBuffer> replacementData { m_blockingContentFilter->replacementData() };
289     ResourceResponse response { URL(), ASCIILiteral("text/html"), static_cast<long long>(replacementData->size()), ASCIILiteral("UTF-8") };
290     SubstituteData substituteData { WTFMove(replacementData), error.failingURL(), response, SubstituteData::SessionHistoryVisibility::Hidden };
291     SetForScope<bool> loadingBlockedPage { m_isLoadingBlockedPage, true };
292     m_documentLoader.frameLoader()->load(FrameLoadRequest(*m_documentLoader.frame(), blockedPageURL(), ShouldOpenExternalURLsPolicy::ShouldNotAllow, substituteData));
293 }
294
295 } // namespace WebCore
296
297 #endif // ENABLE(CONTENT_FILTERING)