Remove Cocoa CFURLConnection loading code
[WebKit-https.git] / Source / WebCore / platform / network / mac / ResourceHandleMac.mm
1 /*
2  * Copyright (C) 2004-2017 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 #import "config.h"
27 #import "ResourceHandleInternal.h"
28
29 #import "AuthenticationChallenge.h"
30 #import "AuthenticationMac.h"
31 #import "CachedResourceLoader.h"
32 #import "CookieStorage.h"
33 #import "CredentialStorage.h"
34 #import "FormDataStreamMac.h"
35 #import "Frame.h"
36 #import "FrameLoader.h"
37 #import "HTTPHeaderNames.h"
38 #import "Logging.h"
39 #import "MIMETypeRegistry.h"
40 #import "NetworkStorageSession.h"
41 #import "NetworkingContext.h"
42 #import "ResourceError.h"
43 #import "ResourceResponse.h"
44 #import "SharedBuffer.h"
45 #import "SubresourceLoader.h"
46 #import "SynchronousLoaderClient.h"
47 #import "WebCoreResourceHandleAsOperationQueueDelegate.h"
48 #import "WebCoreURLResponse.h"
49 #import <pal/spi/cf/CFNetworkSPI.h>
50 #import <pal/spi/cocoa/NSURLConnectionSPI.h>
51 #import <wtf/BlockObjCExceptions.h>
52 #import <wtf/CompletionHandler.h>
53 #import <wtf/Ref.h>
54 #import <wtf/SchedulePair.h>
55 #import <wtf/text/Base64.h>
56 #import <wtf/text/CString.h>
57
58 #if PLATFORM(IOS)
59 #import "RuntimeApplicationChecks.h"
60 #import "WebCoreThreadRun.h"
61 #endif
62
63 using namespace WebCore;
64
65 @interface NSURLConnection ()
66 -(id)_initWithRequest:(NSURLRequest *)request delegate:(id)delegate usesCache:(BOOL)usesCacheFlag maxContentLength:(long long)maxContentLength startImmediately:(BOOL)startImmediately connectionProperties:(NSDictionary *)connectionProperties;
67 @end
68
69 namespace WebCore {
70     
71 static void applyBasicAuthorizationHeader(ResourceRequest& request, const Credential& credential)
72 {
73     String authenticationHeader = "Basic " + base64Encode(String(credential.user() + ":" + credential.password()).utf8());
74
75     request.setHTTPHeaderField(HTTPHeaderName::Authorization, authenticationHeader);
76 }
77
78 static NSOperationQueue *operationQueueForAsyncClients()
79 {
80     static NSOperationQueue *queue;
81     if (!queue) {
82         queue = [[NSOperationQueue alloc] init];
83         // Default concurrent operation count depends on current system workload, but delegate methods are mostly idling in IPC, so we can run as many as needed.
84         [queue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
85     }
86     return queue;
87 }
88
89 ResourceHandleInternal::~ResourceHandleInternal()
90 {
91 }
92
93 ResourceHandle::~ResourceHandle()
94 {
95     releaseDelegate();
96     d->m_currentWebChallenge.setAuthenticationClient(0);
97
98     LOG(Network, "Handle %p destroyed", this);
99 }
100
101 #if PLATFORM(IOS)
102 static bool synchronousWillSendRequestEnabled()
103 {
104     static bool disabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitDisableSynchronousWillSendRequestPreferenceKey"] || IOSApplication::isIBooks();
105     return !disabled;
106 }
107 #endif
108
109 #if PLATFORM(COCOA)
110 void ResourceHandle::applySniffingPoliciesAndStoragePartitionIfNeeded(NSURLRequest*& nsRequest, bool shouldContentSniff, bool shouldContentEncodingSniff)
111 {
112 #if !PLATFORM(MAC)
113     UNUSED_PARAM(shouldContentEncodingSniff);
114 #elif __MAC_OS_X_VERSION_MIN_REQUIRED < 101302
115     shouldContentEncodingSniff = true;
116 #endif
117 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
118     String storagePartition = d->m_context->storageSession().cookieStoragePartition(firstRequest());
119 #else
120     String storagePartition;
121 #endif
122     if (shouldContentSniff && shouldContentEncodingSniff && storagePartition.isEmpty())
123         return;
124
125     auto mutableRequest = adoptNS([nsRequest mutableCopy]);
126
127 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101302
128     if (!shouldContentEncodingSniff)
129         [mutableRequest _setProperty:@(YES) forKey:(NSString *)kCFURLRequestContentDecoderSkipURLCheck];
130 #endif
131
132     if (!shouldContentSniff)
133         [mutableRequest _setProperty:@(NO) forKey:(NSString *)_kCFURLConnectionPropertyShouldSniff];
134
135 #if HAVE(CFNETWORK_STORAGE_PARTITIONING)
136     if (!storagePartition.isEmpty())
137         [mutableRequest _setProperty:storagePartition forKey:@"__STORAGE_PARTITION_IDENTIFIER"];
138 #endif
139
140     nsRequest = mutableRequest.autorelease();
141
142 }
143 #endif
144
145 #if !PLATFORM(IOS)
146 void ResourceHandle::createNSURLConnection(id delegate, bool shouldUseCredentialStorage, bool shouldContentSniff, bool shouldContentEncodingSniff, SchedulingBehavior schedulingBehavior)
147 #else
148 void ResourceHandle::createNSURLConnection(id delegate, bool shouldUseCredentialStorage, bool shouldContentSniff, bool shouldContentEncodingSniff, SchedulingBehavior schedulingBehavior, NSDictionary *connectionProperties)
149 #endif
150 {
151 #if !HAVE(TIMINGDATAOPTIONS)
152     setCollectsTimingData();
153 #endif
154
155     // Credentials for ftp can only be passed in URL, the connection:didReceiveAuthenticationChallenge: delegate call won't be made.
156     if ((!d->m_user.isEmpty() || !d->m_pass.isEmpty()) && !firstRequest().url().protocolIsInHTTPFamily()) {
157         URL urlWithCredentials(firstRequest().url());
158         urlWithCredentials.setUser(d->m_user);
159         urlWithCredentials.setPass(d->m_pass);
160         firstRequest().setURL(urlWithCredentials);
161     }
162
163     if (shouldUseCredentialStorage && firstRequest().url().protocolIsInHTTPFamily()) {
164         if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
165             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
166             // try and reuse the credential preemptively, as allowed by RFC 2617.
167             d->m_initialCredential = d->m_context->storageSession().credentialStorage().get(firstRequest().cachePartition(), firstRequest().url());
168         } else {
169             // If there is already a protection space known for the URL, update stored credentials before sending a request.
170             // This makes it possible to implement logout by sending an XMLHttpRequest with known incorrect credentials, and aborting it immediately
171             // (so that an authentication dialog doesn't pop up).
172             d->m_context->storageSession().credentialStorage().set(firstRequest().cachePartition(), Credential(d->m_user, d->m_pass, CredentialPersistenceNone), firstRequest().url());
173         }
174     }
175         
176     if (!d->m_initialCredential.isEmpty()) {
177         // FIXME: Support Digest authentication, and Proxy-Authorization.
178         applyBasicAuthorizationHeader(firstRequest(), d->m_initialCredential);
179     }
180
181     NSURLRequest *nsRequest = firstRequest().nsURLRequest(UpdateHTTPBody);
182     applySniffingPoliciesAndStoragePartitionIfNeeded(nsRequest, shouldContentSniff, shouldContentEncodingSniff);
183
184     if (d->m_storageSession)
185         nsRequest = [copyRequestWithStorageSession(d->m_storageSession.get(), nsRequest) autorelease];
186
187     ASSERT([NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)]);
188
189 #if PLATFORM(IOS)
190     // FIXME: This code is different from iOS code in ResourceHandleCFNet.cpp in that here we respect stream properties that were present in client properties.
191     NSDictionary *streamPropertiesFromClient = [connectionProperties objectForKey:@"kCFURLConnectionSocketStreamProperties"];
192     NSMutableDictionary *streamProperties = streamPropertiesFromClient ? [[streamPropertiesFromClient mutableCopy] autorelease] : [NSMutableDictionary dictionary];
193 #else
194     NSMutableDictionary *streamProperties = [NSMutableDictionary dictionary];
195 #endif
196
197     if (!shouldUseCredentialStorage) {
198         // Avoid using existing connections, because they may be already authenticated.
199         [streamProperties setObject:@"WebKitPrivateSession" forKey:@"_kCFURLConnectionSessionID"];
200     }
201
202     if (schedulingBehavior == SchedulingBehavior::Synchronous) {
203         // Synchronous requests should not be subject to regular connection count limit to avoid deadlocks.
204         // If we are using all available connections for async requests, and make a sync request, then prior
205         // requests may get stuck waiting for delegate calls while we are in nested run loop, and the sync
206         // request won't start because there are no available connections.
207         // Connections are grouped by their socket stream properties, with each group having a separate count.
208         [streamProperties setObject:@TRUE forKey:@"_WebKitSynchronousRequest"];
209     }
210
211     RetainPtr<CFDataRef> sourceApplicationAuditData = d->m_context->sourceApplicationAuditData();
212     if (sourceApplicationAuditData)
213         [streamProperties setObject:(NSData *)sourceApplicationAuditData.get() forKey:@"kCFStreamPropertySourceApplication"];
214
215 #if PLATFORM(IOS)
216     NSMutableDictionary *propertyDictionary = [NSMutableDictionary dictionaryWithDictionary:connectionProperties];
217     [propertyDictionary setObject:streamProperties forKey:@"kCFURLConnectionSocketStreamProperties"];
218     const bool usesCache = false;
219     if (synchronousWillSendRequestEnabled())
220         CFURLRequestSetShouldStartSynchronously([nsRequest _CFURLRequest], 1);
221 #else
222     NSMutableDictionary *propertyDictionary = [NSMutableDictionary dictionaryWithObject:streamProperties forKey:@"kCFURLConnectionSocketStreamProperties"];
223     const bool usesCache = true;
224 #endif
225 #if HAVE(TIMINGDATAOPTIONS)
226     [propertyDictionary setObject:@{@"_kCFURLConnectionPropertyTimingDataOptions": @(_TimingDataOptionsEnableW3CNavigationTiming)} forKey:@"kCFURLConnectionURLConnectionProperties"];
227 #endif
228
229     // This is used to signal that to CFNetwork that this connection should be considered
230     // web content for purposes of App Transport Security.
231     [propertyDictionary setObject:@{@"NSAllowsArbitraryLoadsInWebContent": @YES} forKey:@"_kCFURLConnectionPropertyATSFrameworkOverrides"];
232
233     d->m_connection = adoptNS([[NSURLConnection alloc] _initWithRequest:nsRequest delegate:delegate usesCache:usesCache maxContentLength:0 startImmediately:NO connectionProperties:propertyDictionary]);
234 }
235
236 bool ResourceHandle::start()
237 {
238     if (!d->m_context)
239         return false;
240
241     BEGIN_BLOCK_OBJC_EXCEPTIONS;
242
243     // If NetworkingContext is invalid then we are no longer attached to a Page,
244     // this must be an attempted load from an unload event handler, so let's just block it.
245     if (!d->m_context->isValid())
246         return false;
247
248     d->m_storageSession = d->m_context->storageSession().platformSession();
249
250     // FIXME: Do not use the sync version of shouldUseCredentialStorage when the client returns true from usesAsyncCallbacks.
251     bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
252
253     SchedulingBehavior schedulingBehavior = client() && client()->loadingSynchronousXHR() ? SchedulingBehavior::Synchronous : SchedulingBehavior::Asynchronous;
254
255 #if !PLATFORM(IOS)
256     createNSURLConnection(
257         ResourceHandle::makeDelegate(shouldUseCredentialStorage, nullptr),
258         shouldUseCredentialStorage,
259         d->m_shouldContentSniff || d->m_context->localFileContentSniffingEnabled(),
260         d->m_shouldContentEncodingSniff,
261         schedulingBehavior);
262 #else
263     createNSURLConnection(
264         ResourceHandle::makeDelegate(shouldUseCredentialStorage, nullptr),
265         shouldUseCredentialStorage,
266         d->m_shouldContentSniff || d->m_context->localFileContentSniffingEnabled(),
267         d->m_shouldContentEncodingSniff,
268         schedulingBehavior,
269         (NSDictionary *)client()->connectionProperties(this).get());
270 #endif
271
272     [connection() setDelegateQueue:operationQueueForAsyncClients()];
273     [connection() start];
274
275     LOG(Network, "Handle %p starting connection %p for %@", this, connection(), firstRequest().nsURLRequest(DoNotUpdateHTTPBody));
276     
277     if (d->m_connection) {
278         if (d->m_defersLoading)
279             connection().defersCallbacks = YES;
280
281         return true;
282     }
283
284     END_BLOCK_OBJC_EXCEPTIONS;
285
286     return false;
287 }
288
289 void ResourceHandle::cancel()
290 {
291     LOG(Network, "Handle %p cancel connection %p", this, d->m_connection.get());
292
293     // Leaks were seen on HTTP tests without this; can be removed once <rdar://problem/6886937> is fixed.
294     if (d->m_currentMacChallenge)
295         [[d->m_currentMacChallenge sender] cancelAuthenticationChallenge:d->m_currentMacChallenge];
296
297     [d->m_connection.get() cancel];
298 }
299
300 void ResourceHandle::platformSetDefersLoading(bool defers)
301 {
302     if (d->m_connection)
303         [d->m_connection setDefersCallbacks:defers];
304 }
305
306 void ResourceHandle::schedule(SchedulePair& pair)
307 {
308     NSRunLoop *runLoop = pair.nsRunLoop();
309     if (!runLoop)
310         return;
311     [d->m_connection.get() scheduleInRunLoop:runLoop forMode:(NSString *)pair.mode()];
312 }
313
314 void ResourceHandle::unschedule(SchedulePair& pair)
315 {
316     if (NSRunLoop *runLoop = pair.nsRunLoop())
317         [d->m_connection.get() unscheduleFromRunLoop:runLoop forMode:(NSString *)pair.mode()];
318 }
319
320 id ResourceHandle::makeDelegate(bool shouldUseCredentialStorage, MessageQueue<Function<void()>>* queue)
321 {
322     ASSERT(!d->m_delegate);
323
324     id <NSURLConnectionDelegate> delegate;
325     if (shouldUseCredentialStorage)
326         delegate = [[WebCoreResourceHandleAsOperationQueueDelegate alloc] initWithHandle:this messageQueue:queue];
327     else
328         delegate = [[WebCoreResourceHandleWithCredentialStorageAsOperationQueueDelegate alloc] initWithHandle:this messageQueue:queue];
329
330     d->m_delegate = delegate;
331     [delegate release];
332
333     return d->m_delegate.get();
334 }
335
336 id ResourceHandle::delegate()
337 {
338     if (!d->m_delegate)
339         return makeDelegate(false, nullptr);
340     return d->m_delegate.get();
341 }
342
343 void ResourceHandle::releaseDelegate()
344 {
345     if (!d->m_delegate)
346         return;
347     [d->m_delegate.get() detachHandle];
348     d->m_delegate = nil;
349 }
350
351 NSURLConnection *ResourceHandle::connection() const
352 {
353     return d->m_connection.get();
354 }
355     
356 CFStringRef ResourceHandle::synchronousLoadRunLoopMode()
357 {
358     return CFSTR("WebCoreSynchronousLoaderRunLoopMode");
359 }
360
361 void ResourceHandle::platformLoadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentialsPolicy storedCredentialsPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data)
362 {
363     LOG(Network, "ResourceHandle::platformLoadResourceSynchronously:%@ storedCredentialsPolicy:%u", request.nsURLRequest(DoNotUpdateHTTPBody), static_cast<unsigned>(storedCredentialsPolicy));
364
365     ASSERT(!request.isEmpty());
366     
367     SynchronousLoaderClient client;
368     client.setAllowStoredCredentials(storedCredentialsPolicy == StoredCredentialsPolicy::Use);
369
370     bool defersLoading = false;
371     bool shouldContentSniff = true;
372     bool shouldContentEncodingSniff = true;
373     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(context, request, &client, defersLoading, shouldContentSniff, shouldContentEncodingSniff));
374
375     handle->d->m_storageSession = context->storageSession().platformSession();
376
377     if (context && handle->d->m_scheduledFailureType != NoFailure) {
378         error = context->blockedError(request);
379         return;
380     }
381
382     bool shouldUseCredentialStorage = storedCredentialsPolicy == StoredCredentialsPolicy::Use;
383 #if !PLATFORM(IOS)
384     handle->createNSURLConnection(
385         handle->makeDelegate(shouldUseCredentialStorage, &client.messageQueue()),
386         shouldUseCredentialStorage,
387         handle->shouldContentSniff() || context->localFileContentSniffingEnabled(),
388         handle->shouldContentEncodingSniff(),
389         SchedulingBehavior::Synchronous);
390 #else
391     handle->createNSURLConnection(
392         handle->makeDelegate(shouldUseCredentialStorage, &client.messageQueue()), // A synchronous request cannot turn into a download, so there is no need to proxy the delegate.
393         shouldUseCredentialStorage,
394         handle->shouldContentSniff() || (context && context->localFileContentSniffingEnabled()),
395         handle->shouldContentEncodingSniff(),
396         SchedulingBehavior::Synchronous,
397         (NSDictionary *)handle->client()->connectionProperties(handle.get()).get());
398 #endif
399
400     [handle->connection() setDelegateQueue:operationQueueForAsyncClients()];
401     [handle->connection() start];
402     
403     do {
404         if (auto task = client.messageQueue().waitForMessage())
405             (*task)();
406     } while (!client.messageQueue().killed());
407
408     error = client.error();
409     
410     [handle->connection() cancel];
411
412     if (error.isNull())
413         response = client.response();
414
415     data.swap(client.mutableData());
416 }
417
418 void ResourceHandle::willSendRequest(ResourceRequest&& request, ResourceResponse&& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
419 {
420     ASSERT(!redirectResponse.isNull());
421
422     if (redirectResponse.httpStatusCode() == 307) {
423         String lastHTTPMethod = d->m_lastHTTPMethod;
424         if (!equalIgnoringASCIICase(lastHTTPMethod, request.httpMethod())) {
425             request.setHTTPMethod(lastHTTPMethod);
426     
427             FormData* body = d->m_firstRequest.httpBody();
428             if (!equalLettersIgnoringASCIICase(lastHTTPMethod, "get") && body && !body->isEmpty())
429                 request.setHTTPBody(body);
430
431             String originalContentType = d->m_firstRequest.httpContentType();
432             if (!originalContentType.isEmpty())
433                 request.setHTTPHeaderField(HTTPHeaderName::ContentType, originalContentType);
434         }
435     }
436
437     // Should not set Referer after a redirect from a secure resource to non-secure one.
438     if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https") && d->m_context->shouldClearReferrerOnHTTPSToHTTPRedirect())
439         request.clearHTTPReferrer();
440
441     const URL& url = request.url();
442     d->m_user = url.user();
443     d->m_pass = url.pass();
444     d->m_lastHTTPMethod = request.httpMethod();
445     request.removeCredentials();
446
447     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
448         // The network layer might carry over some headers from the original request that
449         // we want to strip here because the redirect is cross-origin.
450         request.clearHTTPAuthorization();
451         request.clearHTTPOrigin();
452     } else {
453         // Only consider applying authentication credentials if this is actually a redirect and the redirect
454         // URL didn't include credentials of its own.
455         if (d->m_user.isEmpty() && d->m_pass.isEmpty() && !redirectResponse.isNull()) {
456             Credential credential = d->m_context->storageSession().credentialStorage().get(request.cachePartition(), request.url());
457             if (!credential.isEmpty()) {
458                 d->m_initialCredential = credential;
459                 
460                 // FIXME: Support Digest authentication, and Proxy-Authorization.
461                 applyBasicAuthorizationHeader(request, d->m_initialCredential);
462             }
463         }
464     }
465
466     client()->willSendRequestAsync(this, WTFMove(request), WTFMove(redirectResponse), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (ResourceRequest&& request) mutable {
467         // Client call may not preserve the session, especially if the request is sent over IPC.
468         if (!request.isNull())
469             request.setStorageSession(d->m_storageSession.get());
470         completionHandler(WTFMove(request));
471     });
472 }
473
474 void ResourceHandle::continueDidReceiveResponse()
475 {
476     [delegate() continueDidReceiveResponse];
477 }
478
479 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
480 {
481     ASSERT(!d->m_currentMacChallenge);
482     ASSERT(d->m_currentWebChallenge.isNull());
483     // Since NSURLConnection networking relies on keeping a reference to the original NSURLAuthenticationChallenge,
484     // we make sure that is actually present
485     ASSERT(challenge.nsURLAuthenticationChallenge());
486
487     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
488     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
489     if (challenge.protectionSpace().isProxy()) {
490         // Cannot use receivedRequestToContinueWithoutCredential(), because current challenge is not yet set.
491         [challenge.sender() continueWithoutCredentialForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
492         return;
493     }
494
495     if (tryHandlePasswordBasedAuthentication(challenge))
496         return;
497
498 #if PLATFORM(IOS)
499     // If the challenge is for a proxy protection space, look for default credentials in
500     // the keychain.  CFNetwork used to handle this until WebCore was changed to always
501     // return NO to -connectionShouldUseCredentialStorage: for <rdar://problem/7704943>.
502     if (!challenge.previousFailureCount() && challenge.protectionSpace().isProxy()) {
503         NSURLAuthenticationChallenge *macChallenge = mac(challenge);
504         if (NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:[macChallenge protectionSpace]]) {
505             [challenge.sender() useCredential:credential forAuthenticationChallenge:macChallenge];
506             return;
507         }
508     }
509 #endif // PLATFORM(IOS)
510
511     d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
512     d->m_currentWebChallenge = core(d->m_currentMacChallenge);
513     d->m_currentWebChallenge.setAuthenticationClient(this);
514
515     // FIXME: Several concurrent requests can return with the an authentication challenge for the same protection space.
516     // We should avoid making additional client calls for the same protection space when already waiting for the user,
517     // because typing the same credentials several times is annoying.
518     if (client())
519         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
520     else {
521         clearAuthentication();
522         [challenge.sender() performDefaultHandlingForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
523     }
524 }
525
526 bool ResourceHandle::tryHandlePasswordBasedAuthentication(const AuthenticationChallenge& challenge)
527 {
528     if (!challenge.protectionSpace().isPasswordBased())
529         return false;
530
531     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
532         NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:d->m_user
533                                                                    password:d->m_pass
534                                                                 persistence:NSURLCredentialPersistenceForSession];
535         d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
536         d->m_currentWebChallenge = challenge;
537         receivedCredential(challenge, Credential(credential));
538         [credential release];
539         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
540         d->m_user = String();
541         d->m_pass = String();
542         return true;
543     }
544
545     // FIXME: Do not use the sync version of shouldUseCredentialStorage when the client returns true from usesAsyncCallbacks.
546     if (!client() || client()->shouldUseCredentialStorage(this)) {
547         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
548             // The stored credential wasn't accepted, stop using it.
549             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
550             // but the observable effect should be very minor, if any.
551             d->m_context->storageSession().credentialStorage().remove(d->m_partition, challenge.protectionSpace());
552         }
553
554         if (!challenge.previousFailureCount()) {
555             Credential credential = d->m_context->storageSession().credentialStorage().get(d->m_partition, challenge.protectionSpace());
556             if (!credential.isEmpty() && credential != d->m_initialCredential) {
557                 ASSERT(credential.persistence() == CredentialPersistenceNone);
558                 if (challenge.failureResponse().httpStatusCode() == 401) {
559                     // Store the credential back, possibly adding it as a default for this directory.
560                     d->m_context->storageSession().credentialStorage().set(d->m_partition, credential, challenge.protectionSpace(), challenge.failureResponse().url());
561                 }
562                 [challenge.sender() useCredential:credential.nsCredential() forAuthenticationChallenge:mac(challenge)];
563                 return true;
564             }
565         }
566     }
567
568     return false;
569 }
570
571 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
572 bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
573 {
574     if (ResourceHandleClient* client = this->client())
575         client->canAuthenticateAgainstProtectionSpaceAsync(this, protectionSpace);
576     else
577         continueCanAuthenticateAgainstProtectionSpace(false);
578     return false; // Ignored by caller.
579 }
580
581 void ResourceHandle::continueCanAuthenticateAgainstProtectionSpace(bool result)
582 {
583     [(id)delegate() continueCanAuthenticateAgainstProtectionSpace:result];
584 }
585 #endif
586
587 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
588 {
589     LOG(Network, "Handle %p receivedCredential", this);
590
591     ASSERT(!challenge.isNull());
592     if (challenge != d->m_currentWebChallenge)
593         return;
594
595     // FIXME: Support empty credentials. Currently, an empty credential cannot be stored in WebCore credential storage, as that's empty value for its map.
596     if (credential.isEmpty()) {
597         receivedRequestToContinueWithoutCredential(challenge);
598         return;
599     }
600
601     if (credential.persistence() == CredentialPersistenceForSession && challenge.protectionSpace().authenticationScheme() != ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested) {
602         // Manage per-session credentials internally, because once NSURLCredentialPersistenceForSession is used, there is no way
603         // to ignore it for a particular request (short of removing it altogether).
604         Credential webCredential(credential, CredentialPersistenceNone);
605         URL urlToStore;
606         if (challenge.failureResponse().httpStatusCode() == 401)
607             urlToStore = challenge.failureResponse().url();
608         d->m_context->storageSession().credentialStorage().set(d->m_partition, webCredential, ProtectionSpace([d->m_currentMacChallenge protectionSpace]), urlToStore);
609         [[d->m_currentMacChallenge sender] useCredential:webCredential.nsCredential() forAuthenticationChallenge:d->m_currentMacChallenge];
610     } else
611         [[d->m_currentMacChallenge sender] useCredential:credential.nsCredential() forAuthenticationChallenge:d->m_currentMacChallenge];
612
613     clearAuthentication();
614 }
615
616 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
617 {
618     LOG(Network, "Handle %p receivedRequestToContinueWithoutCredential", this);
619
620     ASSERT(!challenge.isNull());
621     if (challenge != d->m_currentWebChallenge)
622         return;
623
624     [[d->m_currentMacChallenge sender] continueWithoutCredentialForAuthenticationChallenge:d->m_currentMacChallenge];
625
626     clearAuthentication();
627 }
628
629 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
630 {
631     LOG(Network, "Handle %p receivedCancellation", this);
632
633     if (challenge != d->m_currentWebChallenge)
634         return;
635
636     if (client())
637         client()->receivedCancellation(this, challenge);
638 }
639
640 void ResourceHandle::receivedRequestToPerformDefaultHandling(const AuthenticationChallenge& challenge)
641 {
642     LOG(Network, "Handle %p receivedRequestToPerformDefaultHandling", this);
643
644     ASSERT(!challenge.isNull());
645     if (challenge != d->m_currentWebChallenge)
646         return;
647
648     [[d->m_currentMacChallenge sender] performDefaultHandlingForAuthenticationChallenge:d->m_currentMacChallenge];
649
650     clearAuthentication();
651 }
652
653 void ResourceHandle::receivedChallengeRejection(const AuthenticationChallenge& challenge)
654 {
655     LOG(Network, "Handle %p receivedChallengeRejection", this);
656
657     ASSERT(!challenge.isNull());
658     if (challenge != d->m_currentWebChallenge)
659         return;
660
661     [[d->m_currentMacChallenge sender] rejectProtectionSpaceAndContinueWithChallenge:d->m_currentMacChallenge];
662
663     clearAuthentication();
664 }
665
666 void ResourceHandle::continueWillCacheResponse(NSCachedURLResponse *response)
667 {
668     [(id)delegate() continueWillCacheResponse:response];
669 }
670
671 void ResourceHandle::getConnectionTimingData(NSURLConnection *connection, NetworkLoadMetrics& timing)
672 {
673     copyTimingData([connection _timingData], timing);
674 }
675
676 } // namespace WebCore