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