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