1/*
2 * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 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 "WebCoreResourceHandleAsOperationQueueDelegate.h"
28
29#if !USE(CFNETWORK)
30
31#import "AuthenticationChallenge.h"
32#import "AuthenticationMac.h"
33#import "Logging.h"
34#import "ResourceHandle.h"
35#import "ResourceHandleClient.h"
36#import "ResourceRequest.h"
37#import "ResourceResponse.h"
38#import "SharedBuffer.h"
39#import "WebCoreURLResponse.h"
40#import <wtf/MainThread.h>
41
42@interface NSURLRequest (Details)
43- (id)_propertyForKey:(NSString *)key;
44@end
45
46using namespace WebCore;
47
48@implementation WebCoreResourceHandleAsOperationQueueDelegate
49
50- (id)initWithHandle:(ResourceHandle*)handle
51{
52    self = [self init];
53    if (!self)
54        return nil;
55
56    m_handle = handle;
57    m_semaphore = dispatch_semaphore_create(0);
58
59    return self;
60}
61
62- (void)detachHandle
63{
64    m_handle = 0;
65
66    m_requestResult = nullptr;
67    m_cachedResponseResult = nullptr;
68    m_boolResult = NO;
69    dispatch_semaphore_signal(m_semaphore); // OK to signal even if we are not waiting.
70}
71
72- (void)dealloc
73{
74    dispatch_release(m_semaphore);
75    [super dealloc];
76}
77
78- (void)continueWillSendRequest:(NSURLRequest *)newRequest
79{
80    m_requestResult = newRequest;
81    dispatch_semaphore_signal(m_semaphore);
82}
83
84- (void)continueDidReceiveResponse
85{
86    dispatch_semaphore_signal(m_semaphore);
87}
88
89- (void)continueCanAuthenticateAgainstProtectionSpace:(BOOL)canAuthenticate
90{
91    m_boolResult = canAuthenticate;
92    dispatch_semaphore_signal(m_semaphore);
93}
94
95- (void)continueWillCacheResponse:(NSCachedURLResponse *)response
96{
97    m_cachedResponseResult = response;
98    dispatch_semaphore_signal(m_semaphore);
99}
100
101- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
102{
103    ASSERT(!isMainThread());
104    UNUSED_PARAM(connection);
105
106    redirectResponse = synthesizeRedirectResponseIfNecessary(connection, newRequest, redirectResponse);
107
108    // See <rdar://problem/5380697>. This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often.
109    if (!redirectResponse)
110        return newRequest;
111
112#if !LOG_DISABLED
113    if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
114        LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:%d, Location:<%@>", m_handle, connection, [newRequest description], static_cast<int>([(id)redirectResponse statusCode]), [[(id)redirectResponse allHeaderFields] objectForKey:@"Location"]);
115    else
116        LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]);
117#endif
118
119    RetainPtr<id> protector(self);
120
121    dispatch_async(dispatch_get_main_queue(), ^{
122        if (!m_handle) {
123            m_requestResult = nullptr;
124            dispatch_semaphore_signal(m_semaphore);
125            return;
126        }
127
128        ResourceRequest request = newRequest;
129
130        m_handle->willSendRequest(request, redirectResponse);
131    });
132
133    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
134    return m_requestResult.autorelease();
135}
136
137- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
138{
139    ASSERT(!isMainThread());
140    UNUSED_PARAM(connection);
141
142    LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
143
144    dispatch_async(dispatch_get_main_queue(), ^{
145        if (!m_handle) {
146            [[challenge sender] cancelAuthenticationChallenge:challenge];
147            return;
148        }
149        m_handle->didReceiveAuthenticationChallenge(core(challenge));
150    });
151}
152
153- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
154{
155    // FIXME: We probably don't need to implement this (see <rdar://problem/8960124>).
156
157    ASSERT(!isMainThread());
158    UNUSED_PARAM(connection);
159
160    LOG(Network, "Handle %p delegate connection:%p didCancelAuthenticationChallenge:%p", m_handle, connection, challenge);
161
162    dispatch_async(dispatch_get_main_queue(), ^{
163        if (!m_handle)
164            return;
165        m_handle->didCancelAuthenticationChallenge(core(challenge));
166    });
167}
168
169#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
170- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
171{
172    ASSERT(!isMainThread());
173    UNUSED_PARAM(connection);
174
175    LOG(Network, "Handle %p delegate connection:%p canAuthenticateAgainstProtectionSpace:%@://%@:%u realm:%@ method:%@ %@%@", m_handle, connection, [protectionSpace protocol], [protectionSpace host], [protectionSpace port], [protectionSpace realm], [protectionSpace authenticationMethod], [protectionSpace isProxy] ? @"proxy:" : @"", [protectionSpace isProxy] ? [protectionSpace proxyType] : @"");
176
177    RetainPtr<id> protector(self);
178
179    dispatch_async(dispatch_get_main_queue(), ^{
180        if (!m_handle) {
181            m_boolResult = NO;
182            dispatch_semaphore_signal(m_semaphore);
183            return;
184        }
185        m_handle->canAuthenticateAgainstProtectionSpace(ProtectionSpace(protectionSpace));
186    });
187
188    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
189    return m_boolResult;
190}
191#endif
192
193- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
194{
195    ASSERT(!isMainThread());
196
197    LOG(Network, "Handle %p delegate connection:%p didReceiveResponse:%p (HTTP status %d, reported MIMEType '%s')", m_handle, connection, r, [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0, [[r MIMEType] UTF8String]);
198
199    RetainPtr<id> protector(self);
200
201    dispatch_async(dispatch_get_main_queue(), ^{
202        if (!m_handle) {
203            dispatch_semaphore_signal(m_semaphore);
204            return;
205        }
206
207        // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
208        int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0;
209        if (statusCode != 304)
210            adjustMIMETypeIfNecessary([r _CFURLResponse]);
211
212        if ([m_handle->firstRequest().nsURLRequest(DoNotUpdateHTTPBody) _propertyForKey:@"ForceHTMLMIMEType"])
213            [r _setMIMEType:@"text/html"];
214
215        ResourceResponse resourceResponse(r);
216#if ENABLE(WEB_TIMING)
217        ResourceHandle::getConnectionTimingData(connection, resourceResponse.resourceLoadTiming());
218#else
219        UNUSED_PARAM(connection);
220#endif
221        m_handle->client()->didReceiveResponseAsync(m_handle, resourceResponse);
222    });
223
224    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
225}
226
227#if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
228- (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
229{
230    ASSERT(!isMainThread());
231    UNUSED_PARAM(connection);
232
233    LOG(Network, "Handle %p delegate connection:%p didReceiveDataArray:%p arraySize:%d", m_handle, connection, dataArray, [dataArray count]);
234
235    dispatch_async(dispatch_get_main_queue(), ^{
236        if (!dataArray)
237            return;
238
239        if (!m_handle || !m_handle->client())
240            return;
241
242        m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapCFDataArray(reinterpret_cast<CFArrayRef>(dataArray)), -1);
243        // The call to didReceiveData above can cancel a load, and if so, the delegate (self) could have been deallocated by this point.
244    });
245}
246#endif
247
248- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
249{
250    ASSERT(!isMainThread());
251    UNUSED_PARAM(connection);
252    UNUSED_PARAM(lengthReceived);
253
254    LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
255
256    dispatch_async(dispatch_get_main_queue(), ^{
257        if (!m_handle || !m_handle->client())
258            return;
259        // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
260        // However, with today's computers and networking speeds, this won't happen in practice.
261        // Could be an issue with a giant local file.
262
263        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
264        // -1 means we do not provide any data about transfer size to inspector so it would use
265        // Content-Length headers or content size to show transfer size.
266        m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapNSData(data), -1);
267    });
268}
269
270- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
271{
272    ASSERT(!isMainThread());
273    UNUSED_PARAM(connection);
274    UNUSED_PARAM(bytesWritten);
275
276    LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
277
278    dispatch_async(dispatch_get_main_queue(), ^{
279        if (!m_handle || !m_handle->client())
280            return;
281        m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
282    });
283}
284
285- (void)connectionDidFinishLoading:(NSURLConnection *)connection
286{
287    ASSERT(!isMainThread());
288    UNUSED_PARAM(connection);
289
290    LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
291
292    dispatch_async(dispatch_get_main_queue(), ^{
293        if (!m_handle || !m_handle->client())
294            return;
295
296        m_handle->client()->didFinishLoading(m_handle, 0);
297    });
298}
299
300- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
301{
302    ASSERT(!isMainThread());
303    UNUSED_PARAM(connection);
304
305    LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
306
307    dispatch_async(dispatch_get_main_queue(), ^{
308        if (!m_handle || !m_handle->client())
309            return;
310
311        m_handle->client()->didFail(m_handle, error);
312    });
313}
314
315
316- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
317{
318    ASSERT(!isMainThread());
319    UNUSED_PARAM(connection);
320
321    LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
322
323    RetainPtr<id> protector(self);
324
325    dispatch_async(dispatch_get_main_queue(), ^{
326        if (!m_handle || !m_handle->client()) {
327            m_cachedResponseResult = nullptr;
328            dispatch_semaphore_signal(m_semaphore);
329            return;
330        }
331
332        m_handle->client()->willCacheResponseAsync(m_handle, cachedResponse);
333    });
334
335    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
336    return m_cachedResponseResult.autorelease();
337}
338
339@end
340
341@implementation WebCoreResourceHandleWithCredentialStorageAsOperationQueueDelegate
342
343- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
344{
345    ASSERT(!isMainThread());
346    UNUSED_PARAM(connection);
347    return NO;
348}
349
350@end
351
352#endif // !USE(CFNETWORK)
353
354