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