ec60079b67c8fdb45b221dc9ed74b7e404a088d2
[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     
609             FormData* body = m_handle->request().httpBody();
610             if (!equalIgnoringCase(originalMethod, "GET") && body && !body->isEmpty())
611                 WebCore::setHTTPBody(mutableRequest, body);
612
613             String originalContentType = m_handle->request().httpContentType();
614             if (!originalContentType.isEmpty())
615                 [mutableRequest setValue:originalContentType forHTTPHeaderField:@"Content-Type"];
616
617             newRequest = [mutableRequest autorelease];
618         }
619     }
620
621     CallbackGuard guard;
622     ResourceRequest request = newRequest;
623     m_handle->willSendRequest(request, redirectResponse);
624
625     if (!ResourceHandle::didSendBodyDataDelegateExists()) {
626         // The client may change the request's body stream, in which case we have to re-associate
627         // the handle with the new stream so upload progress callbacks continue to work correctly.
628         NSInputStream* oldBodyStream = [newRequest HTTPBodyStream];
629         NSInputStream* newBodyStream = [request.nsURLRequest() HTTPBodyStream];
630         if (oldBodyStream != newBodyStream) {
631             disassociateStreamWithResourceHandle(oldBodyStream);
632             associateStreamWithResourceHandle(newBodyStream, m_handle);
633         }
634     }
635
636     return request.nsURLRequest();
637 }
638
639 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
640 {
641     UNUSED_PARAM(connection);
642
643     LOG(Network, "Handle %p delegate connectionShouldUseCredentialStorage:%p", m_handle, connection);
644
645     if (!m_handle)
646         return NO;
647
648     CallbackGuard guard;
649     return m_handle->shouldUseCredentialStorage();
650 }
651
652 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
653 {
654     UNUSED_PARAM(connection);
655
656     LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
657
658     if (!m_handle)
659         return;
660     CallbackGuard guard;
661     m_handle->didReceiveAuthenticationChallenge(core(challenge));
662 }
663
664 - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
665 {
666     UNUSED_PARAM(connection);
667
668     LOG(Network, "Handle %p delegate connection:%p didCancelAuthenticationChallenge:%p", m_handle, connection, challenge);
669
670     if (!m_handle)
671         return;
672     CallbackGuard guard;
673     m_handle->didCancelAuthenticationChallenge(core(challenge));
674 }
675
676 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
677 {
678     UNUSED_PARAM(connection);
679
680     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]);
681
682     if (!m_handle || !m_handle->client())
683         return;
684     CallbackGuard guard;
685
686     [r adjustMIMETypeIfNecessary];
687
688     if ([m_handle->request().nsURLRequest() _propertyForKey:@"ForceHTMLMIMEType"])
689         [r _setMIMEType:@"text/html"];
690
691 #if ENABLE(WML)
692     const KURL& url = [r URL];
693     if (url.isLocalFile()) {
694         // FIXME: Workaround for <rdar://problem/6917571>: The WML file extension ".wml" is not mapped to
695         // the right MIME type, work around that CFNetwork problem, to unbreak WML support for local files.
696         const String& path = url.path();
697   
698         DEFINE_STATIC_LOCAL(const String, wmlExt, (".wml"));
699         if (path.endsWith(wmlExt, false)) {
700             static NSString* defaultMIMETypeString = [(NSString*) defaultMIMEType() retain];
701             if ([[r MIMEType] isEqualToString:defaultMIMETypeString])
702                 [r _setMIMEType:@"text/vnd.wap.wml"];
703         }
704     }
705 #endif
706
707     m_handle->client()->didReceiveResponse(m_handle, r);
708 }
709
710 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
711 {
712     UNUSED_PARAM(connection);
713
714     LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
715
716     if (!m_handle || !m_handle->client())
717         return;
718     // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
719     // However, with today's computers and networking speeds, this won't happen in practice.
720     // Could be an issue with a giant local file.
721     CallbackGuard guard;
722     m_handle->client()->didReceiveData(m_handle, (const char*)[data bytes], [data length], static_cast<int>(lengthReceived));
723 }
724
725 - (void)connection:(NSURLConnection *)connection willStopBufferingData:(NSData *)data
726 {
727     UNUSED_PARAM(connection);
728
729     LOG(Network, "Handle %p delegate connection:%p willStopBufferingData:%p", m_handle, connection, data);
730
731     if (!m_handle || !m_handle->client())
732         return;
733     // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
734     // However, with today's computers and networking speeds, this won't happen in practice.
735     // Could be an issue with a giant local file.
736     CallbackGuard guard;
737     m_handle->client()->willStopBufferingData(m_handle, (const char*)[data bytes], static_cast<int>([data length]));
738 }
739
740 - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
741 {
742     UNUSED_PARAM(connection);
743     UNUSED_PARAM(bytesWritten);
744
745     LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
746
747     if (!m_handle || !m_handle->client())
748         return;
749     CallbackGuard guard;
750     m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
751 }
752
753 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
754 {
755     UNUSED_PARAM(connection);
756
757     LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
758
759     if (!m_handle || !m_handle->client())
760         return;
761     CallbackGuard guard;
762
763     if (!ResourceHandle::didSendBodyDataDelegateExists())
764         disassociateStreamWithResourceHandle([m_handle->request().nsURLRequest() HTTPBodyStream]);
765
766     m_handle->client()->didFinishLoading(m_handle);
767 }
768
769 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
770 {
771     UNUSED_PARAM(connection);
772
773     LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
774
775     if (!m_handle || !m_handle->client())
776         return;
777     CallbackGuard guard;
778
779     if (!ResourceHandle::didSendBodyDataDelegateExists())
780         disassociateStreamWithResourceHandle([m_handle->request().nsURLRequest() HTTPBodyStream]);
781
782     m_handle->client()->didFail(m_handle, error);
783 }
784
785 #ifdef BUILDING_ON_TIGER
786 - (void)_callConnectionWillCacheResponseWithInfo:(NSMutableDictionary *)info
787 {
788     NSURLConnection *connection = [info objectForKey:@"connection"];
789     NSCachedURLResponse *cachedResponse = [info objectForKey:@"cachedResponse"];
790     NSCachedURLResponse *result = [self connection:connection willCacheResponse:cachedResponse];
791     if (result)
792         [info setObject:result forKey:@"result"];
793 }
794 #endif
795
796 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
797 {
798     LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
799
800 #ifdef BUILDING_ON_TIGER
801     // On Tiger CFURLConnection can sometimes call the connection:willCacheResponse: delegate method on
802     // a secondary thread instead of the main thread. If this happens perform the work on the main thread.
803     if (!pthread_main_np()) {
804         NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
805         if (connection)
806             [info setObject:connection forKey:@"connection"];
807         if (cachedResponse)
808             [info setObject:cachedResponse forKey:@"cachedResponse"];
809
810         // Include synchronous url connection's mode as an acceptable run loopmode
811         // <rdar://problem/5511842>
812         NSArray *modes = [[NSArray alloc] initWithObjects:(NSString *)kCFRunLoopCommonModes, @"NSSynchronousURLConnection_PrivateMode", nil];        
813         [self performSelectorOnMainThread:@selector(_callConnectionWillCacheResponseWithInfo:) withObject:info waitUntilDone:YES modes:modes];
814         [modes release];
815
816         NSCachedURLResponse *result = [[info valueForKey:@"result"] retain];
817         [info release];
818
819         return [result autorelease];
820     }
821 #else
822     UNUSED_PARAM(connection);
823 #endif
824
825 #ifndef NDEBUG
826     if (isInitializingConnection)
827         LOG_ERROR("connection:willCacheResponse: was called inside of [NSURLConnection initWithRequest:delegate:] (4067625)");
828 #endif
829
830     if (!m_handle || !m_handle->client())
831         return nil;
832
833     CallbackGuard guard;
834     
835     NSCachedURLResponse *newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse);
836     if (newResponse != cachedResponse)
837         return newResponse;
838     
839     CacheStoragePolicy policy = static_cast<CacheStoragePolicy>([newResponse storagePolicy]);
840         
841     m_handle->client()->willCacheResponse(m_handle, policy);
842
843     if (static_cast<NSURLCacheStoragePolicy>(policy) != [newResponse storagePolicy])
844         newResponse = [[[NSCachedURLResponse alloc] initWithResponse:[newResponse response]
845                                                                 data:[newResponse data]
846                                                             userInfo:[newResponse userInfo]
847                                                        storagePolicy:static_cast<NSURLCacheStoragePolicy>(policy)] autorelease];
848
849     return newResponse;
850 }
851
852 - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
853 {
854     if (!m_handle)
855         return;
856     m_handle->receivedCredential(core(challenge), core(credential));
857 }
858
859 - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
860 {
861     if (!m_handle)
862         return;
863     m_handle->receivedRequestToContinueWithoutCredential(core(challenge));
864 }
865
866 - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
867 {
868     if (!m_handle)
869         return;
870     m_handle->receivedCancellation(core(challenge));
871 }
872
873 @end
874
875 #ifndef BUILDING_ON_TIGER
876
877 @implementation WebCoreSynchronousLoader
878
879 - (BOOL)_isDone
880 {
881     return m_isDone;
882 }
883
884 - (void)dealloc
885 {
886     [m_url release];
887     [m_user release];
888     [m_pass release];
889     [m_response release];
890     [m_data release];
891     [m_error release];
892     
893     [super dealloc];
894 }
895
896 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
897 {
898     UNUSED_PARAM(connection);
899
900     LOG(Network, "WebCoreSynchronousLoader delegate connection:%p willSendRequest:%@ redirectResponse:%p", connection, [newRequest description], redirectResponse);
901
902     // FIXME: This needs to be fixed to follow the redirect correctly even for cross-domain requests.
903     if (m_url && !protocolHostAndPortAreEqual(m_url, [newRequest URL])) {
904         m_error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
905         m_isDone = YES;
906         return nil;
907     }
908
909     NSURL *copy = [[newRequest URL] copy];
910     [m_url release];
911     m_url = copy;
912
913     if (redirectResponse) {
914         // Take user/pass out of the URL.
915         [m_user release];
916         [m_pass release];
917         m_user = [[m_url user] copy];
918         m_pass = [[m_url password] copy];
919         if (m_user || m_pass) {
920             ResourceRequest requestWithoutCredentials = newRequest;
921             requestWithoutCredentials.removeCredentials();
922             return requestWithoutCredentials.nsURLRequest();
923         }
924     }
925
926     return newRequest;
927 }
928
929 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
930 {
931     UNUSED_PARAM(connection);
932
933     LOG(Network, "WebCoreSynchronousLoader delegate connectionShouldUseCredentialStorage:%p", connection);
934
935     // FIXME: We should ask FrameLoaderClient whether using credential storage is globally forbidden.
936     return m_allowStoredCredentials;
937 }
938
939 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
940 {
941     UNUSED_PARAM(connection);
942
943     LOG(Network, "WebCoreSynchronousLoader delegate connection:%p didReceiveAuthenticationChallenge:%p", connection, challenge);
944
945     if (m_user && m_pass) {
946         NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:m_user
947                                                                    password:m_pass
948                                                                 persistence:NSURLCredentialPersistenceNone];
949         KURL urlToStore;
950         if ([[challenge failureResponse] isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse*)[challenge failureResponse] statusCode] == 401)
951             urlToStore = m_url;
952         CredentialStorage::set(core(credential), core([challenge protectionSpace]), urlToStore);
953         
954         [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
955         [credential release];
956         [m_user release];
957         [m_pass release];
958         m_user = 0;
959         m_pass = 0;
960         return;
961     }
962     if ([challenge previousFailureCount] == 0 && m_allowStoredCredentials) {
963         Credential credential = CredentialStorage::get(core([challenge protectionSpace]));
964         if (!credential.isEmpty() && credential != m_initialCredential) {
965             ASSERT(credential.persistence() == CredentialPersistenceNone);
966             [[challenge sender] useCredential:mac(credential) forAuthenticationChallenge:challenge];
967             return;
968         }
969     }
970     // FIXME: The user should be asked for credentials, as in async case.
971     [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
972 }
973
974 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
975 {
976     UNUSED_PARAM(connection);
977
978     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]);
979
980     NSURLResponse *r = [response copy];
981     
982     [m_response release];
983     m_response = r;
984 }
985
986 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
987 {
988     UNUSED_PARAM(connection);
989
990     LOG(Network, "WebCoreSynchronousLoader delegate connection:%p didReceiveData:%p", connection, data);
991
992     if (!m_data)
993         m_data = [[NSMutableData alloc] init];
994     
995     [m_data appendData:data];
996 }
997
998 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
999 {
1000     UNUSED_PARAM(connection);
1001
1002     LOG(Network, "WebCoreSynchronousLoader delegate connectionDidFinishLoading:%p", connection);
1003
1004     m_isDone = YES;
1005 }
1006
1007 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
1008 {
1009     UNUSED_PARAM(connection);
1010
1011     LOG(Network, "WebCoreSynchronousLoader delegate connection:%p didFailWithError:%@", connection, error);
1012
1013     ASSERT(!m_error);
1014     
1015     m_error = [error retain];
1016     m_isDone = YES;
1017 }
1018
1019 - (NSData *)_data
1020 {
1021     return [[m_data retain] autorelease];
1022 }
1023
1024 - (NSURLResponse *)_response
1025 {
1026     return [[m_response retain] autorelease];
1027 }
1028
1029 - (NSError *)_error
1030 {
1031     return [[m_error retain] autorelease];
1032 }
1033
1034 + (NSData *)loadRequest:(NSURLRequest *)request allowStoredCredentials:(BOOL)allowStoredCredentials returningResponse:(NSURLResponse **)response error:(NSError **)error
1035 {
1036     LOG(Network, "WebCoreSynchronousLoader loadRequest:%@ allowStoredCredentials:%u", request, allowStoredCredentials);
1037
1038     WebCoreSynchronousLoader *delegate = [[WebCoreSynchronousLoader alloc] init];
1039
1040     KURL url([request URL]);
1041     delegate->m_user = [nsStringNilIfEmpty(url.user()) retain];
1042     delegate->m_pass = [nsStringNilIfEmpty(url.pass()) retain];
1043     delegate->m_allowStoredCredentials = allowStoredCredentials;
1044
1045     NSURLConnection *connection;
1046
1047     // Take user/pass out of the URL.
1048     // Credentials for ftp can only be passed in URL, the connection:didReceiveAuthenticationChallenge: delegate call won't be made.
1049     if ((delegate->m_user || delegate->m_pass) && url.protocolInHTTPFamily()) {
1050         ResourceRequest requestWithoutCredentials = request;
1051         requestWithoutCredentials.removeCredentials();
1052         connection = [[NSURLConnection alloc] initWithRequest:requestWithoutCredentials.nsURLRequest() delegate:delegate startImmediately:NO];
1053     } else {
1054         // <rdar://problem/7174050> - For URLs that match the paths of those previously challenged for HTTP Basic authentication, 
1055         // try and reuse the credential preemptively, as allowed by RFC 2617.
1056         ResourceRequest requestWithInitialCredentials = request;
1057         if (allowStoredCredentials && url.protocolInHTTPFamily())
1058             delegate->m_initialCredential = CredentialStorage::getDefaultAuthenticationCredential(url);
1059             
1060         if (!delegate->m_initialCredential.isEmpty()) {
1061             String authHeader = "Basic " + encodeBasicAuthorization(delegate->m_initialCredential.user(), delegate->m_initialCredential.password());
1062             requestWithInitialCredentials.addHTTPHeaderField("Authorization", authHeader);
1063         }
1064         connection = [[NSURLConnection alloc] initWithRequest:requestWithInitialCredentials.nsURLRequest() delegate:delegate startImmediately:NO];
1065     }
1066
1067     [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:WebCoreSynchronousLoaderRunLoopMode];
1068     [connection start];
1069     
1070     while (![delegate _isDone])
1071         [[NSRunLoop currentRunLoop] runMode:WebCoreSynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]];
1072
1073     NSData *data = [delegate _data];
1074     *response = [delegate _response];
1075     *error = [delegate _error];
1076     
1077     [connection cancel];
1078     
1079     [connection release];
1080     [delegate release];
1081
1082     LOG(Network, "WebCoreSynchronousLoader done");
1083
1084     return data;
1085 }
1086
1087 @end
1088
1089 #endif