e5b13191d786522502d56038a92f47428a9245cc
[WebKit-https.git] / Source / WebCore / contentextensions / ContentExtensionsBackend.cpp
1 /*
2  * Copyright (C) 2014 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 "CompiledContentExtension.h"
32 #include "ContentExtension.h"
33 #include "ContentExtensionsDebugging.h"
34 #include "DFABytecodeInterpreter.h"
35 #include "Document.h"
36 #include "DocumentLoader.h"
37 #include "Frame.h"
38 #include "MainFrame.h"
39 #include "ResourceLoadInfo.h"
40 #include "URL.h"
41 #include "UserContentController.h"
42 #include <wtf/NeverDestroyed.h>
43 #include <wtf/text/CString.h>
44
45 namespace WebCore {
46
47 namespace ContentExtensions {
48     
49 void ContentExtensionsBackend::addContentExtension(const String& identifier, RefPtr<CompiledContentExtension> compiledContentExtension)
50 {
51     ASSERT(!identifier.isEmpty());
52     if (identifier.isEmpty())
53         return;
54
55     if (!compiledContentExtension) {
56         removeContentExtension(identifier);
57         return;
58     }
59
60     RefPtr<ContentExtension> extension = ContentExtension::create(identifier, adoptRef(*compiledContentExtension.leakRef()));
61     m_contentExtensions.set(identifier, WTF::move(extension));
62 }
63
64 void ContentExtensionsBackend::removeContentExtension(const String& identifier)
65 {
66     m_contentExtensions.remove(identifier);
67 }
68
69 void ContentExtensionsBackend::removeAllContentExtensions()
70 {
71     m_contentExtensions.clear();
72 }
73
74 Vector<Action> ContentExtensionsBackend::actionsForResourceLoad(const ResourceLoadInfo& resourceLoadInfo) const
75 {
76 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
77     double addedTimeStart = monotonicallyIncreasingTime();
78 #endif
79     if (resourceLoadInfo.resourceURL.protocolIsData())
80         return Vector<Action>();
81
82     const String& urlString = resourceLoadInfo.resourceURL.string();
83     ASSERT_WITH_MESSAGE(urlString.containsOnlyASCII(), "A decoded URL should only contain ASCII characters. The matching algorithm assumes the input is ASCII.");
84     const CString& urlCString = urlString.utf8();
85
86     Vector<Action> finalActions;
87     ResourceFlags flags = resourceLoadInfo.getResourceFlags();
88     for (auto& contentExtension : m_contentExtensions.values()) {
89         RELEASE_ASSERT(contentExtension);
90         const CompiledContentExtension& compiledExtension = contentExtension->compiledExtension();
91         
92         // FIXME: These should use a different Vector<bool> to keep track of which memory pages are used when doing memory reporting. Or just remove the memory reporting completely.
93         DFABytecodeInterpreter withoutDomainsInterpreter(compiledExtension.filtersWithoutDomainsBytecode(), compiledExtension.filtersWithoutDomainsBytecodeLength(), contentExtension->m_pagesUsed);
94         DFABytecodeInterpreter::Actions triggeredActions = withoutDomainsInterpreter.interpret(urlCString, flags);
95         
96         // Check to see if there are any actions triggered with if- or unless-domain and check the domain if there are.
97         DFABytecodeInterpreter withDomainsInterpreter(compiledExtension.filtersWithDomainsBytecode(), compiledExtension.filtersWithDomainsBytecodeLength(), contentExtension->m_pagesUsed);
98         
99         DFABytecodeInterpreter::Actions withDomainsPossibleActions = withDomainsInterpreter.interpret(urlCString, flags);
100         if (!withDomainsPossibleActions.isEmpty()) {
101             DFABytecodeInterpreter domainsInterpreter(compiledExtension.domainFiltersBytecode(), compiledExtension.domainFiltersBytecodeLength(), contentExtension->m_pagesUsed);
102             DFABytecodeInterpreter::Actions domainsActions = domainsInterpreter.interpret(resourceLoadInfo.mainDocumentURL.host().utf8(), flags);
103             
104             DFABytecodeInterpreter::Actions ifDomainActions;
105             DFABytecodeInterpreter::Actions unlessDomainActions;
106             for (uint64_t action : domainsActions) {
107                 if (action & IfDomainFlag)
108                     ifDomainActions.add(action);
109                 else
110                     unlessDomainActions.add(action);
111             }
112             
113             for (uint64_t action : withDomainsPossibleActions) {
114                 if (ifDomainActions.contains(action)) {
115                     // If an if-domain trigger matches, add the action.
116                     ASSERT(action & IfDomainFlag);
117                     triggeredActions.add(action & ~IfDomainFlag);
118                 } else if (!(action & IfDomainFlag) && !unlessDomainActions.contains(action)) {
119                     // If this action did not need an if-domain, it must have been an unless-domain rule.
120                     // Add the action unless it matched an unless-domain trigger.
121                     triggeredActions.add(action);
122                 }
123             }
124         }
125         
126         const SerializedActionByte* actions = compiledExtension.actions();
127         const unsigned actionsLength = compiledExtension.actionsLength();
128         
129         bool sawIgnorePreviousRules = false;
130         if (!triggeredActions.isEmpty()) {
131             Vector<unsigned> actionLocations;
132             actionLocations.reserveInitialCapacity(triggeredActions.size());
133             for (auto actionLocation : triggeredActions)
134                 actionLocations.append(static_cast<unsigned>(actionLocation));
135             std::sort(actionLocations.begin(), actionLocations.end());
136
137             // Add actions in reverse order to properly deal with IgnorePreviousRules.
138             for (unsigned i = actionLocations.size(); i; i--) {
139                 Action action = Action::deserialize(actions, actionsLength, actionLocations[i - 1]);
140                 action.setExtensionIdentifier(contentExtension->identifier());
141                 if (action.type() == ActionType::IgnorePreviousRules) {
142                     sawIgnorePreviousRules = true;
143                     break;
144                 }
145                 finalActions.append(action);
146             }
147         }
148         if (!sawIgnorePreviousRules) {
149             finalActions.append(Action(ActionType::CSSDisplayNoneStyleSheet, contentExtension->identifier()));
150             finalActions.last().setExtensionIdentifier(contentExtension->identifier());
151         }
152     }
153 #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING
154     double addedTimeEnd = monotonicallyIncreasingTime();
155     WTFLogAlways("Time added: %f microseconds %s", (addedTimeEnd - addedTimeStart) * 1.0e6, resourceLoadInfo.resourceURL.string().utf8().data());
156 #endif
157     return finalActions;
158 }
159
160 StyleSheetContents* ContentExtensionsBackend::globalDisplayNoneStyleSheet(const String& identifier) const
161 {
162     const auto& contentExtension = m_contentExtensions.get(identifier);
163     return contentExtension ? contentExtension->globalDisplayNoneStyleSheet() : nullptr;
164 }
165
166 void ContentExtensionsBackend::processContentExtensionRulesForLoad(ResourceRequest& request, ResourceType resourceType, DocumentLoader& initiatingDocumentLoader)
167 {
168     Document* currentDocument = nullptr;
169     URL mainDocumentURL;
170
171     if (initiatingDocumentLoader.frame()) {
172         currentDocument = initiatingDocumentLoader.frame()->document();
173
174         if (Document* mainDocument = initiatingDocumentLoader.frame()->mainFrame().document())
175             mainDocumentURL = mainDocument->url();
176     }
177
178     ResourceLoadInfo resourceLoadInfo = { request.url(), mainDocumentURL, resourceType };
179     Vector<ContentExtensions::Action> actions = actionsForResourceLoad(resourceLoadInfo);
180
181     StringBuilder css;
182     bool willBlockLoad = false;
183     for (const auto& action : actions) {
184         switch (action.type()) {
185         case ContentExtensions::ActionType::BlockLoad:
186             willBlockLoad = true;
187             break;
188         case ContentExtensions::ActionType::BlockCookies:
189             request.setAllowCookies(false);
190             break;
191         case ContentExtensions::ActionType::CSSDisplayNoneSelector:
192             if (resourceType == ResourceType::Document)
193                 initiatingDocumentLoader.addPendingContentExtensionDisplayNoneSelector(action.extensionIdentifier(), action.stringArgument(), action.actionID());
194             else if (currentDocument)
195                 currentDocument->styleSheetCollection().addDisplayNoneSelector(action.extensionIdentifier(), action.stringArgument(), action.actionID());
196             break;
197         case ContentExtensions::ActionType::CSSDisplayNoneStyleSheet: {
198             StyleSheetContents* styleSheetContents = globalDisplayNoneStyleSheet(action.stringArgument());
199             if (styleSheetContents) {
200                 if (resourceType == ResourceType::Document)
201                     initiatingDocumentLoader.addPendingContentExtensionSheet(action.stringArgument(), *styleSheetContents);
202                 else if (currentDocument)
203                     currentDocument->styleSheetCollection().maybeAddContentExtensionSheet(action.stringArgument(), *styleSheetContents);
204             }
205             break;
206         }
207         case ContentExtensions::ActionType::IgnorePreviousRules:
208         case ContentExtensions::ActionType::InvalidAction:
209             RELEASE_ASSERT_NOT_REACHED();
210         }
211     }
212
213     if (willBlockLoad) {
214         if (currentDocument)
215             currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Content blocker prevented frame displaying ", mainDocumentURL.string(), " from loading a resource from ", request.url().string()));
216         request = ResourceRequest();
217     }
218 }
219
220 const String& ContentExtensionsBackend::displayNoneCSSRule()
221 {
222     static NeverDestroyed<const String> rule(ASCIILiteral("display:none !important;"));
223     return rule;
224 }
225
226 } // namespace ContentExtensions
227
228 } // namespace WebCore
229
230 #endif // ENABLE(CONTENT_EXTENSIONS)