Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2011 Google 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 are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "DocumentThreadableLoader.h"
     33 
     34 #include "AuthenticationChallenge.h"
     35 #include "CrossOriginAccessControl.h"
     36 #include "CrossOriginPreflightResultCache.h"
     37 #include "Document.h"
     38 #include "DocumentThreadableLoaderClient.h"
     39 #include "Frame.h"
     40 #include "FrameLoader.h"
     41 #include "ResourceHandle.h"
     42 #include "ResourceLoadScheduler.h"
     43 #include "ResourceRequest.h"
     44 #include "SecurityOrigin.h"
     45 #include "SubresourceLoader.h"
     46 #include "ThreadableLoaderClient.h"
     47 #include <wtf/UnusedParam.h>
     48 
     49 namespace WebCore {
     50 
     51 void DocumentThreadableLoader::loadResourceSynchronously(Document* document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options)
     52 {
     53     // The loader will be deleted as soon as this function exits.
     54     RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, &client, LoadSynchronously, request, options, String()));
     55     ASSERT(loader->hasOneRef());
     56 }
     57 
     58 PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document* document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options, const String& optionalOutgoingReferrer)
     59 {
     60     RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options, optionalOutgoingReferrer));
     61     if (!loader->m_loader)
     62         loader = 0;
     63     return loader.release();
     64 }
     65 
     66 DocumentThreadableLoader::DocumentThreadableLoader(Document* document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options, const String& optionalOutgoingReferrer)
     67     : m_client(client)
     68     , m_document(document)
     69     , m_options(options)
     70     , m_optionalOutgoingReferrer(optionalOutgoingReferrer)
     71     , m_sameOriginRequest(document->securityOrigin()->canRequest(request.url()))
     72     , m_async(blockingBehavior == LoadAsynchronously)
     73 {
     74     ASSERT(document);
     75     ASSERT(client);
     76     // Setting an outgoing referer is only supported in the async code path.
     77     ASSERT(m_async || m_optionalOutgoingReferrer.isEmpty());
     78 
     79     if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) {
     80         loadRequest(request, DoSecurityCheck);
     81         return;
     82     }
     83 
     84     if (m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) {
     85         m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are not supported."));
     86         return;
     87     }
     88 
     89     ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl);
     90 
     91     OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request));
     92     crossOriginRequest->removeCredentials();
     93     crossOriginRequest->setAllowCookies(m_options.allowCredentials);
     94 
     95     if (!m_options.forcePreflight && isSimpleCrossOriginAccessRequest(crossOriginRequest->httpMethod(), crossOriginRequest->httpHeaderFields()))
     96         makeSimpleCrossOriginAccessRequest(*crossOriginRequest);
     97     else {
     98         m_actualRequest = crossOriginRequest.release();
     99 
    100         if (CrossOriginPreflightResultCache::shared().canSkipPreflight(document->securityOrigin()->toString(), m_actualRequest->url(), m_options.allowCredentials, m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields()))
    101             preflightSuccess();
    102         else
    103             makeCrossOriginAccessRequestWithPreflight(*m_actualRequest);
    104     }
    105 }
    106 
    107 void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const ResourceRequest& request)
    108 {
    109     ASSERT(isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields()));
    110 
    111     // Cross-origin requests are only defined for HTTP. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied.
    112     if (!request.url().protocolInHTTPFamily()) {
    113         m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP."));
    114         return;
    115     }
    116 
    117     // Make a copy of the passed request so that we can modify some details.
    118     ResourceRequest crossOriginRequest(request);
    119     crossOriginRequest.setHTTPOrigin(m_document->securityOrigin()->toString());
    120 
    121     loadRequest(crossOriginRequest, DoSecurityCheck);
    122 }
    123 
    124 void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request)
    125 {
    126     ResourceRequest preflightRequest(request.url());
    127     preflightRequest.removeCredentials();
    128     preflightRequest.setHTTPOrigin(m_document->securityOrigin()->toString());
    129     preflightRequest.setAllowCookies(m_options.allowCredentials);
    130     preflightRequest.setHTTPMethod("OPTIONS");
    131     preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
    132 
    133     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
    134 
    135     if (requestHeaderFields.size() > 0) {
    136         Vector<UChar> headerBuffer;
    137         HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
    138         append(headerBuffer, it->first);
    139         ++it;
    140 
    141         HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
    142         for (; it != end; ++it) {
    143             headerBuffer.append(',');
    144             headerBuffer.append(' ');
    145             append(headerBuffer, it->first);
    146         }
    147 
    148         preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", String::adopt(headerBuffer));
    149     }
    150 
    151     preflightRequest.setPriority(request.priority());
    152 
    153     loadRequest(preflightRequest, DoSecurityCheck);
    154 }
    155 
    156 DocumentThreadableLoader::~DocumentThreadableLoader()
    157 {
    158     if (m_loader)
    159         m_loader->clearClient();
    160 }
    161 
    162 void DocumentThreadableLoader::cancel()
    163 {
    164     if (!m_loader)
    165         return;
    166 
    167     m_loader->cancel();
    168     m_loader->clearClient();
    169     m_loader = 0;
    170     m_client = 0;
    171 }
    172 
    173 void DocumentThreadableLoader::setDefersLoading(bool value)
    174 {
    175     if (m_loader)
    176         m_loader->setDefersLoading(value);
    177 }
    178 
    179 void DocumentThreadableLoader::willSendRequest(SubresourceLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse)
    180 {
    181     ASSERT(m_client);
    182     ASSERT_UNUSED(loader, loader == m_loader);
    183 
    184     if (!isAllowedRedirect(request.url())) {
    185         RefPtr<DocumentThreadableLoader> protect(this);
    186         m_client->didFailRedirectCheck();
    187         request = ResourceRequest();
    188     } else {
    189         if (m_client->isDocumentThreadableLoaderClient())
    190             static_cast<DocumentThreadableLoaderClient*>(m_client)->willSendRequest(request, redirectResponse);
    191     }
    192 }
    193 
    194 void DocumentThreadableLoader::didSendData(SubresourceLoader* loader, unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
    195 {
    196     ASSERT(m_client);
    197     ASSERT_UNUSED(loader, loader == m_loader);
    198 
    199     m_client->didSendData(bytesSent, totalBytesToBeSent);
    200 }
    201 
    202 void DocumentThreadableLoader::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response)
    203 {
    204     ASSERT(m_client);
    205     ASSERT_UNUSED(loader, loader == m_loader);
    206 
    207     String accessControlErrorDescription;
    208     if (m_actualRequest) {
    209         if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin(), accessControlErrorDescription)) {
    210             preflightFailure(response.url(), accessControlErrorDescription);
    211             return;
    212         }
    213 
    214         OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials));
    215         if (!preflightResult->parse(response, accessControlErrorDescription)
    216             || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription)
    217             || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) {
    218             preflightFailure(response.url(), accessControlErrorDescription);
    219             return;
    220         }
    221 
    222         CrossOriginPreflightResultCache::shared().appendEntry(m_document->securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release());
    223     } else {
    224         if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) {
    225             if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin(), accessControlErrorDescription)) {
    226                 m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription));
    227                 return;
    228             }
    229         }
    230 
    231         m_client->didReceiveResponse(response);
    232     }
    233 }
    234 
    235 void DocumentThreadableLoader::didReceiveData(SubresourceLoader* loader, const char* data, int dataLength)
    236 {
    237     ASSERT(m_client);
    238     ASSERT_UNUSED(loader, loader == m_loader);
    239 
    240     // Preflight data should be invisible to clients.
    241     if (m_actualRequest)
    242         return;
    243 
    244     m_client->didReceiveData(data, dataLength);
    245 }
    246 
    247 void DocumentThreadableLoader::didReceiveCachedMetadata(SubresourceLoader* loader, const char* data, int dataLength)
    248 {
    249     ASSERT(m_client);
    250     ASSERT_UNUSED(loader, loader == m_loader);
    251 
    252     // Preflight data should be invisible to clients.
    253     if (m_actualRequest)
    254         return;
    255 
    256     m_client->didReceiveCachedMetadata(data, dataLength);
    257 }
    258 
    259 void DocumentThreadableLoader::didFinishLoading(SubresourceLoader* loader, double finishTime)
    260 {
    261     ASSERT(loader == m_loader);
    262     ASSERT(m_client);
    263     didFinishLoading(loader->identifier(), finishTime);
    264 }
    265 
    266 void DocumentThreadableLoader::didFinishLoading(unsigned long identifier, double finishTime)
    267 {
    268     if (m_actualRequest) {
    269         ASSERT(!m_sameOriginRequest);
    270         ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl);
    271         preflightSuccess();
    272     } else
    273         m_client->didFinishLoading(identifier, finishTime);
    274 }
    275 
    276 void DocumentThreadableLoader::didFail(SubresourceLoader* loader, const ResourceError& error)
    277 {
    278     ASSERT(m_client);
    279     // m_loader may be null if we arrive here via SubresourceLoader::create in the ctor
    280     ASSERT_UNUSED(loader, loader == m_loader || !m_loader);
    281 
    282     m_client->didFail(error);
    283 }
    284 
    285 bool DocumentThreadableLoader::getShouldUseCredentialStorage(SubresourceLoader* loader, bool& shouldUseCredentialStorage)
    286 {
    287     ASSERT_UNUSED(loader, loader == m_loader || !m_loader);
    288 
    289     if (!m_options.allowCredentials) {
    290         shouldUseCredentialStorage = false;
    291         return true;
    292     }
    293 
    294     return false; // Only FrameLoaderClient can ultimately permit credential use.
    295 }
    296 
    297 void DocumentThreadableLoader::didReceiveAuthenticationChallenge(SubresourceLoader* loader, const AuthenticationChallenge& challenge)
    298 {
    299     ASSERT(loader == m_loader);
    300     // Users are not prompted for credentials for cross-origin requests.
    301     if (!m_sameOriginRequest) {
    302 #if PLATFORM(MAC) || USE(CFNETWORK) || USE(CURL)
    303         loader->handle()->receivedRequestToContinueWithoutCredential(challenge);
    304 #else
    305         // These platforms don't provide a way to continue without credentials, cancel the load altogether.
    306         UNUSED_PARAM(challenge);
    307         RefPtr<DocumentThreadableLoader> protect(this);
    308         m_client->didFail(loader->blockedError());
    309         cancel();
    310 #endif
    311     }
    312 }
    313 
    314 void DocumentThreadableLoader::receivedCancellation(SubresourceLoader* loader, const AuthenticationChallenge& challenge)
    315 {
    316     ASSERT(m_client);
    317     ASSERT_UNUSED(loader, loader == m_loader);
    318     m_client->didReceiveAuthenticationCancellation(challenge.failureResponse());
    319 }
    320 
    321 void DocumentThreadableLoader::preflightSuccess()
    322 {
    323     OwnPtr<ResourceRequest> actualRequest;
    324     actualRequest.swap(m_actualRequest);
    325 
    326     actualRequest->setHTTPOrigin(m_document->securityOrigin()->toString());
    327 
    328     // It should be ok to skip the security check since we already asked about the preflight request.
    329     loadRequest(*actualRequest, SkipSecurityCheck);
    330 }
    331 
    332 void DocumentThreadableLoader::preflightFailure(const String& url, const String& errorDescription)
    333 {
    334     m_actualRequest = 0; // Prevent didFinishLoading() from bypassing access check.
    335     m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, url, errorDescription));
    336 }
    337 
    338 void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck)
    339 {
    340     // Any credential should have been removed from the cross-site requests.
    341     const KURL& requestURL = request.url();
    342     ASSERT(m_sameOriginRequest || requestURL.user().isEmpty());
    343     ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty());
    344 
    345     if (m_async) {
    346         // Don't sniff content or send load callbacks for the preflight request.
    347         bool sendLoadCallbacks = m_options.sendLoadCallbacks && !m_actualRequest;
    348         bool sniffContent = m_options.sniffContent && !m_actualRequest;
    349         // Keep buffering the data for the preflight request.
    350         bool shouldBufferData = m_options.shouldBufferData || m_actualRequest;
    351 
    352         // Clear the loader so that any callbacks from SubresourceLoader::create will not have the old loader.
    353         m_loader = 0;
    354         m_loader = resourceLoadScheduler()->scheduleSubresourceLoad(m_document->frame(), this, request, ResourceLoadPriorityMedium, securityCheck, sendLoadCallbacks,
    355                                                                     sniffContent, m_optionalOutgoingReferrer, shouldBufferData);
    356         return;
    357     }
    358 
    359     // FIXME: ThreadableLoaderOptions.sniffContent is not supported for synchronous requests.
    360     StoredCredentials storedCredentials = m_options.allowCredentials ? AllowStoredCredentials : DoNotAllowStoredCredentials;
    361 
    362     Vector<char> data;
    363     ResourceError error;
    364     ResourceResponse response;
    365     unsigned long identifier = std::numeric_limits<unsigned long>::max();
    366     if (m_document->frame())
    367         identifier = m_document->frame()->loader()->loadResourceSynchronously(request, storedCredentials, error, response, data);
    368 
    369     // No exception for file:/// resources, see <rdar://problem/4962298>.
    370     // Also, if we have an HTTP response, then it wasn't a network error in fact.
    371     if (!error.isNull() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) {
    372         m_client->didFail(error);
    373         return;
    374     }
    375 
    376     // FIXME: FrameLoader::loadSynchronously() does not tell us whether a redirect happened or not, so we guess by comparing the
    377     // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was
    378     // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials.
    379     if (requestURL != response.url() && !isAllowedRedirect(response.url())) {
    380         m_client->didFailRedirectCheck();
    381         return;
    382     }
    383 
    384     didReceiveResponse(0, response);
    385 
    386     const char* bytes = static_cast<const char*>(data.data());
    387     int len = static_cast<int>(data.size());
    388     didReceiveData(0, bytes, len);
    389 
    390     didFinishLoading(identifier, 0.0);
    391 }
    392 
    393 bool DocumentThreadableLoader::isAllowedRedirect(const KURL& url)
    394 {
    395     if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests)
    396         return true;
    397 
    398     // FIXME: We need to implement access control for each redirect. This will require some refactoring though, because the code
    399     // that processes redirects doesn't know about access control and expects a synchronous answer from its client about whether
    400     // a redirect should proceed.
    401     return m_sameOriginRequest && m_document->securityOrigin()->canRequest(url);
    402 }
    403 
    404 } // namespace WebCore
    405