Add a separate class for networking related storage
[WebKit-https.git] / Source / WebCore / platform / network / mac / ResourceHandleMac.mm
1 /*
2  * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 COMPUTER, 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 COMPUTER, 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 #if !USE(CFNETWORK)
30
31 #import "AuthenticationChallenge.h"
32 #import "AuthenticationMac.h"
33 #import "BlobRegistry.h"
34 #import "BlockExceptions.h"
35 #import "CookieStorage.h"
36 #import "CredentialStorage.h"
37 #import "CachedResourceLoader.h"
38 #import "EmptyProtocolDefinitions.h"
39 #import "FormDataStreamMac.h"
40 #import "Frame.h"
41 #import "FrameLoader.h"
42 #import "Logging.h"
43 #import "MIMETypeRegistry.h"
44 #import "NetworkingContext.h"
45 #import "Page.h"
46 #import "ResourceError.h"
47 #import "ResourceResponse.h"
48 #import "SchedulePair.h"
49 #import "Settings.h"
50 #import "SharedBuffer.h"
51 #import "SubresourceLoader.h"
52 #import "WebCoreSystemInterface.h"
53 #import "WebCoreURLResponse.h"
54 #import <wtf/UnusedParam.h>
55 #import <wtf/text/Base64.h>
56 #import <wtf/text/CString.h>
57
58 using namespace WebCore;
59
60 @interface WebCoreResourceHandleAsDelegate : NSObject <NSURLConnectionDelegate> {
61     ResourceHandle* m_handle;
62 }
63 - (id)initWithHandle:(ResourceHandle*)handle;
64 - (void)detachHandle;
65 @end
66
67 // WebCoreNSURLConnectionDelegateProxy exists so that we can cast m_proxy to it in order
68 // to disambiguate the argument type in the -setDelegate: call.  This avoids a spurious
69 // warning that the compiler would otherwise emit.
70 @interface WebCoreNSURLConnectionDelegateProxy : NSObject <NSURLConnectionDelegate>
71 - (void)setDelegate:(id<NSURLConnectionDelegate>)delegate;
72 @end
73
74 @interface NSURLConnection (Details)
75 -(id)_initWithRequest:(NSURLRequest *)request delegate:(id)delegate usesCache:(BOOL)usesCacheFlag maxContentLength:(long long)maxContentLength startImmediately:(BOOL)startImmediately connectionProperties:(NSDictionary *)connectionProperties;
76 @end
77
78 @interface NSURLRequest (Details)
79 - (id)_propertyForKey:(NSString *)key;
80 @end
81
82 class WebCoreSynchronousLoaderClient : public ResourceHandleClient {
83 public:
84     static PassOwnPtr<WebCoreSynchronousLoaderClient> create()
85     {
86         return adoptPtr(new WebCoreSynchronousLoaderClient);
87     }
88
89     virtual ~WebCoreSynchronousLoaderClient();
90
91     void setAllowStoredCredentials(bool allow) { m_allowStoredCredentials = allow; }
92     NSURLResponse *response() { return m_response; }
93     NSMutableData *data() { return m_data; }
94     NSError *error() { return m_error; }
95     bool isDone() { return m_isDone; }
96
97 private:
98     WebCoreSynchronousLoaderClient()
99         : m_allowStoredCredentials(false)
100         , m_response(0)
101         , m_data(0)
102         , m_error(0)
103         , m_isDone(false)
104     {
105     }
106
107     virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse& /*redirectResponse*/);
108     virtual bool shouldUseCredentialStorage(ResourceHandle*);
109     virtual void didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge&);
110     virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
111     virtual void didReceiveData(ResourceHandle*, const char*, int, int /*encodedDataLength*/);
112     virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
113     virtual void didFail(ResourceHandle*, const ResourceError&);
114 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
115     virtual bool canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&);
116 #endif
117
118     bool m_allowStoredCredentials;
119     NSURLResponse *m_response;
120     NSMutableData *m_data;
121     NSError *m_error;
122     bool m_isDone;
123 };
124
125 namespace WebCore {
126
127 static void applyBasicAuthorizationHeader(ResourceRequest& request, const Credential& credential)
128 {
129     String authenticationHeader = "Basic " + base64Encode(String(credential.user() + ":" + credential.password()).utf8());
130     request.clearHTTPAuthorization(); // FIXME: Should addHTTPHeaderField be smart enough to not build comma-separated lists in headers like Authorization?
131     request.addHTTPHeaderField("Authorization", authenticationHeader);
132 }
133
134 ResourceHandleInternal::~ResourceHandleInternal()
135 {
136 }
137
138 ResourceHandle::~ResourceHandle()
139 {
140     releaseDelegate();
141     d->m_currentWebChallenge.setAuthenticationClient(0);
142
143     LOG(Network, "Handle %p destroyed", this);
144 }
145
146 static bool shouldRelaxThirdPartyCookiePolicy(NetworkingContext* context, const KURL& url)
147 {
148     // If a URL already has cookies, then we'll relax the 3rd party cookie policy and accept new cookies.
149
150     RetainPtr<CFHTTPCookieStorageRef> cfCookieStorage = context->storageSession().cookieStorage();
151     NSHTTPCookieAcceptPolicy cookieAcceptPolicy = static_cast<NSHTTPCookieAcceptPolicy>(wkGetHTTPCookieAcceptPolicy(cfCookieStorage.get()));
152
153     if (cookieAcceptPolicy != NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain)
154         return false;
155
156     NSArray *cookies = wkHTTPCookiesForURL(cfCookieStorage.get(), url);
157
158     return [cookies count];
159 }
160
161 void ResourceHandle::createNSURLConnection(id delegate, bool shouldUseCredentialStorage, bool shouldRelaxThirdPartyCookiePolicy, bool shouldContentSniff)
162 {
163     // Credentials for ftp can only be passed in URL, the connection:didReceiveAuthenticationChallenge: delegate call won't be made.
164     if ((!d->m_user.isEmpty() || !d->m_pass.isEmpty()) && !firstRequest().url().protocolIsInHTTPFamily()) {
165         KURL urlWithCredentials(firstRequest().url());
166         urlWithCredentials.setUser(d->m_user);
167         urlWithCredentials.setPass(d->m_pass);
168         firstRequest().setURL(urlWithCredentials);
169     }
170
171     if (shouldRelaxThirdPartyCookiePolicy)
172         firstRequest().setFirstPartyForCookies(firstRequest().url());
173
174     if (shouldUseCredentialStorage && firstRequest().url().protocolIsInHTTPFamily()) {
175         if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
176             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
177             // try and reuse the credential preemptively, as allowed by RFC 2617.
178             d->m_initialCredential = CredentialStorage::get(firstRequest().url());
179         } else {
180             // If there is already a protection space known for the URL, update stored credentials before sending a request.
181             // This makes it possible to implement logout by sending an XMLHttpRequest with known incorrect credentials, and aborting it immediately
182             // (so that an authentication dialog doesn't pop up).
183             CredentialStorage::set(Credential(d->m_user, d->m_pass, CredentialPersistenceNone), firstRequest().url());
184         }
185     }
186         
187     if (!d->m_initialCredential.isEmpty()) {
188         // FIXME: Support Digest authentication, and Proxy-Authorization.
189         applyBasicAuthorizationHeader(firstRequest(), d->m_initialCredential);
190     }
191
192     NSURLRequest *nsRequest = firstRequest().nsURLRequest();
193     if (!shouldContentSniff) {
194         NSMutableURLRequest *mutableRequest = [[nsRequest mutableCopy] autorelease];
195         wkSetNSURLRequestShouldContentSniff(mutableRequest, NO);
196         nsRequest = mutableRequest;
197     }
198
199 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
200     ASSERT([NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)]);
201     static bool supportsSettingConnectionProperties = true;
202 #else
203     static bool supportsSettingConnectionProperties = [NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)];
204 #endif
205
206     if (d->m_storageSession)
207         nsRequest = [wkCopyRequestWithStorageSession(d->m_storageSession.get(), nsRequest) autorelease];
208
209     if (supportsSettingConnectionProperties) {
210         NSDictionary *sessionID = shouldUseCredentialStorage ? [NSDictionary dictionary] : [NSDictionary dictionaryWithObject:@"WebKitPrivateSession" forKey:@"_kCFURLConnectionSessionID"];
211         NSDictionary *propertyDictionary = [NSDictionary dictionaryWithObject:sessionID forKey:@"kCFURLConnectionSocketStreamProperties"];
212         d->m_connection.adoptNS([[NSURLConnection alloc] _initWithRequest:nsRequest delegate:delegate usesCache:YES maxContentLength:0 startImmediately:NO connectionProperties:propertyDictionary]);
213         return;
214     }
215
216     d->m_connection.adoptNS([[NSURLConnection alloc] initWithRequest:nsRequest delegate:delegate startImmediately:NO]);
217     return;
218
219 }
220
221 bool ResourceHandle::start(NetworkingContext* context)
222 {
223     if (!context)
224         return false;
225
226     BEGIN_BLOCK_OBJC_EXCEPTIONS;
227
228     // If NetworkingContext is invalid then we are no longer attached to a Page,
229     // this must be an attempted load from an unload event handler, so let's just block it.
230     if (!context->isValid())
231         return false;
232
233     d->m_storageSession = context->storageSession().platformSession();
234
235     ASSERT(!d->m_proxy);
236     d->m_proxy.adoptNS(wkCreateNSURLConnectionDelegateProxy());
237     [static_cast<WebCoreNSURLConnectionDelegateProxy*>(d->m_proxy.get()) setDelegate:ResourceHandle::delegate()];
238
239     bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
240
241     d->m_needsSiteSpecificQuirks = context->needsSiteSpecificQuirks();
242
243     createNSURLConnection(
244         d->m_proxy.get(),
245         shouldUseCredentialStorage,
246         shouldRelaxThirdPartyCookiePolicy(context, firstRequest().url()),
247         d->m_shouldContentSniff || context->localFileContentSniffingEnabled());
248
249     bool scheduled = false;
250     if (SchedulePairHashSet* scheduledPairs = context->scheduledRunLoopPairs()) {
251         SchedulePairHashSet::iterator end = scheduledPairs->end();
252         for (SchedulePairHashSet::iterator it = scheduledPairs->begin(); it != end; ++it) {
253             if (NSRunLoop *runLoop = (*it)->nsRunLoop()) {
254                 [connection() scheduleInRunLoop:runLoop forMode:(NSString *)(*it)->mode()];
255                 scheduled = true;
256             }
257         }
258     }
259
260     if (NSOperationQueue *operationQueue = context->scheduledOperationQueue()) {
261         ASSERT(!scheduled);
262         [connection() setDelegateQueue:operationQueue];
263         scheduled = true;
264     }
265
266     // Start the connection if we did schedule with at least one runloop.
267     // We can't start the connection until we have one runloop scheduled.
268     if (scheduled)
269         [connection() start];
270     else
271         d->m_startWhenScheduled = true;
272
273     LOG(Network, "Handle %p starting connection %p for %@", this, connection(), firstRequest().nsURLRequest());
274     
275     if (d->m_connection) {
276         if (d->m_defersLoading)
277             wkSetNSURLConnectionDefersCallbacks(connection(), YES);
278
279         return true;
280     }
281
282     END_BLOCK_OBJC_EXCEPTIONS;
283
284     return false;
285 }
286
287 void ResourceHandle::cancel()
288 {
289     LOG(Network, "Handle %p cancel connection %p", this, d->m_connection.get());
290
291     // Leaks were seen on HTTP tests without this; can be removed once <rdar://problem/6886937> is fixed.
292     if (d->m_currentMacChallenge)
293         [[d->m_currentMacChallenge sender] cancelAuthenticationChallenge:d->m_currentMacChallenge];
294
295     [d->m_connection.get() cancel];
296 }
297
298 void ResourceHandle::platformSetDefersLoading(bool defers)
299 {
300     if (d->m_connection)
301         wkSetNSURLConnectionDefersCallbacks(d->m_connection.get(), defers);
302 }
303
304 void ResourceHandle::schedule(SchedulePair* pair)
305 {
306     NSRunLoop *runLoop = pair->nsRunLoop();
307     if (!runLoop)
308         return;
309     [d->m_connection.get() scheduleInRunLoop:runLoop forMode:(NSString *)pair->mode()];
310     if (d->m_startWhenScheduled) {
311         [d->m_connection.get() start];
312         d->m_startWhenScheduled = false;
313     }
314 }
315
316 void ResourceHandle::unschedule(SchedulePair* pair)
317 {
318     if (NSRunLoop *runLoop = pair->nsRunLoop())
319         [d->m_connection.get() unscheduleFromRunLoop:runLoop forMode:(NSString *)pair->mode()];
320 }
321
322 WebCoreResourceHandleAsDelegate *ResourceHandle::delegate()
323 {
324     if (!d->m_delegate) {
325         WebCoreResourceHandleAsDelegate *delegate = [[WebCoreResourceHandleAsDelegate alloc] initWithHandle:this];
326         d->m_delegate = delegate;
327         [delegate release];
328     }
329     return d->m_delegate.get();
330 }
331
332 void ResourceHandle::releaseDelegate()
333 {
334     if (!d->m_delegate)
335         return;
336     if (d->m_proxy)
337         [d->m_proxy.get() setDelegate:nil];
338     [d->m_delegate.get() detachHandle];
339     d->m_delegate = nil;
340 }
341
342 id ResourceHandle::releaseProxy()
343 {
344     id proxy = [[d->m_proxy.get() retain] autorelease];
345     d->m_proxy = nil;
346     [proxy setDelegate:nil];
347     return proxy;
348 }
349
350 NSURLConnection *ResourceHandle::connection() const
351 {
352     return d->m_connection.get();
353 }
354
355 bool ResourceHandle::loadsBlocked()
356 {
357     return false;
358 }
359
360 bool ResourceHandle::willLoadFromCache(ResourceRequest& request, Frame*)
361 {
362     request.setCachePolicy(ReturnCacheDataDontLoad);
363     NSURLResponse *nsURLResponse = nil;
364     BEGIN_BLOCK_OBJC_EXCEPTIONS;
365
366     [NSURLConnection sendSynchronousRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:nil];
367     
368     END_BLOCK_OBJC_EXCEPTIONS;
369     
370     return nsURLResponse;
371 }
372
373 CFStringRef ResourceHandle::synchronousLoadRunLoopMode()
374 {
375     return CFSTR("WebCoreSynchronousLoaderRunLoopMode");
376 }
377
378 void ResourceHandle::loadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
379 {
380     LOG(Network, "ResourceHandle::loadResourceSynchronously:%@ allowStoredCredentials:%u", request.nsURLRequest(), storedCredentials);
381
382 #if ENABLE(BLOB)
383     if (request.url().protocolIs("blob"))
384         if (blobRegistry().loadResourceSynchronously(request, error, response, data))
385             return;
386 #endif
387
388     NSError *nsError = nil;
389     NSURLResponse *nsURLResponse = nil;
390     NSData *result = nil;
391
392     ASSERT(!request.isEmpty());
393     
394     OwnPtr<WebCoreSynchronousLoaderClient> client = WebCoreSynchronousLoaderClient::create();
395     client->setAllowStoredCredentials(storedCredentials == AllowStoredCredentials);
396
397     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(request, client.get(), false /*defersLoading*/, true /*shouldContentSniff*/));
398
399     handle->d->m_storageSession = context->storageSession().platformSession();
400
401     if (context && handle->d->m_scheduledFailureType != NoFailure) {
402         error = context->blockedError(request);
403         return;
404     }
405
406     handle->createNSURLConnection(
407         handle->delegate(), // A synchronous request cannot turn into a download, so there is no need to proxy the delegate.
408         storedCredentials == AllowStoredCredentials,
409         shouldRelaxThirdPartyCookiePolicy(context, request.url()),
410         handle->shouldContentSniff() || context->localFileContentSniffingEnabled());
411
412     [handle->connection() scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:(NSString *)synchronousLoadRunLoopMode()];
413     [handle->connection() start];
414     
415     while (!client->isDone())
416         [[NSRunLoop currentRunLoop] runMode:(NSString *)synchronousLoadRunLoopMode() beforeDate:[NSDate distantFuture]];
417
418     result = client->data();
419     nsURLResponse = client->response();
420     nsError = client->error();
421     
422     [handle->connection() cancel];
423
424
425     if (!nsError)
426         response = nsURLResponse;
427     else {
428         response = ResourceResponse(request.url(), String(), 0, String(), String());
429         if ([nsError domain] == NSURLErrorDomain)
430             switch ([nsError code]) {
431                 case NSURLErrorUserCancelledAuthentication:
432                     // FIXME: we should really return the actual HTTP response, but sendSynchronousRequest doesn't provide us with one.
433                     response.setHTTPStatusCode(401);
434                     break;
435                 default:
436                     response.setHTTPStatusCode([nsError code]);
437             }
438         else
439             response.setHTTPStatusCode(404);
440     }
441     
442     data.resize([result length]);
443     memcpy(data.data(), [result bytes], [result length]);
444     
445     error = nsError;
446 }
447
448 void ResourceHandle::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
449 {
450     const KURL& url = request.url();
451     d->m_user = url.user();
452     d->m_pass = url.pass();
453     d->m_lastHTTPMethod = request.httpMethod();
454     request.removeCredentials();
455
456     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
457         // If the network layer carries over authentication headers from the original request
458         // in a cross-origin redirect, we want to clear those headers here.
459         // As of Lion, CFNetwork no longer does this.
460         request.clearHTTPAuthorization();
461     } else {
462         // Only consider applying authentication credentials if this is actually a redirect and the redirect
463         // URL didn't include credentials of its own.
464         if (d->m_user.isEmpty() && d->m_pass.isEmpty() && !redirectResponse.isNull()) {
465             Credential credential = CredentialStorage::get(request.url());
466             if (!credential.isEmpty()) {
467                 d->m_initialCredential = credential;
468                 
469                 // FIXME: Support Digest authentication, and Proxy-Authorization.
470                 applyBasicAuthorizationHeader(request, d->m_initialCredential);
471             }
472         }
473     }
474
475     RefPtr<ResourceHandle> protect(this);
476     client()->willSendRequest(this, request, redirectResponse);
477
478     // Client call may not preserve the session, especially if the request is sent over IPC.
479     if (!request.isNull())
480         request.setStorageSession(d->m_storageSession.get());
481 }
482
483 bool ResourceHandle::shouldUseCredentialStorage()
484 {
485     if (client())
486         return client()->shouldUseCredentialStorage(this);
487
488     return false;
489 }
490
491 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
492 {
493     ASSERT(!d->m_currentMacChallenge);
494     ASSERT(d->m_currentWebChallenge.isNull());
495     // Since NSURLConnection networking relies on keeping a reference to the original NSURLAuthenticationChallenge,
496     // we make sure that is actually present
497     ASSERT(challenge.nsURLAuthenticationChallenge());
498
499 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
500     // Proxy authentication is handled by CFNetwork internally. We can get here if the user cancels
501     // CFNetwork authentication dialog, and we shouldn't ask the client to display another one in that case.
502     if (challenge.protectionSpace().isProxy()) {
503         // Cannot use receivedRequestToContinueWithoutCredential(), because current challenge is not yet set.
504         [challenge.sender() continueWithoutCredentialForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
505         return;
506     }
507 #endif
508
509     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
510         NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:d->m_user
511                                                                    password:d->m_pass
512                                                                 persistence:NSURLCredentialPersistenceForSession];
513         d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
514         d->m_currentWebChallenge = challenge;
515         receivedCredential(challenge, core(credential));
516         [credential release];
517         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
518         d->m_user = String();
519         d->m_pass = String();
520         return;
521     }
522
523     if (!client() || client()->shouldUseCredentialStorage(this)) {
524         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
525             // The stored credential wasn't accepted, stop using it.
526             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
527             // but the observable effect should be very minor, if any.
528             CredentialStorage::remove(challenge.protectionSpace());
529         }
530
531         if (!challenge.previousFailureCount()) {
532             Credential credential = CredentialStorage::get(challenge.protectionSpace());
533             if (!credential.isEmpty() && credential != d->m_initialCredential) {
534                 ASSERT(credential.persistence() == CredentialPersistenceNone);
535                 if (challenge.failureResponse().httpStatusCode() == 401) {
536                     // Store the credential back, possibly adding it as a default for this directory.
537                     CredentialStorage::set(credential, challenge.protectionSpace(), challenge.failureResponse().url());
538                 }
539                 [challenge.sender() useCredential:mac(credential) forAuthenticationChallenge:mac(challenge)];
540                 return;
541             }
542         }
543     }
544
545     d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
546     d->m_currentWebChallenge = core(d->m_currentMacChallenge);
547     d->m_currentWebChallenge.setAuthenticationClient(this);
548
549     // FIXME: Several concurrent requests can return with the an authentication challenge for the same protection space.
550     // We should avoid making additional client calls for the same protection space when already waiting for the user,
551     // because typing the same credentials several times is annoying.
552     if (client())
553         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
554 }
555
556 void ResourceHandle::didCancelAuthenticationChallenge(const AuthenticationChallenge& challenge)
557 {
558     ASSERT(d->m_currentMacChallenge);
559     ASSERT(d->m_currentMacChallenge == challenge.nsURLAuthenticationChallenge());
560     ASSERT(!d->m_currentWebChallenge.isNull());
561
562     if (client())
563         client()->didCancelAuthenticationChallenge(this, challenge);
564 }
565
566 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
567 bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
568 {
569     if (client())
570         return client()->canAuthenticateAgainstProtectionSpace(this, protectionSpace);
571         
572     return false;
573 }
574 #endif
575
576 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
577 {
578     ASSERT(!challenge.isNull());
579     if (challenge != d->m_currentWebChallenge)
580         return;
581
582     // FIXME: Support empty credentials. Currently, an empty credential cannot be stored in WebCore credential storage, as that's empty value for its map.
583     if (credential.isEmpty()) {
584         receivedRequestToContinueWithoutCredential(challenge);
585         return;
586     }
587
588     if (credential.persistence() == CredentialPersistenceForSession && (!d->m_needsSiteSpecificQuirks || ![[[mac(challenge) protectionSpace] host] isEqualToString:@"gallery.me.com"])) {
589         // Manage per-session credentials internally, because once NSURLCredentialPersistenceForSession is used, there is no way
590         // to ignore it for a particular request (short of removing it altogether).
591         // <rdar://problem/6867598> gallery.me.com is temporarily whitelisted, so that QuickTime plug-in could see the credentials.
592         Credential webCredential(credential, CredentialPersistenceNone);
593         KURL urlToStore;
594         if (challenge.failureResponse().httpStatusCode() == 401)
595             urlToStore = challenge.failureResponse().url();
596         CredentialStorage::set(webCredential, core([d->m_currentMacChallenge protectionSpace]), urlToStore);
597         [[d->m_currentMacChallenge sender] useCredential:mac(webCredential) forAuthenticationChallenge:d->m_currentMacChallenge];
598     } else
599         [[d->m_currentMacChallenge sender] useCredential:mac(credential) forAuthenticationChallenge:d->m_currentMacChallenge];
600
601     clearAuthentication();
602 }
603
604 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
605 {
606     ASSERT(!challenge.isNull());
607     if (challenge != d->m_currentWebChallenge)
608         return;
609
610     [[d->m_currentMacChallenge sender] continueWithoutCredentialForAuthenticationChallenge:d->m_currentMacChallenge];
611
612     clearAuthentication();
613 }
614
615 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
616 {
617     if (challenge != d->m_currentWebChallenge)
618         return;
619
620     if (client())
621         client()->receivedCancellation(this, challenge);
622 }
623
624 } // namespace WebCore
625
626 @implementation WebCoreResourceHandleAsDelegate
627
628 - (id)initWithHandle:(ResourceHandle*)handle
629 {
630     self = [self init];
631     if (!self)
632         return nil;
633     m_handle = handle;
634     return self;
635 }
636
637 - (void)detachHandle
638 {
639     m_handle = 0;
640 }
641
642 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
643 {
644     UNUSED_PARAM(connection);
645
646     // the willSendRequest call may cancel this load, in which case self could be deallocated
647     RetainPtr<WebCoreResourceHandleAsDelegate> protect(self);
648
649     if (!m_handle || !m_handle->client())
650         return nil;
651     
652     // See <rdar://problem/5380697> .  This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often.
653     if (!redirectResponse)
654         return newRequest;
655
656 #if !LOG_DISABLED
657     if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
658         LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:%d, Location:<%@>", m_handle, connection, [newRequest description], static_cast<int>([(id)redirectResponse statusCode]), [[(id)redirectResponse allHeaderFields] objectForKey:@"Location"]);
659     else
660         LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]); 
661 #endif
662
663     if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)redirectResponse statusCode] == 307) {
664         String lastHTTPMethod = m_handle->lastHTTPMethod();
665         if (!equalIgnoringCase(lastHTTPMethod, String([newRequest HTTPMethod]))) {
666             NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
667             [mutableRequest setHTTPMethod:lastHTTPMethod];
668     
669             FormData* body = m_handle->firstRequest().httpBody();
670             if (!equalIgnoringCase(lastHTTPMethod, "GET") && body && !body->isEmpty())
671                 WebCore::setHTTPBody(mutableRequest, body);
672
673             String originalContentType = m_handle->firstRequest().httpContentType();
674             if (!originalContentType.isEmpty())
675                 [mutableRequest setValue:originalContentType forHTTPHeaderField:@"Content-Type"];
676
677             newRequest = [mutableRequest autorelease];
678         }
679     }
680
681     ResourceRequest request = newRequest;
682
683     // Should not set Referer after a redirect from a secure resource to non-secure one.
684     if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https"))
685         request.clearHTTPReferrer();
686
687     m_handle->willSendRequest(request, redirectResponse);
688
689     return request.nsURLRequest();
690 }
691
692 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
693 {
694     UNUSED_PARAM(connection);
695
696     LOG(Network, "Handle %p delegate connectionShouldUseCredentialStorage:%p", m_handle, connection);
697
698     if (!m_handle)
699         return NO;
700
701     return m_handle->shouldUseCredentialStorage();
702 }
703
704 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
705 {
706     UNUSED_PARAM(connection);
707
708     LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
709
710     if (!m_handle) {
711         [[challenge sender] cancelAuthenticationChallenge:challenge];
712         return;
713     }
714     m_handle->didReceiveAuthenticationChallenge(core(challenge));
715 }
716
717 - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
718 {
719     UNUSED_PARAM(connection);
720
721     LOG(Network, "Handle %p delegate connection:%p didCancelAuthenticationChallenge:%p", m_handle, connection, challenge);
722
723     if (!m_handle)
724         return;
725     m_handle->didCancelAuthenticationChallenge(core(challenge));
726 }
727
728 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
729 - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
730 {
731 #if LOG_DISABLED
732     UNUSED_PARAM(connection);
733 #endif
734
735     LOG(Network, "Handle %p delegate connection:%p canAuthenticateAgainstProtectionSpace:%p", m_handle, connection, protectionSpace);
736
737     if (!m_handle)
738         return NO;
739
740     return m_handle->canAuthenticateAgainstProtectionSpace(core(protectionSpace));
741 }
742 #endif
743
744 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
745 {
746     UNUSED_PARAM(connection);
747
748     LOG(Network, "Handle %p delegate connection:%p didReceiveResponse:%p (HTTP status %d, reported MIMEType '%s')", m_handle, connection, r, [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0, [[r MIMEType] UTF8String]);
749
750     if (!m_handle || !m_handle->client())
751         return;
752
753     // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
754     int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0;
755     if (statusCode != 304)
756         adjustMIMETypeIfNecessary([r _CFURLResponse]);
757
758     if ([m_handle->firstRequest().nsURLRequest() _propertyForKey:@"ForceHTMLMIMEType"])
759         [r _setMIMEType:@"text/html"];
760
761     m_handle->client()->didReceiveResponse(m_handle, r);
762 }
763
764 #if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
765 - (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
766 {
767     UNUSED_PARAM(connection);
768     LOG(Network, "Handle %p delegate connection:%p didReceiveDataArray:%p arraySize:%d", m_handle, connection, dataArray, [dataArray count]);
769
770     if (!dataArray)
771         return;
772
773     if (!m_handle || !m_handle->client())
774         return;
775
776     m_handle->handleDataArray(reinterpret_cast<CFArrayRef>(dataArray));
777     // The call to didReceiveData above can cancel a load, and if so, the delegate (self) could have been deallocated by this point.
778 }
779 #endif
780
781 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
782 {
783     UNUSED_PARAM(connection);
784     UNUSED_PARAM(lengthReceived);
785
786     LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
787
788     if (!m_handle || !m_handle->client())
789         return;
790     // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
791     // However, with today's computers and networking speeds, this won't happen in practice.
792     // Could be an issue with a giant local file.
793
794     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
795     // -1 means we do not provide any data about transfer size to inspector so it would use
796     // Content-Length headers or content size to show transfer size.
797     m_handle->client()->didReceiveData(m_handle, (const char*)[data bytes], [data length], -1);
798 }
799
800 - (void)connection:(NSURLConnection *)connection willStopBufferingData:(NSData *)data
801 {
802     UNUSED_PARAM(connection);
803
804     LOG(Network, "Handle %p delegate connection:%p willStopBufferingData:%p", m_handle, connection, data);
805
806     if (!m_handle || !m_handle->client())
807         return;
808     // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
809     // However, with today's computers and networking speeds, this won't happen in practice.
810     // Could be an issue with a giant local file.
811     m_handle->client()->willStopBufferingData(m_handle, (const char*)[data bytes], static_cast<int>([data length]));
812 }
813
814 - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
815 {
816     UNUSED_PARAM(connection);
817     UNUSED_PARAM(bytesWritten);
818
819     LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
820
821     if (!m_handle || !m_handle->client())
822         return;
823     m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
824 }
825
826 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
827 {
828     UNUSED_PARAM(connection);
829
830     LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
831
832     if (!m_handle || !m_handle->client())
833         return;
834
835     m_handle->client()->didFinishLoading(m_handle, 0);
836 }
837
838 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
839 {
840     UNUSED_PARAM(connection);
841
842     LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
843
844     if (!m_handle || !m_handle->client())
845         return;
846
847     m_handle->client()->didFail(m_handle, error);
848 }
849
850
851 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
852 {
853     LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
854
855     UNUSED_PARAM(connection);
856
857     if (!m_handle || !m_handle->client())
858         return nil;
859
860     // Workaround for <rdar://problem/6300990> Caching does not respect Vary HTTP header.
861     // FIXME: WebCore cache has issues with Vary, too (bug 58797, bug 71509).
862     if ([[cachedResponse response] isKindOfClass:[NSHTTPURLResponse class]]
863         && [[(NSHTTPURLResponse *)[cachedResponse response] allHeaderFields] objectForKey:@"Vary"])
864         return nil;
865
866     NSCachedURLResponse *newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse);
867     if (newResponse != cachedResponse)
868         return newResponse;
869     
870     return newResponse;
871 }
872
873 @end
874
875
876 WebCoreSynchronousLoaderClient::~WebCoreSynchronousLoaderClient()
877 {
878     [m_response release];
879     [m_data release];
880     [m_error release];
881 }
882
883 void WebCoreSynchronousLoaderClient::willSendRequest(ResourceHandle* handle, ResourceRequest& request, const ResourceResponse& /*redirectResponse*/)
884 {
885     // FIXME: This needs to be fixed to follow the redirect correctly even for cross-domain requests.
886     if (!protocolHostAndPortAreEqual(handle->firstRequest().url(), request.url())) {
887         ASSERT(!m_error);
888         m_error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
889         m_isDone = true;
890         request = 0;
891         return;
892     }
893 }
894
895 bool WebCoreSynchronousLoaderClient::shouldUseCredentialStorage(ResourceHandle*)
896 {
897     // FIXME: We should ask FrameLoaderClient whether using credential storage is globally forbidden.
898     return m_allowStoredCredentials;
899 }
900
901 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
902 bool WebCoreSynchronousLoaderClient::canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&)
903 {
904     // FIXME: We should ask FrameLoaderClient. <http://webkit.org/b/65196>
905     return true;
906 }
907 #endif
908
909 void WebCoreSynchronousLoaderClient::didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge)
910 {
911     // FIXME: The user should be asked for credentials, as in async case.
912     [challenge.sender() continueWithoutCredentialForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
913 }
914
915 void WebCoreSynchronousLoaderClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
916 {
917     [m_response release];
918     m_response = [response.nsURLResponse() copy];
919 }
920
921 void WebCoreSynchronousLoaderClient::didReceiveData(ResourceHandle*, const char* data, int length, int /*encodedDataLength*/)
922 {
923     if (!m_data)
924         m_data = [[NSMutableData alloc] init];
925     [m_data appendBytes:data length:length];
926 }
927
928 void WebCoreSynchronousLoaderClient::didFinishLoading(ResourceHandle*, double)
929 {
930     m_isDone = true;
931 }
932
933 void WebCoreSynchronousLoaderClient::didFail(ResourceHandle*, const ResourceError& error)
934 {
935     ASSERT(!m_error);
936
937     m_error = [error copy];
938     m_isDone = true;
939 }
940
941
942 #endif // !USE(CFNETWORK)