/* * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "ResourceHandle.h" #import "ResourceHandleInternal.h" #import "AuthenticationMac.h" #import "BlockExceptions.h" #import "DocLoader.h" #import "Frame.h" #import "FrameLoader.h" #import "ResourceError.h" #import "ResourceResponse.h" #import "SharedBuffer.h" #import "SubresourceLoader.h" #import "AuthenticationChallenge.h" #import "WebCoreSystemInterface.h" using namespace WebCore; @interface WebCoreResourceHandleAsDelegate : NSObject { ResourceHandle* m_handle; #ifndef BUILDING_ON_TIGER NSURL *m_url; #endif } - (id)initWithHandle:(ResourceHandle*)handle; - (void)detachHandle; @end @interface NSURLConnection (NSURLConnectionTigerPrivate) - (NSData *)_bufferedData; @end @interface NSURLProtocol (WebFoundationSecret) + (void)_removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request; @end #ifndef BUILDING_ON_TIGER @interface WebCoreSynchronousLoader : NSObject { NSURL *m_url; NSURLResponse *m_response; NSMutableData *m_data; NSError *m_error; BOOL m_isDone; } + (NSData *)loadRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error; @end static NSString *WebCoreSynchronousLoaderRunLoopMode = @"WebCoreSynchronousLoaderRunLoopMode"; #endif namespace WebCore { static unsigned inNSURLConnectionCallback; static bool NSURLConnectionSupportsBufferedData; #ifndef NDEBUG static bool isInitializingConnection; #endif ResourceHandleInternal::~ResourceHandleInternal() { } ResourceHandle::~ResourceHandle() { releaseDelegate(); } bool ResourceHandle::start(Frame* frame) { if (!frame) return false; BEGIN_BLOCK_OBJC_EXCEPTIONS; // If we are no longer attached to a Page, this must be an attempted load from an // onUnload handler, so let's just block it. if (!frame->page()) return false; #ifndef NDEBUG isInitializingConnection = YES; #endif id delegate; if (d->m_mightDownloadFromHandle) { ASSERT(!d->m_proxy); d->m_proxy = wkCreateNSURLConnectionDelegateProxy(); [d->m_proxy.get() setDelegate:ResourceHandle::delegate()]; [d->m_proxy.get() release]; delegate = d->m_proxy.get(); } else delegate = ResourceHandle::delegate(); NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:d->m_request.nsURLRequest() delegate:delegate]; #ifndef NDEBUG isInitializingConnection = NO; #endif d->m_connection = connection; [connection release]; if (d->m_defersLoading) wkSetNSURLConnectionDefersCallbacks(d->m_connection.get(), YES); if (d->m_connection) return true; END_BLOCK_OBJC_EXCEPTIONS; return false; } void ResourceHandle::cancel() { [d->m_connection.get() cancel]; } void ResourceHandle::setDefersLoading(bool defers) { d->m_defersLoading = defers; wkSetNSURLConnectionDefersCallbacks(d->m_connection.get(), defers); } WebCoreResourceHandleAsDelegate *ResourceHandle::delegate() { if (!d->m_delegate) { WebCoreResourceHandleAsDelegate *delegate = [[WebCoreResourceHandleAsDelegate alloc] initWithHandle:this]; d->m_delegate = delegate; [delegate release]; } return d->m_delegate.get(); } void ResourceHandle::releaseDelegate() { if (!d->m_delegate) return; if (d->m_proxy) [d->m_proxy.get() setDelegate:nil]; [d->m_delegate.get() detachHandle]; d->m_delegate = nil; } bool ResourceHandle::supportsBufferedData() { static bool initialized = false; if (!initialized) { NSURLConnectionSupportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)]; initialized = true; } return NSURLConnectionSupportsBufferedData; } PassRefPtr ResourceHandle::bufferedData() { if (ResourceHandle::supportsBufferedData()) return SharedBuffer::wrapNSData([d->m_connection.get() _bufferedData]); return 0; } id ResourceHandle::releaseProxy() { id proxy = [[d->m_proxy.get() retain] autorelease]; d->m_proxy = nil; [proxy setDelegate:nil]; return proxy; } NSURLConnection *ResourceHandle::connection() const { return d->m_connection.get(); } bool ResourceHandle::loadsBlocked() { return inNSURLConnectionCallback != 0; } bool ResourceHandle::willLoadFromCache(ResourceRequest& request) { request.setCachePolicy(ReturnCacheDataDontLoad); NSURLResponse *nsURLResponse = nil; BEGIN_BLOCK_OBJC_EXCEPTIONS; [NSURLConnection sendSynchronousRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:nil]; END_BLOCK_OBJC_EXCEPTIONS; return nsURLResponse; } void ResourceHandle::loadResourceSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector& data) { NSError *nsError = nil; NSURLResponse *nsURLResponse = nil; NSData *result = nil; ASSERT(!request.isEmpty()); BEGIN_BLOCK_OBJC_EXCEPTIONS; #ifndef BUILDING_ON_TIGER result = [WebCoreSynchronousLoader loadRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:&nsError]; #else result = [NSURLConnection sendSynchronousRequest:request.nsURLRequest() returningResponse:&nsURLResponse error:&nsError]; #endif END_BLOCK_OBJC_EXCEPTIONS; if (nsError == nil) response = nsURLResponse; else { response = ResourceResponse(request.url(), String(), 0, String(), String()); if ([nsError domain] == NSURLErrorDomain) switch ([nsError code]) { case NSURLErrorUserCancelledAuthentication: // FIXME: we should really return the actual HTTP response, but sendSynchronousRequest doesn't provide us with one. response.setHTTPStatusCode(401); break; default: response.setHTTPStatusCode([nsError code]); } else response.setHTTPStatusCode(404); } data.resize([result length]); memcpy(data.data(), [result bytes], [result length]); error = nsError; } void ResourceHandle::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge) { ASSERT(!d->m_currentMacChallenge); ASSERT(d->m_currentWebChallenge.isNull()); // Since NSURLConnection networking relies on keeping a reference to the original NSURLAuthenticationChallenge, // we make sure that is actually present ASSERT(challenge.nsURLAuthenticationChallenge()); d->m_currentMacChallenge = challenge.nsURLAuthenticationChallenge(); NSURLAuthenticationChallenge *webChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:d->m_currentMacChallenge sender:(id)delegate()]; d->m_currentWebChallenge = core(webChallenge); [webChallenge release]; if (client()) client()->didReceiveAuthenticationChallenge(this, d->m_currentWebChallenge); } void ResourceHandle::didCancelAuthenticationChallenge(const AuthenticationChallenge& challenge) { ASSERT(d->m_currentMacChallenge); ASSERT(!d->m_currentWebChallenge.isNull()); ASSERT(d->m_currentWebChallenge == challenge); if (client()) client()->didCancelAuthenticationChallenge(this, d->m_currentWebChallenge); } void ResourceHandle::receivedCredential(const AuthenticationChallenge& challenge, const Credential& credential) { ASSERT(!challenge.isNull()); if (challenge != d->m_currentWebChallenge) return; [[d->m_currentMacChallenge sender] useCredential:mac(credential) forAuthenticationChallenge:d->m_currentMacChallenge]; clearAuthentication(); } void ResourceHandle::receivedRequestToContinueWithoutCredential(const AuthenticationChallenge& challenge) { ASSERT(!challenge.isNull()); if (challenge != d->m_currentWebChallenge) return; [[d->m_currentMacChallenge sender] continueWithoutCredentialForAuthenticationChallenge:d->m_currentMacChallenge]; clearAuthentication(); } void ResourceHandle::receivedCancellation(const AuthenticationChallenge& challenge) { if (challenge != d->m_currentWebChallenge) return; if (client()) client()->receivedCancellation(this, challenge); } } // namespace WebCore @implementation WebCoreResourceHandleAsDelegate - (id)initWithHandle:(ResourceHandle*)handle { self = [self init]; if (!self) return nil; m_handle = handle; return self; } #ifndef BUILDING_ON_TIGER - (void)dealloc { [m_url release]; [super dealloc]; } #endif - (void)detachHandle { m_handle = 0; } - (NSURLRequest *)connection:(NSURLConnection *)con willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse { // the willSendRequest call may cancel this load, in which case self could be deallocated RetainPtr protect(self); if (!m_handle || !m_handle->client()) return nil; // See . This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often. if (!redirectResponse) return newRequest; ++inNSURLConnectionCallback; ResourceRequest request = newRequest; m_handle->client()->willSendRequest(m_handle, request, redirectResponse); --inNSURLConnectionCallback; #ifndef BUILDING_ON_TIGER NSURL *copy = [[request.nsURLRequest() URL] copy]; [m_url release]; m_url = copy; #endif return request.nsURLRequest(); } - (void)connection:(NSURLConnection *)con didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { #ifndef BUILDING_ON_TIGER if ([challenge previousFailureCount] == 0) { NSString *user = [m_url user]; NSString *password = [m_url password]; if (user && password) { NSURLCredential *credential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceForSession]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; return; } } #endif if (!m_handle) return; ++inNSURLConnectionCallback; m_handle->didReceiveAuthenticationChallenge(core(challenge)); --inNSURLConnectionCallback; } - (void)connection:(NSURLConnection *)con didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (!m_handle) return; ++inNSURLConnectionCallback; m_handle->didCancelAuthenticationChallenge(core(challenge)); --inNSURLConnectionCallback; } - (void)connection:(NSURLConnection *)con didReceiveResponse:(NSURLResponse *)r { if (!m_handle || !m_handle->client()) return; ++inNSURLConnectionCallback; m_handle->client()->didReceiveResponse(m_handle, r); --inNSURLConnectionCallback; } - (void)connection:(NSURLConnection *)con didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived { if (!m_handle || !m_handle->client()) return; // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing. // However, with today's computers and networking speeds, this won't happen in practice. // Could be an issue with a giant local file. ++inNSURLConnectionCallback; m_handle->client()->didReceiveData(m_handle, (const char*)[data bytes], [data length], static_cast(lengthReceived)); --inNSURLConnectionCallback; } - (void)connection:(NSURLConnection *)con willStopBufferingData:(NSData *)data { if (!m_handle || !m_handle->client()) return; // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing. // However, with today's computers and networking speeds, this won't happen in practice. // Could be an issue with a giant local file. ++inNSURLConnectionCallback; m_handle->client()->willStopBufferingData(m_handle, (const char*)[data bytes], static_cast([data length])); --inNSURLConnectionCallback; } - (void)connectionDidFinishLoading:(NSURLConnection *)con { if (!m_handle || !m_handle->client()) return; ++inNSURLConnectionCallback; m_handle->client()->didFinishLoading(m_handle); --inNSURLConnectionCallback; } - (void)connection:(NSURLConnection *)con didFailWithError:(NSError *)error { if (!m_handle || !m_handle->client()) return; ++inNSURLConnectionCallback; m_handle->client()->didFail(m_handle, error); --inNSURLConnectionCallback; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { #ifndef NDEBUG if (isInitializingConnection) LOG_ERROR("connection:willCacheResponse: was called inside of [NSURLConnection initWithRequest:delegate:] (4067625)"); #endif if (!m_handle || !m_handle->client()) return nil; ++inNSURLConnectionCallback; NSCachedURLResponse * newResponse = m_handle->client()->willCacheResponse(m_handle, cachedResponse); if (newResponse != cachedResponse) return newResponse; CacheStoragePolicy policy = static_cast([newResponse storagePolicy]); m_handle->client()->willCacheResponse(m_handle, policy); if (static_cast(policy) != [newResponse storagePolicy]) newResponse = [[[NSCachedURLResponse alloc] initWithResponse:[newResponse response] data:[newResponse data] userInfo:[newResponse userInfo] storagePolicy:static_cast(policy)] autorelease]; --inNSURLConnectionCallback; return newResponse; } - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (!m_handle) return; m_handle->receivedCredential(core(challenge), core(credential)); } - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (!m_handle) return; m_handle->receivedRequestToContinueWithoutCredential(core(challenge)); } - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (!m_handle) return; m_handle->receivedCancellation(core(challenge)); } @end #ifndef BUILDING_ON_TIGER @implementation WebCoreSynchronousLoader - (BOOL)_isDone { return m_isDone; } - (void)dealloc { [m_url release]; [m_response release]; [m_data release]; [m_error release]; [super dealloc]; } - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse { NSURL *copy = [[newRequest URL] copy]; [m_url release]; m_url = copy; return newRequest; } - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge previousFailureCount] == 0) { NSString *user = [m_url user]; NSString *password = [m_url password]; if (user && password) { NSURLCredential *credential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistenceForSession]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; return; } } [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSURLResponse *r = [response copy]; [m_response release]; m_response = r; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { if (!m_data) m_data = [[NSMutableData alloc] init]; [m_data appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { m_isDone = YES; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { ASSERT(!m_error); m_error = [error retain]; m_isDone = YES; } - (NSData *)_data { return [[m_data retain] autorelease]; } - (NSURLResponse *)_response { return [[m_response retain] autorelease]; } - (NSError *)_error { return [[m_error retain] autorelease]; } + (NSData *)loadRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error { WebCoreSynchronousLoader *delegate = [[WebCoreSynchronousLoader alloc] init]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:delegate startImmediately:NO]; [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:WebCoreSynchronousLoaderRunLoopMode]; [connection start]; while (![delegate _isDone]) [[NSRunLoop currentRunLoop] runMode:WebCoreSynchronousLoaderRunLoopMode beforeDate:[NSDate distantFuture]]; NSData *data = [delegate _data]; *response = [delegate _response]; *error = [delegate _error]; [connection cancel]; [connection release]; [delegate release]; return data; } @end #endif