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 | |
50 | namespace WebCore { |
51 | |
52 | namespace ContentExtensions { |
53 | |
54 | void 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 | |
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 | auto 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 | |
141 | void 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 | |
147 | StyleSheetContents* ContentExtensionsBackend::globalDisplayNoneStyleSheet(const String& identifier) const |
148 | { |
149 | const auto& contentExtension = m_contentExtensions.get(identifier); |
150 | return contentExtension ? contentExtension->globalDisplayNoneStyleSheet() : nullptr; |
151 | } |
152 | |
153 | ContentRuleListResults 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 | |
238 | ContentRuleListResults 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 | |
273 | const String& ContentExtensionsBackend::displayNoneCSSRule() |
274 | { |
275 | static NeverDestroyed<const String> rule(MAKE_STATIC_STRING_IMPL("display:none !important;" )); |
276 | return rule; |
277 | } |
278 | |
279 | void 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 | |