WebKit:
[WebKit-https.git] / WebKit / Loader / WebLoader.m
1 /*
2  * Copyright (C) 2005 Apple Computer, 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import <WebKit/WebLoader.h>
30
31 #import <Foundation/NSURLAuthenticationChallenge.h>
32 #import <Foundation/NSURLConnection.h>
33 #import <Foundation/NSURLRequest.h>
34 #import <Foundation/NSURLResponse.h>
35
36 #import <JavaScriptCore/Assertions.h>
37 #import <WebKit/WebDataProtocol.h>
38 #import <WebKit/WebKitErrors.h>
39 #import <WebKit/WebKitErrorsPrivate.h>
40 #import <WebKit/WebFrameLoader.h>
41
42 #import <WebKit/WebNSURLRequestExtras.h>
43 #import <WebKit/WebKitNSStringExtras.h>
44 #import <WebKit/WebResourcePrivate.h>
45 #import <WebKitSystemInterface.h>
46
47 static unsigned inNSURLConnectionCallback;
48 static BOOL NSURLConnectionSupportsBufferedData;
49
50 @interface NSURLConnection (NSURLConnectionTigerPrivate)
51 - (NSData *)_bufferedData;
52 @end
53
54 @interface WebLoader (WebNSURLAuthenticationChallengeSender) <NSURLAuthenticationChallengeSender>
55 @end
56
57 @implementation WebLoader (WebNSURLAuthenticationChallengeSender) 
58
59 - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
60 {
61     if (challenge == nil || challenge != currentWebChallenge) {
62         return;
63     }
64
65     [[currentConnectionChallenge sender] useCredential:credential forAuthenticationChallenge:currentConnectionChallenge];
66
67     [currentConnectionChallenge release];
68     currentConnectionChallenge = nil;
69     
70     [currentWebChallenge release];
71     currentWebChallenge = nil;
72 }
73
74 - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
75 {
76     if (challenge == nil || challenge != currentWebChallenge) {
77         return;
78     }
79
80     [[currentConnectionChallenge sender] continueWithoutCredentialForAuthenticationChallenge:currentConnectionChallenge];
81
82     [currentConnectionChallenge release];
83     currentConnectionChallenge = nil;
84     
85     [currentWebChallenge release];
86     currentWebChallenge = nil;
87 }
88
89 - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
90 {
91     if (challenge == nil || challenge != currentWebChallenge) {
92         return;
93     }
94
95     [self cancel];
96 }
97
98 @end
99
100 // This declaration is only needed to ease the transition to a new SPI.  It can be removed
101 // moving forward beyond Tiger 8A416.
102 @interface NSURLProtocol (WebFoundationSecret) 
103 + (void)_removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
104 @end
105
106 @implementation WebLoader
107
108 + (void)initialize
109 {
110     NSURLConnectionSupportsBufferedData = [NSURLConnection instancesRespondToSelector:@selector(_bufferedData)];
111 }
112
113 - (void)releaseResources
114 {
115     ASSERT(!reachedTerminalState);
116     
117     // It's possible that when we release the handle, it will be
118     // deallocated and release the last reference to this object.
119     // We need to retain to avoid accessing the object after it
120     // has been deallocated and also to avoid reentering this method.
121     
122     [self retain];
123
124     // We need to set reachedTerminalState to YES before we release
125     // the resources to prevent a double dealloc of WebView <rdar://problem/4372628>
126
127     reachedTerminalState = YES;
128
129     [identifier release];
130     identifier = nil;
131
132     [connection release];
133     connection = nil;
134
135     [frameLoader release];
136     frameLoader = nil;
137     
138     [resource release];
139     resource = nil;
140     
141     [resourceData release];
142     resourceData = nil;
143
144     [self release];
145 }
146
147 - (void)dealloc
148 {
149     ASSERT(reachedTerminalState);
150     [request release];
151     [response release];
152     [originalURL release];
153     [super dealloc];
154 }
155
156 - (void)deliverResource
157 {
158     ASSERT(resource);
159     ASSERT(waitingToDeliverResource);
160     
161     if (!defersCallbacks) {
162         [self didReceiveResponse:[resource _response]];
163         NSData *data = [resource data];
164         [self didReceiveData:data lengthReceived:[data length]];
165         [self didFinishLoading];
166         deliveredResource = YES;
167         waitingToDeliverResource = NO;
168     }
169 }
170
171 - (void)deliverResourceAfterDelay
172 {
173     if (resource && !defersCallbacks && !waitingToDeliverResource && !deliveredResource) {
174         [self performSelector:@selector(deliverResource) withObject:nil afterDelay:0];
175         waitingToDeliverResource = YES;
176     }
177 }
178
179 // The following 2 methods are copied from [NSHTTPURLProtocol _cachedResponsePassesValidityChecks] and modified for our needs.
180 // FIXME: It would be nice to eventually to share this code somehow.
181 - (BOOL)_canUseResourceForRequest:(NSURLRequest *)theRequest
182 {
183     NSURLRequestCachePolicy policy = [theRequest cachePolicy];
184         
185     if (policy == NSURLRequestReturnCacheDataElseLoad) {
186         return YES;
187     } else if (policy == NSURLRequestReturnCacheDataDontLoad) {
188         return YES;
189     } else if (policy == NSURLRequestReloadIgnoringCacheData) {
190         return NO;
191     } else if ([theRequest valueForHTTPHeaderField:@"must-revalidate"] != nil) {
192         return NO;
193     } else if ([theRequest valueForHTTPHeaderField:@"proxy-revalidate"] != nil) {
194         return NO;
195     } else if ([theRequest valueForHTTPHeaderField:@"If-Modified-Since"] != nil) {
196         return NO;
197     } else if ([theRequest valueForHTTPHeaderField:@"Cache-Control"] != nil) {
198         return NO;
199     } else if ([[theRequest HTTPMethod] _webkit_isCaseInsensitiveEqualToString:@"POST"]) {
200         return NO;
201     } else {
202         return YES;
203     }
204 }
205
206 - (BOOL)_canUseResourceWithResponse:(NSURLResponse *)theResponse
207 {
208     if (WKGetNSURLResponseMustRevalidate(theResponse)) {
209         return NO;
210     } else if (WKGetNSURLResponseCalculatedExpiration(theResponse) - CFAbsoluteTimeGetCurrent() < 1) {
211         return NO;
212     } else {
213         return YES;
214     }
215 }
216
217 - (BOOL)loadWithRequest:(NSURLRequest *)r
218 {
219     ASSERT(connection == nil);
220     ASSERT(resource == nil);
221     
222     NSURL *URL = [[r URL] retain];
223     [originalURL release];
224     originalURL = URL;
225     
226     deliveredResource = NO;
227     waitingToDeliverResource = NO;
228
229     NSURLRequest *clientRequest = [self willSendRequest:r redirectResponse:nil];
230     if (clientRequest == nil) {
231         NSError *badURLError = [NSError _webKitErrorWithDomain:NSURLErrorDomain 
232                                                           code:NSURLErrorCancelled
233                                                            URL:[r URL]];
234         [self didFailWithError:badURLError];
235         return NO;
236     }
237     r = clientRequest;
238     
239     if ([[r URL] isEqual:originalURL] && [self _canUseResourceForRequest:r]) {
240         resource = [frameLoader _archivedSubresourceForURL:originalURL];
241         if (resource != nil) {
242             if ([self _canUseResourceWithResponse:[resource _response]]) {
243                 [resource retain];
244                 // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
245                 [self deliverResourceAfterDelay];
246                 return YES;
247             } else {
248                 resource = nil;
249             }
250         }
251     }
252     
253 #ifndef NDEBUG
254     isInitializingConnection = YES;
255 #endif
256     connection = [[NSURLConnection alloc] initWithRequest:r delegate:self];
257 #ifndef NDEBUG
258     isInitializingConnection = NO;
259 #endif
260     if (defersCallbacks) {
261         WKSetNSURLConnectionDefersCallbacks(connection, YES);
262     }
263
264     return YES;
265 }
266
267 - (void)setDefersCallbacks:(BOOL)defers
268 {
269     defersCallbacks = defers;
270     WKSetNSURLConnectionDefersCallbacks(connection, defers);
271     // Deliver the resource after a delay because callers don't expect to receive callbacks while calling this method.
272     [self deliverResourceAfterDelay];
273 }
274
275 - (BOOL)defersCallbacks
276 {
277     return defersCallbacks;
278 }
279
280 - (void)setFrameLoader:(WebFrameLoader *)fl
281 {
282     ASSERT(fl);
283     
284     [fl retain];
285     [frameLoader release];
286     frameLoader = fl;
287
288     [self setDefersCallbacks:[frameLoader _defersCallbacks]];
289 }
290
291 - (WebFrameLoader *)frameLoader
292 {
293     return frameLoader;
294 }
295
296 - (void)addData:(NSData *)data
297 {
298     // Don't buffer data if we're loading it from a WebResource.
299     if (resource == nil) {
300         if (NSURLConnectionSupportsBufferedData) {
301             // Buffer data only if the connection has handed us the data because is has stopped buffering it.
302             if (resourceData != nil) {
303                 [resourceData appendData:data];
304             }
305         } else {
306             if (resourceData == nil) {
307                 resourceData = [[NSMutableData alloc] init];
308             }
309             [resourceData appendData:data];
310         }
311     }
312 }
313
314 - (NSData *)resourceData
315 {
316     if (resource != nil) {
317         return [resource data];
318     }
319     if (resourceData != nil) {
320         // Retain and autorelease resourceData since releaseResources (which releases resourceData) may be called 
321         // before the caller of this method has an opporuntity to retain the returned data (4070729).
322         return [[resourceData retain] autorelease];
323     }
324     if (NSURLConnectionSupportsBufferedData) {
325         return [connection _bufferedData];
326     }
327     return nil;
328 }
329
330 - (void)clearResourceData
331 {
332     [resourceData setLength:0];
333 }
334
335 - (NSURLRequest *)willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
336 {
337     ASSERT(!reachedTerminalState);
338     NSMutableURLRequest *mutableRequest = [[newRequest mutableCopy] autorelease];
339     NSMutableURLRequest *clientRequest;
340     NSURLRequest *updatedRequest;
341     BOOL haveDataSchemeRequest = NO;
342     
343     // retain/release self in this delegate method since the additional processing can do
344     // anything including possibly releasing self; one example of this is 3266216
345     [self retain];
346
347     newRequest = mutableRequest;
348
349     // If we have a special "applewebdata" scheme URL we send a fake request to the delegate.
350     clientRequest = [mutableRequest _webDataRequestExternalRequest];
351     if (!clientRequest)
352         clientRequest = mutableRequest;
353     else
354         haveDataSchemeRequest = YES;
355     
356     if (identifier == nil)
357         identifier = [frameLoader _identifierForInitialRequest:clientRequest];
358
359     updatedRequest = [frameLoader _willSendRequest:clientRequest forResource:identifier redirectResponse:redirectResponse];
360
361     if (!haveDataSchemeRequest)
362         newRequest = updatedRequest;
363     else {
364         // If the delegate modified the request use that instead of
365         // our applewebdata request, otherwise use the original
366         // applewebdata request.
367         if (![updatedRequest isEqual:clientRequest]) {
368             newRequest = updatedRequest;
369         
370             // The respondsToSelector: check is only necessary for people building/running prior to Tier 8A416.
371             if ([NSURLProtocol respondsToSelector:@selector(_removePropertyForKey:inRequest:)] &&
372                 [newRequest isKindOfClass:[NSMutableURLRequest class]]) {
373                 NSMutableURLRequest *mr = (NSMutableURLRequest *)newRequest;
374                 [NSURLProtocol _removePropertyForKey:[NSURLRequest _webDataRequestPropertyKey] inRequest:mr];
375             }
376
377         }
378     }
379
380     // Store a copy of the request.
381     [request autorelease];
382
383     request = [newRequest copy];
384
385     [self release];
386     return request;
387 }
388
389 - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
390 {
391     ASSERT(!reachedTerminalState);
392     ASSERT(!currentConnectionChallenge);
393     ASSERT(!currentWebChallenge);
394
395     // retain/release self in this delegate method since the additional processing can do
396     // anything including possibly releasing self; one example of this is 3266216
397     [self retain];
398     currentConnectionChallenge = [challenge retain];;
399     currentWebChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:self];
400
401     [frameLoader _didReceiveAuthenticationChallenge:currentWebChallenge forResource:identifier];
402
403     [self release];
404 }
405
406 - (void)didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
407 {
408     ASSERT(!reachedTerminalState);
409     ASSERT(currentConnectionChallenge);
410     ASSERT(currentWebChallenge);
411     ASSERT(currentConnectionChallenge = challenge);
412
413     // retain/release self in this delegate method since the additional processing can do
414     // anything including possibly releasing self; one example of this is 3266216
415     [self retain];
416     [frameLoader _didCancelAuthenticationChallenge:currentWebChallenge forResource:identifier];
417     [self release];
418 }
419
420 - (void)didReceiveResponse:(NSURLResponse *)r
421 {
422     ASSERT(!reachedTerminalState);
423
424     // retain/release self in this delegate method since the additional processing can do
425     // anything including possibly releasing self; one example of this is 3266216
426     [self retain]; 
427
428     // If the URL is one of our whacky applewebdata URLs then
429     // fake up a substitute URL to present to the delegate.
430     if([WebDataProtocol _webIsDataProtocolURL:[r URL]]) {
431         r = [[[NSURLResponse alloc] initWithURL:[request _webDataRequestExternalURL] MIMEType:[r MIMEType] expectedContentLength:[r expectedContentLength] textEncodingName:[r textEncodingName]] autorelease];
432     }
433
434     [r retain];
435     [response release];
436     response = r;
437
438     [frameLoader _didReceiveResponse:r forResource:identifier];
439
440     [self release];
441 }
442
443 - (void)didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
444 {
445     // The following assertions are not quite valid here, since a subclass
446     // might override didReceiveData: in a way that invalidates them. This
447     // happens with the steps listed in 3266216
448     // ASSERT(con == connection);
449     // ASSERT(!reachedTerminalState);
450
451     // retain/release self in this delegate method since the additional processing can do
452     // anything including possibly releasing self; one example of this is 3266216
453     [self retain];
454     
455     [self addData:data];
456     
457     [frameLoader _didReceiveData:data contentLength:lengthReceived forResource:identifier];
458
459     [self release];
460 }
461
462 - (void)willStopBufferingData:(NSData *)data
463 {
464     ASSERT(resourceData == nil);
465     resourceData = [data mutableCopy];
466 }
467
468 - (void)signalFinish
469 {
470     signalledFinish = YES;
471     [frameLoader _didFinishLoadingForResource:identifier];
472 }
473
474 - (void)didFinishLoading
475 {
476     // If load has been cancelled after finishing (which could happen with a 
477     // javascript that changes the window location), do nothing.
478     if (cancelledFlag)
479         return;
480     
481     ASSERT(!reachedTerminalState);
482
483     if (!signalledFinish)
484         [self signalFinish];
485
486     [self releaseResources];
487 }
488
489 - (void)didFailWithError:(NSError *)error
490 {
491     if (cancelledFlag) {
492         return;
493     }
494     
495     ASSERT(!reachedTerminalState);
496
497     // retain/release self in this delegate method since the additional processing can do
498     // anything including possibly releasing self; one example of this is 3266216
499     [self retain];
500
501     [frameLoader _didFailLoadingWithError:error forResource:identifier];
502
503     [self releaseResources];
504     [self release];
505 }
506
507 - (NSCachedURLResponse *)willCacheResponse:(NSCachedURLResponse *)cachedResponse
508 {
509     // When in private browsing mode, prevent caching to disk
510     if ([cachedResponse storagePolicy] == NSURLCacheStorageAllowed && [frameLoader _privateBrowsingEnabled]) {
511         cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
512                                                                    data:[cachedResponse data]
513                                                                userInfo:[cachedResponse userInfo]
514                                                           storagePolicy:NSURLCacheStorageAllowedInMemoryOnly] autorelease];
515     }
516     return cachedResponse;
517 }
518
519 - (NSURLRequest *)connection:(NSURLConnection *)con willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
520 {
521     ASSERT(con == connection);
522     ++inNSURLConnectionCallback;
523     NSURLRequest *result = [self willSendRequest:newRequest redirectResponse:redirectResponse];
524     --inNSURLConnectionCallback;
525     return result;
526 }
527
528 - (void)connection:(NSURLConnection *)con didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
529 {
530     ASSERT(con == connection);
531     ++inNSURLConnectionCallback;
532     [self didReceiveAuthenticationChallenge:challenge];
533     --inNSURLConnectionCallback;
534 }
535
536 - (void)connection:(NSURLConnection *)con didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
537 {
538     ASSERT(con == connection);
539     ++inNSURLConnectionCallback;
540     [self didCancelAuthenticationChallenge:challenge];
541     --inNSURLConnectionCallback;
542 }
543
544 - (void)connection:(NSURLConnection *)con didReceiveResponse:(NSURLResponse *)r
545 {
546     ASSERT(con == connection);
547     ++inNSURLConnectionCallback;
548     [self didReceiveResponse:r];
549     --inNSURLConnectionCallback;
550 }
551
552 - (void)connection:(NSURLConnection *)con didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
553 {
554     ASSERT(con == connection);
555     ++inNSURLConnectionCallback;
556     [self didReceiveData:data lengthReceived:lengthReceived];
557     --inNSURLConnectionCallback;
558 }
559
560 - (void)connection:(NSURLConnection *)con willStopBufferingData:(NSData *)data
561 {
562     ASSERT(con == connection);
563     ++inNSURLConnectionCallback;
564     [self willStopBufferingData:data];
565     --inNSURLConnectionCallback;
566 }
567
568 - (void)connectionDidFinishLoading:(NSURLConnection *)con
569 {
570     // don't worry about checking connection consistency if this load
571     // got cancelled while finishing.
572     ASSERT(cancelledFlag || con == connection);
573     ++inNSURLConnectionCallback;
574     [self didFinishLoading];
575     --inNSURLConnectionCallback;
576 }
577
578 - (void)connection:(NSURLConnection *)con didFailWithError:(NSError *)error
579 {
580     ASSERT(con == connection);
581     ++inNSURLConnectionCallback;
582     [self didFailWithError:error];
583     --inNSURLConnectionCallback;
584 }
585
586 - (NSCachedURLResponse *)connection:(NSURLConnection *)con willCacheResponse:(NSCachedURLResponse *)cachedResponse
587 {
588 #ifndef NDEBUG
589     if (connection == nil && isInitializingConnection) {
590         LOG_ERROR("connection:willCacheResponse: was called inside of [NSURLConnection initWithRequest:delegate:] (40676250)");
591     }
592 #endif
593     ++inNSURLConnectionCallback;
594     NSCachedURLResponse *result = [self willCacheResponse:cachedResponse];
595     --inNSURLConnectionCallback;
596     return result;
597 }
598
599 - (void)cancelWithError:(NSError *)error
600 {
601     ASSERT(!reachedTerminalState);
602
603     // This flag prevents bad behvior when loads that finish cause the
604     // load itself to be cancelled (which could happen with a javascript that 
605     // changes the window location). This is used to prevent both the body
606     // of this method and the body of connectionDidFinishLoading: running
607     // for a single delegate. Cancelling wins.
608     cancelledFlag = YES;
609     
610     [currentConnectionChallenge release];
611     currentConnectionChallenge = nil;
612     
613     [currentWebChallenge release];
614     currentWebChallenge = nil;
615
616     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(deliverResource) object:nil];
617     [connection cancel];
618
619     [frameLoader _didFailLoadingWithError:error forResource:identifier];
620
621     [self releaseResources];
622 }
623
624 - (void)cancel
625 {
626     if (!reachedTerminalState) {
627         [self cancelWithError:[self cancelledError]];
628     }
629 }
630
631 - (NSError *)cancelledError
632 {
633     return [NSError _webKitErrorWithDomain:NSURLErrorDomain
634                                       code:NSURLErrorCancelled
635                                        URL:[request URL]];
636 }
637
638 - (void)setIdentifier: ident
639 {
640     if (identifier != ident){
641         [identifier release];
642         identifier = [ident retain];
643     }
644 }
645
646 - (NSURLResponse *)response
647 {
648     return response;
649 }
650
651 + (BOOL)inConnectionCallback
652 {
653     return inNSURLConnectionCallback != 0;
654 }
655
656 @end