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