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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ResourceHandleCFURLConnectionDelegateWithOperationQueue.h"
28
29#if USE(CFNETWORK)
30
31#include "AuthenticationCF.h"
32#include "AuthenticationChallenge.h"
33#include "Logging.h"
34#include "ResourceHandle.h"
35#include "ResourceHandleClient.h"
36#include "ResourceResponse.h"
37#include "SharedBuffer.h"
38#include "WebCoreSystemInterface.h"
39#include "WebCoreURLResponse.h"
40#include <wtf/MainThread.h>
41#include <wtf/text/CString.h>
42#include <wtf/text/WTFString.h>
43
44namespace WebCore {
45
46ResourceHandleCFURLConnectionDelegateWithOperationQueue::ResourceHandleCFURLConnectionDelegateWithOperationQueue(ResourceHandle* handle)
47    : ResourceHandleCFURLConnectionDelegate(handle)
48    , m_queue(dispatch_queue_create("com.apple.WebCore/CFNetwork", DISPATCH_QUEUE_SERIAL))
49    , m_semaphore(dispatch_semaphore_create(0))
50{
51    dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
52    dispatch_set_target_queue(m_queue, backgroundQueue);
53}
54
55ResourceHandleCFURLConnectionDelegateWithOperationQueue::~ResourceHandleCFURLConnectionDelegateWithOperationQueue()
56{
57    dispatch_release(m_semaphore);
58    dispatch_release(m_queue);
59}
60
61bool ResourceHandleCFURLConnectionDelegateWithOperationQueue::hasHandle() const
62{
63    return !!m_handle;
64}
65
66void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupRequest(CFMutableURLRequestRef request)
67{
68#if PLATFORM(IOS)
69    CFURLRequestSetShouldStartSynchronously(request, 1);
70#endif
71    CFURLRef requestURL = CFURLRequestGetURL(request);
72    if (!requestURL)
73        return;
74    m_originalScheme = adoptCF(CFURLCopyScheme(requestURL));
75}
76
77void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupConnectionScheduling(CFURLConnectionRef connection)
78{
79    CFURLConnectionSetDelegateDispatchQueue(connection, m_queue);
80}
81
82CFURLRequestRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(CFURLRequestRef cfRequest, CFURLResponseRef originalRedirectResponse)
83{
84    // If the protocols of the new request and the current request match, this is not an HSTS redirect and we don't need to synthesize a redirect response.
85    if (!originalRedirectResponse) {
86        RetainPtr<CFStringRef> newScheme = adoptCF(CFURLCopyScheme(CFURLRequestGetURL(cfRequest)));
87        if (CFStringCompare(newScheme.get(), m_originalScheme.get(), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
88            CFRetain(cfRequest);
89            return cfRequest;
90        }
91    }
92
93    ASSERT(!isMainThread());
94
95    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
96
97    dispatch_async(dispatch_get_main_queue(), ^{
98        if (!protector->hasHandle()) {
99            continueWillSendRequest(nullptr);
100            return;
101        }
102
103        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
104
105        RetainPtr<CFURLResponseRef> redirectResponse = synthesizeRedirectResponseIfNecessary(cfRequest, originalRedirectResponse);
106        ASSERT(redirectResponse);
107
108        ResourceRequest request = createResourceRequest(cfRequest, redirectResponse.get());
109        m_handle->willSendRequest(request, redirectResponse.get());
110    });
111
112    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
113
114    return m_requestResult.leakRef();
115}
116
117void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(CFURLConnectionRef connection, CFURLResponseRef cfResponse)
118{
119    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
120
121    dispatch_async(dispatch_get_main_queue(), ^{
122        if (!protector->hasHandle()) {
123            continueDidReceiveResponse();
124            return;
125        }
126
127        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
128
129        // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
130        CFHTTPMessageRef msg = wkGetCFURLResponseHTTPResponse(cfResponse);
131        int statusCode = msg ? CFHTTPMessageGetResponseStatusCode(msg) : 0;
132
133        if (statusCode != 304)
134            adjustMIMETypeIfNecessary(cfResponse);
135
136#if !PLATFORM(IOS)
137        if (_CFURLRequestCopyProtocolPropertyForKey(m_handle->firstRequest().cfURLRequest(DoNotUpdateHTTPBody), CFSTR("ForceHTMLMIMEType")))
138            wkSetCFURLResponseMIMEType(cfResponse, CFSTR("text/html"));
139#endif // !PLATFORM(IOS)
140
141        ResourceResponse resourceResponse(cfResponse);
142#if ENABLE(WEB_TIMING)
143        ResourceHandle::getConnectionTimingData(connection, resourceResponse.resourceLoadTiming());
144#else
145        UNUSED_PARAM(connection);
146#endif
147
148        m_handle->client()->didReceiveResponseAsync(m_handle, resourceResponse);
149    });
150    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
151}
152
153void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(CFDataRef data, CFIndex originalLength)
154{
155    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
156    CFRetain(data);
157
158    dispatch_async(dispatch_get_main_queue(), ^{
159        if (protector->hasHandle()) {
160            LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
161
162            m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapCFData(data), originalLength);
163        }
164
165        CFRelease(data);
166    });
167}
168
169void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading()
170{
171    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
172    dispatch_async(dispatch_get_main_queue(), ^{
173        if (!protector->hasHandle())
174            return;
175
176        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
177
178        m_handle->client()->didFinishLoading(m_handle, 0);
179    });
180}
181
182void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(CFErrorRef error)
183{
184    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
185    CFRetain(error);
186    dispatch_async(dispatch_get_main_queue(), ^{
187        if (protector->hasHandle()) {
188            LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
189
190            m_handle->client()->didFail(m_handle, ResourceError(error));
191        }
192
193        CFRelease(error);
194    });
195}
196
197CFCachedURLResponseRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(CFCachedURLResponseRef cachedResponse)
198{
199    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
200
201    dispatch_async(dispatch_get_main_queue(), ^{
202        if (!protector->hasHandle()) {
203            continueWillCacheResponse(nullptr);
204            return;
205        }
206
207        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
208
209        m_handle->client()->willCacheResponseAsync(m_handle, cachedResponse);
210    });
211    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
212    return m_cachedResponseResult.leakRef();
213}
214
215void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(CFURLAuthChallengeRef challenge)
216{
217    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
218    CFRetain(challenge);
219    dispatch_async(dispatch_get_main_queue(), ^{
220        if (protector->hasHandle()) {
221            LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
222
223            m_handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge, m_handle));
224        }
225
226        CFRelease(challenge);
227    });
228}
229
230void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite)
231{
232    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
233    dispatch_async(dispatch_get_main_queue(), ^{
234        if (!protector->hasHandle())
235            return;
236
237        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
238
239        m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
240    });
241}
242
243Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::shouldUseCredentialStorage()
244{
245    return NO;
246}
247
248#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
249Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(CFURLProtectionSpaceRef protectionSpace)
250{
251    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
252
253    dispatch_async(dispatch_get_main_queue(), ^{
254        if (!protector->hasHandle()) {
255            continueCanAuthenticateAgainstProtectionSpace(false);
256            return;
257        }
258
259        LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
260
261        ProtectionSpace coreProtectionSpace = ProtectionSpace(protectionSpace);
262#if PLATFORM(IOS)
263        if (coreProtectionSpace.authenticationScheme() == ProtectionSpaceAuthenticationSchemeUnknown) {
264            m_boolResult = false;
265            dispatch_semaphore_signal(m_semaphore);
266            return;
267        }
268#endif // PLATFORM(IOS)
269        m_handle->canAuthenticateAgainstProtectionSpace(coreProtectionSpace);
270    });
271    dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
272    return m_boolResult;
273}
274#endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
275
276#if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
277void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveDataArray(CFArrayRef dataArray)
278{
279    RefPtr<ResourceHandleCFURLConnectionDelegateWithOperationQueue> protector(this);
280    CFRetain(dataArray);
281    dispatch_async(dispatch_get_main_queue(), ^{
282        if (protector->hasHandle()) {
283            LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(handle=%p) (%s)", m_handle, m_handle->firstRequest().url().string().utf8().data());
284
285            m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapCFDataArray(dataArray), -1);
286        }
287        CFRelease(dataArray);
288    });
289}
290#endif // USE(NETWORK_CFDATA_ARRAY_CALLBACK)
291
292void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueWillSendRequest(CFURLRequestRef request)
293{
294    m_requestResult = request;
295    dispatch_semaphore_signal(m_semaphore);
296}
297
298void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueDidReceiveResponse()
299{
300    dispatch_semaphore_signal(m_semaphore);
301}
302
303void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueWillCacheResponse(CFCachedURLResponseRef response)
304{
305    m_cachedResponseResult = response;
306    dispatch_semaphore_signal(m_semaphore);
307}
308
309#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
310void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
311{
312    m_boolResult = canAuthenticate;
313    dispatch_semaphore_signal(m_semaphore);
314}
315#endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
316} // namespace WebCore
317
318#endif // USE(CFNETWORK)
319