| 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 | |