• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
loadResourceSynchronously(Document * document,const ResourceRequest & request,ThreadableLoaderClient & client,const ThreadableLoaderOptions & options)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 
create(Document * document,ThreadableLoaderClient * client,const ResourceRequest & request,const ThreadableLoaderOptions & options)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 
DocumentThreadableLoader(Document * document,ThreadableLoaderClient * client,BlockingBehavior blockingBehavior,const ResourceRequest & request,const ThreadableLoaderOptions & options)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 
makeSimpleCrossOriginAccessRequest(const ResourceRequest & request)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 
makeCrossOriginAccessRequestWithPreflight(const ResourceRequest & request)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 
~DocumentThreadableLoader()146 DocumentThreadableLoader::~DocumentThreadableLoader()
147 {
148     if (m_loader)
149         m_loader->clearClient();
150 }
151 
cancel()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 
willSendRequest(SubresourceLoader * loader,ResourceRequest & request,const ResourceResponse &)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 
didSendData(SubresourceLoader * loader,unsigned long long bytesSent,unsigned long long totalBytesToBeSent)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 
didReceiveResponse(SubresourceLoader * loader,const ResourceResponse & response)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 
didReceiveData(SubresourceLoader * loader,const char * data,int lengthReceived)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 
didFinishLoading(SubresourceLoader * loader)223 void DocumentThreadableLoader::didFinishLoading(SubresourceLoader* loader)
224 {
225     ASSERT(loader == m_loader);
226     ASSERT(m_client);
227     didFinishLoading(loader->identifier());
228 }
229 
didFinishLoading(unsigned long identifier)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 
didFail(SubresourceLoader * loader,const ResourceError & error)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 
getShouldUseCredentialStorage(SubresourceLoader * loader,bool & shouldUseCredentialStorage)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 
didReceiveAuthenticationChallenge(SubresourceLoader * loader,const AuthenticationChallenge &)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 
receivedCancellation(SubresourceLoader * loader,const AuthenticationChallenge & challenge)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 
preflightSuccess()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 
preflightFailure()288 void DocumentThreadableLoader::preflightFailure()
289 {
290     m_actualRequest = 0; // Prevent didFinishLoading() from bypassing access check.
291     m_client->didFail(ResourceError());
292 }
293 
loadRequest(const ResourceRequest & request,SecurityCheckPolicy securityCheck)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 
isAllowedRedirect(const KURL & url)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