1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#import "config.h" 27#import "ContentFilter.h" 28 29#if USE(CONTENT_FILTERING) 30 31#import "ResourceResponse.h" 32#import "SoftLinking.h" 33#import <objc/runtime.h> 34 35#if defined(__has_include) && __has_include(<WebContentAnalysis/WebFilterEvaluator.h>) 36#import <WebContentAnalysis/WebFilterEvaluator.h> 37#else 38static const OSStatus kWFEStateBuffering = 2; 39@interface WebFilterEvaluator : NSObject 40+ (BOOL)isManagedSession; 41- (BOOL)wasBlocked; 42- (NSData *)addData:(NSData *)receivedData; 43- (NSData *)dataComplete; 44- (OSStatus)filterState; 45- (id)initWithResponse:(NSURLResponse *)response; 46@end 47#endif 48 49SOFT_LINK_PRIVATE_FRAMEWORK(WebContentAnalysis); 50SOFT_LINK_CLASS(WebContentAnalysis, WebFilterEvaluator); 51 52#if HAVE(NE_FILTER_SOURCE) 53 54#if defined(__has_include) && __has_include(<NetworkExtension/NEFilterSource.h>) 55#import <NetworkExtension/NEFilterSource.h> 56#else 57typedef NS_ENUM(NSInteger, NEFilterSourceStatus) { 58 NEFilterSourceStatusPass = 1, 59 NEFilterSourceStatusBlock = 2, 60 NEFilterSourceStatusNeedsMoreData = 3, 61 NEFilterSourceStatusError = 4, 62}; 63 64typedef NS_ENUM(NSInteger, NEFilterSourceDirection) { 65 NEFilterSourceDirectionOutbound = 1, 66 NEFilterSourceDirectionInbound = 2, 67}; 68 69@interface NEFilterSource : NSObject 70+ (BOOL)filterRequired; 71- (id)initWithURL:(NSURL *)url direction:(NEFilterSourceDirection)direction socketIdentifier:(uint64_t)socketIdentifier; 72- (void)addData:(NSData *)data withCompletionQueue:(dispatch_queue_t)queue completionHandler:(void (^)(NEFilterSourceStatus, NSData *))completionHandler; 73- (void)dataCompleteWithCompletionQueue:(dispatch_queue_t)queue completionHandler:(void (^)(NEFilterSourceStatus, NSData *))completionHandler; 74@property (readonly) NEFilterSourceStatus status; 75@property (readonly) NSURL *url; 76@property (readonly) NEFilterSourceDirection direction; 77@property (readonly) uint64_t socketIdentifier; 78@end 79#endif 80 81SOFT_LINK_FRAMEWORK(NetworkExtension); 82SOFT_LINK_CLASS(NetworkExtension, NEFilterSource); 83 84#endif // HAVE(NE_FILTER_SOURCE) 85 86namespace WebCore { 87 88ContentFilter::ContentFilter() 89#if HAVE(NE_FILTER_SOURCE) 90 : m_neFilterSourceStatus(NEFilterSourceStatusNeedsMoreData) 91 , m_neFilterSourceQueue(0) 92#endif 93{ 94} 95 96ContentFilter::ContentFilter(const ResourceResponse& response) 97#if HAVE(NE_FILTER_SOURCE) 98 : m_neFilterSourceStatus(NEFilterSourceStatusNeedsMoreData) 99 , m_neFilterSourceQueue(0) 100#endif 101{ 102 if ([getWebFilterEvaluatorClass() isManagedSession]) 103 m_platformContentFilter = adoptNS([[getWebFilterEvaluatorClass() alloc] initWithResponse:response.nsURLResponse()]); 104 105#if HAVE(NE_FILTER_SOURCE) 106 if ([getNEFilterSourceClass() filterRequired]) { 107 m_neFilterSource = adoptNS([[getNEFilterSourceClass() alloc] initWithURL:[response.nsURLResponse() URL] direction:NEFilterSourceDirectionInbound socketIdentifier:0]); 108 m_neFilterSourceQueue = dispatch_queue_create("com.apple.WebCore.NEFilterSourceQueue", DISPATCH_QUEUE_SERIAL); 109 110 long long expectedContentSize = [response.nsURLResponse() expectedContentLength]; 111 if (expectedContentSize < 0) 112 m_originalData = adoptNS([[NSMutableData alloc] init]); 113 else 114 m_originalData = adoptNS([[NSMutableData alloc] initWithCapacity:(NSUInteger)expectedContentSize]); 115 } 116#endif 117} 118 119ContentFilter::~ContentFilter() 120{ 121#if HAVE(NE_FILTER_SOURCE) 122 if (m_neFilterSourceQueue) 123 dispatch_release(m_neFilterSourceQueue); 124#endif 125} 126 127bool ContentFilter::canHandleResponse(const ResourceResponse& response) 128{ 129 if (!response.url().protocolIsInHTTPFamily()) 130 return false; 131 132 if ([getWebFilterEvaluatorClass() isManagedSession]) { 133#if PLATFORM(MAC) 134 if (response.url().protocolIs("https")) 135#endif 136 return true; 137 } 138 139#if HAVE(NE_FILTER_SOURCE) 140 return [getNEFilterSourceClass() filterRequired]; 141#else 142 return false; 143#endif 144} 145 146void ContentFilter::addData(const char* data, int length) 147{ 148 ASSERT(needsMoreData()); 149 150 if (m_platformContentFilter) { 151 ASSERT(![m_replacementData.get() length]); 152 m_replacementData = [m_platformContentFilter addData:[NSData dataWithBytesNoCopy:(void*)data length:length freeWhenDone:NO]]; 153 ASSERT(needsMoreData() || [m_replacementData.get() length]); 154 } 155 156#if HAVE(NE_FILTER_SOURCE) 157 if (!m_neFilterSource) 158 return; 159 160 // FIXME: NEFilterSource doesn't buffer data like WebFilterEvaluator does, 161 // so we need to do it ourselves so getReplacementData() can return the 162 // original bytes back to the loader. We should find a way to remove this 163 // additional copy. 164 [m_originalData appendBytes:data length:length]; 165 166 dispatch_semaphore_t neFilterSourceSemaphore = dispatch_semaphore_create(0); 167 [m_neFilterSource addData:[NSData dataWithBytes:(void*)data length:length] withCompletionQueue:m_neFilterSourceQueue completionHandler:^(NEFilterSourceStatus status, NSData *) { 168 m_neFilterSourceStatus = status; 169 dispatch_semaphore_signal(neFilterSourceSemaphore); 170 }]; 171 172 // FIXME: We have to block here since DocumentLoader expects to have a 173 // blocked/not blocked answer from the filter immediately after calling 174 // addData(). We should find a way to make this asynchronous. 175 dispatch_semaphore_wait(neFilterSourceSemaphore, DISPATCH_TIME_FOREVER); 176 dispatch_release(neFilterSourceSemaphore); 177#endif 178} 179 180void ContentFilter::finishedAddingData() 181{ 182 ASSERT(needsMoreData()); 183 184 if (m_platformContentFilter) { 185 ASSERT(![m_replacementData.get() length]); 186 m_replacementData = [m_platformContentFilter dataComplete]; 187 } 188 189#if HAVE(NE_FILTER_SOURCE) 190 if (!m_neFilterSource) 191 return; 192 193 dispatch_semaphore_t neFilterSourceSemaphore = dispatch_semaphore_create(0); 194 [m_neFilterSource dataCompleteWithCompletionQueue:m_neFilterSourceQueue completionHandler:^(NEFilterSourceStatus status, NSData *) { 195 m_neFilterSourceStatus = status; 196 dispatch_semaphore_signal(neFilterSourceSemaphore); 197 }]; 198 199 // FIXME: We have to block here since DocumentLoader expects to have a 200 // blocked/not blocked answer from the filter immediately after calling 201 // finishedAddingData(). We should find a way to make this asynchronous. 202 dispatch_semaphore_wait(neFilterSourceSemaphore, DISPATCH_TIME_FOREVER); 203 dispatch_release(neFilterSourceSemaphore); 204#endif 205 206 ASSERT(!needsMoreData()); 207} 208 209bool ContentFilter::needsMoreData() const 210{ 211 return [m_platformContentFilter filterState] == kWFEStateBuffering 212#if HAVE(NE_FILTER_SOURCE) 213 || (m_neFilterSource && m_neFilterSourceStatus == NEFilterSourceStatusNeedsMoreData) 214#endif 215 ; 216} 217 218bool ContentFilter::didBlockData() const 219{ 220 return [m_platformContentFilter wasBlocked] 221#if HAVE(NE_FILTER_SOURCE) 222 || (m_neFilterSource && m_neFilterSourceStatus == NEFilterSourceStatusBlock) 223#endif 224 ; 225} 226 227const char* ContentFilter::getReplacementData(int& length) const 228{ 229 ASSERT(!needsMoreData()); 230 231 if (didBlockData()) { 232 length = [m_replacementData length]; 233 return static_cast<const char*>([m_replacementData bytes]); 234 } 235 236 NSData *originalData = m_replacementData.get(); 237#if HAVE(NE_FILTER_SOURCE) 238 if (!originalData) 239 originalData = m_originalData.get(); 240#endif 241 242 length = [originalData length]; 243 return static_cast<const char*>([originalData bytes]); 244} 245 246static NSString * const platformContentFilterKey = @"platformContentFilter"; 247 248void ContentFilter::encode(NSKeyedArchiver *archiver) const 249{ 250 if ([getWebFilterEvaluatorClass() conformsToProtocol:@protocol(NSSecureCoding)]) 251 [archiver encodeObject:m_platformContentFilter.get() forKey:platformContentFilterKey]; 252} 253 254bool ContentFilter::decode(NSKeyedUnarchiver *unarchiver, ContentFilter& contentFilter) 255{ 256 @try { 257 if ([getWebFilterEvaluatorClass() conformsToProtocol:@protocol(NSSecureCoding)]) 258 contentFilter.m_platformContentFilter = (WebFilterEvaluator *)[unarchiver decodeObjectOfClass:getWebFilterEvaluatorClass() forKey:platformContentFilterKey]; 259 return true; 260 } @catch (NSException *exception) { 261 LOG_ERROR("The platform content filter being decoded is not a WebFilterEvaluator."); 262 } 263 264 return false; 265} 266 267} // namespace WebCore 268 269#endif // USE(CONTENT_FILTERING) 270