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 49namespace WebCore { 50 51void 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 58PassRefPtr<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 66DocumentThreadableLoader::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 107void 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 124void 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 156DocumentThreadableLoader::~DocumentThreadableLoader() 157{ 158 if (m_loader) 159 m_loader->clearClient(); 160} 161 162void 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 173void DocumentThreadableLoader::setDefersLoading(bool value) 174{ 175 if (m_loader) 176 m_loader->setDefersLoading(value); 177} 178 179void 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 194void 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 202void 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 235void 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 247void 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 259void DocumentThreadableLoader::didFinishLoading(SubresourceLoader* loader, double finishTime) 260{ 261 ASSERT(loader == m_loader); 262 ASSERT(m_client); 263 didFinishLoading(loader->identifier(), finishTime); 264} 265 266void 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 276void 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 285bool 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 297void 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 314void 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 321void 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 332void 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 338void 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 393bool 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