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