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