Move WebCore into Source
[WebKit-https.git] / Source / WebCore / platform / network / mac / ResourceHandleMac.mm
1 /*
2  * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010 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 "Base64.h"
34 #import "BlobRegistry.h"
35 #import "BlockExceptions.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 "Page.h"
45 #import "ResourceError.h"
46 #import "ResourceResponse.h"
47 #import "SchedulePair.h"
48 #import "Settings.h"
49 #import "SharedBuffer.h"
50 #import "SubresourceLoader.h"
51 #import "WebCoreSystemInterface.h"
52 #import "WebCoreURLResponse.h"
53 #import <wtf/text/CString.h>
54 #import <wtf/UnusedParam.h>
55
56 #ifdef BUILDING_ON_TIGER
57 typedef int NSInteger;
58 #endif
59
60 using namespace WebCore;
61
62 @interface WebCoreResourceHandleAsDelegate : NSObject <NSURLConnectionDelegate> {
63     ResourceHandle* m_handle;
64 }
65 - (id)initWithHandle:(ResourceHandle*)handle;
66 - (void)detachHandle;
67 @end
68
69 // WebCoreNSURLConnectionDelegateProxy exists so that we can cast m_proxy to it in order
70 // to disambiguate the argument type in the -setDelegate: call.  This avoids a spurious
71 // warning that the compiler would otherwise emit.
72 @interface WebCoreNSURLConnectionDelegateProxy : NSObject <NSURLConnectionDelegate>
73 - (void)setDelegate:(id<NSURLConnectionDelegate>)delegate;
74 @end
75
76 @interface NSURLConnection (NSURLConnectionTigerPrivate)
77 - (NSData *)_bufferedData;
78 @end
79
80 @interface NSURLConnection (Details)
81 -(id)_initWithRequest:(NSURLRequest *)request delegate:(id)delegate usesCache:(BOOL)usesCacheFlag maxContentLength:(long long)maxContentLength startImmediately:(BOOL)startImmediately connectionProperties:(NSDictionary *)connectionProperties;
82 @end
83
84 @interface NSURLRequest (Details)
85 - (id)_propertyForKey:(NSString *)key;
86 @end
87
88 #ifndef BUILDING_ON_TIGER
89
90 class WebCoreSynchronousLoaderClient : public ResourceHandleClient {
91 public:
92     static PassOwnPtr<WebCoreSynchronousLoaderClient> create()
93     {
94         return adoptPtr(new WebCoreSynchronousLoaderClient);
95     }
96
97     virtual ~WebCoreSynchronousLoaderClient();
98
99     void setAllowStoredCredentials(bool allow) { m_allowStoredCredentials = allow; }
100     NSURLResponse *response() { return m_response; }
101     NSMutableData *data() { return m_data; }
102     NSError *error() { return m_error; }
103     bool isDone() { return m_isDone; }
104
105 private:
106     WebCoreSynchronousLoaderClient()
107         : m_allowStoredCredentials(false)
108         , m_response(0)
109         , m_data(0)
110         , m_error(0)
111         , m_isDone(false)
112     {
113     }
114
115     virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse& /*redirectResponse*/);
116     virtual bool shouldUseCredentialStorage(ResourceHandle*);
117     virtual void didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge&);
118     virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
119     virtual void didReceiveData(ResourceHandle*, const char*, int, int /*lengthReceived*/);
120     virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
121     virtual void didFail(ResourceHandle*, const ResourceError&);
122 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
123     virtual bool canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&);
124 #endif
125
126     bool m_allowStoredCredentials;
127     NSURLResponse *m_response;
128     NSMutableData *m_data;
129     NSError *m_error;
130     bool m_isDone;
131 };
132
133 static NSString *WebCoreSynchronousLoaderRunLoopMode = @"WebCoreSynchronousLoaderRunLoopMode";
134
135 #endif
136
137 namespace WebCore {
138
139 #ifdef BUILDING_ON_TIGER
140 static unsigned inNSURLConnectionCallback;
141 #endif
142
143 #ifndef NDEBUG
144 static bool isInitializingConnection;
145 #endif
146     
147 class CallbackGuard {
148 public:
149     CallbackGuard()
150     {
151 #ifdef BUILDING_ON_TIGER
152         ++inNSURLConnectionCallback;
153 #endif
154     }
155     ~CallbackGuard()
156     {
157 #ifdef BUILDING_ON_TIGER
158         ASSERT(inNSURLConnectionCallback > 0);
159         --inNSURLConnectionCallback;
160 #endif
161     }
162 };
163
164 #ifndef BUILDING_ON_TIGER
165 static String encodeBasicAuthorization(const String& user, const String& password)
166 {
167     CString unencodedString = (user + ":" + password).utf8();
168     Vector<char> unencoded(unencodedString.length());
169     std::copy(unencodedString.data(), unencodedString.data() + unencodedString.length(), unencoded.begin());
170     Vector<char> encoded;
171     base64Encode(unencoded, encoded);
172     return String(encoded.data(), encoded.size());
173 }
174 #endif
175
176 ResourceHandleInternal::~ResourceHandleInternal()
177 {
178 }
179
180 ResourceHandle::~ResourceHandle()
181 {
182     releaseDelegate();
183     d->m_currentWebChallenge.setAuthenticationClient(0);
184
185     LOG(Network, "Handle %p destroyed", this);
186 }
187
188 static const double MaxFoundationVersionWithoutdidSendBodyDataDelegate = 677.21;
189 bool ResourceHandle::didSendBodyDataDelegateExists()
190 {
191     return NSFoundationVersionNumber > MaxFoundationVersionWithoutdidSendBodyDataDelegate;
192 }
193
194 void ResourceHandle::createNSURLConnection(id delegate, bool shouldUseCredentialStorage, bool shouldContentSniff)
195 {
196     // Credentials for ftp can only be passed in URL, the connection:didReceiveAuthenticationChallenge: delegate call won't be made.
197     if ((!d->m_user.isEmpty() || !d->m_pass.isEmpty())
198 #ifndef BUILDING_ON_TIGER
199      && !firstRequest().url().protocolInHTTPFamily() // On Tiger, always pass credentials in URL, so that they get stored even if the request gets cancelled right away.
200 #endif
201     ) {
202         KURL urlWithCredentials(firstRequest().url());
203         urlWithCredentials.setUser(d->m_user);
204         urlWithCredentials.setPass(d->m_pass);
205         firstRequest().setURL(urlWithCredentials);
206     }
207
208     // If a URL already has cookies, then we'll relax the 3rd party cookie policy and accept new cookies.
209     NSHTTPCookieStorage *sharedStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
210     if ([sharedStorage cookieAcceptPolicy] == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain && [[sharedStorage cookiesForURL:firstRequest().url()] count])
211         firstRequest().setFirstPartyForCookies(firstRequest().url());
212
213 #if !defined(BUILDING_ON_TIGER)
214     if (shouldUseCredentialStorage && firstRequest().url().protocolInHTTPFamily()) {
215         if (d->m_user.isEmpty() && d->m_pass.isEmpty()) {
216             // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
217             // try and reuse the credential preemptively, as allowed by RFC 2617.
218             d->m_initialCredential = CredentialStorage::get(firstRequest().url());
219         } else {
220             // If there is already a protection space known for the URL, update stored credentials before sending a request.
221             // This makes it possible to implement logout by sending an XMLHttpRequest with known incorrect credentials, and aborting it immediately
222             // (so that an authentication dialog doesn't pop up).
223             CredentialStorage::set(Credential(d->m_user, d->m_pass, CredentialPersistenceNone), firstRequest().url());
224         }
225     }
226         
227     if (!d->m_initialCredential.isEmpty()) {
228         // FIXME: Support Digest authentication, and Proxy-Authorization.
229         String authHeader = "Basic " + encodeBasicAuthorization(d->m_initialCredential.user(), d->m_initialCredential.password());
230         firstRequest().addHTTPHeaderField("Authorization", authHeader);
231     }
232
233     NSURLRequest *nsRequest = firstRequest().nsURLRequest();
234     if (!shouldContentSniff) {
235         NSMutableURLRequest *mutableRequest = [[nsRequest copy] autorelease];
236         wkSetNSURLRequestShouldContentSniff(mutableRequest, NO);
237         nsRequest = mutableRequest;
238     }
239
240 #if !defined(BUILDING_ON_LEOPARD)
241     ASSERT([NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)]);
242     static bool supportsSettingConnectionProperties = true;
243 #else
244     static bool supportsSettingConnectionProperties = [NSURLConnection instancesRespondToSelector:@selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)];
245 #endif
246
247     if (supportsSettingConnectionProperties) {
248         NSDictionary *sessionID = shouldUseCredentialStorage ? [NSDictionary dictionary] : [NSDictionary dictionaryWithObject:@"WebKitPrivateSession" forKey:@"_kCFURLConnectionSessionID"];
249         NSDictionary *propertyDictionary = [NSDictionary dictionaryWithObject:sessionID forKey:@"kCFURLConnectionSocketStreamProperties"];
250         d->m_connection.adoptNS([[NSURLConnection alloc] _initWithRequest:nsRequest delegate:delegate usesCache:YES maxContentLength:0 startImmediately:NO connectionProperties:propertyDictionary]);
251         return;
252     }
253
254     d->m_connection.adoptNS([[NSURLConnection alloc] initWithRequest:nsRequest delegate:delegate startImmediately:NO]);
255     return;
256
257 #else
258     // Building on Tiger. Don't use WebCore credential storage, don't try to disable content sniffing.
259     UNUSED_PARAM(shouldUseCredentialStorage);
260     UNUSED_PARAM(shouldContentSniff);
261     d->m_connection.adoptNS([[NSURLConnection alloc] initWithRequest:firstRequest().nsURLRequest() delegate:delegate]);
262 #endif
263 }
264
265 bool ResourceHandle::start(NetworkingContext* context)
266 {
267     if (!context)
268         return false;
269
270     BEGIN_BLOCK_OBJC_EXCEPTIONS;
271
272     // If NetworkingContext is invalid then we are no longer attached to a Page,
273     // this must be an attempted load from an unload event handler, so let's just block it.
274     if (!context->isValid())
275         return false;
276
277 #ifndef NDEBUG
278     isInitializingConnection = YES;
279 #endif
280
281     ASSERT(!d->m_proxy);
282     d->m_proxy.adoptNS(wkCreateNSURLConnectionDelegateProxy());
283     [static_cast<WebCoreNSURLConnectionDelegateProxy*>(d->m_proxy.get()) setDelegate:ResourceHandle::delegate()];
284
285     bool shouldUseCredentialStorage = !client() || client()->shouldUseCredentialStorage(this);
286
287     if (!ResourceHandle::didSendBodyDataDelegateExists())
288         associateStreamWithResourceHandle([firstRequest().nsURLRequest() HTTPBodyStream], this);
289
290 #ifdef BUILDING_ON_TIGER
291     // A conditional request sent by WebCore (e.g. to update appcache) can be for a resource that is not cacheable by NSURLConnection,
292     // which can get confused and fail to load it in this case.
293     if (firstRequest().isConditional())
294         firstRequest().setCachePolicy(ReloadIgnoringCacheData);
295 #endif
296
297     d->m_needsSiteSpecificQuirks = context->needsSiteSpecificQuirks();
298
299     createNSURLConnection(
300         d->m_proxy.get(),
301         shouldUseCredentialStorage,
302         d->m_shouldContentSniff || context->localFileContentSniffingEnabled());
303
304 #ifndef BUILDING_ON_TIGER
305     bool scheduled = false;
306     if (SchedulePairHashSet* scheduledPairs = context->scheduledRunLoopPairs()) {
307         SchedulePairHashSet::iterator end = scheduledPairs->end();
308         for (SchedulePairHashSet::iterator it = scheduledPairs->begin(); it != end; ++it) {
309             if (NSRunLoop *runLoop = (*it)->nsRunLoop()) {
310                 [connection() scheduleInRunLoop:runLoop forMode:(NSString *)(*it)->mode()];
311                 scheduled = true;
312             }
313         }
314     }
315
316     // Start the connection if we did schedule with at least one runloop.
317     // We can't start the connection until we have one runloop scheduled.
318     if (scheduled)
319         [connection() start];
320     else
321         d->m_startWhenScheduled = true;
322 #endif
323
324 #ifndef NDEBUG
325     isInitializingConnection = NO;
326 #endif
327
328     LOG(Network, "Handle %p starting connection %p for %@", this, connection(), firstRequest().nsURLRequest());
329     
330     if (d->m_connection) {
331         if (d->m_defersLoading)
332             wkSetNSURLConnectionDefersCallbacks(connection(), YES);
333
334         return true;
335     }
336
337     END_BLOCK_OBJC_EXCEPTIONS;
338
339     return false;
340 }
341
342 void ResourceHandle::cancel()
343 {
344     LOG(Network, "Handle %p cancel connection %p", this, d->m_connection.get());
345
346     // Leaks were seen on HTTP tests without this; can be removed once <rdar://problem/6886937> is fixed.
347     if (d->m_currentMacChallenge)
348         [[d->m_currentMacChallenge sender] cancelAuthenticationChallenge:d->m_currentMacChallenge];
349
350     if (!ResourceHandle::didSendBodyDataDelegateExists())
351         disassociateStreamWithResourceHandle([firstRequest().nsURLRequest() HTTPBodyStream]);
352     [d->m_connection.get() cancel];
353 }
354
355 void ResourceHandle::platformSetDefersLoading(bool defers)
356 {
357     if (d->m_connection)
358         wkSetNSURLConnectionDefersCallbacks(d->m_connection.get(), defers);
359 }
360
361 void ResourceHandle::schedule(SchedulePair* pair)
362 {
363 #ifndef BUILDING_ON_TIGER
364     NSRunLoop *runLoop = pair->nsRunLoop();
365     if (!runLoop)
366         return;
367     [d->m_connection.get() scheduleInRunLoop:runLoop forMode:(NSString *)pair->mode()];
368     if (d->m_startWhenScheduled) {
369         [d->m_connection.get() start];
370         d->m_startWhenScheduled = false;
371     }
372 #else
373     UNUSED_PARAM(pair);
374 #endif
375 }
376
377 void ResourceHandle::unschedule(SchedulePair* pair)
378 {
379 #ifndef BUILDING_ON_TIGER
380     if (NSRunLoop *runLoop = pair->nsRunLoop())
381         [d->m_connection.get() unscheduleFromRunLoop:runLoop forMode:(NSString *)pair->mode()];
382 #else
383     UNUSED_PARAM(pair);
384 #endif
385 }
386
387 WebCoreResourceHandleAsDelegate *ResourceHandle::delegate()
388 {
389     if (!d->m_delegate) {
390         WebCoreResourceHandleAsDelegate *delegate = [[WebCoreResourceHandleAsDelegate alloc] initWithHandle:this];
391         d->m_delegate = delegate;
392         [delegate release];
393     }
394     return d->m_delegate.get();
395 }
396
397 void ResourceHandle::releaseDelegate()
398 {
399     if (!d->m_delegate)
400         return;
401     if (d->m_proxy)
402         [d->m_proxy.get() setDelegate:nil];
403     [d->m_delegate.get() detachHandle];
404     d->m_delegate = nil;
405 }
406
407 bool ResourceHandle::supportsBufferedData()
408 {
409     static bool supportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)];
410     return supportsBufferedData;
411 }
412
413 PassRefPtr<SharedBuffer> ResourceHandle::bufferedData()
414 {
415     if (ResourceHandle::supportsBufferedData())
416         return SharedBuffer::wrapNSData([d->m_connection.get() _bufferedData]);
417
418     return 0;
419 }
420
421 id ResourceHandle::releaseProxy()
422 {
423     id proxy = [[d->m_proxy.get() retain] autorelease];
424     d->m_proxy = nil;
425     [proxy setDelegate:nil];
426     return proxy;
427 }
428
429 NSURLConnection *ResourceHandle::connection() const
430 {
431     return d->m_connection.get();
432 }
433
434 bool ResourceHandle::loadsBlocked()
435 {
436 #ifndef BUILDING_ON_TIGER
437     return false;
438 #else
439     // On Tiger, if we're in an NSURLConnection callback, that blocks all other NSURLConnection callbacks.
440     // On Leopard and newer, it blocks only callbacks on that same NSURLConnection object, which is not
441     // a problem in practice.
442     return inNSURLConnectionCallback != 0;
443 #endif
444 }
445
446 bool ResourceHandle::willLoadFromCache(ResourceRequest& request, Frame*)
447 {
448 #ifndef BUILDING_ON_TIGER
449     request.setCachePolicy(ReturnCacheDataDontLoad);
450     NSURLResponse *nsURLResponse = nil;
451     BEGIN_BLOCK_OBJC_EXCEPTIONS;
452     
453    [NSURLConnection sendSynchronousRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:nil];
454     
455     END_BLOCK_OBJC_EXCEPTIONS;
456     
457     return nsURLResponse;
458 #else
459     // <rdar://problem/6803217> - Re-enable after <rdar://problem/6786454> is resolved.
460     UNUSED_PARAM(request);
461     return false;
462 #endif
463 }
464
465 void ResourceHandle::loadResourceSynchronously(NetworkingContext* context, const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data)
466 {
467     LOG(Network, "ResourceHandle::loadResourceSynchronously:%@ allowStoredCredentials:%u", request.nsURLRequest(), storedCredentials);
468
469 #if ENABLE(BLOB)
470     if (request.url().protocolIs("blob"))
471         if (blobRegistry().loadResourceSynchronously(request, error, response, data))
472             return;
473 #endif
474
475     NSError *nsError = nil;
476     NSURLResponse *nsURLResponse = nil;
477     NSData *result = nil;
478
479     ASSERT(!request.isEmpty());
480     
481 #ifndef BUILDING_ON_TIGER
482     OwnPtr<WebCoreSynchronousLoaderClient> client = WebCoreSynchronousLoaderClient::create();
483     client->setAllowStoredCredentials(storedCredentials == AllowStoredCredentials);
484
485     RefPtr<ResourceHandle> handle = adoptRef(new ResourceHandle(request, client.get(), false /*defersLoading*/, true /*shouldContentSniff*/));
486
487     if (context && handle->d->m_scheduledFailureType != NoFailure) {
488         error = context->blockedError(request);
489         return;
490     }
491
492     handle->createNSURLConnection(
493         handle->delegate(), // A synchronous request cannot turn into a download, so there is no need to proxy the delegate.
494         storedCredentials == AllowStoredCredentials,
495         handle->shouldContentSniff() || (context && context->localFileContentSniffingEnabled()));
496
497     [handle->connection() scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:WebCoreSynchronousLoaderRunLoopMode];
498     [handle->connection() start];
499     
500     while (!client->isDone())
501         [[NSRunLoop currentRunLoop] runMode:WebCoreSynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]];
502
503     result = client->data();
504     nsURLResponse = client->response();
505     nsError = client->error();
506     
507     [handle->connection() cancel];
508
509 #else
510     UNUSED_PARAM(storedCredentials);
511     UNUSED_PARAM(context);
512     NSURLRequest *firstRequest = request.nsURLRequest();
513
514     // If a URL already has cookies, then we'll relax the 3rd party cookie policy and accept new cookies.
515     NSHTTPCookieStorage *sharedStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
516     if ([sharedStorage cookieAcceptPolicy] == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain && [[sharedStorage cookiesForURL:[firstRequest URL]] count]) {
517         NSMutableURLRequest *mutableRequest = [[firstRequest mutableCopy] autorelease];
518         [mutableRequest setMainDocumentURL:[mutableRequest URL]];
519         firstRequest = mutableRequest;
520     }
521
522     BEGIN_BLOCK_OBJC_EXCEPTIONS;
523     result = [NSURLConnection sendSynchronousRequest:firstRequest returningResponse:&nsURLResponse error:&nsError];
524     END_BLOCK_OBJC_EXCEPTIONS;
525 #endif
526
527     if (!nsError)
528         response = nsURLResponse;
529     else {
530         response = ResourceResponse(request.url(), String(), 0, String(), String());
531         if ([nsError domain] == NSURLErrorDomain)
532             switch ([nsError code]) {
533                 case NSURLErrorUserCancelledAuthentication:
534                     // FIXME: we should really return the actual HTTP response, but sendSynchronousRequest doesn't provide us with one.
535                     response.setHTTPStatusCode(401);
536                     break;
537                 default:
538                     response.setHTTPStatusCode([nsError code]);
539             }
540         else
541             response.setHTTPStatusCode(404);
542     }
543     
544     data.resize([result length]);
545     memcpy(data.data(), [result bytes], [result length]);
546     
547     error = nsError;
548 }
549
550 void ResourceHandle::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse)
551 {
552     const KURL& url = request.url();
553     d->m_user = url.user();
554     d->m_pass = url.pass();
555     d->m_lastHTTPMethod = request.httpMethod();
556     request.removeCredentials();
557     if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
558         request.clearHTTPAuthorization();
559
560     client()->willSendRequest(this, request, redirectResponse);
561 }
562
563 bool ResourceHandle::shouldUseCredentialStorage()
564 {
565     if (client())
566         return client()->shouldUseCredentialStorage(this);
567
568     return false;
569 }
570
571 void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge)
572 {
573     ASSERT(!d->m_currentMacChallenge);
574     ASSERT(d->m_currentWebChallenge.isNull());
575     // Since NSURLConnection networking relies on keeping a reference to the original NSURLAuthenticationChallenge,
576     // we make sure that is actually present
577     ASSERT(challenge.nsURLAuthenticationChallenge());
578
579     if (!d->m_user.isNull() && !d->m_pass.isNull()) {
580         NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:d->m_user
581                                                                    password:d->m_pass
582                                                                 persistence:NSURLCredentialPersistenceForSession];
583         d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
584         d->m_currentWebChallenge = challenge;
585         receivedCredential(challenge, core(credential));
586         [credential release];
587         // FIXME: Per the specification, the user shouldn't be asked for credentials if there were incorrect ones provided explicitly.
588         d->m_user = String();
589         d->m_pass = String();
590         return;
591     }
592
593 #ifndef BUILDING_ON_TIGER
594     if (!client() || client()->shouldUseCredentialStorage(this)) {
595         if (!d->m_initialCredential.isEmpty() || challenge.previousFailureCount()) {
596             // The stored credential wasn't accepted, stop using it.
597             // There is a race condition here, since a different credential might have already been stored by another ResourceHandle,
598             // but the observable effect should be very minor, if any.
599             CredentialStorage::remove(challenge.protectionSpace());
600         }
601
602         if (!challenge.previousFailureCount()) {
603             Credential credential = CredentialStorage::get(challenge.protectionSpace());
604             if (!credential.isEmpty() && credential != d->m_initialCredential) {
605                 ASSERT(credential.persistence() == CredentialPersistenceNone);
606                 if (challenge.failureResponse().httpStatusCode() == 401) {
607                     // Store the credential back, possibly adding it as a default for this directory.
608                     CredentialStorage::set(credential, challenge.protectionSpace(), firstRequest().url());
609                 }
610                 [challenge.sender() useCredential:mac(credential) forAuthenticationChallenge:mac(challenge)];
611                 return;
612             }
613         }
614     }
615 #endif
616
617     d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge();
618     d->m_currentWebChallenge = core(d->m_currentMacChallenge);
619     d->m_currentWebChallenge.setAuthenticationClient(this);
620
621     if (client())
622         client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge);
623 }
624
625 void ResourceHandle::didCancelAuthenticationChallenge(const AuthenticationChallenge& challenge)
626 {
627     ASSERT(d->m_currentMacChallenge);
628     ASSERT(d->m_currentMacChallenge == challenge.nsURLAuthenticationChallenge());
629     ASSERT(!d->m_currentWebChallenge.isNull());
630
631     if (client())
632         client()->didCancelAuthenticationChallenge(this, challenge);
633 }
634
635 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
636 bool ResourceHandle::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace)
637 {
638     if (client())
639         return client()->canAuthenticateAgainstProtectionSpace(this, protectionSpace);
640         
641     return false;
642 }
643 #endif
644
645 void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential)
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 #ifdef BUILDING_ON_TIGER
658     if (credential.persistence() == CredentialPersistenceNone) {
659         // NSURLCredentialPersistenceNone doesn't work on Tiger, so we have to use session persistence.
660         Credential webCredential(credential.user(), credential.password(), CredentialPersistenceForSession);
661         [[d->m_currentMacChallenge sender] useCredential:mac(webCredential) forAuthenticationChallenge:d->m_currentMacChallenge];
662     } else
663 #else
664     if (credential.persistence() == CredentialPersistenceForSession && (!d->m_needsSiteSpecificQuirks || ![[[mac(challenge) protectionSpace] host] isEqualToString:@"gallery.me.com"])) {
665         // Manage per-session credentials internally, because once NSURLCredentialPersistenceForSession is used, there is no way
666         // to ignore it for a particular request (short of removing it altogether).
667         // <rdar://problem/6867598> gallery.me.com is temporarily whitelisted, so that QuickTime plug-in could see the credentials.
668         Credential webCredential(credential, CredentialPersistenceNone);
669         KURL urlToStore;
670         if (challenge.failureResponse().httpStatusCode() == 401)
671             urlToStore = firstRequest().url();
672         CredentialStorage::set(webCredential, core([d->m_currentMacChallenge protectionSpace]), urlToStore);
673         [[d->m_currentMacChallenge sender] useCredential:mac(webCredential) forAuthenticationChallenge:d->m_currentMacChallenge];
674     } else
675 #endif
676         [[d->m_currentMacChallenge sender] useCredential:mac(credential) forAuthenticationChallenge:d->m_currentMacChallenge];
677
678     clearAuthentication();
679 }
680
681 void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge)
682 {
683     ASSERT(!challenge.isNull());
684     if (challenge != d->m_currentWebChallenge)
685         return;
686
687     [[d->m_currentMacChallenge sender] continueWithoutCredentialForAuthenticationChallenge:d->m_currentMacChallenge];
688
689     clearAuthentication();
690 }
691
692 void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge)
693 {
694     if (challenge != d->m_currentWebChallenge)
695         return;
696
697     if (client())
698         client()->receivedCancellation(this, challenge);
699 }
700
701 } // namespace WebCore
702
703 @implementation WebCoreResourceHandleAsDelegate
704
705 - (id)initWithHandle:(ResourceHandle*)handle
706 {
707     self = [self init];
708     if (!self)
709         return nil;
710     m_handle = handle;
711     return self;
712 }
713
714 - (void)detachHandle
715 {
716     m_handle = 0;
717 }
718
719 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
720 {
721     UNUSED_PARAM(connection);
722
723     // the willSendRequest call may cancel this load, in which case self could be deallocated
724     RetainPtr<WebCoreResourceHandleAsDelegate> protect(self);
725
726     if (!m_handle || !m_handle->client())
727         return nil;
728     
729     // See <rdar://problem/5380697> .  This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often.
730     if (!redirectResponse)
731         return newRequest;
732
733 #if !LOG_DISABLED
734     if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
735         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"]);
736     else
737         LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]); 
738 #endif
739
740     if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)redirectResponse statusCode] == 307) {
741         String lastHTTPMethod = m_handle->lastHTTPMethod();
742         if (!equalIgnoringCase(lastHTTPMethod, String([newRequest HTTPMethod]))) {
743             NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
744             [mutableRequest setHTTPMethod:lastHTTPMethod];
745     
746             FormData* body = m_handle->firstRequest().httpBody();
747             if (!equalIgnoringCase(lastHTTPMethod, "GET") && body && !body->isEmpty())
748                 WebCore::setHTTPBody(mutableRequest, body);
749
750             String originalContentType = m_handle->firstRequest().httpContentType();
751             if (!originalContentType.isEmpty())
752                 [mutableRequest setValue:originalContentType forHTTPHeaderField:@"Content-Type"];
753
754             newRequest = [mutableRequest autorelease];
755         }
756     }
757
758     CallbackGuard guard;
759     ResourceRequest request = newRequest;
760
761     // Should not set Referer after a redirect from a secure resource to non-secure one.
762     if (!request.url().protocolIs("https") && protocolIs(request.httpReferrer(), "https"))
763         request.clearHTTPReferrer();
764
765     m_handle->willSendRequest(request, redirectResponse);
766
767     if (!ResourceHandle::didSendBodyDataDelegateExists()) {
768         // The client may change the request's body stream, in which case we have to re-associate
769         // the handle with the new stream so upload progress callbacks continue to work correctly.
770         NSInputStream* oldBodyStream = [newRequest HTTPBodyStream];
771         NSInputStream* newBodyStream = [request.nsURLRequest() HTTPBodyStream];
772         if (oldBodyStream != newBodyStream) {
773             disassociateStreamWithResourceHandle(oldBodyStream);
774             associateStreamWithResourceHandle(newBodyStream, m_handle);
775         }
776     }
777
778     return request.nsURLRequest();
779 }
780
781 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
782 {
783     UNUSED_PARAM(connection);
784
785     LOG(Network, "Handle %p delegate connectionShouldUseCredentialStorage:%p", m_handle, connection);
786
787     if (!m_handle)
788         return NO;
789
790     CallbackGuard guard;
791     return m_handle->shouldUseCredentialStorage();
792 }
793
794 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
795 {
796     UNUSED_PARAM(connection);
797
798     LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
799
800     if (!m_handle)
801         return;
802     CallbackGuard guard;
803     m_handle->didReceiveAuthenticationChallenge(core(challenge));
804 }
805
806 - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
807 {
808     UNUSED_PARAM(connection);
809
810     LOG(Network, "Handle %p delegate connection:%p didCancelAuthenticationChallenge:%p", m_handle, connection, challenge);
811
812     if (!m_handle)
813         return;
814     CallbackGuard guard;
815     m_handle->didCancelAuthenticationChallenge(core(challenge));
816 }
817
818 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
819 - (BOOL)connection:(NSURLConnection *)unusedConnection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
820 {
821     UNUSED_PARAM(unusedConnection);
822     
823     if (!m_handle)
824         return NO;
825         
826     CallbackGuard guard;
827     return m_handle->canAuthenticateAgainstProtectionSpace(core(protectionSpace));
828 }
829 #endif
830
831 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
832 {
833     UNUSED_PARAM(connection);
834
835     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]);
836
837     if (!m_handle || !m_handle->client())
838         return;
839     CallbackGuard guard;
840
841     // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
842     int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0;
843     if (statusCode != 304)
844         [r adjustMIMETypeIfNecessary];
845
846     if ([m_handle->firstRequest().nsURLRequest() _propertyForKey:@"ForceHTMLMIMEType"])
847         [r _setMIMEType:@"text/html"];
848
849 #if ENABLE(WML)
850     const KURL& url = [r URL];
851     if (url.isLocalFile()) {
852         // FIXME: Workaround for <rdar://problem/6917571>: The WML file extension ".wml" is not mapped to
853         // the right MIME type, work around that CFNetwork problem, to unbreak WML support for local files.
854         const String& path = url.path();
855   
856         DEFINE_STATIC_LOCAL(const String, wmlExt, (".wml"));
857         if (path.endsWith(wmlExt, false)) {
858             static NSString* defaultMIMETypeString = [(NSString*) defaultMIMEType() retain];
859             if ([[r MIMEType] isEqualToString:defaultMIMETypeString])
860                 [r _setMIMEType:@"text/vnd.wap.wml"];
861         }
862     }
863 #endif
864
865     m_handle->client()->didReceiveResponse(m_handle, r);
866 }
867
868 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
869 {
870     UNUSED_PARAM(connection);
871
872     LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
873
874     if (!m_handle || !m_handle->client())
875         return;
876     // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
877     // However, with today's computers and networking speeds, this won't happen in practice.
878     // Could be an issue with a giant local file.
879     CallbackGuard guard;
880     m_handle->client()->didReceiveData(m_handle, (const char*)[data bytes], [data length], static_cast<int>(lengthReceived));
881 }
882
883 - (void)connection:(NSURLConnection *)connection willStopBufferingData:(NSData *)data
884 {
885     UNUSED_PARAM(connection);
886
887     LOG(Network, "Handle %p delegate connection:%p willStopBufferingData:%p", m_handle, connection, data);
888
889     if (!m_handle || !m_handle->client())
890         return;
891     // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
892     // However, with today's computers and networking speeds, this won't happen in practice.
893     // Could be an issue with a giant local file.
894     CallbackGuard guard;
895     m_handle->client()->willStopBufferingData(m_handle, (const char*)[data bytes], static_cast<int>([data length]));
896 }
897
898 - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
899 {
900     UNUSED_PARAM(connection);
901     UNUSED_PARAM(bytesWritten);
902
903     LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
904
905     if (!m_handle || !m_handle->client())
906         return;
907     CallbackGuard guard;
908     m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
909 }
910
911 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
912 {
913     UNUSED_PARAM(connection);
914
915     LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
916
917     if (!m_handle || !m_handle->client())
918         return;
919     CallbackGuard guard;
920
921     if (!ResourceHandle::didSendBodyDataDelegateExists())
922         disassociateStreamWithResourceHandle([m_handle->firstRequest().nsURLRequest() HTTPBodyStream]);
923
924     m_handle->client()->didFinishLoading(m_handle, 0);
925 }
926
927 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
928 {
929     UNUSED_PARAM(connection);
930
931     LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
932
933     if (!m_handle || !m_handle->client())
934         return;
935     CallbackGuard guard;
936
937     if (!ResourceHandle::didSendBodyDataDelegateExists())
938         disassociateStreamWithResourceHandle([m_handle->firstRequest().nsURLRequest() HTTPBodyStream]);
939
940     m_handle->client()->didFail(m_handle, error);
941 }
942
943 #ifdef BUILDING_ON_TIGER
944 - (void)_callConnectionWillCacheResponseWithInfo:(NSMutableDictionary *)info
945 {
946     NSURLConnection *connection = [info objectForKey:@"connection"];
947     NSCachedURLResponse *cachedResponse = [info objectForKey:@"cachedResponse"];
948     NSCachedURLResponse *result = [self connection:connection willCacheResponse:cachedResponse];
949     if (result)
950         [info setObject:result forKey:@"result"];
951 }
952 #endif
953
954 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
955 {
956     LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
957
958 #ifdef BUILDING_ON_TIGER
959     // On Tiger CFURLConnection can sometimes call the connection:willCacheResponse: delegate method on
960     // a secondary thread instead of the main thread. If this happens perform the work on the main thread.
961     if (!pthread_main_np()) {
962         NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
963         if (connection)
964             [info setObject:connection forKey:@"connection"];
965         if (cachedResponse)
966             [info setObject:cachedResponse forKey:@"cachedResponse"];
967
968         // Include synchronous url connection's mode as an acceptable run loopmode
969         // <rdar://problem/5511842>
970         NSArray *modes = [[NSArray alloc] initWithObjects:(NSString *)kCFRunLoopCommonModes, @"NSSynchronousURLConnection_PrivateMode", nil];        
971         [self performSelectorOnMainThread:@selector(_callConnectionWillCacheResponseWithInfo:) withObject:info waitUntilDone:YES modes:modes];
972         [modes release];
973
974         NSCachedURLResponse *result = [[info valueForKey:@"result"] retain];
975         [info release];
976
977         return [result autorelease];
978     }
979 #else
980     UNUSED_PARAM(connection);
981 #endif
982
983 #ifndef NDEBUG
984     if (isInitializingConnection)
985         LOG_ERROR("connection:willCacheResponse: was called inside of [NSURLConnection initWithRequest:delegate:] (4067625)");
986 #endif
987
988     if (!m_handle || !m_handle->client())
989         return nil;
990
991     CallbackGuard guard;
992     
993     NSCachedURLResponse *newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse);
994     if (newResponse != cachedResponse)
995         return newResponse;
996     
997     CacheStoragePolicy policy = static_cast<CacheStoragePolicy>([newResponse storagePolicy]);
998         
999     m_handle->client()->willCacheResponse(m_handle, policy);
1000
1001     if (static_cast<NSURLCacheStoragePolicy>(policy) != [newResponse storagePolicy])
1002         newResponse = [[[NSCachedURLResponse alloc] initWithResponse:[newResponse response]
1003                                                                 data:[newResponse data]
1004                                                             userInfo:[newResponse userInfo]
1005                                                        storagePolicy:static_cast<NSURLCacheStoragePolicy>(policy)] autorelease];
1006
1007     return newResponse;
1008 }
1009
1010 @end
1011
1012 #ifndef BUILDING_ON_TIGER
1013
1014 WebCoreSynchronousLoaderClient::~WebCoreSynchronousLoaderClient()
1015 {
1016     [m_response release];
1017     [m_data release];
1018     [m_error release];
1019 }
1020
1021 void WebCoreSynchronousLoaderClient::willSendRequest(ResourceHandle* handle, ResourceRequest& request, const ResourceResponse& /*redirectResponse*/)
1022 {
1023     // FIXME: This needs to be fixed to follow the redirect correctly even for cross-domain requests.
1024     if (!protocolHostAndPortAreEqual(handle->firstRequest().url(), request.url())) {
1025         ASSERT(!m_error);
1026         m_error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
1027         m_isDone = true;
1028         request = 0;
1029         return;
1030     }
1031 }
1032
1033 bool WebCoreSynchronousLoaderClient::shouldUseCredentialStorage(ResourceHandle*)
1034 {
1035     // FIXME: We should ask FrameLoaderClient whether using credential storage is globally forbidden.
1036     return m_allowStoredCredentials;
1037 }
1038
1039 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
1040 bool WebCoreSynchronousLoaderClient::canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace&)
1041 {
1042     // FIXME: We should ask FrameLoaderClient.
1043     return true;
1044 }
1045 #endif
1046
1047 void WebCoreSynchronousLoaderClient::didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge)
1048 {
1049     // FIXME: The user should be asked for credentials, as in async case.
1050     [challenge.sender() continueWithoutCredentialForAuthenticationChallenge:challenge.nsURLAuthenticationChallenge()];
1051 }
1052
1053 void WebCoreSynchronousLoaderClient::didReceiveResponse(ResourceHandle*, const ResourceResponse& response)
1054 {
1055     [m_response release];
1056     m_response = [response.nsURLResponse() copy];
1057 }
1058
1059 void WebCoreSynchronousLoaderClient::didReceiveData(ResourceHandle*, const char* data, int length, int /*lengthReceived*/)
1060 {
1061     if (!m_data)
1062         m_data = [[NSMutableData alloc] init];
1063     [m_data appendBytes:data length:length];
1064 }
1065
1066 void WebCoreSynchronousLoaderClient::didFinishLoading(ResourceHandle*, double)
1067 {
1068     m_isDone = true;
1069 }
1070
1071 void WebCoreSynchronousLoaderClient::didFail(ResourceHandle*, const ResourceError& error)
1072 {
1073     ASSERT(!m_error);
1074
1075     m_error = [error copy];
1076     m_isDone = true;
1077 }
1078
1079 #endif // BUILDING_ON_TIGER
1080
1081 #endif // !USE(CFNETWORK)