Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2009 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 "Frame.h"
     39 #include "FrameLoader.h"
     40 #include "ResourceRequest.h"
     41 #include "SecurityOrigin.h"
     42 #include "SubresourceLoader.h"
     43 #include "ThreadableLoaderClient.h"
     44 
     45 namespace WebCore {
     46 
     47 void DocumentThreadableLoader::loadResourceSynchronously(Document* document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options)
     48 {
     49     // The loader will be deleted as soon as this function exits.
     50     RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, &client, LoadSynchronously, request, options));
     51     ASSERT(loader->hasOneRef());
     52 }
     53 
     54 PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document* document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options)
     55 {
     56     RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options));
     57     if (!loader->m_loader)
     58         loader = 0;
     59     return loader.release();
     60 }
     61 
     62 DocumentThreadableLoader::DocumentThreadableLoader(Document* document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options)
     63     : m_client(client)
     64     , m_document(document)
     65     , m_options(options)
     66     , m_sameOriginRequest(document->securityOrigin()->canRequest(request.url()))
     67     , m_async(blockingBehavior == LoadAsynchronously)
     68 {
     69     ASSERT(document);
     70     ASSERT(client);
     71 
     72     if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) {
     73         loadRequest(request, DoSecurityCheck);
     74         return;
     75     }
     76 
     77     if (m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) {
     78         m_client->didFail(ResourceError());
     79         return;
     80     }
     81 
     82     ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl);
     83 
     84     if (!m_options.forcePreflight && isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields()))
     85         makeSimpleCrossOriginAccessRequest(request);
     86     else {
     87         m_actualRequest.set(new ResourceRequest(request));
     88         m_actualRequest->setAllowCookies(m_options.allowCredentials);
     89 
     90         if (CrossOriginPreflightResultCache::shared().canSkipPreflight(document->securityOrigin()->toString(), request.url(), m_options.allowCredentials, request.httpMethod(), request.httpHeaderFields()))
     91             preflightSuccess();
     92         else
     93             makeCrossOriginAccessRequestWithPreflight(request);
     94     }
     95 }
     96 
     97 void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const ResourceRequest& request)
     98 {
     99     ASSERT(isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields()));
    100 
    101     // 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.
    102     if (!request.url().protocolInHTTPFamily()) {
    103         m_client->didFail(ResourceError());
    104         return;
    105     }
    106 
    107     // Make a copy of the passed request so that we can modify some details.
    108     ResourceRequest crossOriginRequest(request);
    109     crossOriginRequest.removeCredentials();
    110     crossOriginRequest.setAllowCookies(m_options.allowCredentials);
    111     crossOriginRequest.setHTTPOrigin(m_document->securityOrigin()->toString());
    112 
    113     loadRequest(crossOriginRequest, DoSecurityCheck);
    114 }
    115 
    116 void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request)
    117 {
    118     ResourceRequest preflightRequest(request.url());
    119     preflightRequest.removeCredentials();
    120     preflightRequest.setHTTPOrigin(m_document->securityOrigin()->toString());
    121     preflightRequest.setAllowCookies(m_options.allowCredentials);
    122     preflightRequest.setHTTPMethod("OPTIONS");
    123     preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
    124 
    125     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
    126 
    127     if (requestHeaderFields.size() > 0) {
    128         Vector<UChar> headerBuffer;
    129         HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
    130         append(headerBuffer, it->first);
    131         ++it;
    132 
    133         HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
    134         for (; it != end; ++it) {
    135             headerBuffer.append(',');
    136             headerBuffer.append(' ');
    137             append(headerBuffer, it->first);
    138         }
    139 
    140         preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", String::adopt(headerBuffer));
    141     }
    142 
    143     loadRequest(preflightRequest, DoSecurityCheck);
    144 }
    145 
    146 DocumentThreadableLoader::~DocumentThreadableLoader()
    147 {
    148     if (m_loader)
    149         m_loader->clearClient();
    150 }
    151 
    152 void DocumentThreadableLoader::cancel()
    153 {
    154     if (!m_loader)
    155         return;
    156 
    157     m_loader->cancel();
    158     m_loader->clearClient();
    159     m_loader = 0;
    160     m_client = 0;
    161 }
    162 
    163 void DocumentThreadableLoader::willSendRequest(SubresourceLoader* loader, ResourceRequest& request, const ResourceResponse&)
    164 {
    165     ASSERT(m_client);
    166     ASSERT_UNUSED(loader, loader == m_loader);
    167 
    168     if (!isAllowedRedirect(request.url())) {
    169         RefPtr<DocumentThreadableLoader> protect(this);
    170         m_client->didFailRedirectCheck();
    171         request = ResourceRequest();
    172     }
    173 }
    174 
    175 void DocumentThreadableLoader::didSendData(SubresourceLoader* loader, unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
    176 {
    177     ASSERT(m_client);
    178     ASSERT_UNUSED(loader, loader == m_loader);
    179 
    180     m_client->didSendData(bytesSent, totalBytesToBeSent);
    181 }
    182 
    183 void DocumentThreadableLoader::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response)
    184 {
    185     ASSERT(m_client);
    186     ASSERT_UNUSED(loader, loader == m_loader);
    187 
    188     if (m_actualRequest) {
    189         if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin())) {
    190             preflightFailure();
    191             return;
    192         }
    193 
    194         OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials));
    195         if (!preflightResult->parse(response)
    196             || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod())
    197             || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields())) {
    198             preflightFailure();
    199             return;
    200         }
    201 
    202         CrossOriginPreflightResultCache::shared().appendEntry(m_document->securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release());
    203     } else {
    204         if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) {
    205             if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin())) {
    206                 m_client->didFail(ResourceError());
    207                 return;
    208             }
    209         }
    210 
    211         m_client->didReceiveResponse(response);
    212     }
    213 }
    214 
    215 void DocumentThreadableLoader::didReceiveData(SubresourceLoader* loader, const char* data, int lengthReceived)
    216 {
    217     ASSERT(m_client);
    218     ASSERT_UNUSED(loader, loader == m_loader);
    219 
    220     m_client->didReceiveData(data, lengthReceived);
    221 }
    222 
    223 void DocumentThreadableLoader::didFinishLoading(SubresourceLoader* loader)
    224 {
    225     ASSERT(loader == m_loader);
    226     ASSERT(m_client);
    227     didFinishLoading(loader->identifier());
    228 }
    229 
    230 void DocumentThreadableLoader::didFinishLoading(unsigned long identifier)
    231 {
    232     if (m_actualRequest) {
    233         ASSERT(!m_sameOriginRequest);
    234         ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl);
    235         preflightSuccess();
    236     } else
    237         m_client->didFinishLoading(identifier);
    238 }
    239 
    240 void DocumentThreadableLoader::didFail(SubresourceLoader* loader, const ResourceError& error)
    241 {
    242     ASSERT(m_client);
    243     // m_loader may be null if we arrive here via SubresourceLoader::create in the ctor
    244     ASSERT_UNUSED(loader, loader == m_loader || !m_loader);
    245 
    246     m_client->didFail(error);
    247 }
    248 
    249 bool DocumentThreadableLoader::getShouldUseCredentialStorage(SubresourceLoader* loader, bool& shouldUseCredentialStorage)
    250 {
    251     ASSERT_UNUSED(loader, loader == m_loader || !m_loader);
    252 
    253     if (!m_options.allowCredentials) {
    254         shouldUseCredentialStorage = false;
    255         return true;
    256     }
    257 
    258     return false; // Only FrameLoaderClient can ultimately permit credential use.
    259 }
    260 
    261 void DocumentThreadableLoader::didReceiveAuthenticationChallenge(SubresourceLoader* loader, const AuthenticationChallenge&)
    262 {
    263     ASSERT(loader == m_loader);
    264     // Users are not prompted for credentials for cross-origin requests.
    265     if (!m_sameOriginRequest) {
    266         RefPtr<DocumentThreadableLoader> protect(this);
    267         m_client->didFail(loader->blockedError());
    268         cancel();
    269     }
    270 }
    271 
    272 void DocumentThreadableLoader::receivedCancellation(SubresourceLoader* loader, const AuthenticationChallenge& challenge)
    273 {
    274     ASSERT(m_client);
    275     ASSERT_UNUSED(loader, loader == m_loader);
    276     m_client->didReceiveAuthenticationCancellation(challenge.failureResponse());
    277 }
    278 
    279 void DocumentThreadableLoader::preflightSuccess()
    280 {
    281     OwnPtr<ResourceRequest> actualRequest;
    282     actualRequest.swap(m_actualRequest);
    283 
    284     // It should be ok to skip the security check since we already asked about the preflight request.
    285     loadRequest(*actualRequest, SkipSecurityCheck);
    286 }
    287 
    288 void DocumentThreadableLoader::preflightFailure()
    289 {
    290     m_actualRequest = 0; // Prevent didFinishLoading() from bypassing access check.
    291     m_client->didFail(ResourceError());
    292 }
    293 
    294 void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck)
    295 {
    296     if (m_async) {
    297         // Don't sniff content or send load callbacks for the preflight request.
    298         bool sendLoadCallbacks = m_options.sendLoadCallbacks && !m_actualRequest;
    299         bool sniffContent = m_options.sniffContent && !m_actualRequest;
    300 
    301         // Clear the loader so that any callbacks from SubresourceLoader::create will not have the old loader.
    302         m_loader = 0;
    303         m_loader = SubresourceLoader::create(m_document->frame(), this, request, securityCheck, sendLoadCallbacks, sniffContent);
    304         return;
    305     }
    306 
    307     // FIXME: ThreadableLoaderOptions.sniffContent is not supported for synchronous requests.
    308     StoredCredentials storedCredentials = m_options.allowCredentials ? AllowStoredCredentials : DoNotAllowStoredCredentials;
    309 
    310     Vector<char> data;
    311     ResourceError error;
    312     ResourceResponse response;
    313     unsigned long identifier = std::numeric_limits<unsigned long>::max();
    314     if (m_document->frame())
    315         identifier = m_document->frame()->loader()->loadResourceSynchronously(request, storedCredentials, error, response, data);
    316 
    317     // No exception for file:/// resources, see <rdar://problem/4962298>.
    318     // Also, if we have an HTTP response, then it wasn't a network error in fact.
    319     if (!error.isNull() && !request.url().isLocalFile() && response.httpStatusCode() <= 0) {
    320         m_client->didFail(error);
    321         return;
    322     }
    323 
    324     // FIXME: FrameLoader::loadSynchronously() does not tell us whether a redirect happened or not, so we guess by comparing the
    325     // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was
    326     // requested.
    327     if (request.url() != response.url() && !isAllowedRedirect(response.url())) {
    328         m_client->didFailRedirectCheck();
    329         return;
    330     }
    331 
    332     didReceiveResponse(0, response);
    333 
    334     const char* bytes = static_cast<const char*>(data.data());
    335     int len = static_cast<int>(data.size());
    336     didReceiveData(0, bytes, len);
    337 
    338     didFinishLoading(identifier);
    339 }
    340 
    341 bool DocumentThreadableLoader::isAllowedRedirect(const KURL& url)
    342 {
    343     if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests)
    344         return true;
    345 
    346     // FIXME: We need to implement access control for each redirect. This will require some refactoring though, because the code
    347     // that processes redirects doesn't know about access control and expects a synchronous answer from its client about whether
    348     // a redirect should proceed.
    349     return m_sameOriginRequest && m_document->securityOrigin()->canRequest(url);
    350 }
    351 
    352 } // namespace WebCore
    353