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