1/* 2 * Copyright (C) 2011, 2012 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 "CachedRawResource.h" 35#include "CachedResourceLoader.h" 36#include "CachedResourceRequest.h" 37#include "CrossOriginAccessControl.h" 38#include "CrossOriginPreflightResultCache.h" 39#include "Document.h" 40#include "Frame.h" 41#include "FrameLoader.h" 42#include "InspectorInstrumentation.h" 43#include "ResourceError.h" 44#include "ResourceRequest.h" 45#include "SchemeRegistry.h" 46#include "SecurityOrigin.h" 47#include "SubresourceLoader.h" 48#include "ThreadableLoaderClient.h" 49#include <wtf/Assertions.h> 50#include <wtf/Ref.h> 51 52#if ENABLE(INSPECTOR) 53#include "ProgressTracker.h" 54#endif 55 56namespace WebCore { 57 58void DocumentThreadableLoader::loadResourceSynchronously(Document& document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) 59{ 60 // The loader will be deleted as soon as this function exits. 61 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadSynchronously, request, options)); 62 ASSERT(loader->hasOneRef()); 63} 64 65PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document& document, ThreadableLoaderClient& client, const ResourceRequest& request, const ThreadableLoaderOptions& options) 66{ 67 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options)); 68 if (!loader->m_resource) 69 loader = 0; 70 return loader.release(); 71} 72 73DocumentThreadableLoader::DocumentThreadableLoader(Document& document, ThreadableLoaderClient& client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options) 74 : m_client(&client) 75 , m_document(document) 76 , m_options(options) 77 , m_sameOriginRequest(securityOrigin()->canRequest(request.url())) 78 , m_simpleRequest(true) 79 , m_async(blockingBehavior == LoadAsynchronously) 80{ 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(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are not supported.")); 91 return; 92 } 93 94 makeCrossOriginAccessRequest(request); 95} 96 97void DocumentThreadableLoader::makeCrossOriginAccessRequest(const ResourceRequest& request) 98{ 99 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 100 101 OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request)); 102 updateRequestForAccessControl(*crossOriginRequest, securityOrigin(), m_options.allowCredentials()); 103 104 if ((m_options.preflightPolicy == ConsiderPreflight && isSimpleCrossOriginAccessRequest(crossOriginRequest->httpMethod(), crossOriginRequest->httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight) 105 makeSimpleCrossOriginAccessRequest(*crossOriginRequest); 106 else { 107 m_simpleRequest = false; 108 m_actualRequest = crossOriginRequest.release(); 109 110 if (CrossOriginPreflightResultCache::shared().canSkipPreflight(securityOrigin()->toString(), m_actualRequest->url(), m_options.allowCredentials(), m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields())) 111 preflightSuccess(); 112 else 113 makeCrossOriginAccessRequestWithPreflight(*m_actualRequest); 114 } 115} 116 117void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const ResourceRequest& request) 118{ 119 ASSERT(m_options.preflightPolicy != ForcePreflight); 120 ASSERT(m_options.preflightPolicy == PreventPreflight || isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields())); 121 122 // 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. 123 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())) { 124 m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP.")); 125 return; 126 } 127 128 loadRequest(request, DoSecurityCheck); 129} 130 131void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request) 132{ 133 ResourceRequest preflightRequest = createAccessControlPreflightRequest(request, securityOrigin()); 134 loadRequest(preflightRequest, DoSecurityCheck); 135} 136 137DocumentThreadableLoader::~DocumentThreadableLoader() 138{ 139 if (m_resource) 140 m_resource->removeClient(this); 141} 142 143void DocumentThreadableLoader::cancel() 144{ 145 Ref<DocumentThreadableLoader> protect(*this); 146 147 // Cancel can re-enter and m_resource might be null here as a result. 148 if (m_client && m_resource) { 149 // FIXME: This error is sent to the client in didFail(), so it should not be an internal one. Use FrameLoaderClient::cancelledError() instead. 150 ResourceError error(errorDomainWebKitInternal, 0, m_resource->url(), "Load cancelled"); 151 error.setIsCancellation(true); 152 didFail(m_resource->identifier(), error); 153 } 154 clearResource(); 155 m_client = 0; 156} 157 158void DocumentThreadableLoader::setDefersLoading(bool value) 159{ 160 if (m_resource) 161 m_resource->setDefersLoading(value); 162} 163 164void DocumentThreadableLoader::clearResource() 165{ 166 // Script can cancel and restart a request reentrantly within removeClient(), 167 // which could lead to calling CachedResource::removeClient() multiple times for 168 // this DocumentThreadableLoader. Save off a copy of m_resource and clear it to 169 // prevent the reentrancy. 170 if (CachedResourceHandle<CachedRawResource> resource = m_resource) { 171 m_resource = 0; 172 resource->removeClient(this); 173 } 174} 175 176void DocumentThreadableLoader::redirectReceived(CachedResource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) 177{ 178 ASSERT(m_client); 179 ASSERT_UNUSED(resource, resource == m_resource); 180 181 Ref<DocumentThreadableLoader> protect(*this); 182 // Allow same origin requests to continue after allowing clients to audit the redirect. 183 if (isAllowedRedirect(request.url())) 184 return; 185 186 // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported 187 // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check. 188 if (m_options.crossOriginRequestPolicy == UseAccessControl) { 189 bool allowRedirect = false; 190 if (m_simpleRequest) { 191 String accessControlErrorDescription; 192 allowRedirect = SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol()) 193 && request.url().user().isEmpty() 194 && request.url().pass().isEmpty() 195 && passesAccessControlCheck(redirectResponse, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription); 196 } 197 198 if (allowRedirect) { 199 if (m_resource) 200 clearResource(); 201 202 RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::createFromString(redirectResponse.url()); 203 RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::createFromString(request.url()); 204 // If the request URL origin is not same origin with the original URL origin, set source origin to a globally unique identifier. 205 if (!originalOrigin->isSameSchemeHostPort(requestOrigin.get())) 206 m_options.securityOrigin = SecurityOrigin::createUnique(); 207 // Force any subsequent requests to use these checks. 208 m_sameOriginRequest = false; 209 210 // Remove any headers that may have been added by the network layer that cause access control to fail. 211 request.clearHTTPContentType(); 212 request.clearHTTPReferrer(); 213 request.clearHTTPOrigin(); 214 request.clearHTTPUserAgent(); 215 request.clearHTTPAccept(); 216 makeCrossOriginAccessRequest(request); 217 return; 218 } 219 } 220 221 m_client->didFailRedirectCheck(); 222 request = ResourceRequest(); 223} 224 225void DocumentThreadableLoader::dataSent(CachedResource* resource, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) 226{ 227 ASSERT(m_client); 228 ASSERT_UNUSED(resource, resource == m_resource); 229 m_client->didSendData(bytesSent, totalBytesToBeSent); 230} 231 232void DocumentThreadableLoader::responseReceived(CachedResource* resource, const ResourceResponse& response) 233{ 234 ASSERT_UNUSED(resource, resource == m_resource); 235 didReceiveResponse(m_resource->identifier(), response); 236} 237 238void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) 239{ 240 ASSERT(m_client); 241 242 String accessControlErrorDescription; 243 if (m_actualRequest) { 244#if ENABLE(INSPECTOR) 245 DocumentLoader* loader = m_document.frame()->loader().documentLoader(); 246 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_document.frame(), identifier, response); 247 InspectorInstrumentation::didReceiveResourceResponse(cookie, identifier, loader, response, 0); 248#endif 249 250 if (!passesAccessControlCheck(response, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription)) { 251 preflightFailure(identifier, response.url(), accessControlErrorDescription); 252 return; 253 } 254 255 StoredCredentials allowCredentials = m_options.allowCredentials(); 256 auto preflightResult = std::make_unique<CrossOriginPreflightResultCacheItem>(allowCredentials); 257 if (!preflightResult->parse(response, accessControlErrorDescription) 258 || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) 259 || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { 260 preflightFailure(identifier, response.url(), accessControlErrorDescription); 261 return; 262 } 263 264 CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), WTF::move(preflightResult)); 265 } else { 266 if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { 267 if (!passesAccessControlCheck(response, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription)) { 268 m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription)); 269 return; 270 } 271 } 272 273 m_client->didReceiveResponse(identifier, response); 274 } 275} 276 277void DocumentThreadableLoader::dataReceived(CachedResource* resource, const char* data, int dataLength) 278{ 279 ASSERT_UNUSED(resource, resource == m_resource); 280 didReceiveData(m_resource->identifier(), data, dataLength); 281} 282 283void DocumentThreadableLoader::didReceiveData(unsigned long identifier, const char* data, int dataLength) 284{ 285 ASSERT(m_client); 286 287 // Preflight data should be invisible to clients. 288 if (m_actualRequest) { 289#if ENABLE(INSPECTOR) 290 InspectorInstrumentation::didReceiveData(m_document.frame(), identifier, 0, 0, dataLength); 291#else 292 UNUSED_PARAM(identifier); 293#endif 294 return; 295 } 296 297 m_client->didReceiveData(data, dataLength); 298} 299 300void DocumentThreadableLoader::notifyFinished(CachedResource* resource) 301{ 302 ASSERT(m_client); 303 ASSERT_UNUSED(resource, resource == m_resource); 304 305 if (m_resource->errorOccurred()) 306 didFail(m_resource->identifier(), m_resource->resourceError()); 307 else 308 didFinishLoading(m_resource->identifier(), m_resource->loadFinishTime()); 309} 310 311void DocumentThreadableLoader::didFinishLoading(unsigned long identifier, double finishTime) 312{ 313 if (m_actualRequest) { 314#if ENABLE(INSPECTOR) 315 InspectorInstrumentation::didFinishLoading(m_document.frame(), m_document.frame()->loader().documentLoader(), identifier, finishTime); 316#endif 317 ASSERT(!m_sameOriginRequest); 318 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 319 preflightSuccess(); 320 } else 321 m_client->didFinishLoading(identifier, finishTime); 322} 323 324void DocumentThreadableLoader::didFail(unsigned long identifier, const ResourceError& error) 325{ 326#if ENABLE(INSPECTOR) 327 if (m_actualRequest) 328 InspectorInstrumentation::didFailLoading(m_document.frame(), m_document.frame()->loader().documentLoader(), identifier, error); 329#else 330 UNUSED_PARAM(identifier); 331#endif 332 333 m_client->didFail(error); 334} 335 336void DocumentThreadableLoader::preflightSuccess() 337{ 338 OwnPtr<ResourceRequest> actualRequest; 339 actualRequest.swap(m_actualRequest); 340 341 actualRequest->setHTTPOrigin(securityOrigin()->toString()); 342 343 clearResource(); 344 345 // It should be ok to skip the security check since we already asked about the preflight request. 346 loadRequest(*actualRequest, SkipSecurityCheck); 347} 348 349void DocumentThreadableLoader::preflightFailure(unsigned long identifier, const String& url, const String& errorDescription) 350{ 351 ResourceError error(errorDomainWebKitInternal, 0, url, errorDescription); 352#if ENABLE(INSPECTOR) 353 if (m_actualRequest) 354 InspectorInstrumentation::didFailLoading(m_document.frame(), m_document.frame()->loader().documentLoader(), identifier, error); 355#else 356 UNUSED_PARAM(identifier); 357#endif 358 m_actualRequest = nullptr; // Prevent didFinishLoading() from bypassing access check. 359 m_client->didFailAccessControlCheck(error); 360} 361 362void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck) 363{ 364 // Any credential should have been removed from the cross-site requests. 365 const URL& requestURL = request.url(); 366 m_options.setSecurityCheck(securityCheck); 367 ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); 368 ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); 369 370 if (m_async) { 371 ThreadableLoaderOptions options = m_options; 372 options.setClientCredentialPolicy(DoNotAskClientForCrossOriginCredentials); 373 if (m_actualRequest) { 374 // Don't sniff content or send load callbacks for the preflight request. 375 options.setSendLoadCallbacks(DoNotSendCallbacks); 376 options.setSniffContent(DoNotSniffContent); 377 // Keep buffering the data for the preflight request. 378 options.setDataBufferingPolicy(BufferData); 379 } 380 381 CachedResourceRequest newRequest(request, options); 382#if ENABLE(RESOURCE_TIMING) 383 newRequest.setInitiator(m_options.initiator); 384#endif 385 ASSERT(!m_resource); 386 m_resource = m_document.cachedResourceLoader()->requestRawResource(newRequest); 387 if (m_resource) { 388#if ENABLE(INSPECTOR) 389 if (m_resource->loader()) { 390 unsigned long identifier = m_resource->loader()->identifier(); 391 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(&m_document, identifier, m_client); 392 } 393#endif 394 m_resource->addClient(this); 395 } 396 return; 397 } 398 399 // FIXME: ThreadableLoaderOptions.sniffContent is not supported for synchronous requests. 400 Vector<char> data; 401 ResourceError error; 402 ResourceResponse response; 403 unsigned long identifier = std::numeric_limits<unsigned long>::max(); 404 if (m_document.frame()) 405 identifier = m_document.frame()->loader().loadResourceSynchronously(request, m_options.allowCredentials(), m_options.clientCredentialPolicy(), error, response, data); 406 407 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(&m_document, identifier, m_client); 408 409 if (!error.isNull() && response.httpStatusCode() <= 0) { 410 if (requestURL.isLocalFile()) { 411 // We don't want XMLHttpRequest to raise an exception for file:// resources, see <rdar://problem/4962298>. 412 // FIXME: XMLHttpRequest quirks should be in XMLHttpRequest code, not in DocumentThreadableLoader.cpp. 413 didReceiveResponse(identifier, response); 414 didFinishLoading(identifier, 0.0); 415 return; 416 } 417 m_client->didFail(error); 418 return; 419 } 420 421 // FIXME: FrameLoader::loadSynchronously() does not tell us whether a redirect happened or not, so we guess by comparing the 422 // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was 423 // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials. 424 if (requestURL != response.url() && !isAllowedRedirect(response.url())) { 425 m_client->didFailRedirectCheck(); 426 return; 427 } 428 429 didReceiveResponse(identifier, response); 430 431 const char* bytes = static_cast<const char*>(data.data()); 432 int len = static_cast<int>(data.size()); 433 didReceiveData(identifier, bytes, len); 434 435 didFinishLoading(identifier, 0.0); 436} 437 438bool DocumentThreadableLoader::isAllowedRedirect(const URL& url) 439{ 440 if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) 441 return true; 442 443 return m_sameOriginRequest && securityOrigin()->canRequest(url); 444} 445 446SecurityOrigin* DocumentThreadableLoader::securityOrigin() const 447{ 448 return m_options.securityOrigin ? m_options.securityOrigin.get() : m_document.securityOrigin(); 449} 450 451} // namespace WebCore 452