Fixed: <rdar://problem/4070729> REGRESSION (125-311, Panther-only?): Safari crashes...
[WebKit-https.git] / WebKit / WebView.subproj / WebBaseResourceHandleDelegate.m
1 /*      
2     WebBaseResourceHandleDelegate.m
3     Copyright (c) 2002, Apple Computer, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebBaseResourceHandleDelegate.h>
7
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>
17
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>
29
30 static BOOL NSURLConnectionSupportsBufferedData;
31
32 @interface NSURLConnection (NSURLConnectionTigerPrivate)
33 - (NSData *)_bufferedData;
34 @end
35
36 @interface WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender) <NSURLAuthenticationChallengeSender>
37 @end
38
39 @implementation WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender) 
40
41 - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
42 {
43     if (challenge == nil || challenge != currentWebChallenge) {
44         return;
45     }
46
47     [[currentConnectionChallenge sender] useCredential:credential forAuthenticationChallenge:currentConnectionChallenge];
48
49     [currentConnectionChallenge release];
50     currentConnectionChallenge = nil;
51     
52     [currentWebChallenge release];
53     currentWebChallenge = nil;
54 }
55
56 - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
57 {
58     if (challenge == nil || challenge != currentWebChallenge) {
59         return;
60     }
61
62     [[currentConnectionChallenge sender] continueWithoutCredentialForAuthenticationChallenge:currentConnectionChallenge];
63
64     [currentConnectionChallenge release];
65     currentConnectionChallenge = nil;
66     
67     [currentWebChallenge release];
68     currentWebChallenge = nil;
69 }
70
71 - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
72 {
73     if (challenge == nil || challenge != currentWebChallenge) {
74         return;
75     }
76
77     [self cancel];
78 }
79
80 @end
81
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;
86 @end
87
88 @implementation WebBaseResourceHandleDelegate
89
90 + (void)initialize
91 {
92     NSURLConnectionSupportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)];
93 }
94
95 - (void)releaseResources
96 {
97     ASSERT(!reachedTerminalState);
98     
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.
103     
104     [self retain];
105     
106     [identifier release];
107     identifier = nil;
108
109     [connection release];
110     connection = nil;
111
112     [webView release];
113     webView = nil;
114     
115     [dataSource release];
116     dataSource = nil;
117     
118     [resourceLoadDelegate release];
119     resourceLoadDelegate = nil;
120
121     [downloadDelegate release];
122     downloadDelegate = nil;
123     
124     [resource release];
125     resource = nil;
126     
127     [resourceData release];
128     resourceData = nil;
129     
130     reachedTerminalState = YES;
131     
132     [self release];
133 }
134
135 - (void)dealloc
136 {
137     ASSERT(reachedTerminalState);
138     [request release];
139     [response release];
140     [originalURL release];
141     [super dealloc];
142 }
143
144 - (void)deliverResource
145 {
146     ASSERT(resource);
147     ASSERT(waitingToDeliverResource);
148     
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;
156     }
157 }
158
159 - (void)deliverResourceAfterDelay
160 {
161     if (resource && !defersCallbacks && !waitingToDeliverResource && !deliveredResource) {
162         [self performSelector:@selector(deliverResource) withObject:nil afterDelay:0];
163         waitingToDeliverResource = YES;
164     }
165 }
166
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
170 {
171     NSURLRequestCachePolicy policy = [theRequest cachePolicy];
172         
173     if (policy == NSURLRequestReturnCacheDataElseLoad) {
174         return YES;
175     } else if (policy == NSURLRequestReturnCacheDataDontLoad) {
176         return YES;
177     } else if (policy == NSURLRequestReloadIgnoringCacheData) {
178         return NO;
179     } else if ([theRequest valueForHTTPHeaderField:@"must-revalidate"] != nil) {
180         return NO;
181     } else if ([theRequest valueForHTTPHeaderField:@"proxy-revalidate"] != nil) {
182         return NO;
183     } else if ([theRequest valueForHTTPHeaderField:@"If-Modified-Since"] != nil) {
184         return NO;
185     } else if ([theRequest valueForHTTPHeaderField:@"Cache-Control"] != nil) {
186         return NO;
187     } else if ([[theRequest HTTPMethod] _web_isCaseInsensitiveEqualToString:@"POST"]) {
188         return NO;
189     } else {
190         return YES;
191     }
192 }
193
194 - (BOOL)loadWithRequest:(NSURLRequest *)r
195 {
196     ASSERT(connection == nil);
197     ASSERT(resource == nil);
198     
199     NSURL *URL = [[r URL] retain];
200     [originalURL release];
201     originalURL = URL;
202     
203     deliveredResource = NO;
204     waitingToDeliverResource = NO;
205
206     NSURLRequest *clientRequest = [self willSendRequest:r redirectResponse:nil];
207     if (clientRequest == nil) {
208         NSError *badURLError = [NSError _webKitErrorWithDomain:NSURLErrorDomain 
209                                                           code:NSURLErrorCancelled
210                                                            URL:[r URL]];
211         [self didFailWithError:badURLError];
212         return NO;
213     }
214     r = clientRequest;
215     
216     if ([[r URL] isEqual:originalURL] && [self _canUseResourceForRequest:r]) {
217         resource = [dataSource subresourceForURL:originalURL];
218         if (resource) {
219             [resource retain];
220             // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
221             [self deliverResourceAfterDelay];
222             return YES;
223         }
224     }
225     
226     connection = [[NSURLConnection alloc] initWithRequest:r delegate:self];
227     if (defersCallbacks) {
228         [connection setDefersCallbacks:YES];
229     }
230
231     return YES;
232 }
233
234 - (void)setDefersCallbacks:(BOOL)defers
235 {
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];
240 }
241
242 - (BOOL)defersCallbacks
243 {
244     return defersCallbacks;
245 }
246
247 - (void)setDataSource:(WebDataSource *)d
248 {
249     ASSERT(d);
250     ASSERT([d _webView]);
251     
252     [d retain];
253     [dataSource release];
254     dataSource = d;
255
256     [webView release];
257     webView = [[dataSource _webView] retain];
258     
259     [resourceLoadDelegate release];
260     resourceLoadDelegate = [[webView resourceLoadDelegate] retain];
261     implementations = [webView _resourceLoadDelegateImplementations];
262
263     [downloadDelegate release];
264     downloadDelegate = [[webView downloadDelegate] retain];
265
266     [self setDefersCallbacks:[webView defersCallbacks]];
267 }
268
269 - (WebDataSource *)dataSource
270 {
271     return dataSource;
272 }
273
274 - resourceLoadDelegate
275 {
276     return resourceLoadDelegate;
277 }
278
279 - downloadDelegate
280 {
281     return downloadDelegate;
282 }
283
284 - (void)addData:(NSData *)data
285 {
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];
292             }
293         } else {
294             if (resourceData == nil) {
295                 resourceData = [[NSMutableData alloc] init];
296             }
297             [resourceData appendData:data];
298         }
299     }
300 }
301
302 - (void)saveResource
303 {
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. 
309             ASSERT(originalURL);
310             ASSERT([response MIMEType]);
311             WebResource *newResource = [[WebResource alloc] _initWithData:data
312                                                                       URL:originalURL
313                                                                  MIMEType:[response MIMEType]
314                                                          textEncodingName:[response textEncodingName]
315                                                                 frameName:nil
316                                                                  copyData:NO];
317             if (newResource != nil) {
318                 [dataSource addSubresource:newResource];
319                 [newResource release];
320             } else {
321                 ASSERT_NOT_REACHED();
322             }
323         }
324     }
325 }
326
327 - (NSData *)resourceData
328 {
329     if (resource != nil) {
330         return [resource data];
331     }
332     if (resourceData != nil) {
333         // Retain and autorelease resourceData since releaseResources (which releases resourceData) may be called 
334         // before the caller of this method has an opporuntity to retain the returned data (4070729).
335         return [[resourceData retain] autorelease];
336     }
337     if (NSURLConnectionSupportsBufferedData) {
338         return [connection _bufferedData];
339     }
340     return nil;
341 }
342
343 - (NSURLRequest *)willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
344 {
345     ASSERT(!reachedTerminalState);
346     NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
347     NSURLRequest *clientRequest, *updatedRequest;
348     BOOL haveDataSchemeRequest = NO;
349     
350     // retain/release self in this delegate method since the additional processing can do
351     // anything including possibly releasing self; one example of this is 3266216
352     [self retain];
353
354     [mutableRequest setHTTPUserAgent:[webView userAgentForURL:[newRequest URL]]];
355     newRequest = [mutableRequest autorelease];
356
357     clientRequest = [newRequest _webDataRequestExternalRequest];
358     if(!clientRequest)
359         clientRequest = newRequest;
360     else
361         haveDataSchemeRequest = YES;
362     
363     if (identifier == nil) {
364         // The identifier is released after the last callback, rather than in dealloc
365         // to avoid potential cycles.
366         if (implementations.delegateImplementsIdentifierForRequest)
367             identifier = [[resourceLoadDelegate webView: webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
368         else
369             identifier = [[[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
370     }
371
372     // If we have a special "applewebdata" scheme URL we send a fake request to the delegate.
373     if (implementations.delegateImplementsWillSendRequest)
374         updatedRequest = [resourceLoadDelegate webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
375     else
376         updatedRequest = [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
377         
378     if (!haveDataSchemeRequest)
379         newRequest = updatedRequest;
380     else {
381         // If the delegate modified the request use that instead of
382         // our applewebdata request, otherwise use the original
383         // applewebdata request.
384         if (![updatedRequest isEqual:clientRequest]) {
385             newRequest = updatedRequest;
386         
387             // The respondsToSelector: check is only necessary for people building/running prior to Tier 8A416.
388             if ([NSURLProtocol respondsToSelector:@selector(_removePropertyForKey:inRequest:)] &&
389                 [newRequest isKindOfClass:[NSMutableURLRequest class]]) {
390                 NSMutableURLRequest *mr = (NSMutableURLRequest *)newRequest;
391                 [NSURLProtocol _removePropertyForKey:[NSURLRequest _webDataRequestPropertyKey] inRequest:mr];
392             }
393
394         }
395     }
396
397     // Store a copy of the request.
398     [request autorelease];
399
400     // Client may return a nil request, indicating that the request should be aborted.
401     if (newRequest){
402         request = [newRequest copy];
403     }
404     else {
405         request = nil;
406     }
407
408     [self release];
409     return request;
410 }
411
412 - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
413 {
414     ASSERT(!reachedTerminalState);
415     ASSERT(!currentConnectionChallenge);
416     ASSERT(!currentWebChallenge);
417
418     // retain/release self in this delegate method since the additional processing can do
419     // anything including possibly releasing self; one example of this is 3266216
420     [self retain];
421     currentConnectionChallenge = [challenge retain];;
422     currentWebChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:self];
423
424     if (implementations.delegateImplementsDidReceiveAuthenticationChallenge) {
425         [resourceLoadDelegate webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
426     } else {
427         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
428     }
429     [self release];
430 }
431
432 - (void)didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
433 {
434     ASSERT(!reachedTerminalState);
435     ASSERT(currentConnectionChallenge);
436     ASSERT(currentWebChallenge);
437     ASSERT(currentConnectionChallenge = challenge);
438
439     // retain/release self in this delegate method since the additional processing can do
440     // anything including possibly releasing self; one example of this is 3266216
441     [self retain];
442     if (implementations.delegateImplementsDidCancelAuthenticationChallenge) {
443         [resourceLoadDelegate webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
444     } else {
445         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
446     }
447     [self release];
448 }
449
450 - (void)didReceiveResponse:(NSURLResponse *)r
451 {
452     ASSERT(!reachedTerminalState);
453
454     // retain/release self in this delegate method since the additional processing can do
455     // anything including possibly releasing self; one example of this is 3266216
456     [self retain]; 
457
458     // If the URL is one of our whacky applewebdata URLs then
459     // fake up a substitute URL to present to the delegate.
460     if([WebDataProtocol _webIsDataProtocolURL:[r URL]]) {
461         r = [[[NSURLResponse alloc] initWithURL:[request _webDataRequestExternalURL] MIMEType:[r MIMEType] expectedContentLength:[r expectedContentLength] textEncodingName:[r textEncodingName]] autorelease];
462     }
463
464     [r retain];
465     [response release];
466     response = r;
467
468     [dataSource _addResponse: r];
469
470     [webView _incrementProgressForConnectionDelegate:self response:r];
471         
472     if (implementations.delegateImplementsDidReceiveResponse)
473         [resourceLoadDelegate webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
474     else
475         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
476     [self release];
477 }
478
479 - (void)didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
480 {
481     // The following assertions are not quite valid here, since a subclass
482     // might override didReceiveData: in a way that invalidates them. This
483     // happens with the steps listed in 3266216
484     // ASSERT(con == connection);
485     // ASSERT(!reachedTerminalState);
486
487     // retain/release self in this delegate method since the additional processing can do
488     // anything including possibly releasing self; one example of this is 3266216
489     [self retain];
490     
491     [self addData:data];
492     
493     [webView _incrementProgressForConnectionDelegate:self data:data];
494
495     if (implementations.delegateImplementsDidReceiveContentLength)
496         [resourceLoadDelegate webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
497     else
498         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
499     [self release];
500 }
501
502 - (void)willStopBufferingData:(NSData *)data
503 {
504     ASSERT(resourceData == nil);
505     resourceData = [data mutableCopy];
506 }
507
508 - (void)didFinishLoading
509 {
510     // If load has been cancelled after finishing (which could happen with a 
511     // javascript that changes the window location), do nothing.
512     if (cancelledFlag) {
513         return;
514     }
515     
516     ASSERT(!reachedTerminalState);
517
518     [self saveResource];
519     
520     [webView _completeProgressForConnectionDelegate:self];
521
522     if (implementations.delegateImplementsDidFinishLoadingFromDataSource)
523         [resourceLoadDelegate webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
524     else
525         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
526
527     [self releaseResources];
528 }
529
530 - (void)didFailWithError:(NSError *)error
531 {
532     ASSERT(!reachedTerminalState);
533
534     // retain/release self in this delegate method since the additional processing can do
535     // anything including possibly releasing self; one example of this is 3266216
536     [self retain];
537     [webView _completeProgressForConnectionDelegate:self];
538
539     [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
540
541     [self releaseResources];
542     [self release];
543 }
544
545 - (NSCachedURLResponse *)willCacheResponse:(NSCachedURLResponse *)cachedResponse
546 {
547     // When in private browsing mode, prevent caching to disk
548     if ([cachedResponse storagePolicy] == NSURLCacheStorageAllowed && [[webView preferences] privateBrowsingEnabled]) {
549         cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
550                                                                    data:[cachedResponse data]
551                                                                userInfo:[cachedResponse userInfo]
552                                                           storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] autorelease];
553     }
554     return cachedResponse;
555 }
556
557 - (NSURLRequest *)connection:(NSURLConnection *)con willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
558 {
559     ASSERT(con == connection);
560     return [self willSendRequest:newRequest redirectResponse:redirectResponse];
561 }
562
563 - (void)connection:(NSURLConnection *)con didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
564 {
565     ASSERT(con == connection);
566     [self didReceiveAuthenticationChallenge:challenge];
567 }
568
569 - (void)connection:(NSURLConnection *)con didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
570 {
571     ASSERT(con == connection);
572     [self didCancelAuthenticationChallenge:challenge];
573 }
574
575 - (void)connection:(NSURLConnection *)con didReceiveResponse:(NSURLResponse *)r
576 {
577     ASSERT(con == connection);
578     [self didReceiveResponse:r];
579 }
580
581 - (void)connection:(NSURLConnection *)con didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
582 {
583     ASSERT(con == connection);
584     [self didReceiveData:data lengthReceived:lengthReceived];
585 }
586
587 - (void)connection:(NSURLConnection *)con willStopBufferingData:(NSData *)data
588 {
589     ASSERT(con == connection);
590     [self willStopBufferingData:data];
591 }
592
593 - (void)connectionDidFinishLoading:(NSURLConnection *)con
594 {
595     // don't worry about checking connection consistency if this load
596     // got cancelled while finishing.
597     ASSERT(cancelledFlag || con == connection);
598     [self didFinishLoading];
599 }
600
601 - (void)connection:(NSURLConnection *)con didFailWithError:(NSError *)error
602 {
603     ASSERT(con == connection);
604     [self didFailWithError:error];
605 }
606
607 - (NSCachedURLResponse *)connection:(NSURLConnection *)con willCacheResponse:(NSCachedURLResponse *)cachedResponse
608 {
609     ASSERT(con == connection);
610     return [self willCacheResponse:cachedResponse];
611 }
612
613 - (void)cancelWithError:(NSError *)error
614 {
615     ASSERT(!reachedTerminalState);
616
617     // This flag prevents bad behvior when loads that finish cause the
618     // load itself to be cancelled (which could happen with a javascript that 
619     // changes the window location). This is used to prevent both the body
620     // of this method and the body of connectionDidFinishLoading: running
621     // for a single delegate. Cancelling wins.
622     cancelledFlag = YES;
623     
624     [currentConnectionChallenge release];
625     currentConnectionChallenge = nil;
626     
627     [currentWebChallenge release];
628     currentWebChallenge = nil;
629
630     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(deliverResource) object:nil];
631     [connection cancel];
632
633     [webView _completeProgressForConnectionDelegate:self];
634
635     if (error) {
636         [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
637     }
638
639     [self releaseResources];
640 }
641
642 - (void)cancel
643 {
644     if (!reachedTerminalState) {
645         [self cancelWithError:[self cancelledError]];
646     }
647 }
648
649 - (NSError *)cancelledError
650 {
651     return [NSError _webKitErrorWithDomain:NSURLErrorDomain
652                                       code:NSURLErrorCancelled
653                                        URL:[request URL]];
654 }
655
656 - (void)setIdentifier: ident
657 {
658     if (identifier != ident){
659         [identifier release];
660         identifier = [ident retain];
661     }
662 }
663
664 - (NSURLResponse *)response
665 {
666     return response;
667 }
668
669 @end