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