1 /* 2 * Copyright (C) 2011, 2012 Google Inc. All rights reserved. 3 * Copyright (C) 2013, Intel Corporation 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 #include "core/loader/DocumentThreadableLoader.h" 34 35 #include "core/dom/Document.h" 36 #include "core/fetch/CrossOriginAccessControl.h" 37 #include "core/fetch/FetchRequest.h" 38 #include "core/fetch/Resource.h" 39 #include "core/fetch/ResourceFetcher.h" 40 #include "core/frame/ContentSecurityPolicy.h" 41 #include "core/frame/Frame.h" 42 #include "core/inspector/InspectorInstrumentation.h" 43 #include "core/loader/CrossOriginPreflightResultCache.h" 44 #include "core/loader/DocumentThreadableLoaderClient.h" 45 #include "core/loader/FrameLoader.h" 46 #include "core/loader/ThreadableLoaderClient.h" 47 #include "platform/SharedBuffer.h" 48 #include "platform/network/ResourceRequest.h" 49 #include "platform/weborigin/SchemeRegistry.h" 50 #include "platform/weborigin/SecurityOrigin.h" 51 #include "wtf/Assertions.h" 52 53 namespace WebCore { 54 55 void DocumentThreadableLoader::loadResourceSynchronously(Document* document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) 56 { 57 // The loader will be deleted as soon as this function exits. 58 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, &client, LoadSynchronously, request, options)); 59 ASSERT(loader->hasOneRef()); 60 } 61 62 PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document* document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options) 63 { 64 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options)); 65 if (!loader->resource()) 66 loader = 0; 67 return loader.release(); 68 } 69 70 DocumentThreadableLoader::DocumentThreadableLoader(Document* document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options) 71 : m_client(client) 72 , m_document(document) 73 , m_options(options) 74 , m_sameOriginRequest(securityOrigin()->canRequest(request.url())) 75 , m_simpleRequest(true) 76 , m_async(blockingBehavior == LoadAsynchronously) 77 , m_timeoutTimer(this, &DocumentThreadableLoader::didTimeout) 78 { 79 ASSERT(document); 80 ASSERT(client); 81 // Setting an outgoing referer is only supported in the async code path. 82 ASSERT(m_async || request.httpReferrer().isEmpty()); 83 84 if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) { 85 loadRequest(request, DoSecurityCheck); 86 return; 87 } 88 89 if (m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) { 90 m_client->didFail(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are not supported.")); 91 return; 92 } 93 94 makeCrossOriginAccessRequest(request); 95 } 96 97 void DocumentThreadableLoader::makeCrossOriginAccessRequest(const ResourceRequest& request) 98 { 99 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 100 101 OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request)); 102 103 if ((m_options.preflightPolicy == ConsiderPreflight && isSimpleCrossOriginAccessRequest(crossOriginRequest->httpMethod(), crossOriginRequest->httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight) { 104 updateRequestForAccessControl(*crossOriginRequest, securityOrigin(), m_options.allowCredentials); 105 makeSimpleCrossOriginAccessRequest(*crossOriginRequest); 106 } else { 107 m_simpleRequest = false; 108 // Do not set the Origin header for preflight requests. 109 updateRequestForAccessControl(*crossOriginRequest, 0, m_options.allowCredentials); 110 m_actualRequest = crossOriginRequest.release(); 111 112 if (CrossOriginPreflightResultCache::shared().canSkipPreflight(securityOrigin()->toString(), m_actualRequest->url(), m_options.allowCredentials, m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields())) 113 preflightSuccess(); 114 else 115 makeCrossOriginAccessRequestWithPreflight(*m_actualRequest); 116 } 117 } 118 119 void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const ResourceRequest& request) 120 { 121 ASSERT(m_options.preflightPolicy != ForcePreflight); 122 ASSERT(m_options.preflightPolicy == PreventPreflight || isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields())); 123 124 // Cross-origin requests are only allowed for HTTP and registered schemes. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied. 125 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())) { 126 m_client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP.")); 127 return; 128 } 129 130 loadRequest(request, DoSecurityCheck); 131 } 132 133 void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request) 134 { 135 ResourceRequest preflightRequest = createAccessControlPreflightRequest(request, securityOrigin()); 136 loadRequest(preflightRequest, DoSecurityCheck); 137 } 138 139 DocumentThreadableLoader::~DocumentThreadableLoader() 140 { 141 } 142 143 void DocumentThreadableLoader::cancel() 144 { 145 cancelWithError(ResourceError()); 146 } 147 148 void DocumentThreadableLoader::cancelWithError(const ResourceError& error) 149 { 150 RefPtr<DocumentThreadableLoader> protect(this); 151 152 // Cancel can re-enter and m_resource might be null here as a result. 153 if (m_client && resource()) { 154 ResourceError errorForCallback = error; 155 if (errorForCallback.isNull()) { 156 // FIXME: This error is sent to the client in didFail(), so it should not be an internal one. Use FrameLoaderClient::cancelledError() instead. 157 errorForCallback = ResourceError(errorDomainBlinkInternal, 0, resource()->url().string(), "Load cancelled"); 158 errorForCallback.setIsCancellation(true); 159 } 160 didFail(resource()->identifier(), errorForCallback); 161 } 162 clearResource(); 163 m_client = 0; 164 } 165 166 void DocumentThreadableLoader::setDefersLoading(bool value) 167 { 168 if (resource()) 169 resource()->setDefersLoading(value); 170 } 171 172 void DocumentThreadableLoader::redirectReceived(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) 173 { 174 ASSERT(m_client); 175 ASSERT_UNUSED(resource, resource == this->resource()); 176 177 RefPtr<DocumentThreadableLoader> protect(this); 178 if (!isAllowedByPolicy(request.url())) { 179 m_client->didFailRedirectCheck(); 180 request = ResourceRequest(); 181 return; 182 } 183 184 // Allow same origin requests to continue after allowing clients to audit the redirect. 185 if (isAllowedRedirect(request.url())) { 186 if (m_client->isDocumentThreadableLoaderClient()) 187 static_cast<DocumentThreadableLoaderClient*>(m_client)->willSendRequest(request, redirectResponse); 188 return; 189 } 190 191 // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported 192 // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check if the 193 // original request was not same-origin. 194 if (m_options.crossOriginRequestPolicy == UseAccessControl) { 195 196 InspectorInstrumentation::didReceiveCORSRedirectResponse(m_document->frame(), resource->identifier(), m_document->frame()->loader().documentLoader(), redirectResponse, 0); 197 198 bool allowRedirect = false; 199 String accessControlErrorDescription; 200 201 if (m_simpleRequest) { 202 allowRedirect = checkCrossOriginAccessRedirectionUrl(request.url(), accessControlErrorDescription) 203 && (m_sameOriginRequest || passesAccessControlCheck(redirectResponse, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)); 204 } else { 205 accessControlErrorDescription = "The request was redirected to '"+ request.url().string() + "', which is disallowed for cross-origin requests that require preflight."; 206 } 207 208 if (allowRedirect) { 209 clearResource(); 210 211 RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(redirectResponse.url()); 212 RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::create(request.url()); 213 // If the original request wasn't same-origin, then if the request URL origin is not same origin with the original URL origin, 214 // set the source origin to a globally unique identifier. (If the original request was same-origin, the origin of the new request 215 // should be the original URL origin.) 216 if (!m_sameOriginRequest && !originalOrigin->isSameSchemeHostPort(requestOrigin.get())) 217 m_options.securityOrigin = SecurityOrigin::createUnique(); 218 // Force any subsequent requests to use these checks. 219 m_sameOriginRequest = false; 220 221 // Since the request is no longer same-origin, if the user didn't request credentials in 222 // the first place, update our state so we neither request them nor expect they must be allowed. 223 if (m_options.credentialsRequested == ClientDidNotRequestCredentials) 224 m_options.allowCredentials = DoNotAllowStoredCredentials; 225 226 // Remove any headers that may have been added by the network layer that cause access control to fail. 227 request.clearHTTPContentType(); 228 request.clearHTTPReferrer(); 229 request.clearHTTPOrigin(); 230 request.clearHTTPUserAgent(); 231 request.clearHTTPAccept(); 232 makeCrossOriginAccessRequest(request); 233 return; 234 } 235 236 ResourceError error(errorDomainBlinkInternal, 0, redirectResponse.url().string(), accessControlErrorDescription); 237 m_client->didFailAccessControlCheck(error); 238 } else { 239 m_client->didFailRedirectCheck(); 240 } 241 request = ResourceRequest(); 242 } 243 244 void DocumentThreadableLoader::dataSent(Resource* resource, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) 245 { 246 ASSERT(m_client); 247 ASSERT_UNUSED(resource, resource == this->resource()); 248 m_client->didSendData(bytesSent, totalBytesToBeSent); 249 } 250 251 void DocumentThreadableLoader::dataDownloaded(Resource* resource, int dataLength) 252 { 253 ASSERT(m_client); 254 ASSERT_UNUSED(resource, resource == this->resource()); 255 ASSERT(!m_actualRequest); 256 257 m_client->didDownloadData(dataLength); 258 } 259 260 void DocumentThreadableLoader::responseReceived(Resource* resource, const ResourceResponse& response) 261 { 262 ASSERT_UNUSED(resource, resource == this->resource()); 263 didReceiveResponse(resource->identifier(), response); 264 } 265 266 void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) 267 { 268 ASSERT(m_client); 269 270 String accessControlErrorDescription; 271 if (m_actualRequest) { 272 DocumentLoader* loader = m_document->frame()->loader().documentLoader(); 273 InspectorInstrumentation::didReceiveResourceResponse(m_document->frame(), identifier, loader, response, resource() ? resource()->loader() : 0); 274 275 if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { 276 preflightFailure(identifier, response.url().string(), accessControlErrorDescription); 277 return; 278 } 279 280 if (!passesPreflightStatusCheck(response, accessControlErrorDescription)) { 281 preflightFailure(identifier, response.url().string(), accessControlErrorDescription); 282 return; 283 } 284 285 OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials)); 286 if (!preflightResult->parse(response, accessControlErrorDescription) 287 || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) 288 || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { 289 preflightFailure(identifier, response.url().string(), accessControlErrorDescription); 290 return; 291 } 292 293 CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); 294 } else { 295 if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { 296 if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { 297 m_client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, response.url().string(), accessControlErrorDescription)); 298 return; 299 } 300 } 301 302 m_client->didReceiveResponse(identifier, response); 303 } 304 } 305 306 void DocumentThreadableLoader::dataReceived(Resource* resource, const char* data, int dataLength) 307 { 308 ASSERT(resource == this->resource()); 309 didReceiveData(resource->identifier(), data, dataLength); 310 } 311 312 void DocumentThreadableLoader::didReceiveData(unsigned long identifier, const char* data, int dataLength) 313 { 314 ASSERT(m_client); 315 316 // Preflight data should be invisible to clients. 317 if (m_actualRequest) { 318 InspectorInstrumentation::didReceiveData(m_document->frame(), identifier, 0, 0, dataLength); 319 return; 320 } 321 322 m_client->didReceiveData(data, dataLength); 323 } 324 325 void DocumentThreadableLoader::notifyFinished(Resource* resource) 326 { 327 ASSERT(m_client); 328 ASSERT(resource == this->resource()); 329 330 m_timeoutTimer.stop(); 331 332 if (resource->errorOccurred()) 333 didFail(resource->identifier(), resource->resourceError()); 334 else 335 didFinishLoading(resource->identifier(), resource->loadFinishTime()); 336 } 337 338 void DocumentThreadableLoader::didFinishLoading(unsigned long identifier, double finishTime) 339 { 340 if (m_actualRequest) { 341 InspectorInstrumentation::didFinishLoading(m_document->frame(), identifier, m_document->frame()->loader().documentLoader(), finishTime); 342 ASSERT(!m_sameOriginRequest); 343 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 344 preflightSuccess(); 345 } else 346 m_client->didFinishLoading(identifier, finishTime); 347 } 348 349 void DocumentThreadableLoader::didFail(unsigned long identifier, const ResourceError& error) 350 { 351 if (m_actualRequest) 352 InspectorInstrumentation::didFailLoading(m_document->frame(), identifier, m_document->frame()->loader().documentLoader(), error); 353 354 m_client->didFail(error); 355 } 356 357 void DocumentThreadableLoader::didTimeout(Timer<DocumentThreadableLoader>* timer) 358 { 359 ASSERT_UNUSED(timer, timer == &m_timeoutTimer); 360 361 // Using values from net/base/net_error_list.h ERR_TIMED_OUT, 362 // Same as existing FIXME above - this error should be coming from FrameLoaderClient to be identifiable. 363 static const int timeoutError = -7; 364 ResourceError error("net", timeoutError, resource()->url(), String()); 365 error.setIsTimeout(true); 366 cancelWithError(error); 367 } 368 369 void DocumentThreadableLoader::preflightSuccess() 370 { 371 OwnPtr<ResourceRequest> actualRequest; 372 actualRequest.swap(m_actualRequest); 373 374 actualRequest->setHTTPOrigin(securityOrigin()->toString()); 375 376 clearResource(); 377 378 // It should be ok to skip the security check since we already asked about the preflight request. 379 loadRequest(*actualRequest, SkipSecurityCheck); 380 } 381 382 void DocumentThreadableLoader::preflightFailure(unsigned long identifier, const String& url, const String& errorDescription) 383 { 384 ResourceError error(errorDomainBlinkInternal, 0, url, errorDescription); 385 if (m_actualRequest) 386 InspectorInstrumentation::didFailLoading(m_document->frame(), identifier, m_document->frame()->loader().documentLoader(), error); 387 m_actualRequest = nullptr; // Prevent didFinishLoading() from bypassing access check. 388 m_client->didFailAccessControlCheck(error); 389 } 390 391 void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck) 392 { 393 // Any credential should have been removed from the cross-site requests. 394 const KURL& requestURL = request.url(); 395 m_options.securityCheck = securityCheck; 396 ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); 397 ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); 398 399 ThreadableLoaderOptions options = m_options; 400 if (m_async) { 401 options.crossOriginCredentialPolicy = DoNotAskClientForCrossOriginCredentials; 402 if (m_actualRequest) { 403 // Don't sniff content or send load callbacks for the preflight request. 404 options.sendLoadCallbacks = DoNotSendCallbacks; 405 options.sniffContent = DoNotSniffContent; 406 // Keep buffering the data for the preflight request. 407 options.dataBufferingPolicy = BufferData; 408 } 409 410 if (m_options.timeoutMilliseconds > 0) 411 m_timeoutTimer.startOneShot(m_options.timeoutMilliseconds / 1000.0); 412 413 FetchRequest newRequest(request, m_options.initiator, options); 414 ASSERT(!resource()); 415 setResource(m_document->fetcher()->fetchRawResource(newRequest)); 416 if (resource() && resource()->loader()) { 417 unsigned long identifier = resource()->identifier(); 418 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(m_document, identifier, m_client); 419 } 420 return; 421 } 422 423 FetchRequest fetchRequest(request, m_options.initiator, options); 424 ResourcePtr<Resource> resource = m_document->fetcher()->fetchSynchronously(fetchRequest); 425 ResourceResponse response = resource ? resource->response() : ResourceResponse(); 426 unsigned long identifier = resource ? resource->identifier() : std::numeric_limits<unsigned long>::max(); 427 ResourceError error = resource ? resource->resourceError() : ResourceError(); 428 429 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(m_document, identifier, m_client); 430 431 if (!resource) { 432 m_client->didFail(error); 433 return; 434 } 435 436 // No exception for file:/// resources, see <rdar://problem/4962298>. 437 // Also, if we have an HTTP response, then it wasn't a network error in fact. 438 if (!error.isNull() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) { 439 m_client->didFail(error); 440 return; 441 } 442 443 // FIXME: A synchronous request does not tell us whether a redirect happened or not, so we guess by comparing the 444 // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was 445 // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials. 446 if (requestURL != response.url() && (!isAllowedByPolicy(response.url()) || !isAllowedRedirect(response.url()))) { 447 m_client->didFailRedirectCheck(); 448 return; 449 } 450 451 didReceiveResponse(identifier, response); 452 453 SharedBuffer* data = resource->resourceBuffer(); 454 if (data) 455 didReceiveData(identifier, data->data(), data->size()); 456 457 didFinishLoading(identifier, 0.0); 458 } 459 460 bool DocumentThreadableLoader::isAllowedRedirect(const KURL& url) const 461 { 462 if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) 463 return true; 464 465 return m_sameOriginRequest && securityOrigin()->canRequest(url); 466 } 467 468 bool DocumentThreadableLoader::isAllowedByPolicy(const KURL& url) const 469 { 470 if (m_options.contentSecurityPolicyEnforcement != EnforceConnectSrcDirective) 471 return true; 472 return m_document->contentSecurityPolicy()->allowConnectToSource(url); 473 } 474 475 SecurityOrigin* DocumentThreadableLoader::securityOrigin() const 476 { 477 return m_options.securityOrigin ? m_options.securityOrigin.get() : m_document->securityOrigin(); 478 } 479 480 bool DocumentThreadableLoader::checkCrossOriginAccessRedirectionUrl(const KURL& requestUrl, String& errorDescription) 481 { 482 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestUrl.protocol())) { 483 errorDescription = "The request was redirected to a URL ('" + requestUrl.string() + "') which has a disallowed scheme for cross-origin requests."; 484 return false; 485 } 486 487 if (!(requestUrl.user().isEmpty() && requestUrl.pass().isEmpty())) { 488 errorDescription = "The request was redirected to a URL ('" + requestUrl.string() + "') containing userinfo, which is disallowed for cross-origin requests."; 489 return false; 490 } 491 492 return true; 493 } 494 495 } // namespace WebCore 496