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