Reviewed by Kevin.
[WebKit-https.git] / WebKit / WebView.subproj / WebLoader.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
17 #import <WebKit/WebAssertions.h>
18 #import <WebKit/WebDataProtocol.h>
19 #import <WebKit/WebDataSourcePrivate.h>
20 #import <WebKit/WebDefaultResourceLoadDelegate.h>
21 #import <WebKit/WebKitErrors.h>
22 #import <WebKit/WebKitErrorsPrivate.h>
23 #import <WebKit/WebPreferences.h>
24 #import <WebKit/WebPreferencesPrivate.h>
25 #import <WebKit/WebResourceLoadDelegate.h>
26 #import <WebKit/WebResourcePrivate.h>
27 #import <WebKit/WebViewPrivate.h>
28
29 @interface WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender) <NSURLAuthenticationChallengeSender>
30 @end
31
32 @implementation WebBaseResourceHandleDelegate (WebNSURLAuthenticationChallengeSender) 
33
34 - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
35 {
36     if (challenge == nil || challenge != currentWebChallenge) {
37         return;
38     }
39
40     [[currentConnectionChallenge sender] useCredential:credential forAuthenticationChallenge:currentConnectionChallenge];
41
42     [currentConnectionChallenge release];
43     currentConnectionChallenge = nil;
44     
45     [currentWebChallenge release];
46     currentWebChallenge = nil;
47 }
48
49 - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
50 {
51     if (challenge == nil || challenge != currentWebChallenge) {
52         return;
53     }
54
55     [[currentConnectionChallenge sender] continueWithoutCredentialForAuthenticationChallenge:currentConnectionChallenge];
56
57     [currentConnectionChallenge release];
58     currentConnectionChallenge = nil;
59     
60     [currentWebChallenge release];
61     currentWebChallenge = nil;
62 }
63
64 - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
65 {
66     if (challenge == nil || challenge != currentWebChallenge) {
67         return;
68     }
69
70     [self cancel];
71 }
72
73 @end
74
75 @implementation WebBaseResourceHandleDelegate
76
77 - (void)releaseResources
78 {
79     ASSERT(!reachedTerminalState);
80     
81     // It's possible that when we release the handle, it will be
82     // deallocated and release the last reference to this object.
83     // We need to retain to avoid accessing the object after it
84     // has been deallocated and also to avoid reentering this method.
85     
86     [self retain];
87     
88     [identifier release];
89     identifier = nil;
90
91     [connection release];
92     connection = nil;
93
94     [webView release];
95     webView = nil;
96     
97     [dataSource release];
98     dataSource = nil;
99     
100     [resourceLoadDelegate release];
101     resourceLoadDelegate = nil;
102
103     [downloadDelegate release];
104     downloadDelegate = nil;
105     
106     [resource release];
107     resource = nil;
108     
109     [resourceData release];
110     resourceData = nil;
111     
112     reachedTerminalState = YES;
113     
114     [self release];
115 }
116
117 - (void)dealloc
118 {
119     ASSERT(reachedTerminalState);
120     [request release];
121     [response release];
122     [originalURL release];
123     [super dealloc];
124 }
125
126 - (void)deliverResource
127 {
128     ASSERT(resource);
129     ASSERT(waitingToDeliverResource);
130     
131     if (!defersCallbacks) {
132         [self didReceiveResponse:[resource _response]];
133         NSData *data = [resource data];
134         [self didReceiveData:data lengthReceived:[data length]];
135         [self didFinishLoading];
136         deliveredResource = YES;
137         waitingToDeliverResource = NO;
138     }
139 }
140
141 - (void)deliverResourceAfterDelay
142 {
143     if (resource && !defersCallbacks && !waitingToDeliverResource && !deliveredResource) {
144         [self performSelector:@selector(deliverResource) withObject:nil afterDelay:0];
145         waitingToDeliverResource = YES;
146     }
147 }
148
149 - (BOOL)loadWithRequest:(NSURLRequest *)r
150 {
151     ASSERT(connection == nil);
152     ASSERT(resource == nil);
153     
154     NSURL *URL = [[r URL] retain];
155     [originalURL release];
156     originalURL = URL;
157     
158     deliveredResource = NO;
159     waitingToDeliverResource = NO;
160
161     r = [self connection:connection willSendRequest:r redirectResponse:nil];
162     
163     if ([[r URL] isEqual:originalURL]) {
164         resource = [dataSource subresourceForURL:originalURL];
165         if (resource) {
166             [resource retain];
167             // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
168             [self deliverResourceAfterDelay];
169             return YES;
170         }
171     }
172     
173     connection = [[NSURLConnection alloc] initWithRequest:r delegate:self];
174     if (defersCallbacks) {
175         [connection setDefersCallbacks:YES];
176     }
177
178     return YES;
179 }
180
181 - (void)setDefersCallbacks:(BOOL)defers
182 {
183     defersCallbacks = defers;
184     [connection setDefersCallbacks:defers];
185     // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
186     [self deliverResourceAfterDelay];
187 }
188
189 - (BOOL)defersCallbacks
190 {
191     return defersCallbacks;
192 }
193
194 - (void)setDataSource:(WebDataSource *)d
195 {
196     ASSERT(d);
197     ASSERT([d _webView]);
198     
199     [d retain];
200     [dataSource release];
201     dataSource = d;
202
203     [webView release];
204     webView = [[dataSource _webView] retain];
205     
206     [resourceLoadDelegate release];
207     resourceLoadDelegate = [[webView resourceLoadDelegate] retain];
208     implementations = [webView _resourceLoadDelegateImplementations];
209
210     [downloadDelegate release];
211     downloadDelegate = [[webView downloadDelegate] retain];
212
213     [self setDefersCallbacks:[webView defersCallbacks]];
214 }
215
216 - (WebDataSource *)dataSource
217 {
218     return dataSource;
219 }
220
221 - resourceLoadDelegate
222 {
223     return resourceLoadDelegate;
224 }
225
226 - downloadDelegate
227 {
228     return downloadDelegate;
229 }
230
231 - (void)addData:(NSData *)data
232 {
233     if (!resource) {
234         if (!resourceData) {
235             resourceData = [[NSMutableData alloc] init];
236         }
237         [resourceData appendData:data];
238     }
239 }
240
241 - (void)saveResource
242 {
243     if (!resource && [resourceData length] > 0) {
244         WebResource *newResource = [[WebResource alloc] initWithData:resourceData
245                                                                  URL:originalURL
246                                                             MIMEType:[response MIMEType]
247                                                     textEncodingName:[response textEncodingName]
248                                                            frameName:nil];
249         [dataSource addSubresource:newResource];
250         [newResource release];
251     }
252 }
253
254 - (void)saveResourceWithCachedResponse:(NSCachedURLResponse *)cachedResponse
255 {
256     if (!resource) {
257         // Overwrite the resource saved with saveResource with the cache version to save memory.
258         WebResource *newResource = [[WebResource alloc] _initWithCachedResponse:cachedResponse originalURL:originalURL];
259         [dataSource addSubresource:newResource];
260         [newResource release];
261     }
262 }
263
264 - (NSData *)resourceData
265 {
266     return resource ? [resource data] : resourceData;
267 }
268
269 - (NSURLRequest *)willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
270 {
271     ASSERT(!reachedTerminalState);
272     NSMutableURLRequest *mutableRequest = [newRequest mutableCopy];
273     NSURLRequest *clientRequest, *updatedRequest;
274     BOOL haveDataSchemeRequest = NO;
275     
276     // retain/release self in this delegate method since the additional processing can do
277     // anything including possibly releasing self; one example of this is 3266216
278     [self retain];
279
280     [mutableRequest setHTTPUserAgent:[webView userAgentForURL:[newRequest URL]]];
281     newRequest = [mutableRequest autorelease];
282
283     clientRequest = [newRequest _webDataRequestExternalRequest];
284     if(!clientRequest)
285         clientRequest = newRequest;
286     else
287         haveDataSchemeRequest = YES;
288     
289     if (identifier == nil) {
290         // The identifier is released after the last callback, rather than in dealloc
291         // to avoid potential cycles.
292         if (implementations.delegateImplementsIdentifierForRequest)
293             identifier = [[resourceLoadDelegate webView: webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
294         else
295             identifier = [[[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView identifierForInitialRequest:clientRequest fromDataSource:dataSource] retain];
296     }
297
298     // If we have a special "applewebdata" scheme URL we send a fake request to the delegate.
299     if (implementations.delegateImplementsWillSendRequest)
300         updatedRequest = [resourceLoadDelegate webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
301     else
302         updatedRequest = [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier willSendRequest:clientRequest redirectResponse:redirectResponse fromDataSource:dataSource];
303         
304     if (!haveDataSchemeRequest)
305         newRequest = updatedRequest;
306     else {
307         // If the delegate modified the request use that instead of
308         // our applewebdata request, otherwise use the original
309         // applewebdata request.
310         if (![updatedRequest isEqual:clientRequest])
311             newRequest = updatedRequest;
312     }
313
314     // Store a copy of the request.
315     [request autorelease];
316
317     // Client may return a nil request, indicating that the request should be aborted.
318     if (newRequest){
319         request = [newRequest copy];
320     }
321     else {
322         request = nil;
323     }
324
325     [self release];
326     return request;
327 }
328
329 - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
330 {
331     ASSERT(!reachedTerminalState);
332     ASSERT(!currentConnectionChallenge);
333     ASSERT(!currentWebChallenge);
334
335     // retain/release self in this delegate method since the additional processing can do
336     // anything including possibly releasing self; one example of this is 3266216
337     [self retain];
338     currentConnectionChallenge = [challenge retain];;
339     currentWebChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:self];
340
341     if (implementations.delegateImplementsDidReceiveAuthenticationChallenge) {
342         [resourceLoadDelegate webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
343     } else {
344         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
345     }
346     [self release];
347 }
348
349 - (void)didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
350 {
351     ASSERT(!reachedTerminalState);
352     ASSERT(currentConnectionChallenge);
353     ASSERT(currentWebChallenge);
354     ASSERT(currentConnectionChallenge = challenge);
355
356     // retain/release self in this delegate method since the additional processing can do
357     // anything including possibly releasing self; one example of this is 3266216
358     [self retain];
359     if (implementations.delegateImplementsDidCancelAuthenticationChallenge) {
360         [resourceLoadDelegate webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
361     } else {
362         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didCancelAuthenticationChallenge:currentWebChallenge fromDataSource:dataSource];
363     }
364     [self release];
365 }
366
367 - (void)didReceiveResponse:(NSURLResponse *)r
368 {
369     ASSERT(!reachedTerminalState);
370
371     // retain/release self in this delegate method since the additional processing can do
372     // anything including possibly releasing self; one example of this is 3266216
373     [self retain]; 
374
375     // If the URL is one of our whacky applewebdata URLs then
376     // fake up a substitute URL to present to the delegate.
377     if([WebDataProtocol _webIsDataProtocolURL:[r URL]]) {
378         r = [[[NSURLResponse alloc] initWithURL:[request _webDataRequestExternalURL] MIMEType:[r MIMEType] expectedContentLength:[r expectedContentLength] textEncodingName:[r textEncodingName]] autorelease];
379     }
380
381     [r retain];
382     [response release];
383     response = r;
384
385     [dataSource _addResponse: r];
386
387     [webView _incrementProgressForConnectionDelegate:self response:r];
388         
389     if (implementations.delegateImplementsDidReceiveResponse)
390         [resourceLoadDelegate webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
391     else
392         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveResponse:r fromDataSource:dataSource];
393     [self release];
394 }
395
396 - (void)didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
397 {
398     // The following assertions are not quite valid here, since a subclass
399     // might override didReceiveData: in a way that invalidates them. This
400     // happens with the steps listed in 3266216
401     // ASSERT(con == connection);
402     // ASSERT(!reachedTerminalState);
403
404     // retain/release self in this delegate method since the additional processing can do
405     // anything including possibly releasing self; one example of this is 3266216
406     [self retain];
407     
408     [self addData:data];
409     
410     [webView _incrementProgressForConnectionDelegate:self data:data];
411
412     if (implementations.delegateImplementsDidReceiveContentLength)
413         [resourceLoadDelegate webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
414     else
415         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didReceiveContentLength:lengthReceived fromDataSource:dataSource];
416     [self release];
417 }
418
419 - (void)didFinishLoading
420 {
421     // If load has been cancelled after finishing (which could happen with a 
422     // javascript that changes the window location), do nothing.
423     if (cancelledFlag) {
424         return;
425     }
426     
427     ASSERT(!reachedTerminalState);
428
429     [self saveResource];
430     
431     [webView _completeProgressForConnectionDelegate:self];
432
433     if (implementations.delegateImplementsDidFinishLoadingFromDataSource)
434         [resourceLoadDelegate webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
435     else
436         [[WebDefaultResourceLoadDelegate sharedResourceLoadDelegate] webView:webView resource:identifier didFinishLoadingFromDataSource:dataSource];
437
438     [self releaseResources];
439 }
440
441 - (void)didFailWithError:(NSError *)error
442 {
443     ASSERT(!reachedTerminalState);
444
445     // retain/release self in this delegate method since the additional processing can do
446     // anything including possibly releasing self; one example of this is 3266216
447     [self retain];
448     [webView _completeProgressForConnectionDelegate:self];
449
450     [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
451
452     [self releaseResources];
453     [self release];
454 }
455
456 - (NSCachedURLResponse *)willCacheResponse:(NSCachedURLResponse *)cachedResponse
457 {
458     // When in private browsing mode, prevent caching to disk
459     if ([cachedResponse storagePolicy] == NSURLCacheStorageAllowed && [[WebPreferences standardPreferences] privateBrowsingEnabled]) {
460         cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
461                                                                    data:[cachedResponse data]
462                                                                userInfo:[cachedResponse userInfo]
463                                                           storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] autorelease];
464     }
465     [self saveResourceWithCachedResponse:cachedResponse];
466     return cachedResponse;
467 }
468
469 - (NSURLRequest *)connection:(NSURLConnection *)con willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
470 {
471     ASSERT(con == connection);
472     return [self willSendRequest:newRequest redirectResponse:redirectResponse];
473 }
474
475 - (void)connection:(NSURLConnection *)con didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
476 {
477     ASSERT(con == connection);
478     [self didReceiveAuthenticationChallenge:challenge];
479 }
480
481 - (void)connection:(NSURLConnection *)con didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
482 {
483     ASSERT(con == connection);
484     [self didCancelAuthenticationChallenge:challenge];
485 }
486
487 - (void)connection:(NSURLConnection *)con didReceiveResponse:(NSURLResponse *)r
488 {
489     ASSERT(con == connection);
490     [self didReceiveResponse:r];
491 }
492
493 - (void)connection:(NSURLConnection *)con didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
494 {
495     ASSERT(con == connection);
496     [self didReceiveData:data lengthReceived:lengthReceived];
497 }
498
499 - (void)connectionDidFinishLoading:(NSURLConnection *)con
500 {
501     // don't worry about checking connection consistency if this load
502     // got cancelled while finishing.
503     ASSERT(cancelledFlag || con == connection);
504     [self didFinishLoading];
505 }
506
507 - (void)connection:(NSURLConnection *)con didFailWithError:(NSError *)error
508 {
509     ASSERT(con == connection);
510     [self didFailWithError:error];
511 }
512
513 - (NSCachedURLResponse *)connection:(NSURLConnection *)con willCacheResponse:(NSCachedURLResponse *)cachedResponse
514 {
515     ASSERT(con == connection);
516     return [self willCacheResponse:cachedResponse];
517 }
518
519 - (void)cancelWithError:(NSError *)error
520 {
521     ASSERT(!reachedTerminalState);
522
523     // This flag prevents bad behvior when loads that finish cause the
524     // load itself to be cancelled (which could happen with a javascript that 
525     // changes the window location). This is used to prevent both the body
526     // of this method and the body of connectionDidFinishLoading: running
527     // for a single delegate. Cancelling wins.
528     cancelledFlag = YES;
529     
530     [currentConnectionChallenge release];
531     currentConnectionChallenge = nil;
532     
533     [currentWebChallenge release];
534     currentWebChallenge = nil;
535
536     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(deliverResource) object:nil];
537     [connection cancel];
538
539     [webView _completeProgressForConnectionDelegate:self];
540
541     if (error) {
542         [[webView _resourceLoadDelegateForwarder] webView:webView resource:identifier didFailLoadingWithError:error fromDataSource:dataSource];
543     }
544
545     [self releaseResources];
546 }
547
548 - (void)cancel
549 {
550     if (!reachedTerminalState) {
551         [self cancelWithError:[self cancelledError]];
552     }
553 }
554
555 - (NSError *)cancelledError
556 {
557     return [NSError _webKitErrorWithDomain:NSURLErrorDomain
558                                       code:NSURLErrorCancelled
559                                        URL:[request URL]];
560 }
561
562 - (void)setIdentifier: ident
563 {
564     if (identifier != ident){
565         [identifier release];
566         identifier = [ident retain];
567     }
568 }
569
570 - (NSURLResponse *)response
571 {
572     return response;
573 }
574
575 @end