| /* |
| * 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 HAVE(NETWORK_EXTENSION) |
| |
| #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> |
| |
| SOFT_LINK_FRAMEWORK_OPTIONAL(NetworkExtension); |
| SOFT_LINK_CLASS_OPTIONAL(NetworkExtension, NEFilterSource); |
| |
| 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 = [getNEFilterSourceClass() filterRequired]; |
| 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([allocNEFilterSourceInstance() 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 char* 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()); |
| } |
| |
| #if ENABLE(CONTENT_FILTERING) |
| 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); |
| }]; |
| } |
| }; |
| } |
| #endif |
| |
| 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 |
| } |
| |
| void NetworkExtensionContentFilter::setHasConsumedSandboxExtensions(bool hasConsumedSandboxExtensions) |
| { |
| m_sandboxExtensionsState = (hasConsumedSandboxExtensions ? SandboxExtensionsState::Consumed : SandboxExtensionsState::NotConsumed); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // HAVE(NETWORK_EXTENSION) |