Reviewed by geoff
[WebKit-https.git] / WebKit / Loader / WebSubresourceLoader.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/WebSubresourceLoader.h>
30
31 #import <JavaScriptCore/Assertions.h>
32 #import <Foundation/NSURLResponse.h>
33 #import <WebCore/WebCoreResourceLoader.h>
34 #import <WebKit/WebFormDataStream.h>
35 #import <WebKit/WebFrameLoader.h>
36 #import <WebKitSystemInterface.h>
37
38 @implementation WebSubresourceLoader
39
40 - initWithLoader:(id <WebCoreResourceLoader>)l frameLoader:(WebFrameLoader *)fl
41 {
42     [super init];
43     
44     coreLoader = [l retain];
45
46     [self setFrameLoader:fl];
47     
48     return self;
49 }
50
51 - (void)dealloc
52 {
53     [coreLoader release];
54     [super dealloc];
55 }
56
57 static BOOL isConditionalRequest(NSURLRequest *request)
58 {
59     if ([request valueForHTTPHeaderField:@"If-Match"] ||
60         [request valueForHTTPHeaderField:@"If-Modified-Since"] ||
61         [request valueForHTTPHeaderField:@"If-None-Match"] ||
62         [request valueForHTTPHeaderField:@"If-Range"] ||
63         [request valueForHTTPHeaderField:@"If-Unmodified-Since"])
64         return YES;
65     return NO;
66 }
67
68 static BOOL hasCaseInsensitivePrefix(NSString *str, NSString *prefix)
69 {
70     return str && ([str rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound);
71 }
72
73 static BOOL isFileURLString(NSString *URL)
74 {
75     return hasCaseInsensitivePrefix(URL, @"file:");
76 }
77
78 #define WebReferrer     (@"Referer")
79
80 static void setHTTPReferrer(NSMutableURLRequest *request, NSString *theReferrer)
81 {
82     // Do not set the referrer to a string that refers to a file URL.
83     // That is a potential security hole.
84     if (isFileURLString(theReferrer))
85         return;
86     
87     // Don't allow empty Referer: headers; some servers refuse them
88     if([theReferrer length] == 0)
89         theReferrer = nil;
90     
91     [request setValue:theReferrer forHTTPHeaderField:WebReferrer];
92 }
93
94 + (WebSubresourceLoader *)startLoadingResource:(id <WebCoreResourceLoader>)rLoader
95                                    withRequest:(NSMutableURLRequest *)newRequest
96                                  customHeaders:(NSDictionary *)customHeaders
97                                       referrer:(NSString *)referrer 
98                                 forFrameLoader:(WebFrameLoader *)fl
99 {
100     if ([fl state] == WebFrameStateProvisional)
101         return nil;
102         
103     WKSupportsMultipartXMixedReplace(newRequest);
104
105     WebSubresourceLoader *loader = [[[self alloc] initWithLoader:rLoader frameLoader:fl] autorelease];
106     
107     [fl addSubresourceLoader:loader];
108
109     NSEnumerator *e = [customHeaders keyEnumerator];
110     NSString *key;
111     while ((key = [e nextObject]))
112         [newRequest addValue:[customHeaders objectForKey:key] forHTTPHeaderField:key];
113
114     // Use the original request's cache policy for two reasons:
115     // 1. For POST requests, we mutate the cache policy for the main resource,
116     //    but we do not want this to apply to subresources
117     // 2. Delegates that modify the cache policy using willSendRequest: should
118     //    not affect any other resources. Such changes need to be done
119     //    per request.
120     if (isConditionalRequest(newRequest))
121         [newRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
122     else
123         [newRequest setCachePolicy:[[fl _originalRequest] cachePolicy]];
124     setHTTPReferrer(newRequest, referrer);
125     
126     [fl _addExtraFieldsToRequest:newRequest mainResource:NO alwaysFromRequest:NO];
127             
128     if (![loader loadWithRequest:newRequest])
129         loader = nil;
130     
131     return loader;
132 }
133
134 + (WebSubresourceLoader *)startLoadingResource:(id <WebCoreResourceLoader>)rLoader
135                                     withMethod:(NSString *)method 
136                                            URL:(NSURL *)URL
137                                  customHeaders:(NSDictionary *)customHeaders
138                                       referrer:(NSString *)referrer
139                                  forFrameLoader:(WebFrameLoader *)fl
140 {
141     NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] initWithURL:URL];
142
143     // setHTTPMethod is not called for GET requests to work around <rdar://4464032>.
144     if (![method isEqualToString:@"GET"])
145         [newRequest setHTTPMethod:method];
146
147     WebSubresourceLoader *loader = [self startLoadingResource:rLoader withRequest:newRequest customHeaders:customHeaders referrer:referrer forFrameLoader:fl];
148     [newRequest release];
149
150     return loader;
151 }
152
153 + (WebSubresourceLoader *)startLoadingResource:(id <WebCoreResourceLoader>)rLoader
154                                     withMethod:(NSString *)method 
155                                            URL:(NSURL *)URL
156                                  customHeaders:(NSDictionary *)customHeaders
157                                       postData:(NSArray *)postData
158                                       referrer:(NSString *)referrer
159                                 forFrameLoader:(WebFrameLoader *)fl
160 {
161     NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] initWithURL:URL];
162
163     // setHTTPMethod is not called for GET requests to work around <rdar://4464032>.
164     if (![method isEqualToString:@"GET"])
165         [newRequest setHTTPMethod:method];
166
167     webSetHTTPBody(newRequest, postData);
168
169     WebSubresourceLoader *loader = [self startLoadingResource:rLoader withRequest:newRequest customHeaders:customHeaders referrer:referrer forFrameLoader:fl];
170     [newRequest release];
171
172     return loader;
173
174 }
175
176 - (void)receivedError:(NSError *)error
177 {
178     [frameLoader _receivedError:error];
179 }
180
181 - (NSURLRequest *)willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse;
182 {
183     NSURL *oldURL = [request URL];
184     NSURLRequest *clientRequest = [super willSendRequest:newRequest redirectResponse:redirectResponse];
185     
186     if (clientRequest != nil && oldURL != [clientRequest URL] && ![oldURL isEqual:[clientRequest URL]])
187         [coreLoader redirectedToURL:[clientRequest URL]];
188
189     return clientRequest;
190 }
191
192 - (void)didReceiveResponse:(NSURLResponse *)r
193 {
194     ASSERT(r);
195
196     if ([[r MIMEType] isEqualToString:@"multipart/x-mixed-replace"])
197         loadingMultipartContent = YES;
198
199     // retain/release self in this delegate method since the additional processing can do
200     // anything including possibly releasing self; one example of this is 3266216
201     [self retain];
202     [coreLoader receivedResponse:r];
203     // The coreLoader can cancel a load if it receives a multipart response for a non-image
204     if (reachedTerminalState) {
205         [self release];
206         return;
207     }
208     [super didReceiveResponse:r];
209     [self release];
210     
211     if (loadingMultipartContent && [[self resourceData] length]) {
212         // A subresource loader does not load multipart sections progressively, deliver the previously received data to the coreLoader all at once
213         [coreLoader addData:[self resourceData]];
214         // Clears the data to make way for the next multipart section
215         [self clearResourceData];
216         
217         // After the first multipart section is complete, signal to delegates that this load is "finished" 
218         if (!signalledFinish)
219             [self signalFinish];
220     }
221 }
222
223 - (void)didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived allAtOnce:(BOOL)allAtOnce
224 {
225     // retain/release self in this delegate method since the additional processing can do
226     // anything including possibly releasing self; one example of this is 3266216
227     [self retain];
228     // A subresource loader does not load multipart sections progressively, don't deliver any data to the coreLoader yet
229     if (!loadingMultipartContent)
230         [coreLoader addData:data];
231     [super didReceiveData:data lengthReceived:lengthReceived allAtOnce:allAtOnce];
232     [self release];
233 }
234
235 - (void)signalFinish
236 {
237     [frameLoader removeSubresourceLoader:self];
238     [frameLoader _finishedLoadingResource];
239     [super signalFinish];
240 }
241
242 - (void)didFinishLoading
243 {
244     // Calling _removeSubresourceLoader will likely result in a call to release, so we must retain.
245     [self retain];
246     
247     [coreLoader finishWithData:[self resourceData]];
248     
249     if (!signalledFinish)
250         [self signalFinish];
251         
252     [super didFinishLoading];
253
254     [self release];    
255 }
256
257 - (void)didFailWithError:(NSError *)error
258 {
259     // Calling _removeSubresourceLoader will likely result in a call to release, so we must retain.
260     [self retain];
261     
262     [coreLoader reportError];
263     [frameLoader removeSubresourceLoader:self];
264     [self receivedError:error];
265     [super didFailWithError:error];
266
267     [self release];
268 }
269
270 - (void)cancel
271 {
272     // Calling _removeSubresourceLoader will likely result in a call to release, so we must retain.
273     [self retain];
274         
275     [coreLoader cancel];
276     [frameLoader removeSubresourceLoader:self];
277     [self receivedError:[self cancelledError]];
278     [super cancel];
279
280     [self release];
281 }
282
283 @end