https://bugs.webkit.org/show_bug.cgi?id=175746
<rdar://problem/
33946050>
Reviewed by Alex Christensen.
Source/WebCore:
Update PingLoad to process content extension rules upon redirect. This allows content
extensions to block and upgrade to HTTPS beacon / ping loads.
Because ping loads can outlive the WebProcess, the content extensions rules are passed
to the NetworkProcess when starting the Ping load. The PingLoad can then consult those
rules upon redirect, on the NetworkProcess side.
Tests: http/wpt/beacon/contentextensions/beacon-blocked.html
http/wpt/beacon/contentextensions/beacon-redirect-blocked.html
* contentextensions/ContentExtensionActions.h:
* contentextensions/ContentExtensionsBackend.cpp:
(WebCore::ContentExtensions::ContentExtensionsBackend::forEach):
(WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForPingLoad):
* contentextensions/ContentExtensionsBackend.h:
* page/Page.h:
* page/UserContentProvider.cpp:
(WebCore::UserContentProvider::forEachContentExtension):
* page/UserContentProvider.h:
Source/WebKit:
Update PingLoad to process content extension rules upon redirect. This allows content
extensions to block and upgrade to HTTPS beacon / ping loads.
Because ping loads can outlive the WebProcess, the content extensions rules are passed
to the NetworkProcess when starting the Ping load. The PingLoad can then consult those
rules upon redirect, on the NetworkProcess side.
* NetworkProcess/NetworkResourceLoadParameters.cpp:
(WebKit::NetworkResourceLoadParameters::encode const):
(WebKit::NetworkResourceLoadParameters::decode):
* NetworkProcess/NetworkResourceLoadParameters.h:
* NetworkProcess/PingLoad.cpp:
(WebKit::PingLoad::willPerformHTTPRedirection):
(WebKit::PingLoad::contentExtensionsBackend):
(WebKit::PingLoad::processContentExtensionRulesForLoad):
* NetworkProcess/PingLoad.h:
* WebProcess/Network/WebLoaderStrategy.cpp:
(WebKit::WebLoaderStrategy::startPingLoad):
LayoutTests:
Add layout test coverage for blocking beacon loads via content extensions.
* http/wpt/beacon/connect-src-beacon-redirect-blocked.sub-expected.txt:
* http/wpt/beacon/connect-src-beacon-redirect-blocked.sub.html:
* http/wpt/beacon/contentextensions/beacon-blocked-expected.txt: Added.
* http/wpt/beacon/contentextensions/beacon-blocked.html: Added.
* http/wpt/beacon/contentextensions/beacon-blocked.html.json: Added.
* http/wpt/beacon/contentextensions/beacon-redirect-blocked-expected.txt: Added.
* http/wpt/beacon/contentextensions/beacon-redirect-blocked.html: Added.
* http/wpt/beacon/contentextensions/beacon-redirect-blocked.html.json: Added.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220996
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2017-08-21 Chris Dumez <cdumez@apple.com>
+
+ [Beacon] Content extensions should be able to intercept Beacon / Ping redirects
+ https://bugs.webkit.org/show_bug.cgi?id=175746
+ <rdar://problem/33946050>
+
+ Reviewed by Alex Christensen.
+
+ Add layout test coverage for blocking beacon loads via content extensions.
+
+ * http/wpt/beacon/connect-src-beacon-redirect-blocked.sub-expected.txt:
+ * http/wpt/beacon/connect-src-beacon-redirect-blocked.sub.html:
+ * http/wpt/beacon/contentextensions/beacon-blocked-expected.txt: Added.
+ * http/wpt/beacon/contentextensions/beacon-blocked.html: Added.
+ * http/wpt/beacon/contentextensions/beacon-blocked.html.json: Added.
+ * http/wpt/beacon/contentextensions/beacon-redirect-blocked-expected.txt: Added.
+ * http/wpt/beacon/contentextensions/beacon-redirect-blocked.html: Added.
+ * http/wpt/beacon/contentextensions/beacon-redirect-blocked.html.json: Added.
+
2017-08-21 Matt Lewis <jlewis3@apple.com>
Fixed expectations.
+CONSOLE MESSAGE: Beacon API cannot load http://127.0.0.1:8800/WebKit/beacon/resources/beacon-preflight.py?allowCors=1&cmd=put&id=2539e883-7dfb-4dde-a227-a41c670d5fe1&redirect_status=307&location=http%3A%2F%2F127.0.0.1%3A8800%2FWebKit%2Fbeacon%2Fresources%2Fbeacon-preflight.py%3FallowCors%3D1%26cmd%3Dput%26id%3D2539e883-7dfb-4dde-a227-a41c670d5fe1&count=1. Blocked by Content Security Policy
PASS Redirect is blocked by CSP
function testCORSPreflightRedirectSuccess(what) {
var testBase = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR;
- var id = self.token();
+ var id = "2539e883-7dfb-4dde-a227-a41c670d5fe1"; // Use a static token because the URL is logged.
var target = encodeURIComponent(testBase + "beacon-preflight.py?allowCors=1&cmd=put&id=" + id);
// 307 & 308 redirections are the only ones that maintain the POST method.
--- /dev/null
+CONSOLE MESSAGE: line 9: Content blocker prevented frame displaying http://localhost:8800/WebKit/beacon/contentextensions/beacon-blocked.html from loading a resource from http://localhost:8800/WebKit/beacon/resources/beacon-preflight.py
+CONSOLE MESSAGE: line 9: Beacon API cannot load http://localhost:8800/WebKit/beacon/resources/beacon-preflight.py. Resource blocked by content blocker
+
+PASS Content extensions should be able to block beacons
+
--- /dev/null
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script>
+ const RESOURCES_DIR = "/WebKit/beacon/resources/";
+
+ test(function() {
+ assert_false(navigator.sendBeacon(RESOURCES_DIR + "beacon-preflight.py"), "sendBeacon should return false");
+ }, "Content extensions should be able to block beacons");
+</script>
--- /dev/null
+[
+ {
+ "trigger": {
+ "url-filter": "beacon-preflight.py"
+ },
+ "action": {
+ "type": "block"
+ }
+ }
+]
--- /dev/null
+CONSOLE MESSAGE: Beacon API cannot load http://127.0.0.1:8800/WebKit/beacon/resources/beacon-preflight.py?allowCors=1&cmd=put&id=f470f43c-258c-4c82-b880-ace3bcdb211c&redirect_status=307&location=http%3A%2F%2F127.0.0.1%3A8800%2FWebKit%2Fbeacon%2Fresources%2Fbeacon-preflight.py%3FallowCors%3D1%26cmd%3Dput%26id%3Df470f43c-258c-4c82-b880-ace3bcdb211c&count=1. Blocked by content extension
+
+PASS Content extensions should be able to block beacon redirects
+
--- /dev/null
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+const RESOURCES_DIR = "/WebKit/beacon/resources/";
+
+function pollResult(test, id) {
+ var checkUrl = RESOURCES_DIR + "beacon-preflight.py?cmd=get&id=" + id;
+
+ return new Promise(resolve => {
+ step_timeout(test.step_func(() => {
+ fetch(checkUrl).then(response => {
+ response.json().then(body => {
+ resolve(body);
+ });
+ });
+ }), 1000);
+ });
+}
+
+promise_test(function(test) {
+ var testBase = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR;
+ var id = "f470f43c-258c-4c82-b880-ace3bcdb211c"; // Use a static token since the URL is logged.
+ var target = encodeURIComponent(testBase + "beacon-preflight.py?allowCors=1&cmd=put&id=" + id);
+
+ // 307 & 308 redirections are the only ones that maintain the POST method.
+ var testUrl = RESOURCES_DIR + "redirect.py?redirect_status=307&location=" + target;
+
+ assert_true(navigator.sendBeacon(testUrl), "sendBeacon should return true");
+ return pollResult(test, id) .then(result => {
+ // Redirected URL contains "&count=" which the content filter blocks.
+ assert_equals(result['beacon'], 0, "Did not receive beacon")
+ });
+}, "Content extensions should be able to block beacon redirects");
+</script>
--- /dev/null
+[
+ {
+ "trigger": {
+ "url-filter": "count"
+ },
+ "action": {
+ "type": "block"
+ }
+ }
+]
+2017-08-21 Chris Dumez <cdumez@apple.com>
+
+ [Beacon] Content extensions should be able to intercept Beacon / Ping redirects
+ https://bugs.webkit.org/show_bug.cgi?id=175746
+ <rdar://problem/33946050>
+
+ Reviewed by Alex Christensen.
+
+ Update PingLoad to process content extension rules upon redirect. This allows content
+ extensions to block and upgrade to HTTPS beacon / ping loads.
+
+ Because ping loads can outlive the WebProcess, the content extensions rules are passed
+ to the NetworkProcess when starting the Ping load. The PingLoad can then consult those
+ rules upon redirect, on the NetworkProcess side.
+
+ Tests: http/wpt/beacon/contentextensions/beacon-blocked.html
+ http/wpt/beacon/contentextensions/beacon-redirect-blocked.html
+
+ * contentextensions/ContentExtensionActions.h:
+ * contentextensions/ContentExtensionsBackend.cpp:
+ (WebCore::ContentExtensions::ContentExtensionsBackend::forEach):
+ (WebCore::ContentExtensions::ContentExtensionsBackend::processContentExtensionRulesForPingLoad):
+ * contentextensions/ContentExtensionsBackend.h:
+ * page/Page.h:
+ * page/UserContentProvider.cpp:
+ (WebCore::UserContentProvider::forEachContentExtension):
+ * page/UserContentProvider.h:
+
2017-08-21 Brady Eidson <beidson@apple.com>
ASSERTION FAILED: !m_connections.contains(&connection) in WebCore::SWServer::unregisterConnection(WebCore::SWServer::Connection&).
bool madeHTTPS { false };
};
-void applyBlockedStatusToRequest(const BlockedStatus&, ResourceRequest&);
+WEBCORE_EXPORT void applyBlockedStatusToRequest(const BlockedStatus&, ResourceRequest&);
} // namespace ContentExtensions
return finalActions;
}
+void ContentExtensionsBackend::forEach(const WTF::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 { willBlockLoad, willBlockCookies, willMakeHTTPS };
}
+BlockedStatus ContentExtensionsBackend::processContentExtensionRulesForPingLoad(const URL& url, const URL& mainDocumentURL)
+{
+ if (m_contentExtensions.isEmpty())
+ return { };
+
+ ResourceLoadInfo resourceLoadInfo = { url, mainDocumentURL, ResourceType::Raw };
+ Vector<ContentExtensions::Action> actions = actionsForResourceLoad(resourceLoadInfo);
+
+ bool willBlockLoad = false;
+ bool willBlockCookies = false;
+ bool willMakeHTTPS = false;
+ for (const auto& action : actions) {
+ switch (action.type()) {
+ case ContentExtensions::ActionType::BlockLoad:
+ willBlockLoad = true;
+ break;
+ case ContentExtensions::ActionType::BlockCookies:
+ willBlockCookies = true;
+ break;
+ case ContentExtensions::ActionType::MakeHTTPS:
+ if ((url.protocolIs("http") || url.protocolIs("ws")) && (!url.port() || isDefaultPortForProtocol(url.port().value(), url.protocol())))
+ willMakeHTTPS = true;
+ break;
+ case ContentExtensions::ActionType::CSSDisplayNoneSelector:
+ case ContentExtensions::ActionType::CSSDisplayNoneStyleSheet:
+ break;
+ case ContentExtensions::ActionType::IgnorePreviousRules:
+ case ContentExtensions::ActionType::InvalidAction:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+ }
+
+ return { willBlockLoad, willBlockCookies, willMakeHTTPS };
+}
+
const String& ContentExtensionsBackend::displayNoneCSSRule()
{
static NeverDestroyed<const String> rule(MAKE_STATIC_STRING_IMPL("display:none !important;"));
WEBCORE_EXPORT StyleSheetContents* globalDisplayNoneStyleSheet(const String& identifier) const;
BlockedStatus processContentExtensionRulesForLoad(const URL&, ResourceType, DocumentLoader& initiatingDocumentLoader);
+ WEBCORE_EXPORT BlockedStatus processContentExtensionRulesForPingLoad(const URL&, const URL& mainDocumentURL);
static const String& displayNoneCSSRule();
+ void forEach(const WTF::Function<void(const String&, ContentExtension&)>&);
+
private:
HashMap<String, Ref<ContentExtension>> m_contentExtensions;
};
PluginInfoProvider& pluginInfoProvider();
- UserContentProvider& userContentProvider();
+ WEBCORE_EXPORT UserContentProvider& userContentProvider();
WEBCORE_EXPORT void setUserContentProvider(Ref<UserContentProvider>&&);
VisitedLinkStore& visitedLinkStore();
return userContentExtensionBackend().actionsForResourceLoad(resourceLoadInfo);
}
+
+void UserContentProvider::forEachContentExtension(const WTF::Function<void(const String&, ContentExtensions::ContentExtension&)>& apply, DocumentLoader& initiatingDocumentLoader)
+{
+ if (!contentExtensionsEnabled(initiatingDocumentLoader))
+ return;
+
+ userContentExtensionBackend().forEach(apply);
+}
+
#endif
} // namespace WebCore
// which should always exist.
ContentExtensions::BlockedStatus processContentExtensionRulesForLoad(const URL&, ResourceType, DocumentLoader& initiatingDocumentLoader);
Vector<ContentExtensions::Action> actionsForResourceLoad(const ResourceLoadInfo&, DocumentLoader& initiatingDocumentLoader);
+ WEBCORE_EXPORT void forEachContentExtension(const WTF::Function<void(const String&, ContentExtensions::ContentExtension&)>&, DocumentLoader& initiatingDocumentLoader);
#endif
protected:
+2017-08-21 Chris Dumez <cdumez@apple.com>
+
+ [Beacon] Content extensions should be able to intercept Beacon / Ping redirects
+ https://bugs.webkit.org/show_bug.cgi?id=175746
+ <rdar://problem/33946050>
+
+ Reviewed by Alex Christensen.
+
+ Update PingLoad to process content extension rules upon redirect. This allows content
+ extensions to block and upgrade to HTTPS beacon / ping loads.
+
+ Because ping loads can outlive the WebProcess, the content extensions rules are passed
+ to the NetworkProcess when starting the Ping load. The PingLoad can then consult those
+ rules upon redirect, on the NetworkProcess side.
+
+ * NetworkProcess/NetworkResourceLoadParameters.cpp:
+ (WebKit::NetworkResourceLoadParameters::encode const):
+ (WebKit::NetworkResourceLoadParameters::decode):
+ * NetworkProcess/NetworkResourceLoadParameters.h:
+ * NetworkProcess/PingLoad.cpp:
+ (WebKit::PingLoad::willPerformHTTPRedirection):
+ (WebKit::PingLoad::contentExtensionsBackend):
+ (WebKit::PingLoad::processContentExtensionRulesForLoad):
+ * NetworkProcess/PingLoad.h:
+ * WebProcess/Network/WebLoaderStrategy.cpp:
+ (WebKit::WebLoaderStrategy::startPingLoad):
+
2017-08-21 Adrian Perez de Castro <aperez@igalia.com>
[WPE][GTK] Unused variables in UserMediaProcessManager.cpp
encoder << SecurityOriginData::fromSecurityOrigin(*sourceOrigin);
encoder.encodeEnum(mode);
encoder << cspResponseHeaders;
+
+#if ENABLE(CONTENT_EXTENSIONS)
+ encoder << mainDocumentURL;
+ encoder << contentRuleLists;
+#endif
}
bool NetworkResourceLoadParameters::decode(IPC::Decoder& decoder, NetworkResourceLoadParameters& result)
if (!decoder.decode(result.cspResponseHeaders))
return false;
+#if ENABLE(CONTENT_EXTENSIONS)
+ if (!decoder.decode(result.mainDocumentURL))
+ return false;
+
+ if (!decoder.decode(result.contentRuleLists))
+ return false;
+#endif
+
return true;
}
#include "NetworkLoadParameters.h"
#include "SandboxExtension.h"
+#include "WebCompiledContentRuleListData.h"
#include <WebCore/ContentSecurityPolicyResponseHeaders.h>
#include <WebCore/FetchOptions.h>
#include <WebCore/ResourceHandle.h>
RefPtr<WebCore::SecurityOrigin> sourceOrigin;
WebCore::FetchOptions::Mode mode;
std::optional<WebCore::ContentSecurityPolicyResponseHeaders> cspResponseHeaders;
+
+#if ENABLE(CONTENT_EXTENSIONS)
+ WebCore::URL mainDocumentURL;
+ Vector<std::pair<String, WebCompiledContentRuleListData>> contentRuleLists;
+#endif
};
} // namespace WebKit
#include "NetworkCORSPreflightChecker.h"
#include "NetworkConnectionToWebProcess.h"
#include "SessionTracker.h"
+#include "WebCompiledContentRuleList.h"
#include "WebErrors.h"
#include <WebCore/ContentSecurityPolicy.h>
#include <WebCore/CrossOriginAccessControl.h>
void PingLoad::willPerformHTTPRedirection(ResourceResponse&& redirectResponse, ResourceRequest&& request, RedirectCompletionHandler&& completionHandler)
{
- m_lastRedirectionRequest = request;
-
RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - shouldFollowRedirects? %d", m_parameters.shouldFollowRedirects);
if (!m_parameters.shouldFollowRedirects) {
completionHandler({ });
+ didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Not allowed to follow redirects"), ResourceError::Type::AccessControl });
return;
}
+#if ENABLE(CONTENT_EXTENSIONS)
+ if (processContentExtensionRulesForLoad(request)) {
+ RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect was blocked by content extensions");
+ m_lastRedirectionRequest = request;
+ completionHandler({ });
+ didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Blocked by content extension"), ResourceError::Type::AccessControl });
+ return;
+ }
+#endif
+
+ m_lastRedirectionRequest = request;
+
if (auto* contentSecurityPolicy = this->contentSecurityPolicy()) {
if (!contentSecurityPolicy->allowConnectToSource(request.url(), redirectResponse.isNull() ? ContentSecurityPolicy::RedirectResponseReceived::No : ContentSecurityPolicy::RedirectResponseReceived::Yes)) {
RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect was blocked by CSP");
completionHandler({ });
+ didFinish(ResourceError { String(), 0, currentRequest().url(), ASCIILiteral("Blocked by Content Security Policy"), ResourceError::Type::AccessControl });
return;
}
}
loadRequest(WTFMove(actualRequest));
}
+#if ENABLE(CONTENT_EXTENSIONS)
+
+ContentExtensions::ContentExtensionsBackend& PingLoad::contentExtensionsBackend()
+{
+ if (!m_contentExtensionsBackend) {
+ m_contentExtensionsBackend = std::make_unique<ContentExtensions::ContentExtensionsBackend>();
+ for (auto& pair : m_parameters.contentRuleLists)
+ m_contentExtensionsBackend->addContentExtension(pair.first, WebCompiledContentRuleList::create(WTFMove(pair.second)));
+ }
+ return *m_contentExtensionsBackend;
+}
+
+// Returns true if we should block the load.
+bool PingLoad::processContentExtensionRulesForLoad(ResourceRequest& request)
+{
+ auto status = contentExtensionsBackend().processContentExtensionRulesForPingLoad(request.url(), m_parameters.mainDocumentURL);
+ applyBlockedStatusToRequest(status, request);
+ return status.blockedLoad;
+}
+
+#endif // ENABLE(CONTENT_EXTENSIONS)
+
} // namespace WebKit
#endif // USE(NETWORK_SESSION)
#include "NetworkDataTask.h"
#include "NetworkResourceLoadParameters.h"
+#include <WebCore/ContentExtensionsBackend.h>
#include <WebCore/ResourceError.h>
namespace WebCore {
void makeCrossOriginAccessRequestWithPreflight(WebCore::ResourceRequest&&);
void preflightSuccess(WebCore::ResourceRequest&&);
+#if ENABLE(CONTENT_EXTENSIONS)
+ WebCore::ContentExtensions::ContentExtensionsBackend& contentExtensionsBackend();
+ bool processContentExtensionRulesForLoad(WebCore::ResourceRequest&);
+#endif
+
WebCore::SecurityOrigin& securityOrigin() const;
const WebCore::ResourceRequest& currentRequest() const;
bool m_isSimpleRequest { true };
RedirectCompletionHandler m_redirectHandler;
mutable std::unique_ptr<WebCore::ContentSecurityPolicy> m_contentSecurityPolicy;
+#if ENABLE(CONTENT_EXTENSIONS)
+ std::unique_ptr<WebCore::ContentExtensions::ContentExtensionsBackend> m_contentExtensionsBackend;
+#endif
std::optional<WebCore::ResourceRequest> m_lastRedirectionRequest;
};
#include "NetworkProcessConnection.h"
#include "NetworkResourceLoadParameters.h"
#include "SessionTracker.h"
+#include "WebCompiledContentRuleList.h"
#include "WebCoreArgumentCoders.h"
#include "WebErrors.h"
#include "WebFrame.h"
#include <WebCore/SecurityOrigin.h>
#include <WebCore/Settings.h>
#include <WebCore/SubresourceLoader.h>
+#include <WebCore/UserContentProvider.h>
#include <pal/SessionID.h>
#include <wtf/text/CString.h>
loadParameters.cspResponseHeaders = contentSecurityPolicy->responseHeaders();
}
+#if ENABLE(CONTENT_EXTENSIONS)
+ loadParameters.mainDocumentURL = document->topDocument().url();
+
+ if (auto* documentLoader = frame.loader().documentLoader()) {
+ if (auto* page = frame.page()) {
+ page->userContentProvider().forEachContentExtension([&loadParameters](const String& identifier, ContentExtensions::ContentExtension& contentExtension) {
+ loadParameters.contentRuleLists.append(std::make_pair(identifier, static_cast<const WebCompiledContentRuleList&>(contentExtension.compiledExtension()).data()));
+ }, *documentLoader);
+ }
+ }
+#endif
+
if (completionHandler)
m_pingLoadCompletionHandlers.add(loadParameters.identifier, WTFMove(completionHandler));