[WTF] Use Semaphore and BinarySemaphore instead of dispatch_semaphore_t
[WebKit-https.git] / Source / WebCore / platform / network / cf / ResourceHandleCFNet.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011, 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
28 #include "ResourceHandleInternal.h"
29
30 #include "AuthenticationCF.h"
31 #include "AuthenticationChallenge.h"
32 #include "CachedResourceLoader.h"
33 #include "CredentialStorage.h"
34 #include "FormDataStreamCFNet.h"
35 #include "Frame.h"
36 #include "FrameLoader.h"
37 #include "HTTPHeaderNames.h"
38 #include "Logging.h"
39 #include "NetworkStorageSession.h"
40 #include "NetworkingContext.h"
41 #include "ResourceError.h"
42 #include "ResourceHandleCFURLConnectionDelegateWithOperationQueue.h"
43 #include "ResourceHandleClient.h"
44 #include "ResourceResponse.h"
45 #include "SharedBuffer.h"
46 #include "SynchronousLoaderClient.h"
47 #include <CFNetwork/CFNetwork.h>
48 #include <pal/spi/cf/CFNetworkSPI.h>
49 #include <sys/stat.h>
50 #include <sys/types.h>
51 #include <wtf/CompletionHandler.h>
52 #include <wtf/HashMap.h>
53 #include <wtf/NeverDestroyed.h>
54 #include <wtf/Ref.h>
55 #include <wtf/Threading.h>
56 #include <wtf/text/Base64.h>
57 #include <wtf/text/CString.h>
58
59 #if PLATFORM(WIN)
60 #include <WebKitSystemInterface/WebKitSystemInterface.h>
61 #include <process.h>
62
63 // FIXME: Remove this declaration once it's in WebKitSupportLibrary.
64 extern "C" {
65 __declspec(dllimport) CFURLConnectionRef CFURLConnectionCreateWithProperties(
66   CFAllocatorRef           alloc,
67   CFURLRequestRef          request,
68   CFURLConnectionClient *  client,
69   CFDictionaryRef properties);
70 }
71 #endif
72
73 namespace WebCore {
74
75 #if USE(CFURLCONNECTION)
76
77 static HashSet<String, ASCIICaseInsensitiveHash>& allowsAnyHTTPSCertificateHosts()
78 {
79     static NeverDestroyed<HashSet<String, ASCIICaseInsensitiveHash>> hosts;
80     return hosts;
81 }
82
83 static HashMap<String, RetainPtr<CFDataRef>, ASCIICaseInsensitiveHash>& clientCertificates()
84 {
85     static NeverDestroyed<HashMap<String, RetainPtr<CFDataRef>, ASCIICaseInsensitiveHash>> certs;
86     return certs;
87 }
88
89 static void applyBasicAuthorizationHeader(ResourceRequest& request, const Credential& credential)
90 {
91     String authenticationHeader = "Basic " + base64Encode(String(credential.user() + ":" + credential.password()).utf8());
92
93     request.setHTTPHeaderField(HTTPHeaderName::Authorization, authenticationHeader);
94 }
95
96 ResourceHandleInternal::~ResourceHandleInternal()
97 {
98     if (m_connectionDelegate)
99         m_connectionDelegate->releaseHandle();
100
101     if (m_connection) {
102         LOG(Network, "CFNet - Cancelling connection %p (%s)", m_connection.get(), m_firstRequest.url().string().utf8().data());
103         CFURLConnectionCancel(m_connection.get());
104     }
105 }
106
107 ResourceHandle::~ResourceHandle()
108 {
109     LOG(Network, "CFNet - Destroying job %p (%s)", this, d->m_firstRequest.url().string().utf8().data());
110 }
111     
112 static inline CFStringRef shouldSniffConnectionProperty()
113 {
114 #if PLATFORM(WIN)
115     return CFSTR("_kCFURLConnectionPropertyShouldSniff");
116 #else
117     return _kCFURLConnectionPropertyShouldSniff;
118 #endif
119 }
120
121 void ResourceHandle::createCFURLConnection(bool shouldUseCredentialStorage, bool shouldContentSniff, bool shouldContentEncodingSniff, MessageQueue<Function<void()>>* messageQueue, CFDictionaryRef clientProperties)
122 {
123     if ((!d->m_user.isEmpty() || !d->m_pass.isEmpty()) && !firstRequest().url().protocolIsInHTTPFamily()) {
124         // Credentials for ftp can only be passed in URL, the didReceiveAuthenticationChallenge delegate call won't be made.
125         URL urlWithCredentials(firstRequest().url());
126         urlWithCredentials.setUser(d->m_user);
127         urlWithCredentials.setPass(d->m_pass);
128         firstRequest().setURL(urlWithCredentials);
129     }
130
131     String partition = firstRequest().cachePartition();
132
133     // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication,
134     // try and reuse the credential preemptively, as allowed by RFC 2617.
135     if (shouldUseCredentialStorage && firstRequest().url().protocolIsInHTTPFamily()) {
136         if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
137             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
138             // try and reuse the credential preemptively, as allowed by RFC 2617.
139             d->m_initialCredential = d->m_context->storageSession().credentialStorage().get(partition, firstRequest().url());
140         } else {
141             // If there is already a protection space known for the URL, update stored credentials before sending a request.
142             // This makes it possible to implement logout by sending an XMLHttpRequest with known incorrect credentials, and aborting it immediately
143             // (so that an authentication dialog doesn't pop up).
144             d->m_context->storageSession().credentialStorage().set(partition, Credential(d->m_user, d->m_pass, CredentialPersistenceNone), firstRequest().url());
145         }
146     }
147         
148     if (!d->m_initialCredential.isEmpty()) {
149         // FIXME: Support Digest authentication, and Proxy-Authorization.
150         applyBasicAuthorizationHeader(firstRequest(), d->m_initialCredential);
151     }
152
153     auto request = adoptCF(CFURLRequestCreateMutableCopy(kCFAllocatorDefault, firstRequest().cfURLRequest(UpdateHTTPBody)));
154     if (auto storageSession = d->m_storageSession.get())
155         _CFURLRequestSetStorageSession(request.get(), storageSession);
156
157 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101302
158     if (!shouldContentEncodingSniff)
159         _CFURLRequestSetProtocolProperty(request.get(), kCFURLRequestContentDecoderSkipURLCheck, kCFBooleanTrue);
160 #else
161     UNUSED_PARAM(shouldContentEncodingSniff);
162 #endif
163
164     if (!shouldContentSniff)
165         _CFURLRequestSetProtocolProperty(request.get(), shouldSniffConnectionProperty(), kCFBooleanFalse);
166
167     RetainPtr<CFMutableDictionaryRef> sslProps;
168
169 #if PLATFORM(IOS)
170     sslProps = adoptCF(ResourceHandle::createSSLPropertiesFromNSURLRequest(firstRequest()));
171 #else
172     if (allowsAnyHTTPSCertificateHosts().contains(firstRequest().url().host().toString())) {
173         sslProps = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
174         ALLOW_DEPRECATED_DECLARATIONS_BEGIN
175         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
176         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
177         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue);
178         ALLOW_DEPRECATED_DECLARATIONS_END
179 #if !PLATFORM(WIN) // <rdar://problem/33993462> - Disabling validation of certificate chain breaks SSL on Windows.
180         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
181 #endif
182     }
183
184     auto clientCert = clientCertificates().find(firstRequest().url().host().toString());
185     if (clientCert != clientCertificates().end()) {
186         if (!sslProps)
187             sslProps = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
188 #if PLATFORM(WIN)
189         wkSetClientCertificateInSSLProperties(sslProps.get(), (clientCert->value).get());
190 #endif
191     }
192 #endif // PLATFORM(IOS)
193
194     if (sslProps)
195         CFURLRequestSetSSLProperties(request.get(), sslProps.get());
196
197     CFMutableDictionaryRef streamProperties  = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
198
199     if (!shouldUseCredentialStorage) {
200         // Avoid using existing connections, because they may be already authenticated.
201         CFDictionarySetValue(streamProperties, CFSTR("_kCFURLConnectionSessionID"), CFSTR("WebKitPrivateSession"));
202     }
203
204 #if PLATFORM(COCOA)
205     RetainPtr<CFDataRef> sourceApplicationAuditData = d->m_context->sourceApplicationAuditData();
206     if (sourceApplicationAuditData)
207         CFDictionarySetValue(streamProperties, CFSTR("kCFStreamPropertySourceApplication"), sourceApplicationAuditData.get());
208 #endif
209
210     static const CFStringRef kCFURLConnectionSocketStreamProperties = CFSTR("kCFURLConnectionSocketStreamProperties");
211     RetainPtr<CFMutableDictionaryRef> propertiesDictionary;
212     if (clientProperties)
213         propertiesDictionary = adoptCF(CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, clientProperties));
214     else
215         propertiesDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
216
217 #if HAVE(TIMINGDATAOPTIONS)
218     int64_t value = static_cast<int64_t>(_TimingDataOptionsEnableW3CNavigationTiming);
219     auto enableW3CNavigationTiming = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value));
220     auto timingDataOptionsDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
221     CFDictionaryAddValue(timingDataOptionsDictionary.get(), CFSTR("_kCFURLConnectionPropertyTimingDataOptions"), enableW3CNavigationTiming.get());
222     CFDictionaryAddValue(propertiesDictionary.get(), CFSTR("kCFURLConnectionURLConnectionProperties"), timingDataOptionsDictionary.get());
223 #endif
224
225     // FIXME: This code is different from iOS code in ResourceHandleMac.mm in that here we ignore stream properties that were present in client properties.
226     CFDictionaryAddValue(propertiesDictionary.get(), kCFURLConnectionSocketStreamProperties, streamProperties);
227     CFRelease(streamProperties);
228
229     d->m_connectionDelegate = adoptRef(new ResourceHandleCFURLConnectionDelegateWithOperationQueue(this, messageQueue));
230     d->m_connectionDelegate->setupRequest(request.get());
231
232     CFURLConnectionClient_V6 client = d->m_connectionDelegate->makeConnectionClient();
233     if (shouldUseCredentialStorage)
234         client.shouldUseCredentialStorage = 0;
235
236     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
237     d->m_connection = adoptCF(CFURLConnectionCreateWithProperties(0, request.get(), reinterpret_cast<CFURLConnectionClient*>(&client), propertiesDictionary.get()));
238     ALLOW_DEPRECATED_DECLARATIONS_END
239 }
240
241 bool ResourceHandle::start()
242 {
243     if (!d->m_context)
244         return false;
245
246     // If NetworkingContext is invalid then we are no longer attached to a Page,
247     // this must be an attempted load from an unload handler, so let's just block it.
248     if (!d->m_context->isValid())
249         return false;
250
251     d->m_storageSession = d->m_context->storageSession().platformSession();
252
253     bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
254
255 #if PLATFORM(COCOA) && !HAVE(TIMINGDATAOPTIONS)
256     setCollectsTimingData();
257 #endif
258
259     createCFURLConnection(shouldUseCredentialStorage, d->m_shouldContentSniff, d->m_shouldContentEncodingSniff, nullptr, client()->connectionProperties(this).get());
260     ref();
261
262     d->m_connectionDelegate->setupConnectionScheduling(d->m_connection.get());
263     CFURLConnectionStart(d->m_connection.get());
264
265     LOG(Network, "CFNet - Starting URL %s (handle=%p, conn=%p)", firstRequest().url().string().utf8().data(), this, d->m_connection.get());
266
267     return true;
268 }
269
270 void ResourceHandle::cancel()
271 {
272     if (d->m_connection) {
273         CFURLConnectionCancel(d->m_connection.get());
274         d->m_connection = 0;
275     }
276 }
277
278 void ResourceHandle::willSendRequest(ResourceRequest&& request, ResourceResponse&& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
279 {
280     const URL& url = request.url();
281     d->m_user = url.user();
282     d->m_pass = url.pass();
283     d->m_lastHTTPMethod = request.httpMethod();
284     request.removeCredentials();
285
286     String partition = firstRequest().cachePartition();
287
288     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
289         // The network layer might carry over some headers from the original request that
290         // we want to strip here because the redirect is cross-origin.
291         request.clearHTTPAuthorization();
292         request.clearHTTPOrigin();
293     } else {
294         // Only consider applying authentication credentials if this is actually a redirect and the redirect
295         // URL didn't include credentials of its own.
296         if (d->m_user.isEmpty() && d->m_pass.isEmpty() && !redirectResponse.isNull()) {
297             Credential credential = d->m_context->storageSession().credentialStorage().get(partition, request.url());
298             if (!credential.isEmpty()) {
299                 d->m_initialCredential = credential;
300                 
301                 // FIXME: Support Digest authentication, and Proxy-Authorization.
302                 applyBasicAuthorizationHeader(request, d->m_initialCredential);
303             }
304         }
305     }
306
307     client()->willSendRequestAsync(this, WTFMove(request), WTFMove(redirectResponse), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request) mutable {
308         if (!request.isNull())
309             request.setStorageSession(d->m_storageSession.get());
310         completionHandler(WTFMove(request));
311     });
312 }
313
314 bool ResourceHandle::shouldUseCredentialStorage()
315 {
316     LOG(Network, "CFNet - shouldUseCredentialStorage()");
317     if (ResourceHandleClient* client = this->client())
318         return client->shouldUseCredentialStorage(this);
319     return false;
320 }
321
322 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
323 {
324     LOG(Network, "CFNet - didReceiveAuthenticationChallenge()");
325     ASSERT(d->m_currentWebChallenge.isNull());
326     // Since CFURLConnection networking relies on keeping a reference to the original CFURLAuthChallengeRef,
327     // we make sure that is actually present
328     ASSERT(challenge.cfURLAuthChallengeRef());
329     ASSERT(challenge.authenticationClient() == this); // Should be already set.
330
331 #if !PLATFORM(WIN)
332     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
333     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
334     if (challenge.protectionSpace().isProxy()) {
335         // Cannot use receivedRequestToContinueWithoutCredential(), because current challenge is not yet set.
336         CFURLConnectionUseCredential(d->m_connection.get(), 0, challenge.cfURLAuthChallengeRef());
337         return;
338     }
339 #endif
340
341     if (tryHandlePasswordBasedAuthentication(challenge))
342         return;
343
344     d->m_currentWebChallenge = challenge;
345     
346     if (client())
347         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
348     else {
349         clearAuthentication();
350         CFURLConnectionPerformDefaultHandlingForChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
351     }
352 }
353
354 bool ResourceHandle::tryHandlePasswordBasedAuthentication(const AuthenticationChallenge& challenge)
355 {
356     if (!challenge.protectionSpace().isPasswordBased())
357         return false;
358
359     String partition = firstRequest().cachePartition();
360
361     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
362         RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(CFURLCredentialCreate(kCFAllocatorDefault, d->m_user.createCFString().get(), d->m_pass.createCFString().get(), 0, kCFURLCredentialPersistenceNone));
363 #if PLATFORM(COCOA)
364         Credential credential = Credential(cfCredential.get());
365 #else
366         Credential credential = core(cfCredential.get());
367 #endif
368         
369         URL urlToStore;
370         if (challenge.failureResponse().httpStatusCode() == 401)
371             urlToStore = challenge.failureResponse().url();
372         d->m_context->storageSession().credentialStorage().set(partition, credential, challenge.protectionSpace(), urlToStore);
373         
374         CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
375         d->m_user = String();
376         d->m_pass = String();
377         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
378         return true;
379     }
380
381     if (!client() || client()->shouldUseCredentialStorage(this)) {
382         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
383             // The stored credential wasn't accepted, stop using it.
384             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
385             // but the observable effect should be very minor, if any.
386             d->m_context->storageSession().credentialStorage().remove(partition, challenge.protectionSpace());
387         }
388
389         if (!challenge.previousFailureCount()) {
390             Credential credential = d->m_context->storageSession().credentialStorage().get(partition, challenge.protectionSpace());
391             if (!credential.isEmpty() && credential != d->m_initialCredential) {
392                 ASSERT(credential.persistence() == CredentialPersistenceNone);
393                 if (challenge.failureResponse().httpStatusCode() == 401) {
394                     // Store the credential back, possibly adding it as a default for this directory.
395                     d->m_context->storageSession().credentialStorage().set(partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
396                 }
397 #if PLATFORM(COCOA)
398                 CFURLConnectionUseCredential(d->m_connection.get(), credential.cfCredential(), challenge.cfURLAuthChallengeRef());
399 #else
400                 RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(credential));
401                 CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
402 #endif
403                 return true;
404             }
405         }
406     }
407
408     return false;
409 }
410
411 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
412 bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
413 {
414     if (auto* client = this->client())
415         client->canAuthenticateAgainstProtectionSpaceAsync(this, protectionSpace);
416     else
417         continueCanAuthenticateAgainstProtectionSpace(false);
418     return false; // Ignored by caller.
419 }
420 #endif
421
422 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
423 {
424     LOG(Network, "CFNet - receivedCredential()");
425     ASSERT(!challenge.isNull());
426     ASSERT(challenge.cfURLAuthChallengeRef());
427     if (challenge != d->m_currentWebChallenge)
428         return;
429
430     // FIXME: Support empty credentials. Currently, an empty credential cannot be stored in WebCore credential storage, as that's empty value for its map.
431     if (credential.isEmpty()) {
432         receivedRequestToContinueWithoutCredential(challenge);
433         return;
434     }
435
436     if (credential.persistence() == CredentialPersistenceForSession && challenge.protectionSpace().authenticationScheme() != ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested) {
437         // Manage per-session credentials internally, because once NSURLCredentialPersistencePerSession is used, there is no way
438         // to ignore it for a particular request (short of removing it altogether).
439         Credential webCredential(credential, CredentialPersistenceNone);
440
441         URL urlToStore;
442         if (challenge.failureResponse().httpStatusCode() == 401)
443             urlToStore = challenge.failureResponse().url(); 
444
445         d->m_context->storageSession().credentialStorage().set(firstRequest().cachePartition(), webCredential, challenge.protectionSpace(), urlToStore);
446
447         if (d->m_connection) {
448 #if PLATFORM(COCOA)
449             CFURLConnectionUseCredential(d->m_connection.get(), webCredential.cfCredential(), challenge.cfURLAuthChallengeRef());
450 #else
451             RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(webCredential));
452             CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
453 #endif
454         }
455     } else if (d->m_connection) {
456 #if PLATFORM(COCOA)
457         CFURLConnectionUseCredential(d->m_connection.get(), credential.cfCredential(), challenge.cfURLAuthChallengeRef());
458 #else
459         RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(credential));
460         CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
461 #endif
462     }
463
464     clearAuthentication();
465 }
466
467 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
468 {
469     LOG(Network, "CFNet - receivedRequestToContinueWithoutCredential()");
470     ASSERT(!challenge.isNull());
471     ASSERT(challenge.cfURLAuthChallengeRef());
472     if (challenge != d->m_currentWebChallenge)
473         return;
474
475     if (d->m_connection)
476         CFURLConnectionUseCredential(d->m_connection.get(), 0, challenge.cfURLAuthChallengeRef());
477
478     clearAuthentication();
479 }
480
481 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
482 {
483     LOG(Network, "CFNet - receivedCancellation()");
484     if (challenge != d->m_currentWebChallenge)
485         return;
486
487     if (client())
488         client()->receivedCancellation(this, challenge);
489 }
490
491 void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge& challenge)
492 {
493     LOG(Network, "CFNet - receivedRequestToPerformDefaultHandling()");
494     ASSERT(!challenge.isNull());
495     ASSERT(challenge.cfURLAuthChallengeRef());
496     if (challenge != d->m_currentWebChallenge)
497         return;
498
499     if (d->m_connection)
500         CFURLConnectionPerformDefaultHandlingForChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
501
502     clearAuthentication();
503 }
504
505 void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge& challenge)
506 {
507     LOG(Network, "CFNet - receivedChallengeRejection()");
508     ASSERT(!challenge.isNull());
509     ASSERT(challenge.cfURLAuthChallengeRef());
510     if (challenge != d->m_currentWebChallenge)
511         return;
512
513     if (d->m_connection)
514         CFURLConnectionRejectChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
515
516     clearAuthentication();
517 }
518
519 CFURLStorageSessionRef ResourceHandle::storageSession() const
520 {
521     return d->m_storageSession.get();
522 }
523
524 CFURLConnectionRef ResourceHandle::connection() const
525 {
526     return d->m_connection.get();
527 }
528
529 RetainPtr<CFURLConnectionRef> ResourceHandle::releaseConnectionForDownload()
530 {
531     LOG(Network, "CFNet - Job %p releasing connection %p for download", this, d->m_connection.get());
532     return WTFMove(d->m_connection);
533 }
534
535 CFStringRef ResourceHandle::synchronousLoadRunLoopMode()
536 {
537     return CFSTR("WebCoreSynchronousLoaderRunLoopMode");
538 }
539
540 static void emptyPerform(void*)
541 {
542 }
543
544 void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentialsPolicy storedCredentialsPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data)
545 {
546     LOG(Network, "ResourceHandle::platformLoadResourceSynchronously:%s sstoredCredentialsPolicy:%u", request.url().string().utf8().data(), static_cast<unsigned>(storedCredentialsPolicy));
547
548     ASSERT(!request.isEmpty());
549
550     ASSERT(response.isNull());
551     ASSERT(error.isNull());
552
553     SynchronousLoaderClient client;
554     client.setAllowStoredCredentials(storedCredentialsPolicy == StoredCredentialsPolicy::Use);
555
556     bool defersLoading = false;
557     bool shouldContentSniff = true;
558     bool shouldContentEncodingSniff = true;
559     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &client, defersLoading, shouldContentSniff, shouldContentEncodingSniff));
560
561     handle->d->m_storageSession = context->storageSession().platformSession();
562
563     if (handle->d->m_scheduledFailureType != NoFailure) {
564         error = context->blockedError(request);
565         return;
566     }
567
568     handle->ref();
569     handle->createCFURLConnection(storedCredentialsPolicy == StoredCredentialsPolicy::Use, ResourceHandle::shouldContentSniffURL(request.url()), handle->shouldContentEncodingSniff(), &client.messageQueue(), handle->client()->connectionProperties(handle.get()).get());
570
571     static CFRunLoopRef runLoop = nullptr;
572     if (!runLoop) {
573         BinarySemaphore sem;
574         Thread::create("CFNetwork Loader", [&] {
575             runLoop = CFRunLoopGetCurrent();
576
577             // Must add a source to the run loop to prevent CFRunLoopRun() from exiting.
578             CFRunLoopSourceContext ctxt = { 0, (void*)1 /*must be non-null*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform };
579             CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
580             CFRunLoopAddSource(runLoop, bogusSource, kCFRunLoopDefaultMode);
581             sem.signal();
582
583             while (true)
584                 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1E30, true);
585         });
586         sem.wait();
587     }
588     CFURLConnectionScheduleWithRunLoop(handle->connection(), runLoop, kCFRunLoopDefaultMode);
589     CFURLConnectionScheduleDownloadWithRunLoop(handle->connection(), runLoop, kCFRunLoopDefaultMode);
590     CFURLConnectionStart(handle->connection());
591
592     do {
593         if (auto task = client.messageQueue().waitForMessage())
594             (*task)();
595     } while (!client.messageQueue().killed());
596
597     error = client.error();
598
599     if (handle->connection())
600         CFURLConnectionCancel(handle->connection());
601
602     if (error.isNull())
603         response = client.response();
604
605     data.swap(client.mutableData());
606 }
607
608 void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
609 {
610     allowsAnyHTTPSCertificateHosts().add(host);
611 }
612
613 void ResourceHandle::setClientCertificate(const String& host, CFDataRef certificate)
614 {
615     clientCertificates().set(host, certificate);
616 }
617
618 void ResourceHandle::platformSetDefersLoading(bool defers)
619 {
620     if (!d->m_connection)
621         return;
622
623     if (defers)
624         CFURLConnectionHalt(d->m_connection.get());
625     else
626         CFURLConnectionResume(d->m_connection.get());
627 }
628
629 #if PLATFORM(COCOA)
630 void ResourceHandle::schedule(SchedulePair& pair)
631 {
632     CFRunLoopRef runLoop = pair.runLoop();
633     if (!runLoop)
634         return;
635
636     CFURLConnectionScheduleWithRunLoop(d->m_connection.get(), runLoop, pair.mode());
637     if (d->m_startWhenScheduled) {
638         CFURLConnectionStart(d->m_connection.get());
639         d->m_startWhenScheduled = false;
640     }
641 }
642
643 void ResourceHandle::unschedule(SchedulePair& pair)
644 {
645     CFRunLoopRef runLoop = pair.runLoop();
646     if (!runLoop)
647         return;
648
649     CFURLConnectionUnscheduleFromRunLoop(d->m_connection.get(), runLoop, pair.mode());
650 }
651 #endif
652
653 const ResourceRequest& ResourceHandle::currentRequest() const
654 {
655     return d->m_currentRequest;
656 }
657
658 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
659 void ResourceHandle::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
660 {
661     d->m_connectionDelegate->continueCanAuthenticateAgainstProtectionSpace(canAuthenticate);
662 }
663 #endif
664 #endif // USE(CFURLCONNECTION)
665
666 } // namespace WebCore