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