| /* |
| * Copyright (C) 2014-2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "ContentExtensionsBackend.h" |
| |
| #if ENABLE(CONTENT_EXTENSIONS) |
| |
| #include "Chrome.h" |
| #include "ChromeClient.h" |
| #include "CompiledContentExtension.h" |
| #include "ContentExtension.h" |
| #include "ContentExtensionsDebugging.h" |
| #include "ContentRuleListResults.h" |
| #include "DFABytecodeInterpreter.h" |
| #include "Document.h" |
| #include "DocumentLoader.h" |
| #include "ExtensionStyleSheets.h" |
| #include "Frame.h" |
| #include "FrameLoaderClient.h" |
| #include "Page.h" |
| #include "ResourceLoadInfo.h" |
| #include "ScriptController.h" |
| #include "ScriptSourceCode.h" |
| #include "Settings.h" |
| #include <wtf/URL.h> |
| #include "UserContentController.h" |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/text/CString.h> |
| |
| namespace WebCore::ContentExtensions { |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| #import <WebKitAdditions/ContentRuleListAdditions.mm> |
| #else |
| static void makeSecureIfNecessary(ContentRuleListResults& results, const URL& url, const URL& redirectFrom = { }) |
| { |
| if (redirectFrom.host() == url.host() && redirectFrom.protocolIs("https"_s)) |
| return; |
| |
| if (!url.protocolIs("http"_s)) |
| return; |
| if (url.host() == "www.opengl.org"_s |
| || url.host() == "webkit.org"_s |
| || url.host() == "download"_s) |
| results.summary.madeHTTPS = true; |
| } |
| #endif |
| |
| bool ContentExtensionsBackend::shouldBeMadeSecure(const URL& url) |
| { |
| ContentRuleListResults results; |
| makeSecureIfNecessary(results, url); |
| return results.summary.madeHTTPS; |
| } |
| |
| void ContentExtensionsBackend::addContentExtension(const String& identifier, Ref<CompiledContentExtension> compiledContentExtension, URL&& extensionBaseURL, ContentExtension::ShouldCompileCSS shouldCompileCSS) |
| { |
| ASSERT(!identifier.isEmpty()); |
| if (identifier.isEmpty()) |
| return; |
| |
| auto contentExtension = ContentExtension::create(identifier, WTFMove(compiledContentExtension), WTFMove(extensionBaseURL), shouldCompileCSS); |
| m_contentExtensions.set(identifier, WTFMove(contentExtension)); |
| } |
| |
| void ContentExtensionsBackend::removeContentExtension(const String& identifier) |
| { |
| m_contentExtensions.remove(identifier); |
| } |
| |
| void ContentExtensionsBackend::removeAllContentExtensions() |
| { |
| m_contentExtensions.clear(); |
| } |
| |
| auto ContentExtensionsBackend::actionsFromContentRuleList(const ContentExtension& contentExtension, const String& urlString, const ResourceLoadInfo& resourceLoadInfo, ResourceFlags flags) const -> ActionsFromContentRuleList |
| { |
| ActionsFromContentRuleList actionsStruct; |
| actionsStruct.contentRuleListIdentifier = contentExtension.identifier(); |
| |
| const auto& compiledExtension = contentExtension.compiledExtension(); |
| |
| DFABytecodeInterpreter interpreter(compiledExtension.urlFiltersBytecode()); |
| auto actionLocations = interpreter.interpret(urlString, flags); |
| auto& topURLActions = contentExtension.topURLActions(resourceLoadInfo.mainDocumentURL); |
| auto& frameURLActions = contentExtension.frameURLActions(resourceLoadInfo.frameURL); |
| |
| actionLocations.removeIf([&](uint64_t actionAndFlags) { |
| ResourceFlags flags = actionAndFlags >> 32; |
| auto actionCondition = static_cast<ActionCondition>(flags & ActionConditionMask); |
| switch (actionCondition) { |
| case ActionCondition::None: |
| return false; |
| case ActionCondition::IfTopURL: |
| return !topURLActions.contains(actionAndFlags); |
| case ActionCondition::UnlessTopURL: |
| return topURLActions.contains(actionAndFlags); |
| case ActionCondition::IfFrameURL: |
| return !frameURLActions.contains(actionAndFlags); |
| } |
| ASSERT_NOT_REACHED(); |
| return false; |
| }); |
| |
| auto serializedActions = compiledExtension.serializedActions(); |
| |
| const auto& universalActions = contentExtension.universalActions(); |
| if (auto totalActionCount = actionLocations.size() + universalActions.size()) { |
| Vector<uint32_t> vector; |
| vector.reserveInitialCapacity(totalActionCount); |
| for (uint64_t actionLocation : actionLocations) |
| vector.uncheckedAppend(static_cast<uint32_t>(actionLocation)); |
| for (uint64_t actionLocation : universalActions) |
| vector.uncheckedAppend(static_cast<uint32_t>(actionLocation)); |
| std::sort(vector.begin(), vector.end()); |
| |
| // Add actions in reverse order to properly deal with IgnorePreviousRules. |
| for (auto i = vector.size(); i; i--) { |
| auto action = DeserializedAction::deserialize(serializedActions, vector[i - 1]); |
| if (std::holds_alternative<IgnorePreviousRulesAction>(action.data())) { |
| actionsStruct.sawIgnorePreviousRules = true; |
| break; |
| } |
| actionsStruct.actions.append(WTFMove(action)); |
| } |
| } |
| return actionsStruct; |
| } |
| |
| auto ContentExtensionsBackend::actionsForResourceLoad(const ResourceLoadInfo& resourceLoadInfo) const -> Vector<ActionsFromContentRuleList> |
| { |
| #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING |
| MonotonicTime addedTimeStart = MonotonicTime::now(); |
| #endif |
| if (m_contentExtensions.isEmpty() |
| || !resourceLoadInfo.resourceURL.isValid() |
| || resourceLoadInfo.resourceURL.protocolIsData()) |
| return { }; |
| |
| const String& urlString = resourceLoadInfo.resourceURL.string(); |
| ASSERT_WITH_MESSAGE(urlString.isAllASCII(), "A decoded URL should only contain ASCII characters. The matching algorithm assumes the input is ASCII."); |
| |
| Vector<ActionsFromContentRuleList> actionsVector; |
| actionsVector.reserveInitialCapacity(m_contentExtensions.size()); |
| ASSERT(!(resourceLoadInfo.getResourceFlags() & ActionConditionMask)); |
| const ResourceFlags flags = resourceLoadInfo.getResourceFlags() | ActionConditionMask; |
| for (auto& contentExtension : m_contentExtensions.values()) |
| actionsVector.uncheckedAppend(actionsFromContentRuleList(contentExtension.get(), urlString, resourceLoadInfo, flags)); |
| #if CONTENT_EXTENSIONS_PERFORMANCE_REPORTING |
| MonotonicTime addedTimeEnd = MonotonicTime::now(); |
| dataLogF("Time added: %f microseconds %s \n", (addedTimeEnd - addedTimeStart).microseconds(), resourceLoadInfo.resourceURL.string().utf8().data()); |
| #endif |
| return actionsVector; |
| } |
| |
| void ContentExtensionsBackend::forEach(const Function<void(const String&, ContentExtension&)>& apply) |
| { |
| for (auto& pair : m_contentExtensions) |
| apply(pair.key, pair.value); |
| } |
| |
| StyleSheetContents* ContentExtensionsBackend::globalDisplayNoneStyleSheet(const String& identifier) const |
| { |
| const auto& contentExtension = m_contentExtensions.get(identifier); |
| return contentExtension ? contentExtension->globalDisplayNoneStyleSheet() : nullptr; |
| } |
| |
| ContentRuleListResults ContentExtensionsBackend::processContentRuleListsForLoad(Page& page, const URL& url, OptionSet<ResourceType> resourceType, DocumentLoader& initiatingDocumentLoader, const URL& redirectFrom) |
| { |
| Document* currentDocument = nullptr; |
| URL mainDocumentURL; |
| URL frameURL; |
| bool mainFrameContext = false; |
| |
| if (auto* frame = initiatingDocumentLoader.frame()) { |
| mainFrameContext = frame->isMainFrame(); |
| currentDocument = frame->document(); |
| |
| if (initiatingDocumentLoader.isLoadingMainResource() |
| && frame->isMainFrame() |
| && resourceType == ResourceType::Document) |
| mainDocumentURL = url; |
| else if (auto* mainDocument = frame->mainFrame().document()) |
| mainDocumentURL = mainDocument->url(); |
| } |
| if (currentDocument) |
| frameURL = currentDocument->url(); |
| else |
| frameURL = url; |
| |
| ResourceLoadInfo resourceLoadInfo { url, mainDocumentURL, frameURL, resourceType, mainFrameContext }; |
| auto actions = actionsForResourceLoad(resourceLoadInfo); |
| |
| ContentRuleListResults results; |
| if (page.httpsUpgradeEnabled()) |
| makeSecureIfNecessary(results, url, redirectFrom); |
| results.results.reserveInitialCapacity(actions.size()); |
| for (const auto& actionsFromContentRuleList : actions) { |
| const String& contentRuleListIdentifier = actionsFromContentRuleList.contentRuleListIdentifier; |
| ContentRuleListResults::Result result; |
| for (const auto& action : actionsFromContentRuleList.actions) { |
| std::visit(WTF::makeVisitor([&](const BlockLoadAction&) { |
| results.summary.blockedLoad = true; |
| result.blockedLoad = true; |
| }, [&](const BlockCookiesAction&) { |
| results.summary.blockedCookies = true; |
| result.blockedCookies = true; |
| }, [&](const CSSDisplayNoneSelectorAction& actionData) { |
| if (resourceType == ResourceType::Document) |
| initiatingDocumentLoader.addPendingContentExtensionDisplayNoneSelector(contentRuleListIdentifier, actionData.string, action.actionID()); |
| else if (currentDocument) |
| currentDocument->extensionStyleSheets().addDisplayNoneSelector(contentRuleListIdentifier, actionData.string, action.actionID()); |
| }, [&](const NotifyAction& actionData) { |
| results.summary.hasNotifications = true; |
| result.notifications.append(actionData.string); |
| }, [&](const MakeHTTPSAction&) { |
| if ((url.protocolIs("http"_s) || url.protocolIs("ws"_s)) |
| && (!url.port() || WTF::isDefaultPortForProtocol(url.port().value(), url.protocol()))) { |
| results.summary.madeHTTPS = true; |
| result.madeHTTPS = true; |
| } |
| }, [&](const IgnorePreviousRulesAction&) { |
| RELEASE_ASSERT_NOT_REACHED(); |
| }, [&] (const ModifyHeadersAction& action) { |
| if (initiatingDocumentLoader.allowsActiveContentRuleListActionsForURL(contentRuleListIdentifier, url)) { |
| result.modifiedHeaders = true; |
| results.summary.modifyHeadersActions.append(action); |
| } |
| }, [&] (const RedirectAction& redirectAction) { |
| if (initiatingDocumentLoader.allowsActiveContentRuleListActionsForURL(contentRuleListIdentifier, url)) { |
| result.redirected = true; |
| results.summary.redirectActions.append({ redirectAction, m_contentExtensions.get(contentRuleListIdentifier)->extensionBaseURL() }); |
| } |
| }), action.data()); |
| } |
| |
| if (!actionsFromContentRuleList.sawIgnorePreviousRules) { |
| if (auto* styleSheetContents = globalDisplayNoneStyleSheet(contentRuleListIdentifier)) { |
| if (resourceType == ResourceType::Document) |
| initiatingDocumentLoader.addPendingContentExtensionSheet(contentRuleListIdentifier, *styleSheetContents); |
| else if (currentDocument) |
| currentDocument->extensionStyleSheets().maybeAddContentExtensionSheet(contentRuleListIdentifier, *styleSheetContents); |
| } |
| } |
| |
| results.results.uncheckedAppend({ contentRuleListIdentifier, WTFMove(result) }); |
| } |
| |
| if (currentDocument) { |
| if (results.summary.madeHTTPS) { |
| ASSERT(url.protocolIs("http"_s) || url.protocolIs("ws"_s)); |
| String newProtocol = url.protocolIs("http"_s) ? "https"_s : "wss"_s; |
| currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Promoted URL from ", url.string(), " to ", newProtocol)); |
| } |
| if (results.summary.blockedLoad) { |
| currentDocument->addConsoleMessage(MessageSource::ContentBlocker, MessageLevel::Info, makeString("Content blocker prevented frame displaying ", mainDocumentURL.string(), " from loading a resource from ", url.string())); |
| |
| // Quirk for content-blocker interference with Google's anti-flicker optimization (rdar://problem/45968770). |
| // https://developers.google.com/optimize/ |
| if (currentDocument->settings().googleAntiFlickerOptimizationQuirkEnabled() |
| && ((equalLettersIgnoringASCIICase(url.host(), "www.google-analytics.com"_s) && equalLettersIgnoringASCIICase(url.path(), "/analytics.js"_s)) |
| || (equalLettersIgnoringASCIICase(url.host(), "www.googletagmanager.com"_s) && equalLettersIgnoringASCIICase(url.path(), "/gtm.js"_s)))) { |
| if (auto* frame = currentDocument->frame()) |
| frame->script().evaluateIgnoringException(ScriptSourceCode { "try { window.dataLayer.hide.end(); console.log('Called window.dataLayer.hide.end() in frame ' + document.URL + ' because the content blocker blocked the load of the https://www.google-analytics.com/analytics.js script'); } catch (e) { }"_s }); |
| } |
| } |
| } |
| |
| return results; |
| } |
| |
| ContentRuleListResults ContentExtensionsBackend::processContentRuleListsForPingLoad(const URL& url, const URL& mainDocumentURL, const URL& frameURL) |
| { |
| ResourceLoadInfo resourceLoadInfo { url, mainDocumentURL, frameURL, ResourceType::Ping }; |
| auto actions = actionsForResourceLoad(resourceLoadInfo); |
| |
| ContentRuleListResults results; |
| makeSecureIfNecessary(results, url); |
| for (const auto& actionsFromContentRuleList : actions) { |
| for (const auto& action : actionsFromContentRuleList.actions) { |
| std::visit(WTF::makeVisitor([&](const BlockLoadAction&) { |
| results.summary.blockedLoad = true; |
| }, [&](const BlockCookiesAction&) { |
| results.summary.blockedCookies = true; |
| }, [&](const CSSDisplayNoneSelectorAction&) { |
| }, [&](const NotifyAction&) { |
| // We currently have not implemented notifications from the NetworkProcess to the UIProcess. |
| }, [&](const MakeHTTPSAction&) { |
| if ((url.protocolIs("http"_s) || url.protocolIs("ws"_s)) && (!url.port() || WTF::isDefaultPortForProtocol(url.port().value(), url.protocol()))) |
| results.summary.madeHTTPS = true; |
| }, [&](const IgnorePreviousRulesAction&) { |
| RELEASE_ASSERT_NOT_REACHED(); |
| }, [&] (const ModifyHeadersAction&) { |
| // We currently have not implemented active actions from the network process (CORS preflight). |
| }, [&] (const RedirectAction&) { |
| // We currently have not implemented active actions from the network process (CORS preflight). |
| }), action.data()); |
| } |
| } |
| |
| return results; |
| } |
| |
| const String& ContentExtensionsBackend::displayNoneCSSRule() |
| { |
| static NeverDestroyed<const String> rule(MAKE_STATIC_STRING_IMPL("display:none !important;")); |
| return rule; |
| } |
| |
| void applyResultsToRequest(ContentRuleListResults&& results, Page* page, ResourceRequest& request) |
| { |
| if (results.summary.blockedCookies) |
| request.setAllowCookies(false); |
| |
| if (results.summary.madeHTTPS) { |
| const URL& originalURL = request.url(); |
| ASSERT(originalURL.protocolIs("http"_s)); |
| ASSERT(!originalURL.port() || WTF::isDefaultPortForProtocol(originalURL.port().value(), originalURL.protocol())); |
| |
| URL newURL = originalURL; |
| newURL.setProtocol("https"_s); |
| if (originalURL.port()) |
| newURL.setPort(WTF::defaultPortForProtocol("https"_s).value()); |
| request.setURL(newURL); |
| } |
| |
| for (auto& action : results.summary.modifyHeadersActions) |
| action.applyToRequest(request); |
| |
| for (auto& pair : results.summary.redirectActions) |
| pair.first.applyToRequest(request, pair.second); |
| |
| if (page && results.shouldNotifyApplication()) { |
| results.results.removeAllMatching([](const auto& pair) { |
| return !pair.second.shouldNotifyApplication(); |
| }); |
| page->chrome().client().contentRuleListNotification(request.url(), results); |
| } |
| } |
| |
| } // namespace WebCore::ContentExtensions |
| |
| #endif // ENABLE(CONTENT_EXTENSIONS) |