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 | |
51 | namespace WebCore { |
52 | |
53 | namespace ContentExtensions { |
54 | |
55 | void 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 | |
65 | void ContentExtensionsBackend::removeContentExtension(const String& identifier) |
66 | { |
67 | m_contentExtensions.remove(identifier); |
68 | } |
69 | |
70 | void ContentExtensionsBackend::removeAllContentExtensions() |
71 | { |
72 | m_contentExtensions.clear(); |
73 | } |
74 | |
75 | auto 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 | |
142 | void 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 | |
148 | StyleSheetContents* ContentExtensionsBackend::globalDisplayNoneStyleSheet(const String& identifier) const |
149 | { |
150 | const auto& contentExtension = m_contentExtensions.get(identifier); |
151 | return contentExtension ? contentExtension->globalDisplayNoneStyleSheet() : nullptr; |
152 | } |
153 | |
154 | ContentRuleListResults 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 | |
239 | ContentRuleListResults 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 | |
274 | const String& ContentExtensionsBackend::displayNoneCSSRule() |
275 | { |
276 | static NeverDestroyed<const String> rule(MAKE_STATIC_STRING_IMPL("display:none !important;" )); |
277 | return rule; |
278 | } |
279 | |
280 | void 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 | |