Home | History | Annotate | Download | only in fetch
      1 /*
      2  * Copyright (C) 2006, 2007, 2010, 2011 Apple Inc. All rights reserved.
      3  *           (C) 2007 Graham Dennis (graham.dennis (at) 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/WebThreadedDataReceiver.h"
     44 #include "public/platform/WebURLError.h"
     45 #include "public/platform/WebURLRequest.h"
     46 #include "public/platform/WebURLResponse.h"
     47 #include "wtf/Assertions.h"
     48 #include "wtf/CurrentTime.h"
     49 
     50 namespace blink {
     51 
     52 ResourceLoader::RequestCountTracker::RequestCountTracker(ResourceLoaderHost* host, Resource* resource)
     53     : m_host(host)
     54     , m_resource(resource)
     55 {
     56     m_host->incrementRequestCount(m_resource);
     57 }
     58 
     59 ResourceLoader::RequestCountTracker::~RequestCountTracker()
     60 {
     61     m_host->decrementRequestCount(m_resource);
     62 }
     63 
     64 ResourceLoader::RequestCountTracker::RequestCountTracker(const RequestCountTracker& other)
     65 {
     66     m_host = other.m_host;
     67     m_resource = other.m_resource;
     68     m_host->incrementRequestCount(m_resource);
     69 }
     70 
     71 PassRefPtrWillBeRawPtr<ResourceLoader> ResourceLoader::create(ResourceLoaderHost* host, Resource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
     72 {
     73     RefPtrWillBeRawPtr<ResourceLoader> loader(adoptRefWillBeNoop(new ResourceLoader(host, resource, options)));
     74     loader->init(request);
     75     return loader.release();
     76 }
     77 
     78 ResourceLoader::ResourceLoader(ResourceLoaderHost* host, Resource* resource, const ResourceLoaderOptions& options)
     79     : m_host(host)
     80     , m_notifiedLoadComplete(false)
     81     , m_defersLoading(host->defersLoading())
     82     , m_options(options)
     83     , m_resource(resource)
     84     , m_state(Initialized)
     85     , m_connectionState(ConnectionStateNew)
     86     , m_requestCountTracker(adoptPtr(new RequestCountTracker(host, resource)))
     87 {
     88 }
     89 
     90 ResourceLoader::~ResourceLoader()
     91 {
     92     ASSERT(m_state == Terminated);
     93 }
     94 
     95 void ResourceLoader::trace(Visitor* visitor)
     96 {
     97     visitor->trace(m_host);
     98     visitor->trace(m_resource);
     99 }
    100 
    101 void ResourceLoader::releaseResources()
    102 {
    103     ASSERT(m_state != Terminated);
    104     ASSERT(m_notifiedLoadComplete);
    105     m_requestCountTracker.clear();
    106     m_host->didLoadResource(m_resource);
    107     if (m_state == Terminated)
    108         return;
    109     m_resource->clearLoader();
    110     m_resource->deleteIfPossible();
    111     m_resource = nullptr;
    112     m_host->willTerminateResourceLoader(this);
    113 
    114     ASSERT(m_state != Terminated);
    115 
    116     // It's possible that when we release the loader, 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     RefPtrWillBeRawPtr<ResourceLoader> protector(this);
    121 
    122     m_host.clear();
    123     m_state = Terminated;
    124 
    125     if (m_loader) {
    126         m_loader->cancel();
    127         m_loader.clear();
    128     }
    129 
    130     m_deferredRequest = ResourceRequest();
    131 }
    132 
    133 void ResourceLoader::init(const ResourceRequest& passedRequest)
    134 {
    135     ASSERT(m_state != Terminated);
    136     ResourceRequest request(passedRequest);
    137     m_host->willSendRequest(m_resource->identifier(), request, ResourceResponse(), m_options.initiatorInfo);
    138     ASSERT(m_state != Terminated);
    139     ASSERT(!request.isNull());
    140     m_originalRequest = m_request = applyOptions(request);
    141     m_resource->updateRequest(request);
    142     ASSERT(m_state != Terminated);
    143     m_host->didInitializeResourceLoader(this);
    144 }
    145 
    146 void ResourceLoader::start()
    147 {
    148     ASSERT(!m_loader);
    149     ASSERT(!m_request.isNull());
    150     ASSERT(m_deferredRequest.isNull());
    151 
    152     m_host->willStartLoadingResource(m_resource, m_request);
    153 
    154     if (m_options.synchronousPolicy == RequestSynchronously) {
    155         requestSynchronously();
    156         return;
    157     }
    158 
    159     if (m_defersLoading) {
    160         m_deferredRequest = m_request;
    161         return;
    162     }
    163 
    164     if (m_state == Terminated)
    165         return;
    166 
    167     RELEASE_ASSERT(m_connectionState == ConnectionStateNew);
    168     m_connectionState = ConnectionStateStarted;
    169 
    170     m_loader = adoptPtr(blink::Platform::current()->createURLLoader());
    171     ASSERT(m_loader);
    172     blink::WrappedResourceRequest wrappedRequest(m_request);
    173     m_loader->loadAsynchronously(wrappedRequest, this);
    174 }
    175 
    176 void ResourceLoader::changeToSynchronous()
    177 {
    178     ASSERT(m_options.synchronousPolicy == RequestAsynchronously);
    179     ASSERT(m_loader);
    180     m_loader->cancel();
    181     m_loader.clear();
    182     m_request.setPriority(ResourceLoadPriorityHighest);
    183     m_connectionState = ConnectionStateNew;
    184     requestSynchronously();
    185 }
    186 
    187 void ResourceLoader::setDefersLoading(bool defers)
    188 {
    189     m_defersLoading = defers;
    190     if (m_loader)
    191         m_loader->setDefersLoading(defers);
    192     if (!defers && !m_deferredRequest.isNull()) {
    193         m_request = applyOptions(m_deferredRequest);
    194         m_deferredRequest = ResourceRequest();
    195         start();
    196     }
    197 }
    198 
    199 void ResourceLoader::attachThreadedDataReceiver(PassOwnPtr<blink::WebThreadedDataReceiver> threadedDataReceiver)
    200 {
    201     if (m_loader) {
    202         // The implementor of the WebURLLoader assumes ownership of the
    203         // threaded data receiver if it signals that it got successfully
    204         // attached.
    205         blink::WebThreadedDataReceiver* rawThreadedDataReceiver = threadedDataReceiver.leakPtr();
    206         if (!m_loader->attachThreadedDataReceiver(rawThreadedDataReceiver))
    207             delete rawThreadedDataReceiver;
    208     }
    209 }
    210 
    211 void ResourceLoader::didDownloadData(blink::WebURLLoader*, int length, int encodedDataLength)
    212 {
    213     ASSERT(m_state != Terminated);
    214     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    215     RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse);
    216     m_host->didDownloadData(m_resource, length, encodedDataLength);
    217     if (m_state == Terminated)
    218         return;
    219     m_resource->didDownloadData(length);
    220 }
    221 
    222 void ResourceLoader::didFinishLoadingOnePart(double finishTime, int64 encodedDataLength)
    223 {
    224     // If load has been cancelled after finishing (which could happen with a
    225     // JavaScript that changes the window location), do nothing.
    226     if (m_state == Terminated)
    227         return;
    228 
    229     if (m_notifiedLoadComplete)
    230         return;
    231     m_notifiedLoadComplete = true;
    232     m_host->didFinishLoading(m_resource, finishTime, encodedDataLength);
    233 }
    234 
    235 void ResourceLoader::didChangePriority(ResourceLoadPriority loadPriority, int intraPriorityValue)
    236 {
    237     if (m_loader) {
    238         m_host->didChangeLoadingPriority(m_resource, loadPriority, intraPriorityValue);
    239         ASSERT(m_state != Terminated);
    240         m_loader->didChangePriority(static_cast<blink::WebURLRequest::Priority>(loadPriority), intraPriorityValue);
    241     }
    242 }
    243 
    244 void ResourceLoader::cancelIfNotFinishing()
    245 {
    246     if (m_state != Initialized)
    247         return;
    248     cancel();
    249 }
    250 
    251 void ResourceLoader::cancel()
    252 {
    253     cancel(ResourceError());
    254 }
    255 
    256 void ResourceLoader::cancel(const ResourceError& error)
    257 {
    258     // If the load has already completed - succeeded, failed, or previously cancelled - do nothing.
    259     if (m_state == Terminated)
    260         return;
    261     if (m_state == Finishing) {
    262         releaseResources();
    263         return;
    264     }
    265 
    266     ResourceError nonNullError = error.isNull() ? ResourceError::cancelledError(m_request.url()) : error;
    267 
    268     // This function calls out to clients at several points that might do
    269     // something that causes the last reference to this object to go away.
    270     RefPtrWillBeRawPtr<ResourceLoader> protector(this);
    271 
    272     WTF_LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
    273     if (m_state == Initialized)
    274         m_state = Finishing;
    275     m_resource->setResourceError(nonNullError);
    276 
    277     if (m_loader) {
    278         m_connectionState = ConnectionStateCanceled;
    279         m_loader->cancel();
    280         m_loader.clear();
    281     }
    282 
    283     if (!m_notifiedLoadComplete) {
    284         m_notifiedLoadComplete = true;
    285         m_host->didFailLoading(m_resource, nonNullError);
    286     }
    287 
    288     if (m_state == Finishing)
    289         m_resource->error(Resource::LoadError);
    290     if (m_state != Terminated)
    291         releaseResources();
    292 }
    293 
    294 void ResourceLoader::willSendRequest(blink::WebURLLoader*, blink::WebURLRequest& passedRequest, const blink::WebURLResponse& passedRedirectResponse)
    295 {
    296     ASSERT(m_state != Terminated);
    297     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    298 
    299     ResourceRequest& request(applyOptions(passedRequest.toMutableResourceRequest()));
    300 
    301     ASSERT(!request.isNull());
    302     const ResourceResponse& redirectResponse(passedRedirectResponse.toResourceResponse());
    303     ASSERT(!redirectResponse.isNull());
    304     if (!m_host->canAccessRedirect(m_resource, request, redirectResponse, m_options)) {
    305         cancel();
    306         return;
    307     }
    308     ASSERT(m_state != Terminated);
    309 
    310     applyOptions(request); // canAccessRedirect() can modify m_options so we should re-apply it.
    311     m_host->redirectReceived(m_resource, redirectResponse);
    312     ASSERT(m_state != Terminated);
    313     m_resource->willSendRequest(request, redirectResponse);
    314     if (request.isNull() || m_state == Terminated)
    315         return;
    316 
    317     m_host->willSendRequest(m_resource->identifier(), request, redirectResponse, m_options.initiatorInfo);
    318     ASSERT(m_state != Terminated);
    319     ASSERT(!request.isNull());
    320     m_resource->updateRequest(request);
    321     m_request = request;
    322 }
    323 
    324 void ResourceLoader::didReceiveCachedMetadata(blink::WebURLLoader*, const char* data, int length)
    325 {
    326     RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
    327     ASSERT(m_state == Initialized);
    328     m_resource->setSerializedCachedMetadata(data, length);
    329 }
    330 
    331 void ResourceLoader::didSendData(blink::WebURLLoader*, unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
    332 {
    333     ASSERT(m_state == Initialized);
    334     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    335     m_resource->didSendData(bytesSent, totalBytesToBeSent);
    336 }
    337 
    338 bool ResourceLoader::responseNeedsAccessControlCheck() const
    339 {
    340     // If the fetch was (potentially) CORS enabled, an access control check of the response is required.
    341     return m_options.corsEnabled == IsCORSEnabled;
    342 }
    343 
    344 void ResourceLoader::didReceiveResponse(blink::WebURLLoader*, const blink::WebURLResponse& response)
    345 {
    346     ASSERT(!response.isNull());
    347     ASSERT(m_state == Initialized);
    348 
    349     bool isMultipartPayload = response.isMultipartPayload();
    350     bool isValidStateTransition = (m_connectionState == ConnectionStateStarted || m_connectionState == ConnectionStateReceivedResponse);
    351     // In the case of multipart loads, calls to didReceiveData & didReceiveResponse can be interleaved.
    352     RELEASE_ASSERT(isMultipartPayload || isValidStateTransition);
    353     m_connectionState = ConnectionStateReceivedResponse;
    354 
    355     const ResourceResponse& resourceResponse = response.toResourceResponse();
    356 
    357     if (responseNeedsAccessControlCheck()) {
    358         // If the response successfully validated a cached resource, perform
    359         // the access control with respect to it. Need to do this right here
    360         // before the resource switches clients over to that validated resource.
    361         Resource* resource = m_resource;
    362         if (resource->isCacheValidator() && resourceResponse.httpStatusCode() == 304)
    363             resource = m_resource->resourceToRevalidate();
    364         else
    365             m_resource->setResponse(resourceResponse);
    366         if (!m_host->canAccessResource(resource, m_options.securityOrigin.get(), response.url())) {
    367             m_host->didReceiveResponse(m_resource, resourceResponse);
    368             cancel();
    369             return;
    370         }
    371     }
    372 
    373     // Reference the object in this method since the additional processing can do
    374     // anything including removing the last reference to this object.
    375     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    376     m_resource->responseReceived(resourceResponse);
    377     if (m_state == Terminated)
    378         return;
    379 
    380     m_host->didReceiveResponse(m_resource, resourceResponse);
    381     if (m_state == Terminated)
    382         return;
    383 
    384     if (response.toResourceResponse().isMultipart()) {
    385         // We don't count multiParts in a ResourceFetcher's request count
    386         m_requestCountTracker.clear();
    387         if (!m_resource->isImage()) {
    388             cancel();
    389             return;
    390         }
    391     } else if (isMultipartPayload) {
    392         // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
    393         // After the first multipart section is complete, signal to delegates that this load is "finished"
    394         m_host->subresourceLoaderFinishedLoadingOnePart(this);
    395         ASSERT(m_state != Terminated);
    396         didFinishLoadingOnePart(0, blink::WebURLLoaderClient::kUnknownEncodedDataLength);
    397     }
    398     if (m_state == Terminated)
    399         return;
    400 
    401     if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
    402         return;
    403     m_state = Finishing;
    404 
    405     if (!m_notifiedLoadComplete) {
    406         m_notifiedLoadComplete = true;
    407         m_host->didFailLoading(m_resource, ResourceError::cancelledError(m_request.url()));
    408     }
    409 
    410     ASSERT(m_state != Terminated);
    411     m_resource->error(Resource::LoadError);
    412     cancel();
    413 }
    414 
    415 void ResourceLoader::didReceiveData(blink::WebURLLoader*, const char* data, int length, int encodedDataLength)
    416 {
    417     ASSERT(m_state != Terminated);
    418     RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
    419     m_connectionState = ConnectionStateReceivingData;
    420 
    421     // It is possible to receive data on uninitialized resources if it had an error status code, and we are running a nested message
    422     // loop. When this occurs, ignoring the data is the correct action.
    423     if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
    424         return;
    425     ASSERT(m_state == Initialized);
    426 
    427     // Reference the object in this method since the additional processing can do
    428     // anything including removing the last reference to this object.
    429     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    430 
    431     // FIXME: If we get a resource with more than 2B bytes, this code won't do the right thing.
    432     // However, with today's computers and networking speeds, this won't happen in practice.
    433     // Could be an issue with a giant local file.
    434     m_host->didReceiveData(m_resource, data, length, encodedDataLength);
    435     if (m_state == Terminated)
    436         return;
    437     m_resource->appendData(data, length);
    438 }
    439 
    440 void ResourceLoader::didFinishLoading(blink::WebURLLoader*, double finishTime, int64 encodedDataLength)
    441 {
    442     RELEASE_ASSERT(m_connectionState == ConnectionStateReceivedResponse || m_connectionState == ConnectionStateReceivingData);
    443     m_connectionState = ConnectionStateFinishedLoading;
    444     if (m_state != Initialized)
    445         return;
    446     ASSERT(m_state != Terminated);
    447     WTF_LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
    448 
    449     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    450     ResourcePtr<Resource> protectResource(m_resource);
    451     m_state = Finishing;
    452     didFinishLoadingOnePart(finishTime, encodedDataLength);
    453     if (m_state == Terminated)
    454         return;
    455     m_resource->finish(finishTime);
    456 
    457     // If the load has been cancelled by a delegate in response to didFinishLoad(), do not release
    458     // the resources a second time, they have been released by cancel.
    459     if (m_state == Terminated)
    460         return;
    461     releaseResources();
    462 }
    463 
    464 void ResourceLoader::didFail(blink::WebURLLoader*, const blink::WebURLError& error)
    465 {
    466     m_connectionState = ConnectionStateFailed;
    467     ASSERT(m_state != Terminated);
    468     WTF_LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
    469 
    470     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    471     RefPtrWillBeRawPtr<ResourceLoaderHost> protectHost(m_host.get());
    472     ResourcePtr<Resource> protectResource(m_resource);
    473     m_state = Finishing;
    474     m_resource->setResourceError(error);
    475 
    476     if (!m_notifiedLoadComplete) {
    477         m_notifiedLoadComplete = true;
    478         m_host->didFailLoading(m_resource, error);
    479     }
    480     if (m_state == Terminated)
    481         return;
    482 
    483     m_resource->error(Resource::LoadError);
    484 
    485     if (m_state == Terminated)
    486         return;
    487 
    488     releaseResources();
    489 }
    490 
    491 bool ResourceLoader::isLoadedBy(ResourceLoaderHost* loader) const
    492 {
    493     return m_host->isLoadedBy(loader);
    494 }
    495 
    496 void ResourceLoader::requestSynchronously()
    497 {
    498     OwnPtr<blink::WebURLLoader> loader = adoptPtr(blink::Platform::current()->createURLLoader());
    499     ASSERT(loader);
    500 
    501     // downloadToFile is not supported for synchronous requests.
    502     ASSERT(!m_request.downloadToFile());
    503 
    504     RefPtrWillBeRawPtr<ResourceLoader> protect(this);
    505     RefPtrWillBeRawPtr<ResourceLoaderHost> protectHost(m_host.get());
    506     ResourcePtr<Resource> protectResource(m_resource);
    507 
    508     RELEASE_ASSERT(m_connectionState == ConnectionStateNew);
    509     m_connectionState = ConnectionStateStarted;
    510 
    511     blink::WrappedResourceRequest requestIn(m_request);
    512     blink::WebURLResponse responseOut;
    513     responseOut.initialize();
    514     blink::WebURLError errorOut;
    515     blink::WebData dataOut;
    516     loader->loadSynchronously(requestIn, responseOut, errorOut, dataOut);
    517     if (errorOut.reason) {
    518         didFail(0, errorOut);
    519         return;
    520     }
    521     didReceiveResponse(0, responseOut);
    522     if (m_state == Terminated)
    523         return;
    524     RefPtr<ResourceLoadInfo> resourceLoadInfo = responseOut.toResourceResponse().resourceLoadInfo();
    525     int64 encodedDataLength = resourceLoadInfo ? resourceLoadInfo->encodedDataLength : blink::WebURLLoaderClient::kUnknownEncodedDataLength;
    526     m_host->didReceiveData(m_resource, dataOut.data(), dataOut.size(), encodedDataLength);
    527     m_resource->setResourceBuffer(dataOut);
    528     didFinishLoading(0, monotonicallyIncreasingTime(), encodedDataLength);
    529 }
    530 
    531 ResourceRequest& ResourceLoader::applyOptions(ResourceRequest& request) const
    532 {
    533     request.setAllowStoredCredentials(m_options.allowCredentials == AllowStoredCredentials);
    534     return request;
    535 }
    536 
    537 }
    538