2 WebBaseResourceHandleDelegate.m
3 Copyright (c) 2002, Apple Computer, Inc. All rights reserved.
6 #import <WebKit/WebBaseResourceHandleDelegate.h>
8 #import <Foundation/NSURLAuthenticationChallenge.h>
9 #import <Foundation/NSURLConnection.h>
10 #import <Foundation/NSURLConnectionPrivate.h>
11 #import <Foundation/NSURLRequest.h>
12 #import <Foundation/NSURLRequestPrivate.h>
13 #import <Foundation/NSURLResponse.h>
14 #import <Foundation/NSURLResponsePrivate.h>
15 #import <Foundation/NSError_NSURLExtras.h>
16 #import <Foundation/NSString_NSURLExtras.h>
18 #import <WebKit/WebAssertions.h>
19 #import <WebKit/WebDataProtocol.h>
20 #import <WebKit/WebDataSourcePrivate.h>
21 #import <WebKit/WebDefaultResourceLoadDelegate.h>
22 #import <WebKit/WebKitErrors.h>
23 #import <WebKit/WebKitErrorsPrivate.h>
24 #import <WebKit/WebPreferences.h>
25 #import <WebKit/WebPreferencesPrivate.h>
26 #import <WebKit/WebResourceLoadDelegate.h>
27 #import <WebKit/WebResourcePrivate.h>
28 #import <WebKit/WebViewPrivate.h>
30 static BOOL NSURLConnectionSupportsBufferedData;
32 @interface NSURLConnection (NSURLConnectionTigerPrivate)
33 - (NSData *)_bufferedData;
36 @interface WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender) <NSURLAuthenticationChallengeSender>
39 @implementation WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender)
41 - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
43 if (challenge == nil || challenge != currentWebChallenge) {
47 [[currentConnectionChallenge sender] useCredential:credential forAuthenticationChallenge:currentConnectionChallenge];
49 [currentConnectionChallenge release];
50 currentConnectionChallenge = nil;
52 [currentWebChallenge release];
53 currentWebChallenge = nil;
56 - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
58 if (challenge == nil || challenge != currentWebChallenge) {
62 [[currentConnectionChallenge sender] continueWithoutCredentialForAuthenticationChallenge:currentConnectionChallenge];
64 [currentConnectionChallenge release];
65 currentConnectionChallenge = nil;
67 [currentWebChallenge release];
68 currentWebChallenge = nil;
71 - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
73 if (challenge == nil || challenge != currentWebChallenge) {
82 // This declaration is only needed to ease the transition to a new SPI. It can be removed
83 // moving forward beyond Tiger 8A416.
84 @interface NSURLProtocol (WebFoundationSecret)
85 + (void)_removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
88 @implementation WebBaseResourceHandleDelegate
92 NSURLConnectionSupportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)];
95 - (void)releaseResources
97 ASSERT(!reachedTerminalState);
99 // It's possible that when we release the handle, it will be
100 // deallocated and release the last reference to this object.
101 // We need to retain to avoid accessing the object after it
102 // has been deallocated and also to avoid reentering this method.
106 [identifier release];
109 [connection release];
115 [dataSource release];
118 [resourceLoadDelegate release];
119 resourceLoadDelegate = nil;
121 [downloadDelegate release];
122 downloadDelegate = nil;
127 [resourceData release];
130 reachedTerminalState = YES;
137 ASSERT(reachedTerminalState);
140 [originalURL release];
144 - (void)deliverResource
147 ASSERT(waitingToDeliverResource);
149 if (!defersCallbacks) {
150 [self didReceiveResponse:[resource _response]];
151 NSData *data = [resource data];
152 [self didReceiveData:data lengthReceived:[data length]];
153 [self didFinishLoading];
154 deliveredResource = YES;
155 waitingToDeliverResource = NO;
159 - (void)deliverResourceAfterDelay
161 if (resource && !defersCallbacks && !waitingToDeliverResource && !deliveredResource) {
162 [self performSelector:@selector(deliverResource) withObject:nil afterDelay:0];
163 waitingToDeliverResource = YES;
167 // This is copied from [NSHTTPURLProtocol _cachedResponsePassesValidityChecks] and modified for our needs.
168 // FIXME: It would be nice to eventually to share this code somehow.
169 - (BOOL)_canUseResourceForRequest:(NSURLRequest *)theRequest
171 NSURLRequestCachePolicy policy = [theRequest cachePolicy];
173 if (policy == NSURLRequestReturnCacheDataElseLoad) {
175 } else if (policy == NSURLRequestReturnCacheDataDontLoad) {
177 } else if (policy == NSURLRequestReloadIgnoringCacheData) {
179 } else if ([theRequest valueForHTTPHeaderField:@"must-revalidate"] != nil) {
181 } else if ([theRequest valueForHTTPHeaderField:@"proxy-revalidate"] != nil) {
183 } else if ([theRequest valueForHTTPHeaderField:@"If-Modified-Since"] != nil) {
185 } else if ([theRequest valueForHTTPHeaderField:@"Cache-Control"] != nil) {
187 } else if ([[theRequest HTTPMethod] _web_isCaseInsensitiveEqualToString:@"POST"]) {
194 - (BOOL)loadWithRequest:(NSURLRequest *)r
196 ASSERT(connection == nil);
197 ASSERT(resource == nil);
199 NSURL *URL = [[r URL] retain];
200 [originalURL release];
203 deliveredResource = NO;
204 waitingToDeliverResource = NO;
206 NSURLRequest *clientRequest = [self willSendRequest:r redirectResponse:nil];
207 if (clientRequest == nil) {
208 NSError *badURLError = [NSError _webKitErrorWithDomain:NSURLErrorDomain
209 code:NSURLErrorCancelled
211 [self didFailWithError:badURLError];
216 if ([[r URL] isEqual:originalURL] && [self _canUseResourceForRequest:r]) {
217 resource = [dataSource subresourceForURL:originalURL];
220 // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
221 [self deliverResourceAfterDelay];
226 connection = [[NSURLConnection alloc] initWithRequest:r delegate:self];
227 if (defersCallbacks) {
228 [connection setDefersCallbacks:YES];
234 - (void)setDefersCallbacks:(BOOL)defers
236 defersCallbacks = defers;
237 [connection setDefersCallbacks:defers];
238 // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
239 [self deliverResourceAfterDelay];
242 - (BOOL)defersCallbacks
244 return defersCallbacks;
247 - (void)setDataSource:(WebDataSource *)d
250 ASSERT([d _webView]);
253 [dataSource release];
257 webView = [[dataSource _webView] retain];
259 [resourceLoadDelegate release];
260 resourceLoadDelegate = [[webView resourceLoadDelegate] retain];
261 implementations = [webView _resourceLoadDelegateImplementations];
263 [downloadDelegate release];
264 downloadDelegate = [[webView downloadDelegate] retain];
266 [self setDefersCallbacks:[webView defersCallbacks]];
269 - (WebDataSource *)dataSource
274 - resourceLoadDelegate
276 return resourceLoadDelegate;
281 return downloadDelegate;
284 - (void)addData:(NSData *)data
286 // Don't buffer data if we're loading it from a WebResource.
287 if (resource == nil) {
288 if (NSURLConnectionSupportsBufferedData) {
289 // Buffer data only if the connection has handed us the data because is has stopped buffering it.
290 if (resourceData != nil) {
291 [resourceData appendData:data];
294 if (resourceData == nil) {
295 resourceData = [[NSMutableData alloc] init];
297 [resourceData appendData:data];
304 // Don't save data as a WebResource if it was loaded from a WebResource.
305 if (resource == nil) {
306 NSData *data = [self resourceData];
307 if ([data length] > 0) {
308 // Don't have WebResource copy the data since the data is a NSMutableData that we know won't get modified.
310 ASSERT([response MIMEType]);
311 WebResource *newResource = [[WebResource alloc] _initWithData:data
313 MIMEType:[response MIMEType]
314 textEncodingName:[response textEncodingName]
317 if (newResource != nil) {
318 [dataSource addSubresource:newResource];
319 [newResource release];
321 ASSERT_NOT_REACHED();
327 - (NSData *)resourceData
329 if (resource != nil) {
330 return [resource data];
332 if (resourceData != nil) {
335 if (NSURLConnectionSupportsBufferedData) {
336 return [connection _bufferedData];
341 - (NSURLRequest *)willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
343 ASSERT(!reachedTerminalState);
344 NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
345 NSURLRequest *clientRequest, *updatedRequest;
346 BOOL haveDataSchemeRequest = NO;
348 // retain/release self in this delegate method since the additional processing can do
349 // anything including possibly releasing self; one example of this is 3266216
352 [mutableRequest setHTTPUserAgent:[webView userAgentForURL:[newRequest URL]]];
353 newRequest = [mutableRequest autorelease];
355 clientRequest = [newRequest _webDataRequestExternalRequest];
357 clientRequest = newRequest;
359 haveDataSchemeRequest = YES;
361 if (identifier == nil) {
362 // The identifier is released after the last callback, rather than in dealloc
363 // to avoid potential cycles.
364 if (implementations.delegateImplementsIdentifierForRequest)
365 identifier = [[resourceLoadDelegate webView: webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
367 identifier = [[[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
370 // If we have a special "applewebdata" scheme URL we send a fake request to the delegate.
371 if (implementations.delegateImplementsWillSendRequest)
372 updatedRequest = [resourceLoadDelegate webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
374 updatedRequest = [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
376 if (!haveDataSchemeRequest)
377 newRequest = updatedRequest;
379 // If the delegate modified the request use that instead of
380 // our applewebdata request, otherwise use the original
381 // applewebdata request.
382 if (![updatedRequest isEqual:clientRequest]) {
383 newRequest = updatedRequest;
385 // The respondsToSelector: check is only necessary for people building/running prior to Tier 8A416.
386 if ([NSURLProtocol respondsToSelector:@selector(_removePropertyForKey:inRequest:)] &&
387 [newRequest isKindOfClass:[NSMutableURLRequest class]]) {
388 NSMutableURLRequest *mr = (NSMutableURLRequest *)newRequest;
389 [NSURLProtocol _removePropertyForKey:[NSURLRequest _webDataRequestPropertyKey] inRequest:mr];
395 // Store a copy of the request.
396 [request autorelease];
398 // Client may return a nil request, indicating that the request should be aborted.
400 request = [newRequest copy];
410 - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
412 ASSERT(!reachedTerminalState);
413 ASSERT(!currentConnectionChallenge);
414 ASSERT(!currentWebChallenge);
416 // retain/release self in this delegate method since the additional processing can do
417 // anything including possibly releasing self; one example of this is 3266216
419 currentConnectionChallenge = [challenge retain];;
420 currentWebChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:self];
422 if (implementations.delegateImplementsDidReceiveAuthenticationChallenge) {
423 [resourceLoadDelegate webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
425 [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
430 - (void)didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
432 ASSERT(!reachedTerminalState);
433 ASSERT(currentConnectionChallenge);
434 ASSERT(currentWebChallenge);
435 ASSERT(currentConnectionChallenge = challenge);
437 // retain/release self in this delegate method since the additional processing can do
438 // anything including possibly releasing self; one example of this is 3266216
440 if (implementations.delegateImplementsDidCancelAuthenticationChallenge) {
441 [resourceLoadDelegate webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
443 [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
448 - (void)didReceiveResponse:(NSURLResponse *)r
450 ASSERT(!reachedTerminalState);
452 // retain/release self in this delegate method since the additional processing can do
453 // anything including possibly releasing self; one example of this is 3266216
456 // If the URL is one of our whacky applewebdata URLs then
457 // fake up a substitute URL to present to the delegate.
458 if([WebDataProtocol _webIsDataProtocolURL:[r URL]]) {
459 r = [[[NSURLResponse alloc] initWithURL:[request _webDataRequestExternalURL] MIMEType:[r MIMEType] expectedContentLength:[r expectedContentLength] textEncodingName:[r textEncodingName]] autorelease];
466 [dataSource _addResponse: r];
468 [webView _incrementProgressForConnectionDelegate:self response:r];
470 if (implementations.delegateImplementsDidReceiveResponse)
471 [resourceLoadDelegate webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
473 [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
477 - (void)didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
479 // The following assertions are not quite valid here, since a subclass
480 // might override didReceiveData: in a way that invalidates them. This
481 // happens with the steps listed in 3266216
482 // ASSERT(con == connection);
483 // ASSERT(!reachedTerminalState);
485 // retain/release self in this delegate method since the additional processing can do
486 // anything including possibly releasing self; one example of this is 3266216
491 [webView _incrementProgressForConnectionDelegate:self data:data];
493 if (implementations.delegateImplementsDidReceiveContentLength)
494 [resourceLoadDelegate webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
496 [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
500 - (void)willStopBufferingData:(NSData *)data
502 ASSERT(resourceData == nil);
503 resourceData = [data mutableCopy];
506 - (void)didFinishLoading
508 // If load has been cancelled after finishing (which could happen with a
509 // javascript that changes the window location), do nothing.
514 ASSERT(!reachedTerminalState);
518 [webView _completeProgressForConnectionDelegate:self];
520 if (implementations.delegateImplementsDidFinishLoadingFromDataSource)
521 [resourceLoadDelegate webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
523 [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
525 [self releaseResources];
528 - (void)didFailWithError:(NSError *)error
530 ASSERT(!reachedTerminalState);
532 // retain/release self in this delegate method since the additional processing can do
533 // anything including possibly releasing self; one example of this is 3266216
535 [webView _completeProgressForConnectionDelegate:self];
537 [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
539 [self releaseResources];
543 - (NSCachedURLResponse *)willCacheResponse:(NSCachedURLResponse *)cachedResponse
545 // When in private browsing mode, prevent caching to disk
546 if ([cachedResponse storagePolicy] == NSURLCacheStorageAllowed && [[webView preferences] privateBrowsingEnabled]) {
547 cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
548 data:[cachedResponse data]
549 userInfo:[cachedResponse userInfo]
550 storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] autorelease];
552 return cachedResponse;
555 - (NSURLRequest *)connection:(NSURLConnection *)con willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
557 ASSERT(con == connection);
558 return [self willSendRequest:newRequest redirectResponse:redirectResponse];
561 - (void)connection:(NSURLConnection *)con didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
563 ASSERT(con == connection);
564 [self didReceiveAuthenticationChallenge:challenge];
567 - (void)connection:(NSURLConnection *)con didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
569 ASSERT(con == connection);
570 [self didCancelAuthenticationChallenge:challenge];
573 - (void)connection:(NSURLConnection *)con didReceiveResponse:(NSURLResponse *)r
575 ASSERT(con == connection);
576 [self didReceiveResponse:r];
579 - (void)connection:(NSURLConnection *)con didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
581 ASSERT(con == connection);
582 [self didReceiveData:data lengthReceived:lengthReceived];
585 - (void)connection:(NSURLConnection *)con willStopBufferingData:(NSData *)data
587 ASSERT(con == connection);
588 [self willStopBufferingData:data];
591 - (void)connectionDidFinishLoading:(NSURLConnection *)con
593 // don't worry about checking connection consistency if this load
594 // got cancelled while finishing.
595 ASSERT(cancelledFlag || con == connection);
596 [self didFinishLoading];
599 - (void)connection:(NSURLConnection *)con didFailWithError:(NSError *)error
601 ASSERT(con == connection);
602 [self didFailWithError:error];
605 - (NSCachedURLResponse *)connection:(NSURLConnection *)con willCacheResponse:(NSCachedURLResponse *)cachedResponse
607 ASSERT(con == connection);
608 return [self willCacheResponse:cachedResponse];
611 - (void)cancelWithError:(NSError *)error
613 ASSERT(!reachedTerminalState);
615 // This flag prevents bad behvior when loads that finish cause the
616 // load itself to be cancelled (which could happen with a javascript that
617 // changes the window location). This is used to prevent both the body
618 // of this method and the body of connectionDidFinishLoading: running
619 // for a single delegate. Cancelling wins.
622 [currentConnectionChallenge release];
623 currentConnectionChallenge = nil;
625 [currentWebChallenge release];
626 currentWebChallenge = nil;
628 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(deliverResource) object:nil];
631 [webView _completeProgressForConnectionDelegate:self];
634 [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
637 [self releaseResources];
642 if (!reachedTerminalState) {
643 [self cancelWithError:[self cancelledError]];
647 - (NSError *)cancelledError
649 return [NSError _webKitErrorWithDomain:NSURLErrorDomain
650 code:NSURLErrorCancelled
654 - (void)setIdentifier: ident
656 if (identifier != ident){
657 [identifier release];
658 identifier = [ident retain];
662 - (NSURLResponse *)response