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