50ee3efafe1b1bf81573187e5ddc5a7adaef1c25
[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())) {
173         sslProps = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
174 #pragma clang diagnostic push
175 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
176         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
177         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
178         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue);
179 #pragma clang diagnostic pop
180 #if !PLATFORM(WIN) // <rdar://problem/33993462> - Disabling validation of certificate chain breaks SSL on Windows.
181         CFDictionaryAddValue(sslProps.get(), kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
182 #endif
183     }
184
185     auto clientCert = clientCertificates().find(firstRequest().url().host());
186     if (clientCert != clientCertificates().end()) {
187         if (!sslProps)
188             sslProps = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
189 #if PLATFORM(WIN)
190         wkSetClientCertificateInSSLProperties(sslProps.get(), (clientCert->value).get());
191 #endif
192     }
193 #endif // PLATFORM(IOS)
194
195     if (sslProps)
196         CFURLRequestSetSSLProperties(request.get(), sslProps.get());
197
198     CFMutableDictionaryRef streamProperties  = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
199
200     if (!shouldUseCredentialStorage) {
201         // Avoid using existing connections, because they may be already authenticated.
202         CFDictionarySetValue(streamProperties, CFSTR("_kCFURLConnectionSessionID"), CFSTR("WebKitPrivateSession"));
203     }
204
205 #if PLATFORM(COCOA)
206     RetainPtr<CFDataRef> sourceApplicationAuditData = d->m_context->sourceApplicationAuditData();
207     if (sourceApplicationAuditData)
208         CFDictionarySetValue(streamProperties, CFSTR("kCFStreamPropertySourceApplication"), sourceApplicationAuditData.get());
209 #endif
210
211     static const CFStringRef kCFURLConnectionSocketStreamProperties = CFSTR("kCFURLConnectionSocketStreamProperties");
212     RetainPtr<CFMutableDictionaryRef> propertiesDictionary;
213     if (clientProperties)
214         propertiesDictionary = adoptCF(CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, clientProperties));
215     else
216         propertiesDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
217
218 #if HAVE(TIMINGDATAOPTIONS)
219     int64_t value = static_cast<int64_t>(_TimingDataOptionsEnableW3CNavigationTiming);
220     auto enableW3CNavigationTiming = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value));
221     auto timingDataOptionsDictionary = adoptCF(CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
222     CFDictionaryAddValue(timingDataOptionsDictionary.get(), CFSTR("_kCFURLConnectionPropertyTimingDataOptions"), enableW3CNavigationTiming.get());
223     CFDictionaryAddValue(propertiesDictionary.get(), CFSTR("kCFURLConnectionURLConnectionProperties"), timingDataOptionsDictionary.get());
224 #endif
225
226     // FIXME: This code is different from iOS code in ResourceHandleMac.mm in that here we ignore stream properties that were present in client properties.
227     CFDictionaryAddValue(propertiesDictionary.get(), kCFURLConnectionSocketStreamProperties, streamProperties);
228     CFRelease(streamProperties);
229
230     d->m_connectionDelegate = adoptRef(new ResourceHandleCFURLConnectionDelegateWithOperationQueue(this, messageQueue));
231     d->m_connectionDelegate->setupRequest(request.get());
232
233     CFURLConnectionClient_V6 client = d->m_connectionDelegate->makeConnectionClient();
234     if (shouldUseCredentialStorage)
235         client.shouldUseCredentialStorage = 0;
236
237 #pragma clang diagnostic push
238 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
239     d->m_connection = adoptCF(CFURLConnectionCreateWithProperties(0, request.get(), reinterpret_cast<CFURLConnectionClient*>(&client), propertiesDictionary.get()));
240 #pragma clang diagnostic pop
241 }
242
243 bool ResourceHandle::start()
244 {
245     if (!d->m_context)
246         return false;
247
248     // If NetworkingContext is invalid then we are no longer attached to a Page,
249     // this must be an attempted load from an unload handler, so let's just block it.
250     if (!d->m_context->isValid())
251         return false;
252
253     d->m_storageSession = d->m_context->storageSession().platformSession();
254
255     bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
256
257 #if PLATFORM(COCOA) && !HAVE(TIMINGDATAOPTIONS)
258     setCollectsTimingData();
259 #endif
260
261     createCFURLConnection(shouldUseCredentialStorage, d->m_shouldContentSniff, d->m_shouldContentEncodingSniff, nullptr, client()->connectionProperties(this).get());
262     ref();
263
264     d->m_connectionDelegate->setupConnectionScheduling(d->m_connection.get());
265     CFURLConnectionStart(d->m_connection.get());
266
267     LOG(Network, "CFNet - Starting URL %s (handle=%p, conn=%p)", firstRequest().url().string().utf8().data(), this, d->m_connection.get());
268
269     return true;
270 }
271
272 void ResourceHandle::cancel()
273 {
274     if (d->m_connection) {
275         CFURLConnectionCancel(d->m_connection.get());
276         d->m_connection = 0;
277     }
278 }
279
280 void ResourceHandle::willSendRequest(ResourceRequest&& request, ResourceResponse&& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
281 {
282     const URL& url = request.url();
283     d->m_user = url.user();
284     d->m_pass = url.pass();
285     d->m_lastHTTPMethod = request.httpMethod();
286     request.removeCredentials();
287
288     String partition = firstRequest().cachePartition();
289
290     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
291         // The network layer might carry over some headers from the original request that
292         // we want to strip here because the redirect is cross-origin.
293         request.clearHTTPAuthorization();
294         request.clearHTTPOrigin();
295     } else {
296         // Only consider applying authentication credentials if this is actually a redirect and the redirect
297         // URL didn't include credentials of its own.
298         if (d->m_user.isEmpty() && d->m_pass.isEmpty() && !redirectResponse.isNull()) {
299             Credential credential = d->m_context->storageSession().credentialStorage().get(partition, request.url());
300             if (!credential.isEmpty()) {
301                 d->m_initialCredential = credential;
302                 
303                 // FIXME: Support Digest authentication, and Proxy-Authorization.
304                 applyBasicAuthorizationHeader(request, d->m_initialCredential);
305             }
306         }
307     }
308
309     client()->willSendRequestAsync(this, WTFMove(request), WTFMove(redirectResponse), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request) mutable {
310         if (!request.isNull())
311             request.setStorageSession(d->m_storageSession.get());
312         completionHandler(WTFMove(request));
313     });
314 }
315
316 bool ResourceHandle::shouldUseCredentialStorage()
317 {
318     LOG(Network, "CFNet - shouldUseCredentialStorage()");
319     if (ResourceHandleClient* client = this->client())
320         return client->shouldUseCredentialStorage(this);
321     return false;
322 }
323
324 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
325 {
326     LOG(Network, "CFNet - didReceiveAuthenticationChallenge()");
327     ASSERT(d->m_currentWebChallenge.isNull());
328     // Since CFURLConnection networking relies on keeping a reference to the original CFURLAuthChallengeRef,
329     // we make sure that is actually present
330     ASSERT(challenge.cfURLAuthChallengeRef());
331     ASSERT(challenge.authenticationClient() == this); // Should be already set.
332
333 #if !PLATFORM(WIN)
334     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
335     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
336     if (challenge.protectionSpace().isProxy()) {
337         // Cannot use receivedRequestToContinueWithoutCredential(), because current challenge is not yet set.
338         CFURLConnectionUseCredential(d->m_connection.get(), 0, challenge.cfURLAuthChallengeRef());
339         return;
340     }
341 #endif
342
343     if (tryHandlePasswordBasedAuthentication(challenge))
344         return;
345
346     d->m_currentWebChallenge = challenge;
347     
348     if (client())
349         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
350     else {
351         clearAuthentication();
352         CFURLConnectionPerformDefaultHandlingForChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
353     }
354 }
355
356 bool ResourceHandle::tryHandlePasswordBasedAuthentication(const AuthenticationChallenge& challenge)
357 {
358     if (!challenge.protectionSpace().isPasswordBased())
359         return false;
360
361     String partition = firstRequest().cachePartition();
362
363     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
364         RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(CFURLCredentialCreate(kCFAllocatorDefault, d->m_user.createCFString().get(), d->m_pass.createCFString().get(), 0, kCFURLCredentialPersistenceNone));
365 #if PLATFORM(COCOA)
366         Credential credential = Credential(cfCredential.get());
367 #else
368         Credential credential = core(cfCredential.get());
369 #endif
370         
371         URL urlToStore;
372         if (challenge.failureResponse().httpStatusCode() == 401)
373             urlToStore = challenge.failureResponse().url();
374         d->m_context->storageSession().credentialStorage().set(partition, credential, challenge.protectionSpace(), urlToStore);
375         
376         CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
377         d->m_user = String();
378         d->m_pass = String();
379         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
380         return true;
381     }
382
383     if (!client() || client()->shouldUseCredentialStorage(this)) {
384         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
385             // The stored credential wasn't accepted, stop using it.
386             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
387             // but the observable effect should be very minor, if any.
388             d->m_context->storageSession().credentialStorage().remove(partition, challenge.protectionSpace());
389         }
390
391         if (!challenge.previousFailureCount()) {
392             Credential credential = d->m_context->storageSession().credentialStorage().get(partition, challenge.protectionSpace());
393             if (!credential.isEmpty() && credential != d->m_initialCredential) {
394                 ASSERT(credential.persistence() == CredentialPersistenceNone);
395                 if (challenge.failureResponse().httpStatusCode() == 401) {
396                     // Store the credential back, possibly adding it as a default for this directory.
397                     d->m_context->storageSession().credentialStorage().set(partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
398                 }
399 #if PLATFORM(COCOA)
400                 CFURLConnectionUseCredential(d->m_connection.get(), credential.cfCredential(), challenge.cfURLAuthChallengeRef());
401 #else
402                 RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(credential));
403                 CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
404 #endif
405                 return true;
406             }
407         }
408     }
409
410     return false;
411 }
412
413 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
414 bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
415 {
416     if (auto* client = this->client())
417         client->canAuthenticateAgainstProtectionSpaceAsync(this, protectionSpace);
418     else
419         continueCanAuthenticateAgainstProtectionSpace(false);
420     return false; // Ignored by caller.
421 }
422 #endif
423
424 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
425 {
426     LOG(Network, "CFNet - receivedCredential()");
427     ASSERT(!challenge.isNull());
428     ASSERT(challenge.cfURLAuthChallengeRef());
429     if (challenge != d->m_currentWebChallenge)
430         return;
431
432     // FIXME: Support empty credentials. Currently, an empty credential cannot be stored in WebCore credential storage, as that's empty value for its map.
433     if (credential.isEmpty()) {
434         receivedRequestToContinueWithoutCredential(challenge);
435         return;
436     }
437
438     if (credential.persistence() == CredentialPersistenceForSession && challenge.protectionSpace().authenticationScheme() != ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested) {
439         // Manage per-session credentials internally, because once NSURLCredentialPersistencePerSession is used, there is no way
440         // to ignore it for a particular request (short of removing it altogether).
441         Credential webCredential(credential, CredentialPersistenceNone);
442
443         URL urlToStore;
444         if (challenge.failureResponse().httpStatusCode() == 401)
445             urlToStore = challenge.failureResponse().url(); 
446
447         d->m_context->storageSession().credentialStorage().set(firstRequest().cachePartition(), webCredential, challenge.protectionSpace(), urlToStore);
448
449         if (d->m_connection) {
450 #if PLATFORM(COCOA)
451             CFURLConnectionUseCredential(d->m_connection.get(), webCredential.cfCredential(), challenge.cfURLAuthChallengeRef());
452 #else
453             RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(webCredential));
454             CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
455 #endif
456         }
457     } else if (d->m_connection) {
458 #if PLATFORM(COCOA)
459         CFURLConnectionUseCredential(d->m_connection.get(), credential.cfCredential(), challenge.cfURLAuthChallengeRef());
460 #else
461         RetainPtr<CFURLCredentialRef> cfCredential = adoptCF(createCF(credential));
462         CFURLConnectionUseCredential(d->m_connection.get(), cfCredential.get(), challenge.cfURLAuthChallengeRef());
463 #endif
464     }
465
466     clearAuthentication();
467 }
468
469 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
470 {
471     LOG(Network, "CFNet - receivedRequestToContinueWithoutCredential()");
472     ASSERT(!challenge.isNull());
473     ASSERT(challenge.cfURLAuthChallengeRef());
474     if (challenge != d->m_currentWebChallenge)
475         return;
476
477     if (d->m_connection)
478         CFURLConnectionUseCredential(d->m_connection.get(), 0, challenge.cfURLAuthChallengeRef());
479
480     clearAuthentication();
481 }
482
483 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
484 {
485     LOG(Network, "CFNet - receivedCancellation()");
486     if (challenge != d->m_currentWebChallenge)
487         return;
488
489     if (client())
490         client()->receivedCancellation(this, challenge);
491 }
492
493 void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge& challenge)
494 {
495     LOG(Network, "CFNet - receivedRequestToPerformDefaultHandling()");
496     ASSERT(!challenge.isNull());
497     ASSERT(challenge.cfURLAuthChallengeRef());
498     if (challenge != d->m_currentWebChallenge)
499         return;
500
501     if (d->m_connection)
502         CFURLConnectionPerformDefaultHandlingForChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
503
504     clearAuthentication();
505 }
506
507 void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge& challenge)
508 {
509     LOG(Network, "CFNet - receivedChallengeRejection()");
510     ASSERT(!challenge.isNull());
511     ASSERT(challenge.cfURLAuthChallengeRef());
512     if (challenge != d->m_currentWebChallenge)
513         return;
514
515     if (d->m_connection)
516         CFURLConnectionRejectChallenge(d->m_connection.get(), challenge.cfURLAuthChallengeRef());
517
518     clearAuthentication();
519 }
520
521 CFURLStorageSessionRef ResourceHandle::storageSession() const
522 {
523     return d->m_storageSession.get();
524 }
525
526 CFURLConnectionRef ResourceHandle::connection() const
527 {
528     return d->m_connection.get();
529 }
530
531 RetainPtr<CFURLConnectionRef> ResourceHandle::releaseConnectionForDownload()
532 {
533     LOG(Network, "CFNet - Job %p releasing connection %p for download", this, d->m_connection.get());
534     return WTFMove(d->m_connection);
535 }
536
537 CFStringRef ResourceHandle::synchronousLoadRunLoopMode()
538 {
539     return CFSTR("WebCoreSynchronousLoaderRunLoopMode");
540 }
541
542 static void emptyPerform(void*)
543 {
544 }
545
546 void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentialsPolicy storedCredentialsPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data)
547 {
548     LOG(Network, "ResourceHandle::platformLoadResourceSynchronously:%s sstoredCredentialsPolicy:%u", request.url().string().utf8().data(), static_cast<unsigned>(storedCredentialsPolicy));
549
550     ASSERT(!request.isEmpty());
551
552     ASSERT(response.isNull());
553     ASSERT(error.isNull());
554
555     SynchronousLoaderClient client;
556     client.setAllowStoredCredentials(storedCredentialsPolicy == StoredCredentialsPolicy::Use);
557
558     bool defersLoading = false;
559     bool shouldContentSniff = true;
560     bool shouldContentEncodingSniff = true;
561     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &client, defersLoading, shouldContentSniff, shouldContentEncodingSniff));
562
563     handle->d->m_storageSession = context->storageSession().platformSession();
564
565     if (handle->d->m_scheduledFailureType != NoFailure) {
566         error = context->blockedError(request);
567         return;
568     }
569
570     handle->ref();
571     handle->createCFURLConnection(storedCredentialsPolicy == StoredCredentialsPolicy::Use, ResourceHandle::shouldContentSniffURL(request.url()), handle->shouldContentEncodingSniff(), &client.messageQueue(), handle->client()->connectionProperties(handle.get()).get());
572
573     static CFRunLoopRef runLoop = nullptr;
574     if (!runLoop) {
575         BinarySemaphore sem;
576         Thread::create("CFNetwork Loader", [&] {
577             runLoop = CFRunLoopGetCurrent();
578
579             // Must add a source to the run loop to prevent CFRunLoopRun() from exiting.
580             CFRunLoopSourceContext ctxt = { 0, (void*)1 /*must be non-null*/, 0, 0, 0, 0, 0, 0, 0, emptyPerform };
581             CFRunLoopSourceRef bogusSource = CFRunLoopSourceCreate(0, 0, &ctxt);
582             CFRunLoopAddSource(runLoop, bogusSource, kCFRunLoopDefaultMode);
583             sem.signal();
584
585             while (true)
586                 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1E30, true);
587         });
588         sem.wait(TimeWithDynamicClockType(WallTime::infinity()));
589     }
590     CFURLConnectionScheduleWithRunLoop(handle->connection(), runLoop, kCFRunLoopDefaultMode);
591     CFURLConnectionScheduleDownloadWithRunLoop(handle->connection(), runLoop, kCFRunLoopDefaultMode);
592     CFURLConnectionStart(handle->connection());
593
594     do {
595         if (auto task = client.messageQueue().waitForMessage())
596             (*task)();
597     } while (!client.messageQueue().killed());
598
599     error = client.error();
600
601     if (handle->connection())
602         CFURLConnectionCancel(handle->connection());
603
604     if (error.isNull())
605         response = client.response();
606
607     data.swap(client.mutableData());
608 }
609
610 void ResourceHandle::setHostAllowsAnyHTTPSCertificate(const String& host)
611 {
612     allowsAnyHTTPSCertificateHosts().add(host);
613 }
614
615 void ResourceHandle::setClientCertificate(const String& host, CFDataRef certificate)
616 {
617     clientCertificates().set(host, certificate);
618 }
619
620 void ResourceHandle::platformSetDefersLoading(bool defers)
621 {
622     if (!d->m_connection)
623         return;
624
625     if (defers)
626         CFURLConnectionHalt(d->m_connection.get());
627     else
628         CFURLConnectionResume(d->m_connection.get());
629 }
630
631 #if PLATFORM(COCOA)
632 void ResourceHandle::schedule(SchedulePair& pair)
633 {
634     CFRunLoopRef runLoop = pair.runLoop();
635     if (!runLoop)
636         return;
637
638     CFURLConnectionScheduleWithRunLoop(d->m_connection.get(), runLoop, pair.mode());
639     if (d->m_startWhenScheduled) {
640         CFURLConnectionStart(d->m_connection.get());
641         d->m_startWhenScheduled = false;
642     }
643 }
644
645 void ResourceHandle::unschedule(SchedulePair& pair)
646 {
647     CFRunLoopRef runLoop = pair.runLoop();
648     if (!runLoop)
649         return;
650
651     CFURLConnectionUnscheduleFromRunLoop(d->m_connection.get(), runLoop, pair.mode());
652 }
653 #endif
654
655 const ResourceRequest& ResourceHandle::currentRequest() const
656 {
657     return d->m_currentRequest;
658 }
659
660 void ResourceHandle::continueDidReceiveResponse()
661 {
662     d->m_connectionDelegate->continueDidReceiveResponse();
663 }
664
665 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
666 void ResourceHandle::continueCanAuthenticateAgainstProtectionSpace(bool canAuthenticate)
667 {
668     d->m_connectionDelegate->continueCanAuthenticateAgainstProtectionSpace(canAuthenticate);
669 }
670 #endif
671
672 void ResourceHandle::continueWillCacheResponse(CFCachedURLResponseRef response)
673 {
674     d->m_connectionDelegate->continueWillCacheResponse(response);
675 }
676 #endif // USE(CFURLCONNECTION)
677
678 } // namespace WebCore