Remove Cocoa CFURLConnection loading code
[WebKit-https.git] / Source / WebCore / platform / network / cf / ResourceHandleCFURLConnectionDelegateWithOperationQueue.cpp
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(CFURLCONNECTION)
30
31 #include "AuthenticationCF.h"
32 #include "AuthenticationChallenge.h"
33 #include "Logging.h"
34 #include "MIMETypeRegistry.h"
35 #include "ResourceHandle.h"
36 #include "ResourceHandleClient.h"
37 #include "ResourceResponse.h"
38 #include "SharedBuffer.h"
39 #if !PLATFORM(WIN)
40 #include "WebCoreURLResponse.h"
41 #endif
42 #include <pal/spi/cf/CFNetworkSPI.h>
43 #include <wtf/CompletionHandler.h>
44 #include <wtf/MainThread.h>
45 #include <wtf/Threading.h>
46 #include <wtf/text/CString.h>
47 #include <wtf/text/WTFString.h>
48
49 namespace WebCore {
50
51 ResourceHandleCFURLConnectionDelegateWithOperationQueue::ResourceHandleCFURLConnectionDelegateWithOperationQueue(ResourceHandle* handle, MessageQueue<Function<void()>>* messageQueue)
52     : ResourceHandleCFURLConnectionDelegate(handle)
53     , m_messageQueue(messageQueue)
54 {
55 }
56
57 ResourceHandleCFURLConnectionDelegateWithOperationQueue::~ResourceHandleCFURLConnectionDelegateWithOperationQueue()
58 {
59 }
60
61 bool ResourceHandleCFURLConnectionDelegateWithOperationQueue::hasHandle() const
62 {
63     return !!m_handle;
64 }
65
66 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::releaseHandle()
67 {
68     ResourceHandleCFURLConnectionDelegate::releaseHandle();
69     m_requestResult = nullptr;
70     m_cachedResponseResult = nullptr;
71     m_semaphore.signal();
72 }
73
74 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupRequest(CFMutableURLRequestRef request)
75 {
76     CFURLRef requestURL = CFURLRequestGetURL(request);
77     if (!requestURL)
78         return;
79     m_originalScheme = adoptCF(CFURLCopyScheme(requestURL));
80 }
81
82 LRESULT CALLBACK hookToRemoveCFNetworkMessage(int code, WPARAM wParam, LPARAM lParam)
83 {
84     MSG* msg = reinterpret_cast<MSG*>(lParam);
85     // This message which CFNetwork sends to itself, will block the main thread, remove it.
86     if (msg->message == WM_USER + 0xcf)
87         msg->message = WM_NULL;
88     return CallNextHookEx(nullptr, code, wParam, lParam);
89 }
90
91 static void installHookToRemoveCFNetworkMessageBlockingMainThread()
92 {
93     static HHOOK hook = nullptr;
94     if (!hook) {
95         DWORD threadID = ::GetCurrentThreadId();
96         hook = ::SetWindowsHookExW(WH_GETMESSAGE, hookToRemoveCFNetworkMessage, 0, threadID);
97     }
98 }
99
100 static void emptyPerform(void*)
101 {
102 }
103
104 static CFRunLoopRef getRunLoop()
105 {
106     static CFRunLoopRef runLoop = nullptr;
107
108     if (!runLoop) {
109         BinarySemaphore sem;
110         Thread::create("CFNetwork Loader", [&] {
111             runLoop = CFRunLoopGetCurrent();
112
113             // Must add a source to the run loop to prevent CFRunLoopRun() from exiting.
114             CFRunLoopSourceContext ctxt = { 0, (void*)1 /*must be non-null*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform };
115             CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
116             CFRunLoopAddSource(runLoop, bogusSource, kCFRunLoopDefaultMode);
117             sem.signal();
118
119             while (true)
120                 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1E30, true);
121         });
122         sem.wait(TimeWithDynamicClockType(WallTime::infinity()));
123     }
124
125     return runLoop;
126 }
127
128 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::setupConnectionScheduling(CFURLConnectionRef connection)
129 {
130     installHookToRemoveCFNetworkMessageBlockingMainThread();
131     CFRunLoopRef runLoop = getRunLoop();
132     CFURLConnectionScheduleWithRunLoop(connection, runLoop, kCFRunLoopDefaultMode);
133     CFURLConnectionScheduleDownloadWithRunLoop(connection, runLoop, kCFRunLoopDefaultMode);
134 }
135
136 CFURLRequestRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(CFURLRequestRef cfRequest, CFURLResponseRef originalRedirectResponse)
137 {
138     // 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.
139     if (!originalRedirectResponse) {
140         RetainPtr<CFStringRef> newScheme = adoptCF(CFURLCopyScheme(CFURLRequestGetURL(cfRequest)));
141         if (CFStringCompare(newScheme.get(), m_originalScheme.get(), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
142             CFRetain(cfRequest);
143             return cfRequest;
144         }
145     }
146
147     ASSERT(!isMainThread());
148     
149     auto work = [this, protectedThis = makeRef(*this), cfRequest = RetainPtr<CFURLRequestRef>(cfRequest), originalRedirectResponse = RetainPtr<CFURLResponseRef>(originalRedirectResponse)] () mutable {
150         auto& handle = protectedThis->m_handle;
151         auto completionHandler = [this, protectedThis = WTFMove(protectedThis)] (ResourceRequest&& request) {
152             m_requestResult = request.cfURLRequest(UpdateHTTPBody);
153             m_semaphore.signal();
154         };
155
156         if (!hasHandle()) {
157             completionHandler({ });
158             return;
159         }
160
161         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
162
163         RetainPtr<CFURLResponseRef> redirectResponse = synthesizeRedirectResponseIfNecessary(cfRequest.get(), originalRedirectResponse.get());
164         ASSERT(redirectResponse);
165
166         ResourceRequest request = createResourceRequest(cfRequest.get(), redirectResponse.get());
167         handle->willSendRequest(WTFMove(request), redirectResponse.get(), WTFMove(completionHandler));
168     };
169
170     if (m_messageQueue)
171         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
172     else
173         callOnMainThread(WTFMove(work));
174     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
175
176     return m_requestResult.leakRef();
177 }
178
179 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(CFURLConnectionRef connection, CFURLResponseRef cfResponse)
180 {
181     auto work = [protectedThis = makeRef(*this), cfResponse = RetainPtr<CFURLResponseRef>(cfResponse), connection = RetainPtr<CFURLConnectionRef>(connection)] () {
182         auto& handle = protectedThis->m_handle;
183         
184         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
185             protectedThis->continueDidReceiveResponse();
186             return;
187         }
188
189         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
190
191         // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
192         auto msg = CFURLResponseGetHTTPResponse(cfResponse.get());
193         int statusCode = msg ? CFHTTPMessageGetResponseStatusCode(msg) : 0;
194
195         if (statusCode != 304) {
196             bool isMainResourceLoad = handle->firstRequest().requester() == ResourceRequest::Requester::Main;
197         }
198
199         if (_CFURLRequestCopyProtocolPropertyForKey(handle->firstRequest().cfURLRequest(DoNotUpdateHTTPBody), CFSTR("ForceHTMLMIMEType")))
200             CFURLResponseSetMIMEType(cfResponse.get(), CFSTR("text/html"));
201
202         ResourceResponse resourceResponse(cfResponse.get());
203         resourceResponse.setSource(ResourceResponse::Source::Network);
204         handle->didReceiveResponse(WTFMove(resourceResponse));
205     };
206
207     if (m_messageQueue)
208         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
209     else
210         callOnMainThread(WTFMove(work));
211     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
212 }
213
214 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(CFDataRef data, CFIndex originalLength)
215 {
216     auto work = [protectedThis = makeRef(*this), data = RetainPtr<CFDataRef>(data), originalLength = originalLength] () mutable {
217         auto& handle = protectedThis->m_handle;
218         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
219             return;
220         
221         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
222
223         handle->client()->didReceiveBuffer(handle, SharedBuffer::create(data.get()), originalLength);
224     };
225     
226     if (m_messageQueue)
227         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
228     else
229         callOnMainThread(WTFMove(work));
230 }
231
232 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading()
233 {
234     auto work = [protectedThis = makeRef(*this)] () mutable {
235         auto& handle = protectedThis->m_handle;
236         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
237             protectedThis->m_handle->deref();
238             return;
239         }
240
241         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
242
243         handle->client()->didFinishLoading(handle);
244         if (protectedThis->m_messageQueue) {
245             protectedThis->m_messageQueue->kill();
246             protectedThis->m_messageQueue = nullptr;
247         }
248         protectedThis->m_handle->deref();
249     };
250     
251     if (m_messageQueue)
252         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
253     else
254         callOnMainThread(WTFMove(work));
255 }
256
257 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(CFErrorRef error)
258 {
259     auto work = [protectedThis = makeRef(*this), error = RetainPtr<CFErrorRef>(error)] () mutable {
260         auto& handle = protectedThis->m_handle;
261         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
262             protectedThis->m_handle->deref();
263             return;
264         }
265         
266         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
267
268         handle->client()->didFail(handle, ResourceError(error.get()));
269         if (protectedThis->m_messageQueue) {
270             protectedThis->m_messageQueue->kill();
271             protectedThis->m_messageQueue = nullptr;
272         }
273         protectedThis->m_handle->deref();
274     };
275
276     if (m_messageQueue)
277         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
278     else
279         callOnMainThread(WTFMove(work));
280 }
281
282 CFCachedURLResponseRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(CFCachedURLResponseRef cachedResponse)
283 {
284     // Workaround for <rdar://problem/6300990> Caching does not respect Vary HTTP header.
285     // FIXME: WebCore cache has issues with Vary, too (bug 58797, bug 71509).
286     CFURLResponseRef wrappedResponse = CFCachedURLResponseGetWrappedResponse(cachedResponse);
287     if (CFHTTPMessageRef httpResponse = CFURLResponseGetHTTPResponse(wrappedResponse)) {
288         ASSERT(CFHTTPMessageIsHeaderComplete(httpResponse));
289         RetainPtr<CFStringRef> varyValue = adoptCF(CFHTTPMessageCopyHeaderFieldValue(httpResponse, CFSTR("Vary")));
290         if (varyValue)
291             return nullptr;
292     }
293
294     auto work = [protectedThis = makeRef(*this), cachedResponse = RetainPtr<CFCachedURLResponseRef>(cachedResponse)] () {
295         auto& handle = protectedThis->m_handle;
296         
297         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
298             protectedThis->continueWillCacheResponse(nullptr);
299             return;
300         }
301
302         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
303
304         handle->client()->willCacheResponseAsync(handle, cachedResponse.get());
305     };
306     
307     if (m_messageQueue)
308         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
309     else
310         callOnMainThread(WTFMove(work));
311     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
312     return m_cachedResponseResult.leakRef();
313 }
314
315 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(CFURLAuthChallengeRef challenge)
316 {
317     auto work = [protectedThis = makeRef(*this), challenge = RetainPtr<CFURLAuthChallengeRef>(challenge)] () mutable {
318         auto& handle = protectedThis->m_handle;
319         if (!protectedThis->hasHandle())
320             return;
321         
322         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
323
324         handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge.get(), handle));
325     };
326
327     if (m_messageQueue)
328         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
329     else
330         callOnMainThread(WTFMove(work));
331 }
332
333 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite)
334 {
335     auto work = [protectedThis = makeRef(*this), totalBytesWritten, totalBytesExpectedToWrite] () mutable {
336         auto& handle = protectedThis->m_handle;
337         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
338             return;
339
340         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
341
342         handle->client()->didSendData(handle, totalBytesWritten, totalBytesExpectedToWrite);
343     };
344
345     if (m_messageQueue)
346         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
347     else
348         callOnMainThread(WTFMove(work));
349 }
350
351 Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::shouldUseCredentialStorage()
352 {
353     return false;
354 }
355
356 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
357 Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(CFURLProtectionSpaceRef protectionSpace)
358 {
359     auto work = [protectedThis = makeRef(*this), protectionSpace = RetainPtr<CFURLProtectionSpaceRef>(protectionSpace)] () mutable {
360         auto& handle = protectedThis->m_handle;
361         
362         if (!protectedThis->hasHandle()) {
363             protectedThis->continueCanAuthenticateAgainstProtectionSpace(false);
364             return;
365         }
366
367         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
368
369         ProtectionSpace coreProtectionSpace = ProtectionSpace(protectionSpace.get());
370         handle->canAuthenticateAgainstProtectionSpace(coreProtectionSpace);
371     };
372     
373     if (m_messageQueue)
374         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
375     else
376         callOnMainThread(WTFMove(work));
377     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
378     return m_boolResult;
379 }
380
381 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
382 {
383     m_boolResult = canAuthenticate;
384     m_semaphore.signal();
385 }
386 #endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
387
388 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueDidReceiveResponse()
389 {
390     m_semaphore.signal();
391 }
392
393 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueWillCacheResponse(CFCachedURLResponseRef response)
394 {
395     m_cachedResponseResult = response;
396     m_semaphore.signal();
397 }
398
399 } // namespace WebCore
400
401 #endif // USE(CFURLCONNECTION)