242 lines
9.1 KiB
Plaintext
242 lines
9.1 KiB
Plaintext
/*
|
|
* Copyright (C) 2015 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.
|
|
*/
|
|
|
|
#import "config.h"
|
|
#import "NetworkExtensionContentFilter.h"
|
|
|
|
#if ENABLE(CONTENT_FILTERING)
|
|
|
|
#import "ContentFilterUnblockHandler.h"
|
|
#import "Logging.h"
|
|
#import "ResourceRequest.h"
|
|
#import "ResourceResponse.h"
|
|
#import "RuntimeApplicationChecks.h"
|
|
#import "SharedBuffer.h"
|
|
#import <objc/runtime.h>
|
|
#import <pal/spi/cocoa/NEFilterSourceSPI.h>
|
|
#import <wtf/SoftLinking.h>
|
|
#import <wtf/URL.h>
|
|
#import <wtf/threads/BinarySemaphore.h>
|
|
|
|
static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo)
|
|
{
|
|
ASSERT_WITH_SECURITY_IMPLICATION(!decisionInfo || [decisionInfo isKindOfClass:[NSDictionary class]]);
|
|
return decisionInfo[NEFilterSourceOptionsPageData];
|
|
}
|
|
|
|
namespace WebCore {
|
|
|
|
NetworkExtensionContentFilter::SandboxExtensionsState NetworkExtensionContentFilter::m_sandboxExtensionsState = SandboxExtensionsState::NotSet;
|
|
|
|
bool NetworkExtensionContentFilter::enabled()
|
|
{
|
|
bool enabled = false;
|
|
switch (m_sandboxExtensionsState) {
|
|
case SandboxExtensionsState::Consumed:
|
|
enabled = true;
|
|
break;
|
|
case SandboxExtensionsState::NotConsumed:
|
|
enabled = false;
|
|
break;
|
|
case SandboxExtensionsState::NotSet:
|
|
enabled = isRequired();
|
|
break;
|
|
}
|
|
LOG(ContentFiltering, "NetworkExtensionContentFilter is %s.\n", enabled ? "enabled" : "not enabled");
|
|
return enabled;
|
|
}
|
|
|
|
UniqueRef<NetworkExtensionContentFilter> NetworkExtensionContentFilter::create()
|
|
{
|
|
return makeUniqueRef<NetworkExtensionContentFilter>();
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::initialize(const URL* url)
|
|
{
|
|
ASSERT(!m_queue);
|
|
ASSERT(!m_neFilterSource);
|
|
m_queue = adoptOSObject(dispatch_queue_create("WebKit NetworkExtension Filtering", DISPATCH_QUEUE_SERIAL));
|
|
ASSERT_UNUSED(url, !url);
|
|
m_neFilterSource = adoptNS([[NEFilterSource alloc] initWithDecisionQueue:m_queue.get()]);
|
|
[m_neFilterSource setSourceAppIdentifier:applicationBundleIdentifier()];
|
|
[m_neFilterSource setSourceAppPid:presentingApplicationPID()];
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
|
|
{
|
|
ASSERT(!request.isNull());
|
|
if (!request.url().protocolIsInHTTPFamily() || !enabled()) {
|
|
m_state = State::Allowed;
|
|
return;
|
|
}
|
|
|
|
initialize();
|
|
|
|
if (!redirectResponse.isNull()) {
|
|
responseReceived(redirectResponse);
|
|
if (!needsMoreData())
|
|
return;
|
|
}
|
|
|
|
BinarySemaphore semaphore;
|
|
RetainPtr<NSString> modifiedRequestURLString;
|
|
[m_neFilterSource willSendRequest:request.nsURLRequest(DoNotUpdateHTTPBody) decisionHandler:[this, &modifiedRequestURLString, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
|
|
modifiedRequestURLString = decisionInfo[NEFilterSourceOptionsRedirectURL];
|
|
ASSERT(!modifiedRequestURLString || [modifiedRequestURLString isKindOfClass:[NSString class]]);
|
|
handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
|
|
semaphore.signal();
|
|
}];
|
|
|
|
// FIXME: We have to block here since DocumentLoader expects to have a
|
|
// blocked/not blocked answer from the filter immediately after calling
|
|
// addData(). We should find a way to make this asynchronous.
|
|
semaphore.wait();
|
|
|
|
if (!modifiedRequestURLString)
|
|
return;
|
|
|
|
URL modifiedRequestURL { URL(), modifiedRequestURLString.get() };
|
|
if (!modifiedRequestURL.isValid()) {
|
|
LOG(ContentFiltering, "NetworkExtensionContentFilter failed to convert modified URL string %@ to a URL.\n", modifiedRequestURLString.get());
|
|
return;
|
|
}
|
|
|
|
request.setURL(modifiedRequestURL);
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::responseReceived(const ResourceResponse& response)
|
|
{
|
|
if (!response.url().protocolIsInHTTPFamily()) {
|
|
m_state = State::Allowed;
|
|
return;
|
|
}
|
|
|
|
BinarySemaphore semaphore;
|
|
[m_neFilterSource receivedResponse:response.nsURLResponse() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
|
|
handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
|
|
semaphore.signal();
|
|
}];
|
|
|
|
// FIXME: We have to block here since DocumentLoader expects to have a
|
|
// blocked/not blocked answer from the filter immediately after calling
|
|
// addData(). We should find a way to make this asynchronous.
|
|
semaphore.wait();
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::addData(const uint8_t* data, int length)
|
|
{
|
|
RetainPtr<NSData> copiedData { [NSData dataWithBytes:(void*)data length:length] };
|
|
|
|
BinarySemaphore semaphore;
|
|
[m_neFilterSource receivedData:copiedData.get() decisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
|
|
handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
|
|
semaphore.signal();
|
|
}];
|
|
|
|
// FIXME: We have to block here since DocumentLoader expects to have a
|
|
// blocked/not blocked answer from the filter immediately after calling
|
|
// addData(). We should find a way to make this asynchronous.
|
|
semaphore.wait();
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::finishedAddingData()
|
|
{
|
|
BinarySemaphore semaphore;
|
|
[m_neFilterSource finishedLoadingWithDecisionHandler:[this, &semaphore](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
|
|
handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
|
|
semaphore.signal();
|
|
}];
|
|
|
|
// FIXME: We have to block here since DocumentLoader expects to have a
|
|
// blocked/not blocked answer from the filter immediately after calling
|
|
// finishedAddingData(). We should find a way to make this asynchronous.
|
|
semaphore.wait();
|
|
}
|
|
|
|
Ref<SharedBuffer> NetworkExtensionContentFilter::replacementData() const
|
|
{
|
|
ASSERT(didBlockData());
|
|
return SharedBuffer::create(m_replacementData.get());
|
|
}
|
|
|
|
ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() const
|
|
{
|
|
using DecisionHandlerFunction = ContentFilterUnblockHandler::DecisionHandlerFunction;
|
|
|
|
RetainPtr<NEFilterSource> neFilterSource { m_neFilterSource };
|
|
return ContentFilterUnblockHandler {
|
|
"nefilter-unblock"_s, [neFilterSource](DecisionHandlerFunction decisionHandler) {
|
|
[neFilterSource remediateWithDecisionHandler:[decisionHandler](NEFilterSourceStatus status, NSDictionary *) {
|
|
LOG(ContentFiltering, "NEFilterSource %s the unblock request.\n", status == NEFilterSourceStatusPass ? "allowed" : "did not allow");
|
|
decisionHandler(status == NEFilterSourceStatusPass);
|
|
}];
|
|
}
|
|
};
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::handleDecision(NEFilterSourceStatus status, NSData *replacementData)
|
|
{
|
|
ASSERT_WITH_SECURITY_IMPLICATION(!replacementData || [replacementData isKindOfClass:[NSData class]]);
|
|
|
|
switch (status) {
|
|
case NEFilterSourceStatusPass:
|
|
case NEFilterSourceStatusError:
|
|
case NEFilterSourceStatusWhitelisted:
|
|
case NEFilterSourceStatusBlacklisted:
|
|
m_state = State::Allowed;
|
|
break;
|
|
case NEFilterSourceStatusBlock:
|
|
m_state = State::Blocked;
|
|
break;
|
|
case NEFilterSourceStatusNeedsMoreData:
|
|
m_state = State::Filtering;
|
|
break;
|
|
}
|
|
|
|
if (didBlockData())
|
|
m_replacementData = replacementData;
|
|
#if !LOG_DISABLED
|
|
if (!needsMoreData())
|
|
LOG(ContentFiltering, "NetworkExtensionContentFilter stopped buffering with status %zd and replacement data length %zu.\n", status, replacementData.length);
|
|
#endif
|
|
}
|
|
|
|
bool NetworkExtensionContentFilter::isRequired()
|
|
{
|
|
return [NEFilterSource filterRequired];
|
|
}
|
|
|
|
void NetworkExtensionContentFilter::setHasConsumedSandboxExtensions(bool hasConsumedSandboxExtensions)
|
|
{
|
|
if (m_sandboxExtensionsState == SandboxExtensionsState::Consumed)
|
|
return;
|
|
|
|
m_sandboxExtensionsState = (hasConsumedSandboxExtensions ? SandboxExtensionsState::Consumed : SandboxExtensionsState::NotConsumed);
|
|
}
|
|
|
|
} // namespace WebCore
|
|
|
|
#endif // ENABLE(CONTENT_FILTERING)
|