LayoutTests:
[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] 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 = [dataSource _identifierForInitialRequest:clientRequest];
358
359     updatedRequest = [dataSource _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     [dataSource _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     [dataSource _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     [dataSource _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     [dataSource _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     [dataSource _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     [dataSource _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 && [dataSource _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     [dataSource _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