1 /*
2 * Copyright (C) 2006, 2007, 2010, 2011 Apple Inc. All rights reserved.
3 * (C) 2007 Graham Dennis (graham.dennis@gmail.com)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "core/fetch/ResourceLoader.h"
32
33 #include "core/fetch/Resource.h"
34 #include "core/fetch/ResourceLoaderHost.h"
35 #include "core/fetch/ResourcePtr.h"
36 #include "platform/Logging.h"
37 #include "platform/SharedBuffer.h"
38 #include "platform/exported/WrappedResourceRequest.h"
39 #include "platform/exported/WrappedResourceResponse.h"
40 #include "platform/network/ResourceError.h"
41 #include "public/platform/Platform.h"
42 #include "public/platform/WebData.h"
43 #include "public/platform/WebURLError.h"
44 #include "public/platform/WebURLRequest.h"
45 #include "public/platform/WebURLResponse.h"
46 #include "wtf/Assertions.h"
47 #include "wtf/CurrentTime.h"
48
49 namespace WebCore {
50
RequestCountTracker(ResourceLoaderHost * host,Resource * resource)51 ResourceLoader::RequestCountTracker::RequestCountTracker(ResourceLoaderHost* host, Resource* resource)
52 : m_host(host)
53 , m_resource(resource)
54 {
55 m_host->incrementRequestCount(m_resource);
56 }
57
~RequestCountTracker()58 ResourceLoader::RequestCountTracker::~RequestCountTracker()
59 {
60 m_host->decrementRequestCount(m_resource);
61 }
62
create(ResourceLoaderHost * host,Resource * resource,const ResourceRequest & request,const ResourceLoaderOptions & options)63 PassRefPtr<ResourceLoader> ResourceLoader::create(ResourceLoaderHost* host, Resource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
64 {
65 RefPtr<ResourceLoader> loader(adoptRef(new ResourceLoader(host, resource, options)));
66 loader->init(request);
67 return loader.release();
68 }
69
ResourceLoader(ResourceLoaderHost * host,Resource * resource,const ResourceLoaderOptions & options)70 ResourceLoader::ResourceLoader(ResourceLoaderHost* host, Resource* resource, const ResourceLoaderOptions& options)
71 : m_host(host)
72 , m_notifiedLoadComplete(false)
73 , m_defersLoading(host->defersLoading())
74 , m_options(options)
75 , m_resource(resource)
76 , m_state(Initialized)
77 , m_connectionState(ConnectionStateNew)
78 , m_requestCountTracker(adoptPtr(new RequestCountTracker(host, resource)))
79 {
80 }
81
~ResourceLoader()82 ResourceLoader::~ResourceLoader()
83 {
84 ASSERT(m_state == Terminated);
85 }
86
releaseResources()87 void ResourceLoader::releaseResources()
88 {
89 ASSERT(m_state != Terminated);
90 m_requestCountTracker.clear();
91 m_host->didLoadResource(m_resource);
92 if (m_state == Terminated)
93 return;
94 m_resource->clearLoader();
95 m_host->willTerminateResourceLoader(this);
96
97 ASSERT(m_state != Terminated);
98
99 // It's possible that when we release the loader, it will be
100 // deallocated and release the last reference to this object.
101 // We need to retain to avoid accessing the object after it
102 // has been deallocated and also to avoid reentering this method.
103 RefPtr<ResourceLoader> protector(this);
104
105 m_host.clear();
106 m_state = Terminated;
107
108 if (m_loader) {
109 m_loader->cancel();
110 m_loader.clear();
111 }
112
113 m_deferredRequest = ResourceRequest();
114 }
115
init(const ResourceRequest & passedRequest)116 void ResourceLoader::init(const ResourceRequest& passedRequest)
117 {
118 ResourceRequest request(passedRequest);
119 m_host->willSendRequest(m_resource->identifier(), request, ResourceResponse(), m_options);
120 request.setReportLoadTiming(true);
121 ASSERT(m_state != Terminated);
122 ASSERT(!request.isNull());
123 m_originalRequest = m_request = request;
124 m_host->didInitializeResourceLoader(this);
125 }
126
start()127 void ResourceLoader::start()
128 {
129 ASSERT(!m_loader);
130 ASSERT(!m_request.isNull());
131 ASSERT(m_deferredRequest.isNull());
132
133 m_host->willStartLoadingResource(m_request);
134
135 if (m_options.synchronousPolicy == RequestSynchronously) {
136 requestSynchronously();
137 return;
138 }
139
140 if (m_defersLoading) {
141 m_deferredRequest = m_request;
142 return;
143 }
144
145 if (m_state == Terminated)
146 return;
147
148 RELEASE_ASSERT(m_connectionState == ConnectionStateNew);
149 m_connectionState = ConnectionStateStarted;
150
151 m_loader = adoptPtr(blink::Platform::current()->createURLLoader());
152 ASSERT(m_loader);
153 blink::WrappedResourceRequest wrappedRequest(m_request);
154 wrappedRequest.setAllowStoredCredentials(m_options.allowCredentials == AllowStoredCredentials);
155 m_loader->loadAsynchronously(wrappedRequest, this);
156 }
157
changeToSynchronous()158 void ResourceLoader::changeToSynchronous()
159 {
160 ASSERT(m_options.synchronousPolicy == RequestAsynchronously);
161 ASSERT(m_loader);
162 m_loader->cancel();
163 m_loader.clear();
164 m_request.setPriority(ResourceLoadPriorityHighest);
165 m_connectionState = ConnectionStateNew;
166 requestSynchronously();
167 }
168
setDefersLoading(bool defers)169 void ResourceLoader::setDefersLoading(bool defers)
170 {
171 m_defersLoading = defers;
172 if (m_loader)
173 m_loader->setDefersLoading(defers);
174 if (!defers && !m_deferredRequest.isNull()) {
175 m_request = m_deferredRequest;
176 m_deferredRequest = ResourceRequest();
177 start();
178 }
179 }
180
didDownloadData(blink::WebURLLoader *,int length,int encodedDataLength)181 void ResourceLoader::didDownloadData(blink::WebURLLoader*, int length, int encodedDataLength)
182 {
183 RefPtr<ResourceLoader> protect(this);
184 RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse);
185 m_host->didDownloadData(m_resource, length, encodedDataLength, m_options);
186 m_resource->didDownloadData(length);
187 }
188
didFinishLoadingOnePart(double finishTime)189 void ResourceLoader::didFinishLoadingOnePart(double finishTime)
190 {
191 // If load has been cancelled after finishing (which could happen with a
192 // JavaScript that changes the window location), do nothing.
193 if (m_state == Terminated)
194 return;
195
196 if (m_notifiedLoadComplete)
197 return;
198 m_notifiedLoadComplete = true;
199 m_host->didFinishLoading(m_resource, finishTime, m_options);
200 }
201
didChangePriority(ResourceLoadPriority loadPriority)202 void ResourceLoader::didChangePriority(ResourceLoadPriority loadPriority)
203 {
204 if (m_loader) {
205 m_host->didChangeLoadingPriority(m_resource, loadPriority);
206 m_loader->didChangePriority(static_cast<blink::WebURLRequest::Priority>(loadPriority));
207 }
208 }
209
cancelIfNotFinishing()210 void ResourceLoader::cancelIfNotFinishing()
211 {
212 if (m_state != Initialized)
213 return;
214 cancel();
215 }
216
cancel()217 void ResourceLoader::cancel()
218 {
219 cancel(ResourceError());
220 }
221
cancel(const ResourceError & error)222 void ResourceLoader::cancel(const ResourceError& error)
223 {
224 // If the load has already completed - succeeded, failed, or previously cancelled - do nothing.
225 if (m_state == Terminated)
226 return;
227 if (m_state == Finishing) {
228 releaseResources();
229 return;
230 }
231
232 ResourceError nonNullError = error.isNull() ? ResourceError::cancelledError(m_request.url()) : error;
233
234 // This function calls out to clients at several points that might do
235 // something that causes the last reference to this object to go away.
236 RefPtr<ResourceLoader> protector(this);
237
238 WTF_LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
239 if (m_state == Initialized)
240 m_state = Finishing;
241 m_resource->setResourceError(nonNullError);
242
243 if (m_loader) {
244 m_connectionState = ConnectionStateCanceled;
245 m_loader->cancel();
246 m_loader.clear();
247 }
248
249 m_host->didFailLoading(m_resource, nonNullError, m_options);
250
251 if (m_state == Finishing)
252 m_resource->error(Resource::LoadError);
253 if (m_state != Terminated)
254 releaseResources();
255 }
256
willSendRequest(blink::WebURLLoader *,blink::WebURLRequest & passedRequest,const blink::WebURLResponse & passedRedirectResponse)257 void ResourceLoader::willSendRequest(blink::WebURLLoader*, blink::WebURLRequest& passedRequest, const blink::WebURLResponse& passedRedirectResponse)
258 {
259 RefPtr<ResourceLoader> protect(this);
260
261 ResourceRequest& request(passedRequest.toMutableResourceRequest());
262 ASSERT(!request.isNull());
263 const ResourceResponse& redirectResponse(passedRedirectResponse.toResourceResponse());
264 ASSERT(!redirectResponse.isNull());
265 if (!m_host->shouldRequest(m_resource, request, m_options)) {
266 cancel();
267 return;
268 }
269 m_host->redirectReceived(m_resource, redirectResponse);
270 m_resource->willSendRequest(request, redirectResponse);
271 if (request.isNull() || m_state == Terminated)
272 return;
273
274 m_host->willSendRequest(m_resource->identifier(), request, redirectResponse, m_options);
275 request.setReportLoadTiming(true);
276 ASSERT(!request.isNull());
277 m_request = request;
278 }
279
didReceiveCachedMetadata(blink::WebURLLoader *,const char * data,int length)280 void ResourceLoader::didReceiveCachedMetadata(blink::WebURLLoader*, const char* data, int length)
281 {
282 RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
283 ASSERT(m_state == Initialized);
284 m_resource->setSerializedCachedMetadata(data, length);
285 }
286
didSendData(blink::WebURLLoader *,unsigned long long bytesSent,unsigned long long totalBytesToBeSent)287 void ResourceLoader::didSendData(blink::WebURLLoader*, unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
288 {
289 ASSERT(m_state == Initialized);
290 RefPtr<ResourceLoader> protect(this);
291 m_resource->didSendData(bytesSent, totalBytesToBeSent);
292 }
293
didReceiveResponse(blink::WebURLLoader *,const blink::WebURLResponse & response)294 void ResourceLoader::didReceiveResponse(blink::WebURLLoader*, const blink::WebURLResponse& response)
295 {
296 ASSERT(!response.isNull());
297 ASSERT(m_state == Initialized);
298
299 bool isMultipartPayload = response.isMultipartPayload();
300 bool isValidStateTransition = (m_connectionState == ConnectionStateStarted || m_connectionState == ConnectionStateReceivedResponse);
301 // In the case of multipart loads, calls to didReceiveData & didReceiveResponse can be interleaved.
302 RELEASE_ASSERT(isMultipartPayload || isValidStateTransition);
303 m_connectionState = ConnectionStateReceivedResponse;
304
305 // Reference the object in this method since the additional processing can do
306 // anything including removing the last reference to this object.
307 RefPtr<ResourceLoader> protect(this);
308 m_resource->responseReceived(response.toResourceResponse());
309 if (m_state == Terminated)
310 return;
311
312 m_host->didReceiveResponse(m_resource, response.toResourceResponse(), m_options);
313
314 if (response.toResourceResponse().isMultipart()) {
315 // We don't count multiParts in a ResourceFetcher's request count
316 m_requestCountTracker.clear();
317 if (!m_resource->isImage()) {
318 cancel();
319 return;
320 }
321 } else if (isMultipartPayload) {
322 // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
323 // After the first multipart section is complete, signal to delegates that this load is "finished"
324 m_host->subresourceLoaderFinishedLoadingOnePart(this);
325 didFinishLoadingOnePart(0);
326 }
327
328 if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
329 return;
330 m_state = Finishing;
331 m_resource->error(Resource::LoadError);
332 cancel();
333 }
334
didReceiveData(blink::WebURLLoader *,const char * data,int length,int encodedDataLength)335 void ResourceLoader::didReceiveData(blink::WebURLLoader*, const char* data, int length, int encodedDataLength)
336 {
337 RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
338 m_connectionState = ConnectionStateReceivingData;
339
340 // It is possible to receive data on uninitialized resources if it had an error status code, and we are running a nested message
341 // loop. When this occurs, ignoring the data is the correct action.
342 if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
343 return;
344 ASSERT(m_state == Initialized);
345
346 // Reference the object in this method since the additional processing can do
347 // anything including removing the last reference to this object.
348 RefPtr<ResourceLoader> protect(this);
349
350 // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
351 // However, with today's computers and networking speeds, this won't happen in practice.
352 // Could be an issue with a giant local file.
353 m_host->didReceiveData(m_resource, data, length, encodedDataLength, m_options);
354 m_resource->appendData(data, length);
355 }
356
didFinishLoading(blink::WebURLLoader *,double finishTime)357 void ResourceLoader::didFinishLoading(blink::WebURLLoader*, double finishTime)
358 {
359 RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
360 m_connectionState = ConnectionStateFinishedLoading;
361 if (m_state != Initialized)
362 return;
363 ASSERT(m_state != Terminated);
364 WTF_LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
365
366 RefPtr<ResourceLoader> protect(this);
367 ResourcePtr<Resource> protectResource(m_resource);
368 m_state = Finishing;
369 m_resource->finish(finishTime);
370 didFinishLoadingOnePart(finishTime);
371
372 // If the load has been cancelled by a delegate in response to didFinishLoad(), do not release
373 // the resources a second time, they have been released by cancel.
374 if (m_state == Terminated)
375 return;
376 releaseResources();
377 }
378
didFail(blink::WebURLLoader *,const blink::WebURLError & error)379 void ResourceLoader::didFail(blink::WebURLLoader*, const blink::WebURLError& error)
380 {
381 m_connectionState = ConnectionStateFailed;
382 ASSERT(m_state != Terminated);
383 WTF_LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
384
385 RefPtr<ResourceLoader> protect(this);
386 RefPtr<ResourceLoaderHost> protectHost(m_host);
387 ResourcePtr<Resource> protectResource(m_resource);
388 m_state = Finishing;
389 m_resource->setResourceError(error);
390 m_resource->error(Resource::LoadError);
391
392 if (m_state == Terminated)
393 return;
394
395 if (!m_notifiedLoadComplete) {
396 m_notifiedLoadComplete = true;
397 m_host->didFailLoading(m_resource, error, m_options);
398 }
399
400 releaseResources();
401 }
402
isLoadedBy(ResourceLoaderHost * loader) const403 bool ResourceLoader::isLoadedBy(ResourceLoaderHost* loader) const
404 {
405 return m_host->isLoadedBy(loader);
406 }
407
requestSynchronously()408 void ResourceLoader::requestSynchronously()
409 {
410 OwnPtr<blink::WebURLLoader> loader = adoptPtr(blink::Platform::current()->createURLLoader());
411 ASSERT(loader);
412
413 RefPtr<ResourceLoader> protect(this);
414 RefPtr<ResourceLoaderHost> protectHost(m_host);
415 ResourcePtr<Resource> protectResource(m_resource);
416
417 RELEASE_ASSERT(m_connectionState == ConnectionStateNew);
418 m_connectionState = ConnectionStateStarted;
419
420 blink::WrappedResourceRequest requestIn(m_request);
421 requestIn.setAllowStoredCredentials(m_options.allowCredentials == AllowStoredCredentials);
422 blink::WebURLResponse responseOut;
423 responseOut.initialize();
424 blink::WebURLError errorOut;
425 blink::WebData dataOut;
426 loader->loadSynchronously(requestIn, responseOut, errorOut, dataOut);
427 if (errorOut.reason) {
428 didFail(0, errorOut);
429 return;
430 }
431 didReceiveResponse(0, responseOut);
432 if (m_state == Terminated)
433 return;
434 RefPtr<ResourceLoadInfo> resourceLoadInfo = responseOut.toResourceResponse().resourceLoadInfo();
435 m_host->didReceiveData(m_resource, dataOut.data(), dataOut.size(), resourceLoadInfo ? resourceLoadInfo->encodedDataLength : -1, m_options);
436 m_resource->setResourceBuffer(dataOut);
437 didFinishLoading(0, monotonicallyIncreasingTime());
438 }
439
440 }
441