1/*
2 * Copyright (C) 2014-2017 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 "ContentExtensionsBackend.h"
28
29#if ENABLE(CONTENT_EXTENSIONS)
30
31#include "Chrome.h"
32#include "ChromeClient.h"
33#include "CompiledContentExtension.h"
34#include "ContentExtension.h"
35#include "ContentExtensionsDebugging.h"
36#include "ContentRuleListResults.h"
37#include "DFABytecodeInterpreter.h"
38#include "Document.h"
39#include "DocumentLoader.h"
40#include "ExtensionStyleSheets.h"
41#include "Frame.h"
42#include "FrameLoaderClient.h"
43#include "Page.h"
44#include "ResourceLoadInfo.h"
45#include <wtf/URL.h>
46#include "UserContentController.h"
47#include <wtf/NeverDestroyed.h>
48#include <wtf/text/CString.h>
49
50namespace WebCore {
51
52namespace ContentExtensions {
53
54void ContentExtensionsBackend::addContentExtension(const String& identifier, Ref<CompiledContentExtension> compiledContentExtension, ContentExtension::ShouldCompileCSS shouldCompileCSS)
55{
56 ASSERT(!identifier.isEmpty());
57 if (identifier.isEmpty())
58 return;
59
60 auto contentExtension = ContentExtension::create(identifier, WTFMove(compiledContentExtension), shouldCompileCSS);
61 m_contentExtensions.set(identifier, WTFMove(contentExtension));
62}
63
64void ContentExtensionsBackend::removeContentExtension(const String& identifier)
65{
66 m_contentExtensions.remove(identifier);
67}
68
69void ContentExtensionsBackend::removeAllContentExtensions()
70{
71 m_contentExtensions.clear();
72}
73
74auto ContentExtensionsBackend::actionsForResourceLoad(const ResourceLoadInfo& resourceLoadInfo) const -> Vector<ActionsFromContentRuleList>
75{
76#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
77 MonotonicTime addedTimeStart = MonotonicTime::now();
78#endif
79 if (m_contentExtensions.isEmpty()
80 || !resourceLoadInfo.resourceURL.isValid()
81 || resourceLoadInfo.resourceURL.protocolIsData())
82 return { };
83
84 const String& urlString = resourceLoadInfo.resourceURL.string();
85 ASSERT_WITH_MESSAGE(urlString.isAllASCII(), "A decoded URL should only contain ASCII characters. The matching algorithm assumes the input is ASCII.");
86 const auto urlCString = urlString.utf8();
87
88 Vector<ActionsFromContentRuleList> actionsVector;
89 actionsVector.reserveInitialCapacity(m_contentExtensions.size());
90 const ResourceFlags flags = resourceLoadInfo.getResourceFlags();
91 for (auto& contentExtension : m_contentExtensions.values()) {
92 ActionsFromContentRuleList actionsStruct;
93 actionsStruct.contentRuleListIdentifier = contentExtension->identifier();
94
95 const CompiledContentExtension& compiledExtension = contentExtension->compiledExtension();
96
97 DFABytecodeInterpreter withoutConditionsInterpreter(compiledExtension.filtersWithoutConditionsBytecode(), compiledExtension.filtersWithoutConditionsBytecodeLength());
98 DFABytecodeInterpreter::Actions withoutConditionsActions = withoutConditionsInterpreter.interpret(urlCString, flags);
99
100 URL topURL = resourceLoadInfo.mainDocumentURL;
101 DFABytecodeInterpreter withConditionsInterpreter(compiledExtension.filtersWithConditionsBytecode(), compiledExtension.filtersWithConditionsBytecodeLength());
102 DFABytecodeInterpreter::Actions withConditionsActions = withConditionsInterpreter.interpretWithConditions(urlCString, flags, contentExtension->topURLActions(topURL));
103
104 const SerializedActionByte* actions = compiledExtension.actions();
105 const unsigned actionsLength = compiledExtension.actionsLength();
106
107 const Vector<uint32_t>& universalWithConditions = contentExtension->universalActionsWithConditions(topURL);
108 const Vector<uint32_t>& universalWithoutConditions = contentExtension->universalActionsWithoutConditions();
109 if (!withoutConditionsActions.isEmpty() || !withConditionsActions.isEmpty() || !universalWithConditions.isEmpty() || !universalWithoutConditions.isEmpty()) {
110 Vector<uint32_t> actionLocations;
111 actionLocations.reserveInitialCapacity(withoutConditionsActions.size() + withConditionsActions.size() + universalWithoutConditions.size() + universalWithConditions.size());
112 for (uint64_t actionLocation : withoutConditionsActions)
113 actionLocations.uncheckedAppend(static_cast<uint32_t>(actionLocation));
114 for (uint64_t actionLocation : withConditionsActions)
115 actionLocations.uncheckedAppend(static_cast<uint32_t>(actionLocation));
116 for (uint32_t actionLocation : universalWithoutConditions)
117 actionLocations.uncheckedAppend(actionLocation);
118 for (uint32_t actionLocation : universalWithConditions)
119 actionLocations.uncheckedAppend(actionLocation);
120 std::sort(actionLocations.begin(), actionLocations.end());
121
122 // Add actions in reverse order to properly deal with IgnorePreviousRules.
123 for (unsigned i = actionLocations.size(); i; i--) {
124 Action action = Action::deserialize(actions, actionsLength, actionLocations[i - 1]);
125 if (action.type() == ActionType::IgnorePreviousRules) {
126 actionsStruct.sawIgnorePreviousRules = true;
127 break;
128 }
129 actionsStruct.actions.append(WTFMove(action));
130 }
131 }
132 actionsVector.uncheckedAppend(WTFMove(actionsStruct));
133 }
134#if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
135 MonotonicTime addedTimeEnd = MonotonicTime::now();
136 dataLogF("Time added: %f microseconds %s \n", (addedTimeEnd - addedTimeStart).microseconds(), resourceLoadInfo.resourceURL.string().utf8().data());
137#endif
138 return actionsVector;
139}
140
141void ContentExtensionsBackend::forEach(const WTF::Function<void(const String&, ContentExtension&)>& apply)
142{
143 for (auto& pair : m_contentExtensions)
144 apply(pair.key, pair.value);
145}
146
147StyleSheetContents* ContentExtensionsBackend::globalDisplayNoneStyleSheet(const String& identifier) const
148{
149 const auto& contentExtension = m_contentExtensions.get(identifier);
150 return contentExtension ? contentExtension->globalDisplayNoneStyleSheet() : nullptr;
151}
152
153ContentRuleListResults ContentExtensionsBackend::processContentRuleListsForLoad(const URL& url, OptionSet<ResourceType> resourceType, DocumentLoader& initiatingDocumentLoader)
154{
155 if (m_contentExtensions.isEmpty())
156 return { };
157
158 Document* currentDocument = nullptr;
159 URL mainDocumentURL;
160
161 if (Frame* frame = initiatingDocumentLoader.frame()) {
162 currentDocument = frame->document();
163
164 if (initiatingDocumentLoader.isLoadingMainResource()
165 && frame->isMainFrame()
166 && resourceType == ResourceType::Document)
167 mainDocumentURL = url;
168 else if (Document* mainDocument = frame->mainFrame().document())
169 mainDocumentURL = mainDocument->url();
170 }
171
172 ResourceLoadInfo resourceLoadInfo = { url, mainDocumentURL, resourceType };
173 auto actions = actionsForResourceLoad(resourceLoadInfo);
174
175 ContentRuleListResults results;
176 results.results.reserveInitialCapacity(actions.size());
177 for (const auto& actionsFromContentRuleList : actions) {
178 const String& contentRuleListIdentifier = actionsFromContentRuleList.contentRuleListIdentifier;
179 ContentRuleListResults::Result result;
180 for (const auto& action : actionsFromContentRuleList.actions) {
181 switch (action.type()) {
182 case ContentExtensions::ActionType::BlockLoad:
183 results.summary.blockedLoad = true;
184 result.blockedLoad = true;
185 break;
186 case ContentExtensions::ActionType::BlockCookies:
187 results.summary.blockedCookies = true;
188 result.blockedCookies = true;
189 break;
190 case ContentExtensions::ActionType::CSSDisplayNoneSelector:
191 if (resourceType == ResourceType::Document)
192 initiatingDocumentLoader.addPendingContentExtensionDisplayNoneSelector(contentRuleListIdentifier, action.stringArgument(), action.actionID());
193 else if (currentDocument)
194 currentDocument->extensionStyleSheets().addDisplayNoneSelector(contentRuleListIdentifier, action.stringArgument(), action.actionID());
195 break;
196 case ContentExtensions::ActionType::Notify:
197 results.summary.hasNotifications = true;
198 result.notifications.append(action.stringArgument());
199 break;
200 case ContentExtensions::ActionType::MakeHTTPS: {
201 if ((url.protocolIs("http") || url.protocolIs("ws"))
202 && (!url.port() || WTF::isDefaultPortForProtocol(url.port().value(), url.protocol()))) {
203 results.summary.madeHTTPS = true;
204 result.madeHTTPS = true;
205 }
206 break;
207 }
208 case ContentExtensions::ActionType::IgnorePreviousRules:
209 RELEASE_ASSERT_NOT_REACHED();
210 }
211 }
212
213 if (!actionsFromContentRuleList.sawIgnorePreviousRules) {
214 if (auto* styleSheetContents = globalDisplayNoneStyleSheet(contentRuleListIdentifier)) {
215 if (resourceType == ResourceType::Document)
216 initiatingDocumentLoader.addPendingContentExtensionSheet(contentRuleListIdentifier, *styleSheetContents);
217 else if (currentDocument)
218 currentDocument->extensionStyleSheets().maybeAddContentExtensionSheet(contentRuleListIdentifier, *styleSheetContents);
219 }
220 }
221
222 results.results.uncheckedAppend({ contentRuleListIdentifier, WTFMove(result) });
223 }
224
225 if (currentDocument) {
226 if (results.summary.madeHTTPS) {
227 ASSERT(url.protocolIs("http") || url.protocolIs("ws"));
228 String newProtocol = url.protocolIs("http") ? "https"_s : "wss"_s;
229 currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Content blocker promoted URL from ", url.string(), " to ", newProtocol));
230 }
231 if (results.summary.blockedLoad)
232 currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Content blocker prevented frame displaying ", mainDocumentURL.string(), " from loading a resource from ", url.string()));
233 }
234
235 return results;
236}
237
238ContentRuleListResults ContentExtensionsBackend::processContentRuleListsForPingLoad(const URL& url, const URL& mainDocumentURL)
239{
240 if (m_contentExtensions.isEmpty())
241 return { };
242
243 ResourceLoadInfo resourceLoadInfo = { url, mainDocumentURL, ResourceType::Raw };
244 auto actions = actionsForResourceLoad(resourceLoadInfo);
245
246 ContentRuleListResults results;
247 for (const auto& actionsFromContentRuleList : actions) {
248 for (const auto& action : actionsFromContentRuleList.actions) {
249 switch (action.type()) {
250 case ContentExtensions::ActionType::BlockLoad:
251 results.summary.blockedLoad = true;
252 break;
253 case ContentExtensions::ActionType::BlockCookies:
254 results.summary.blockedCookies = true;
255 break;
256 case ContentExtensions::ActionType::MakeHTTPS:
257 if ((url.protocolIs("http") || url.protocolIs("ws")) && (!url.port() || WTF::isDefaultPortForProtocol(url.port().value(), url.protocol())))
258 results.summary.madeHTTPS = true;
259 break;
260 case ContentExtensions::ActionType::CSSDisplayNoneSelector:
261 case ContentExtensions::ActionType::Notify:
262 // We currently have not implemented notifications from the NetworkProcess to the UIProcess.
263 break;
264 case ContentExtensions::ActionType::IgnorePreviousRules:
265 RELEASE_ASSERT_NOT_REACHED();
266 }
267 }
268 }
269
270 return results;
271}
272
273const String& ContentExtensionsBackend::displayNoneCSSRule()
274{
275 static NeverDestroyed<const String> rule(MAKE_STATIC_STRING_IMPL("display:none !important;"));
276 return rule;
277}
278
279void applyResultsToRequest(ContentRuleListResults&& results, Page* page, ResourceRequest& request)
280{
281 if (results.summary.blockedCookies)
282 request.setAllowCookies(false);
283
284 if (results.summary.madeHTTPS) {
285 const URL& originalURL = request.url();
286 ASSERT(originalURL.protocolIs("http"));
287 ASSERT(!originalURL.port() || WTF::isDefaultPortForProtocol(originalURL.port().value(), originalURL.protocol()));
288
289 URL newURL = originalURL;
290 newURL.setProtocol("https");
291 if (originalURL.port())
292 newURL.setPort(WTF::defaultPortForProtocol("https").value());
293 request.setURL(newURL);
294 }
295
296 if (page && results.shouldNotifyApplication()) {
297 results.results.removeAllMatching([](const auto& pair) {
298 return !pair.second.shouldNotifyApplication();
299 });
300 page->chrome().client().contentRuleListNotification(request.url(), results);
301 }
302}
303
304} // namespace ContentExtensions
305
306} // namespace WebCore
307
308#endif // ENABLE(CONTENT_EXTENSIONS)
309