[Content Filtering] Adopt new NEFilterSource SPI
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Mar 2015 05:44:03 +0000 (05:44 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Mar 2015 05:44:03 +0000 (05:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142710
rdar://problem/19023855

Reviewed by Dan Bernstein.

Teach NetworkExtensionContentFilter to use a new, alternate NEFilterSource SPI on platforms where it is available.

* platform/ContentFilter.cpp:
(WebCore::ContentFilter::types): Renamed HAVE(NE_FILTER_SOURCE) to HAVE(NETWORK_EXTENSION).
* platform/cocoa/NetworkExtensionContentFilter.h: Renamed member variables to remove redundancy, forward-declared NEFilterSourceStatus,
added a dispatch_semaphore member variable to avoid creating and destroying multiple semaphores, and made m_originalData a SharedBuffer.
* platform/cocoa/NetworkExtensionContentFilter.mm:
(decisionInfoReplacementData): Returned the replacement data from a decision handler info dictionary.
(WebCore::createNEFilterSource): Created either an old-style or new-style NEFilterSource object.
(WebCore::NetworkExtensionContentFilter::NetworkExtensionContentFilter): Called receivedResponse:decisionHandler: when using the new SPI.
(WebCore::NetworkExtensionContentFilter::~NetworkExtensionContentFilter): Released the dispatch_semaphore.
(WebCore::NetworkExtensionContentFilter::addData): Appended the copied NSData to m_originalData, avoiding an additional copy previously
being made by NSMutableData. Used the new receivedData:decisionHandler: SPI when appropriate.
(WebCore::NetworkExtensionContentFilter::finishedAddingData): Used the new finishedLoadingWithDecisionHandler: SPI when appropriate.
(WebCore::NetworkExtensionContentFilter::needsMoreData): Changed m_neFilterSourceStatus to m_status.
(WebCore::NetworkExtensionContentFilter::didBlockData): Ditto.
(WebCore::NetworkExtensionContentFilter::getReplacementData): Returned the replacement data from NEFilterSource if the load was blocked.
Otherwise, returned the original data.
(WebCore::NetworkExtensionContentFilter::handleDecision): Added a helper to set m_status and m_replacementData, and to signal m_semaphore.
* platform/spi/cocoa/NEFilterSourceSPI.h: Declared the new NEFilterSource SPI on platforms that support it.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@181523 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/platform/ContentFilter.cpp
Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.h
Source/WebCore/platform/cocoa/NetworkExtensionContentFilter.mm
Source/WebCore/platform/spi/cocoa/NEFilterSourceSPI.h

index e00528b..7fd8d10 100644 (file)
@@ -1,3 +1,32 @@
+2015-03-15  Andy Estes  <aestes@apple.com>
+
+        [Content Filtering] Adopt new NEFilterSource SPI
+        https://bugs.webkit.org/show_bug.cgi?id=142710
+        rdar://problem/19023855
+
+        Reviewed by Dan Bernstein.
+
+        Teach NetworkExtensionContentFilter to use a new, alternate NEFilterSource SPI on platforms where it is available.
+
+        * platform/ContentFilter.cpp:
+        (WebCore::ContentFilter::types): Renamed HAVE(NE_FILTER_SOURCE) to HAVE(NETWORK_EXTENSION).
+        * platform/cocoa/NetworkExtensionContentFilter.h: Renamed member variables to remove redundancy, forward-declared NEFilterSourceStatus,
+        added a dispatch_semaphore member variable to avoid creating and destroying multiple semaphores, and made m_originalData a SharedBuffer.
+        * platform/cocoa/NetworkExtensionContentFilter.mm:
+        (decisionInfoReplacementData): Returned the replacement data from a decision handler info dictionary.
+        (WebCore::createNEFilterSource): Created either an old-style or new-style NEFilterSource object.
+        (WebCore::NetworkExtensionContentFilter::NetworkExtensionContentFilter): Called receivedResponse:decisionHandler: when using the new SPI.
+        (WebCore::NetworkExtensionContentFilter::~NetworkExtensionContentFilter): Released the dispatch_semaphore.
+        (WebCore::NetworkExtensionContentFilter::addData): Appended the copied NSData to m_originalData, avoiding an additional copy previously
+        being made by NSMutableData. Used the new receivedData:decisionHandler: SPI when appropriate.
+        (WebCore::NetworkExtensionContentFilter::finishedAddingData): Used the new finishedLoadingWithDecisionHandler: SPI when appropriate.
+        (WebCore::NetworkExtensionContentFilter::needsMoreData): Changed m_neFilterSourceStatus to m_status.
+        (WebCore::NetworkExtensionContentFilter::didBlockData): Ditto.
+        (WebCore::NetworkExtensionContentFilter::getReplacementData): Returned the replacement data from NEFilterSource if the load was blocked.
+        Otherwise, returned the original data.
+        (WebCore::NetworkExtensionContentFilter::handleDecision): Added a helper to set m_status and m_replacementData, and to signal m_semaphore.
+        * platform/spi/cocoa/NEFilterSourceSPI.h: Declared the new NEFilterSource SPI on platforms that support it.
+
 2015-03-15  Brent Fulgham  <bfulgham@apple.com>
 
         Scroll snap points are not supported on iframe content
index 91f9db2..7d5c2d3 100644 (file)
@@ -40,7 +40,7 @@ Vector<ContentFilter::Type>& ContentFilter::types()
     static NeverDestroyed<Vector<ContentFilter::Type>> types {
         Vector<ContentFilter::Type> {
             type<ParentalControlsContentFilter>(),
-#if HAVE(NE_FILTER_SOURCE)
+#if HAVE(NETWORK_EXTENSION)
             type<NetworkExtensionContentFilter>()
 #endif
         }
index 4640762..9208c2d 100644 (file)
 #define NetworkExtensionContentFilter_h
 
 #include "ContentFilter.h"
+#include "SharedBuffer.h"
+#include <objc/NSObjCRuntime.h>
 #include <wtf/Compiler.h>
+#include <wtf/OSObjectPtr.h>
+#include <wtf/Ref.h>
 #include <wtf/RetainPtr.h>
 
-#define HAVE_NE_FILTER_SOURCE TARGET_OS_EMBEDDED || (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && CPU(X86_64))
+#define HAVE_NETWORK_EXTENSION PLATFORM(IOS) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 && CPU(X86_64))
+
+enum NEFilterSourceStatus : NSInteger;
 
 OBJC_CLASS NEFilterSource;
+OBJC_CLASS NSData;
+OBJC_CLASS NSDictionary;
 OBJC_CLASS NSMutableData;
 
 namespace WebCore {
@@ -46,7 +54,6 @@ public:
     static bool canHandleResponse(const ResourceResponse&);
     static std::unique_ptr<NetworkExtensionContentFilter> create(const ResourceResponse&);
 
-    ~NetworkExtensionContentFilter() override;
     void addData(const char* data, int length) override;
     void finishedAddingData() override;
     bool needsMoreData() const override;
@@ -56,11 +63,14 @@ public:
 
 private:
     explicit NetworkExtensionContentFilter(const ResourceResponse&);
+    void handleDecision(NEFilterSourceStatus, NSData *replacementData);
 
-    long m_neFilterSourceStatus;
+    NEFilterSourceStatus m_status;
+    OSObjectPtr<dispatch_queue_t> m_queue;
+    OSObjectPtr<dispatch_semaphore_t> m_semaphore;
+    Ref<SharedBuffer> m_originalData;
+    RetainPtr<NSData> m_replacementData;
     RetainPtr<NEFilterSource> m_neFilterSource;
-    dispatch_queue_t m_neFilterSourceQueue;
-    RetainPtr<NSMutableData> m_originalData;
 };
 
 } // namespace WebCore
index 63689cc..f6c9c37 100644 (file)
@@ -26,7 +26,7 @@
 #import "config.h"
 #import "NetworkExtensionContentFilter.h"
 
-#if HAVE(NE_FILTER_SOURCE)
+#if HAVE(NETWORK_EXTENSION)
 
 #import "NEFilterSourceSPI.h"
 #import "ResourceResponse.h"
 SOFT_LINK_FRAMEWORK(NetworkExtension);
 SOFT_LINK_CLASS(NetworkExtension, NEFilterSource);
 
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+// FIXME: <rdar://problem/20165664> Expose decisionHandler dictionary keys as NSString constants in NEFilterSource.h
+static NSString * const optionsPageData = @"PageData";
+
+static inline NSData *replacementDataFromDecisionInfo(NSDictionary *decisionInfo)
+{
+    id replacementData = decisionInfo[optionsPageData];
+    ASSERT(!replacementData || [replacementData isKindOfClass:[NSData class]]);
+    return replacementData;
+}
+#endif
+
 namespace WebCore {
 
 bool NetworkExtensionContentFilter::canHandleResponse(const ResourceResponse& response)
@@ -48,80 +60,103 @@ std::unique_ptr<NetworkExtensionContentFilter> NetworkExtensionContentFilter::cr
     return std::make_unique<NetworkExtensionContentFilter>(response);
 }
 
+static inline RetainPtr<NEFilterSource> createNEFilterSource(const URL& url, dispatch_queue_t decisionQueue)
+{
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+    UNUSED_PARAM(url);
+    return adoptNS([allocNEFilterSourceInstance() initWithDecisionQueue:decisionQueue]);
+#else
+    UNUSED_PARAM(decisionQueue);
+    return adoptNS([allocNEFilterSourceInstance() initWithURL:url direction:NEFilterSourceDirectionInbound socketIdentifier:0]);
+#endif
+}
+
 NetworkExtensionContentFilter::NetworkExtensionContentFilter(const ResourceResponse& response)
-    : m_neFilterSourceStatus { NEFilterSourceStatusNeedsMoreData }
-    , m_neFilterSource { adoptNS([allocNEFilterSourceInstance() initWithURL:[response.nsURLResponse() URL] direction:NEFilterSourceDirectionInbound socketIdentifier:0]) }
-    , m_neFilterSourceQueue { dispatch_queue_create("com.apple.WebCore.NEFilterSourceQueue", DISPATCH_QUEUE_SERIAL) }
+    : m_status { NEFilterSourceStatusNeedsMoreData }
+    , m_queue { adoptOSObject(dispatch_queue_create("com.apple.WebCore.NEFilterSourceQueue", DISPATCH_QUEUE_SERIAL)) }
+    , m_semaphore { adoptOSObject(dispatch_semaphore_create(0)) }
+    , m_originalData { *SharedBuffer::create() }
+    , m_neFilterSource { createNEFilterSource(response.url(), m_queue.get()) }
 {
     ASSERT([getNEFilterSourceClass() filterRequired]);
 
-    long long expectedContentSize = [response.nsURLResponse() expectedContentLength];
-    if (expectedContentSize < 0)
-        m_originalData = adoptNS([[NSMutableData alloc] init]);
-    else
-        m_originalData = adoptNS([[NSMutableData alloc] initWithCapacity:(NSUInteger)expectedContentSize]);
-}
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+    [m_neFilterSource receivedResponse:response.nsURLResponse() decisionHandler:[this](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
+        handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
+    }];
 
-NetworkExtensionContentFilter::~NetworkExtensionContentFilter()
-{
-    dispatch_release(m_neFilterSourceQueue);
+    // 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.
+    dispatch_semaphore_wait(m_semaphore.get(), DISPATCH_TIME_FOREVER);
+#endif
 }
 
 void NetworkExtensionContentFilter::addData(const char* data, int length)
 {
+    RetainPtr<NSData> copiedData { [NSData dataWithBytes:(void*)data length:length] };
+
     // FIXME: NEFilterSource doesn't buffer data like WebFilterEvaluator does,
     // so we need to do it ourselves so getReplacementData() can return the
     // original bytes back to the loader. We should find a way to remove this
     // additional copy.
-    [m_originalData appendBytes:data length:length];
+    m_originalData->append((CFDataRef)copiedData.get());
 
-    dispatch_semaphore_t neFilterSourceSemaphore = dispatch_semaphore_create(0);
-    [m_neFilterSource addData:[NSData dataWithBytes:(void*)data length:length] withCompletionQueue:m_neFilterSourceQueue completionHandler:^(NEFilterSourceStatus status, NSData *) {
-        m_neFilterSourceStatus = status;
-        dispatch_semaphore_signal(neFilterSourceSemaphore);
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+    [m_neFilterSource receivedData:copiedData.get() decisionHandler:[this](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
+        handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
+    }];
+#else
+    [m_neFilterSource addData:copiedData.get() withCompletionQueue:m_queue.get() completionHandler:[this](NEFilterSourceStatus status, NSData *replacementData) {
+        ASSERT(!replacementData);
+        handleDecision(status, replacementData);
     }];
+#endif
 
     // 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.
-    dispatch_semaphore_wait(neFilterSourceSemaphore, DISPATCH_TIME_FOREVER);
-    dispatch_release(neFilterSourceSemaphore);
+    dispatch_semaphore_wait(m_semaphore.get(), DISPATCH_TIME_FOREVER);
 }
 
 void NetworkExtensionContentFilter::finishedAddingData()
 {
-    dispatch_semaphore_t neFilterSourceSemaphore = dispatch_semaphore_create(0);
-    [m_neFilterSource dataCompleteWithCompletionQueue:m_neFilterSourceQueue completionHandler:^(NEFilterSourceStatus status, NSData *) {
-        m_neFilterSourceStatus = status;
-        dispatch_semaphore_signal(neFilterSourceSemaphore);
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+    [m_neFilterSource finishedLoadingWithDecisionHandler:[this](NEFilterSourceStatus status, NSDictionary *decisionInfo) {
+        handleDecision(status, replacementDataFromDecisionInfo(decisionInfo));
     }];
+#else
+    [m_neFilterSource dataCompleteWithCompletionQueue:m_queue.get() completionHandler:[this](NEFilterSourceStatus status, NSData *replacementData) {
+        ASSERT(!replacementData);
+        handleDecision(status, replacementData);
+    }];
+#endif
 
     // 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.
-    dispatch_semaphore_wait(neFilterSourceSemaphore, DISPATCH_TIME_FOREVER);
-    dispatch_release(neFilterSourceSemaphore);
+    dispatch_semaphore_wait(m_semaphore.get(), DISPATCH_TIME_FOREVER);
 }
 
 bool NetworkExtensionContentFilter::needsMoreData() const
 {
-    return m_neFilterSourceStatus == NEFilterSourceStatusNeedsMoreData;
+    return m_status == NEFilterSourceStatusNeedsMoreData;
 }
 
 bool NetworkExtensionContentFilter::didBlockData() const
 {
-    return m_neFilterSourceStatus == NEFilterSourceStatusBlock;
+    return m_status == NEFilterSourceStatusBlock;
 }
 
 const char* NetworkExtensionContentFilter::getReplacementData(int& length) const
 {
     if (didBlockData()) {
-        length = 0;
-        return nullptr;
+        length = [m_replacementData length];
+        return static_cast<const char*>([m_replacementData bytes]);
     }
 
-    length = [m_originalData length];
-    return static_cast<const char*>([m_originalData bytes]);
+    length = m_originalData->size();
+    return m_originalData->data();
 }
 
 ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() const
@@ -129,6 +164,14 @@ ContentFilterUnblockHandler NetworkExtensionContentFilter::unblockHandler() cons
     return { };
 }
 
+void NetworkExtensionContentFilter::handleDecision(NEFilterSourceStatus status, NSData *replacementData)
+{
+    m_status = status;
+    if (status == NEFilterSourceStatusBlock)
+        m_replacementData = replacementData;
+    dispatch_semaphore_signal(m_semaphore.get());
+}
+
 } // namespace WebCore
 
-#endif // HAVE(NE_FILTER_SOURCE)
+#endif // HAVE(NETWORK_EXTENSION)
index 7e0b512..a76c432 100644 (file)
@@ -23,6 +23,8 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#define HAVE_MODERN_NE_FILTER_SOURCE (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) || (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000)
+
 #if USE(APPLE_INTERNAL_SDK)
 
 #import <NetworkExtension/NEFilterSource.h>
@@ -44,7 +46,7 @@ typedef NS_ENUM(NSInteger, NEFilterSourceDirection) {
 @interface NEFilterSource : NSObject
 @end
 
-@interface NEFilterSource (Details)
+@interface NEFilterSource (WKLegacyDetails)
 + (BOOL)filterRequired;
 - (id)initWithURL:(NSURL *)url direction:(NEFilterSourceDirection)direction socketIdentifier:(uint64_t)socketIdentifier;
 - (void)addData:(NSData *)data withCompletionQueue:(dispatch_queue_t)queue completionHandler:(void (^)(NEFilterSourceStatus, NSData *))completionHandler;
@@ -55,4 +57,17 @@ typedef NS_ENUM(NSInteger, NEFilterSourceDirection) {
 @property (readonly) uint64_t socketIdentifier;
 @end
 
+#if HAVE(MODERN_NE_FILTER_SOURCE)
+typedef void (^NEFilterSourceDecisionHandler)(NEFilterSourceStatus, NSDictionary *);
+
+@interface NEFilterSource (WKModernDetails)
+- (id)initWithDecisionQueue:(dispatch_queue_t)queue;
+- (void)willSendRequest:(NSURLRequest *)request decisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
+- (void)receivedResponse:(NSURLResponse *)response decisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
+- (void)receivedData:(NSData *)data decisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
+- (void)finishedLoadingWithDecisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
+- (void)remediateWithDecisionHandler:(NEFilterSourceDecisionHandler)decisionHandler;
+@end
+#endif
+
 #endif