ee0c52a80b04b83d963226dfe56dcf69107b6ab9
[WebKit-https.git] / Source / WebCore / platform / network / cf / ResourceHandleCFURLConnectionDelegateWithOperationQueue.cpp
1 /*
2  * Copyright (C) 2013-2018 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 protectedThis = makeRef(*this);
150     auto work = [this, protectedThis = makeRef(*this), cfRequest = RetainPtr<CFURLRequestRef>(cfRequest), originalRedirectResponse = RetainPtr<CFURLResponseRef>(originalRedirectResponse)] () mutable {
151         auto& handle = protectedThis->m_handle;
152         auto completionHandler = [this, protectedThis = WTFMove(protectedThis)] (ResourceRequest&& request) {
153             m_requestResult = request.cfURLRequest(UpdateHTTPBody);
154             m_semaphore.signal();
155         };
156
157         if (!hasHandle()) {
158             completionHandler({ });
159             return;
160         }
161
162         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willSendRequest(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
163
164         RetainPtr<CFURLResponseRef> redirectResponse = synthesizeRedirectResponseIfNecessary(cfRequest.get(), originalRedirectResponse.get());
165         ASSERT(redirectResponse);
166
167         ResourceRequest request = createResourceRequest(cfRequest.get(), redirectResponse.get());
168         handle->willSendRequest(WTFMove(request), redirectResponse.get(), WTFMove(completionHandler));
169     };
170
171     if (m_messageQueue)
172         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
173     else
174         callOnMainThread(WTFMove(work));
175     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
176
177     return m_requestResult.leakRef();
178 }
179
180 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(CFURLConnectionRef connection, CFURLResponseRef cfResponse)
181 {
182     auto protectedThis = makeRef(*this);
183     auto work = [this, protectedThis = makeRef(*this), cfResponse = RetainPtr<CFURLResponseRef>(cfResponse), connection = RetainPtr<CFURLConnectionRef>(connection)] () mutable {
184         if (!hasHandle() || !m_handle->client() || !m_handle->connection()) {
185             m_semaphore.signal();
186             return;
187         }
188
189         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveResponse(handle=%p) (%s)", m_handle, m_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 = m_handle->firstRequest().requester() == ResourceRequest::Requester::Main;
197         }
198
199         if (_CFURLRequestCopyProtocolPropertyForKey(m_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         m_handle->didReceiveResponse(WTFMove(resourceResponse), [this, protectedThis = WTFMove(protectedThis)] {
205             m_semaphore.signal();
206         });
207     };
208
209     if (m_messageQueue)
210         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
211     else
212         callOnMainThread(WTFMove(work));
213     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
214 }
215
216 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(CFDataRef data, CFIndex originalLength)
217 {
218     auto work = [protectedThis = makeRef(*this), data = RetainPtr<CFDataRef>(data), originalLength = originalLength] () mutable {
219         auto& handle = protectedThis->m_handle;
220         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
221             return;
222         
223         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
224
225         handle->client()->didReceiveBuffer(handle, SharedBuffer::create(data.get()), originalLength);
226     };
227     
228     if (m_messageQueue)
229         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
230     else
231         callOnMainThread(WTFMove(work));
232 }
233
234 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading()
235 {
236     auto work = [protectedThis = makeRef(*this)] () mutable {
237         auto& handle = protectedThis->m_handle;
238         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
239             protectedThis->m_handle->deref();
240             return;
241         }
242
243         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFinishLoading(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
244
245         handle->client()->didFinishLoading(handle);
246         if (protectedThis->m_messageQueue) {
247             protectedThis->m_messageQueue->kill();
248             protectedThis->m_messageQueue = nullptr;
249         }
250         protectedThis->m_handle->deref();
251     };
252     
253     if (m_messageQueue)
254         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
255     else
256         callOnMainThread(WTFMove(work));
257 }
258
259 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(CFErrorRef error)
260 {
261     auto work = [protectedThis = makeRef(*this), error = RetainPtr<CFErrorRef>(error)] () mutable {
262         auto& handle = protectedThis->m_handle;
263         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
264             protectedThis->m_handle->deref();
265             return;
266         }
267         
268         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didFail(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
269
270         handle->client()->didFail(handle, ResourceError(error.get()));
271         if (protectedThis->m_messageQueue) {
272             protectedThis->m_messageQueue->kill();
273             protectedThis->m_messageQueue = nullptr;
274         }
275         protectedThis->m_handle->deref();
276     };
277
278     if (m_messageQueue)
279         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
280     else
281         callOnMainThread(WTFMove(work));
282 }
283
284 CFCachedURLResponseRef ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(CFCachedURLResponseRef cachedResponse)
285 {
286     // Workaround for <rdar://problem/6300990> Caching does not respect Vary HTTP header.
287     // FIXME: WebCore cache has issues with Vary, too (bug 58797, bug 71509).
288     CFURLResponseRef wrappedResponse = CFCachedURLResponseGetWrappedResponse(cachedResponse);
289     if (CFHTTPMessageRef httpResponse = CFURLResponseGetHTTPResponse(wrappedResponse)) {
290         ASSERT(CFHTTPMessageIsHeaderComplete(httpResponse));
291         RetainPtr<CFStringRef> varyValue = adoptCF(CFHTTPMessageCopyHeaderFieldValue(httpResponse, CFSTR("Vary")));
292         if (varyValue)
293             return nullptr;
294     }
295
296     auto protectedThis = makeRef(*this);
297     auto work = [protectedThis = makeRef(*this), cachedResponse = RetainPtr<CFCachedURLResponseRef>(cachedResponse)] () {
298         auto& handle = protectedThis->m_handle;
299         
300         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection()) {
301             protectedThis->continueWillCacheResponse(nullptr);
302             return;
303         }
304
305         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::willCacheResponse(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
306
307         handle->client()->willCacheResponseAsync(handle, cachedResponse.get());
308     };
309     
310     if (m_messageQueue)
311         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
312     else
313         callOnMainThread(WTFMove(work));
314     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
315     return m_cachedResponseResult.leakRef();
316 }
317
318 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(CFURLAuthChallengeRef challenge)
319 {
320     auto work = [protectedThis = makeRef(*this), challenge = RetainPtr<CFURLAuthChallengeRef>(challenge)] () mutable {
321         auto& handle = protectedThis->m_handle;
322         if (!protectedThis->hasHandle())
323             return;
324         
325         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didReceiveChallenge(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
326
327         handle->didReceiveAuthenticationChallenge(AuthenticationChallenge(challenge.get(), handle));
328     };
329
330     if (m_messageQueue)
331         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
332     else
333         callOnMainThread(WTFMove(work));
334 }
335
336 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(CFIndex totalBytesWritten, CFIndex totalBytesExpectedToWrite)
337 {
338     auto work = [protectedThis = makeRef(*this), totalBytesWritten, totalBytesExpectedToWrite] () mutable {
339         auto& handle = protectedThis->m_handle;
340         if (!protectedThis->hasHandle() || !handle->client() || !handle->connection())
341             return;
342
343         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::didSendBodyData(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
344
345         handle->client()->didSendData(handle, totalBytesWritten, totalBytesExpectedToWrite);
346     };
347
348     if (m_messageQueue)
349         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
350     else
351         callOnMainThread(WTFMove(work));
352 }
353
354 Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::shouldUseCredentialStorage()
355 {
356     return false;
357 }
358
359 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
360 Boolean ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(CFURLProtectionSpaceRef protectionSpace)
361 {
362     auto protectedThis = makeRef(*this);
363     auto work = [protectedThis = makeRef(*this), protectionSpace = RetainPtr<CFURLProtectionSpaceRef>(protectionSpace)] () mutable {
364         auto& handle = protectedThis->m_handle;
365         
366         if (!protectedThis->hasHandle()) {
367             protectedThis->continueCanAuthenticateAgainstProtectionSpace(false);
368             return;
369         }
370
371         LOG(Network, "CFNet - ResourceHandleCFURLConnectionDelegateWithOperationQueue::canRespondToProtectionSpace(handle=%p) (%s)", handle, handle->firstRequest().url().string().utf8().data());
372
373         ProtectionSpace coreProtectionSpace = ProtectionSpace(protectionSpace.get());
374         handle->canAuthenticateAgainstProtectionSpace(coreProtectionSpace);
375     };
376     
377     if (m_messageQueue)
378         m_messageQueue->append(std::make_unique<Function<void()>>(WTFMove(work)));
379     else
380         callOnMainThread(WTFMove(work));
381     m_semaphore.wait(TimeWithDynamicClockType(WallTime::infinity()));
382     return m_boolResult;
383 }
384
385 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
386 {
387     m_boolResult = canAuthenticate;
388     m_semaphore.signal();
389 }
390 #endif // USE(PROTECTION_SPACE_AUTH_CALLBACK)
391
392 void ResourceHandleCFURLConnectionDelegateWithOperationQueue::continueWillCacheResponse(CFCachedURLResponseRef response)
393 {
394     m_cachedResponseResult = response;
395     m_semaphore.signal();
396 }
397
398 } // namespace WebCore
399
400 #endif // USE(CFURLCONNECTION)