[Fetch API] SubresourceLoader::checkRedirectionCrossOriginAccessControl should not...
[WebKit-https.git] / Source / WebCore / loader / SubresourceLoader.cpp
1 /*
2  * Copyright (C) 2006-2007, 2009, 2016 Apple 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
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "SubresourceLoader.h"
31
32 #include "CachedResourceLoader.h"
33 #include "CrossOriginAccessControl.h"
34 #include "DiagnosticLoggingClient.h"
35 #include "DiagnosticLoggingKeys.h"
36 #include "Document.h"
37 #include "DocumentLoader.h"
38 #include "Frame.h"
39 #include "FrameLoader.h"
40 #include "Logging.h"
41 #include "MainFrame.h"
42 #include "MemoryCache.h"
43 #include "Page.h"
44 #include "ResourceLoadObserver.h"
45 #include "Settings.h"
46 #include <wtf/Ref.h>
47 #include <wtf/RefCountedLeakCounter.h>
48 #include <wtf/StdLibExtras.h>
49 #include <wtf/TemporaryChange.h>
50 #include <wtf/text/CString.h>
51
52 #if PLATFORM(IOS)
53 #include <RuntimeApplicationChecks.h>
54 #endif
55
56 #if ENABLE(CONTENT_EXTENSIONS)
57 #include "ResourceLoadInfo.h"
58 #endif
59
60 namespace WebCore {
61
62 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader"));
63
64 SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader& cachedResourceLoader, const CachedResource& resource)
65     : m_cachedResourceLoader(cachedResourceLoader)
66     , m_resource(resource)
67 {
68     m_cachedResourceLoader.incrementRequestCount(m_resource);
69 }
70
71 SubresourceLoader::RequestCountTracker::~RequestCountTracker()
72 {
73     m_cachedResourceLoader.decrementRequestCount(m_resource);
74 }
75
76 SubresourceLoader::SubresourceLoader(Frame& frame, CachedResource& resource, const ResourceLoaderOptions& options)
77     : ResourceLoader(frame, options)
78     , m_resource(&resource)
79     , m_loadingMultipartContent(false)
80     , m_state(Uninitialized)
81     , m_requestCountTracker(InPlace, frame.document()->cachedResourceLoader(), resource)
82 {
83 #ifndef NDEBUG
84     subresourceLoaderCounter.increment();
85 #endif
86 #if ENABLE(CONTENT_EXTENSIONS)
87     m_resourceType = toResourceType(resource.type());
88 #endif
89 }
90
91 SubresourceLoader::~SubresourceLoader()
92 {
93     ASSERT(m_state != Initialized);
94     ASSERT(reachedTerminalState());
95 #ifndef NDEBUG
96     subresourceLoaderCounter.decrement();
97 #endif
98 }
99
100 RefPtr<SubresourceLoader> SubresourceLoader::create(Frame& frame, CachedResource& resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
101 {
102     RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, resource, options)));
103 #if PLATFORM(IOS)
104     if (!IOSApplication::isWebProcess()) {
105         // On iOS, do not invoke synchronous resource load delegates while resource load scheduling
106         // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>).
107         // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>.
108         subloader->m_iOSOriginalRequest = request;
109         return subloader.release();
110     }
111 #endif
112     if (!subloader->init(request))
113         return nullptr;
114     return subloader;
115 }
116     
117 #if PLATFORM(IOS)
118 bool SubresourceLoader::startLoading()
119 {
120     ASSERT(!IOSApplication::isWebProcess());
121     if (!init(m_iOSOriginalRequest))
122         return false;
123     m_iOSOriginalRequest = ResourceRequest();
124     start();
125     return true;
126 }
127 #endif
128
129 CachedResource* SubresourceLoader::cachedResource()
130 {
131     return m_resource;
132 }
133
134 void SubresourceLoader::cancelIfNotFinishing()
135 {
136     if (m_state != Initialized)
137         return;
138
139     ResourceLoader::cancel();
140 }
141
142 bool SubresourceLoader::init(const ResourceRequest& request)
143 {
144     if (!ResourceLoader::init(request))
145         return false;
146
147     ASSERT(!reachedTerminalState());
148     m_state = Initialized;
149     m_documentLoader->addSubresourceLoader(this);
150
151     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=155633.
152     // SubresourceLoader could use the document origin as a default and set PotentiallyCrossOriginEnabled requests accordingly.
153     // This would simplify resource loader users as they would only need to set fetch mode to Cors.
154     m_origin = m_resource->origin();
155
156     return true;
157 }
158
159 bool SubresourceLoader::isSubresourceLoader()
160 {
161     return true;
162 }
163
164 void SubresourceLoader::willSendRequestInternal(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
165 {
166     // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it.
167     URL previousURL = request().url();
168     Ref<SubresourceLoader> protectedThis(*this);
169
170     if (!newRequest.url().isValid()) {
171         cancel(cannotShowURLError());
172         return;
173     }
174
175     ASSERT(!newRequest.isNull());
176     if (!redirectResponse.isNull()) {
177         if (options().redirect != FetchOptions::Redirect::Follow) {
178             if (options().redirect == FetchOptions::Redirect::Error) {
179                 cancel();
180                 return;
181             }
182
183             ResourceResponse opaqueRedirectedResponse;
184             opaqueRedirectedResponse.setType(ResourceResponse::Type::Opaqueredirect);
185             m_resource->responseReceived(opaqueRedirectedResponse);
186             didFinishLoading(currentTime());
187             return;
188         }
189
190         // CachedResources are keyed off their original request URL.
191         // Requesting the same original URL a second time can redirect to a unique second resource.
192         // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource.
193         // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds.
194         if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) {
195             newRequest.makeUnconditional();
196             MemoryCache::singleton().revalidationFailed(*m_resource);
197             if (m_frame && m_frame->page())
198                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
199         }
200
201         if (!m_documentLoader->cachedResourceLoader().canRequest(m_resource->type(), newRequest.url(), options(), false /* forPreload */, true /* didReceiveRedirectResponse */)) {
202             cancel();
203             return;
204         }
205
206         String errorDescription;
207         if (!checkRedirectionCrossOriginAccessControl(request(), redirectResponse, newRequest, errorDescription)) {
208             String errorMessage = "Cross-origin redirection to " + newRequest.url().string() + " denied by Cross-Origin Resource Sharing policy: " + errorDescription;
209             if (m_frame && m_frame->document())
210                 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorMessage);
211             cancel(ResourceError(String(), 0, request().url(), errorMessage, ResourceError::Type::AccessControl));
212             return;
213         }
214
215         if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) {
216             cancel();
217             return;
218         }
219         m_resource->redirectReceived(newRequest, redirectResponse);
220     }
221
222     if (newRequest.isNull() || reachedTerminalState())
223         return;
224
225     ResourceLoader::willSendRequestInternal(newRequest, redirectResponse);
226     if (newRequest.isNull())
227         cancel();
228
229     ResourceLoadObserver::sharedObserver().logSubresourceLoading(m_frame.get(), newRequest, redirectResponse);
230 }
231
232 void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
233 {
234     ASSERT(m_state == Initialized);
235     Ref<SubresourceLoader> protectedThis(*this);
236     m_resource->didSendData(bytesSent, totalBytesToBeSent);
237 }
238
239 void SubresourceLoader::didReceiveResponse(const ResourceResponse& response)
240 {
241     ASSERT(!response.isNull());
242     ASSERT(m_state == Initialized);
243
244     // Reference the object in this method since the additional processing can do
245     // anything including removing the last reference to this object; one example of this is 3266216.
246     Ref<SubresourceLoader> protectedThis(*this);
247
248     if (shouldIncludeCertificateInfo())
249         response.includeCertificateInfo();
250
251     if (response.isHttpVersion0_9()) {
252         if (m_frame) {
253             String message = "Sandboxing '" + response.url().string() + "' because it is using HTTP/0.9.";
254             m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, identifier());
255             frameLoader()->forceSandboxFlags(SandboxScripts | SandboxPlugins);
256         }
257     }
258
259     if (m_resource->resourceToRevalidate()) {
260         if (response.httpStatusCode() == 304) {
261             // 304 Not modified / Use local copy
262             // Existing resource is ok, just use it updating the expiration time.
263             m_resource->setResponse(response);
264             MemoryCache::singleton().revalidationSucceeded(*m_resource, response);
265             if (m_frame && m_frame->page())
266                 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultPass, ShouldSample::Yes);
267             if (!reachedTerminalState())
268                 ResourceLoader::didReceiveResponse(response);
269             return;
270         }
271         // Did not get 304 response, continue as a regular resource load.
272         MemoryCache::singleton().revalidationFailed(*m_resource);
273         if (m_frame && m_frame->page())
274             m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
275     }
276
277     m_resource->responseReceived(response);
278     if (reachedTerminalState())
279         return;
280
281     ResourceLoader::didReceiveResponse(response);
282     if (reachedTerminalState())
283         return;
284
285     // FIXME: Main resources have a different set of rules for multipart than images do.
286     // Hopefully we can merge those 2 paths.
287     if (response.isMultipart() && m_resource->type() != CachedResource::MainResource) {
288         m_loadingMultipartContent = true;
289
290         // We don't count multiParts in a CachedResourceLoader's request count
291         m_requestCountTracker = Nullopt;
292         if (!m_resource->isImage()) {
293             cancel();
294             return;
295         }
296     }
297
298     auto* buffer = resourceData();
299     if (m_loadingMultipartContent && buffer && buffer->size()) {
300         // The resource data will change as the next part is loaded, so we need to make a copy.
301         m_resource->finishLoading(buffer->copy().ptr());
302         clearResourceData();
303         // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
304         // After the first multipart section is complete, signal to delegates that this load is "finished"
305         m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
306         didFinishLoadingOnePart(0);
307     }
308
309     checkForHTTPStatusCodeError();
310 }
311
312 void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType)
313 {
314     didReceiveDataOrBuffer(data, length, nullptr, encodedDataLength, dataPayloadType);
315 }
316
317 void SubresourceLoader::didReceiveBuffer(Ref<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
318 {
319     didReceiveDataOrBuffer(nullptr, 0, WTFMove(buffer), encodedDataLength, dataPayloadType);
320 }
321
322 void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, RefPtr<SharedBuffer>&& prpBuffer, long long encodedDataLength, DataPayloadType dataPayloadType)
323 {
324     if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
325         return;
326     ASSERT(!m_resource->resourceToRevalidate());
327     ASSERT(!m_resource->errorOccurred());
328     ASSERT(m_state == Initialized);
329     // Reference the object in this method since the additional processing can do
330     // anything including removing the last reference to this object; one example of this is 3266216.
331     Ref<SubresourceLoader> protectedThis(*this);
332     RefPtr<SharedBuffer> buffer = prpBuffer;
333     
334     ResourceLoader::didReceiveDataOrBuffer(data, length, WTFMove(buffer), encodedDataLength, dataPayloadType);
335
336     if (!m_loadingMultipartContent) {
337         if (auto* resourceData = this->resourceData())
338             m_resource->addDataBuffer(*resourceData);
339         else
340             m_resource->addData(buffer ? buffer->data() : data, buffer ? buffer->size() : length);
341     }
342 }
343
344 bool SubresourceLoader::checkForHTTPStatusCodeError()
345 {
346     if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
347         return false;
348
349     m_state = Finishing;
350     m_resource->error(CachedResource::LoadError);
351     cancel();
352     return true;
353 }
354
355 static void logResourceLoaded(Frame* frame, CachedResource::Type type)
356 {
357     if (!frame || !frame->page())
358         return;
359
360     String resourceType;
361     switch (type) {
362     case CachedResource::MainResource:
363         resourceType = DiagnosticLoggingKeys::mainResourceKey();
364         break;
365     case CachedResource::ImageResource:
366         resourceType = DiagnosticLoggingKeys::imageKey();
367         break;
368 #if ENABLE(XSLT)
369     case CachedResource::XSLStyleSheet:
370 #endif
371     case CachedResource::CSSStyleSheet:
372         resourceType = DiagnosticLoggingKeys::styleSheetKey();
373         break;
374     case CachedResource::Script:
375         resourceType = DiagnosticLoggingKeys::scriptKey();
376         break;
377     case CachedResource::FontResource:
378 #if ENABLE(SVG_FONTS)
379     case CachedResource::SVGFontResource:
380 #endif
381         resourceType = DiagnosticLoggingKeys::fontKey();
382         break;
383     case CachedResource::MediaResource:
384     case CachedResource::RawResource:
385         resourceType = DiagnosticLoggingKeys::rawKey();
386         break;
387     case CachedResource::SVGDocumentResource:
388         resourceType = DiagnosticLoggingKeys::svgDocumentKey();
389         break;
390     case CachedResource::LinkPreload:
391 #if ENABLE(LINK_PREFETCH)
392     case CachedResource::LinkPrefetch:
393     case CachedResource::LinkSubresource:
394 #endif
395 #if ENABLE(VIDEO_TRACK)
396     case CachedResource::TextTrackResource:
397 #endif
398         resourceType = DiagnosticLoggingKeys::otherKey();
399         break;
400     }
401     frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceKey(), DiagnosticLoggingKeys::loadedKey(), resourceType, ShouldSample::Yes);
402 }
403
404 bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest, String& errorMessage)
405 {
406     bool crossOriginFlag = m_resource->isCrossOrigin();
407     bool isNextRequestCrossOrigin = m_origin && !m_origin->canRequest(newRequest.url());
408
409     if (isNextRequestCrossOrigin)
410         m_resource->setCrossOrigin();
411
412     ASSERT(options().mode != FetchOptions::Mode::SameOrigin || !m_resource->isCrossOrigin());
413
414     if (options().mode != FetchOptions::Mode::Cors)
415         return true;
416
417     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 8 & 9.
418     if (m_resource->isCrossOrigin() && !isValidCrossOriginRedirectionURL(newRequest.url())) {
419         errorMessage = ASCIILiteral("URL is either a non-HTTP URL or contains credentials.");
420         return false;
421     }
422
423     ASSERT(m_origin);
424     if (crossOriginFlag && !passesAccessControlCheck(redirectResponse, options().allowCredentials, *m_origin, errorMessage))
425         return false;
426
427     bool redirectingToNewOrigin = false;
428     if (m_resource->isCrossOrigin()) {
429         if (!crossOriginFlag && isNextRequestCrossOrigin)
430             redirectingToNewOrigin = true;
431         else
432             redirectingToNewOrigin = !SecurityOrigin::create(previousRequest.url())->canRequest(newRequest.url());
433     }
434
435     // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 10.
436     if (crossOriginFlag && redirectingToNewOrigin)
437         m_origin = SecurityOrigin::createUnique();
438
439     if (redirectingToNewOrigin) {
440         cleanRedirectedRequestForAccessControl(newRequest);
441         updateRequestForAccessControl(newRequest, *m_origin, options().allowCredentials);
442     }
443
444     return true;
445 }
446
447 void SubresourceLoader::didFinishLoading(double finishTime)
448 {
449     if (m_state != Initialized)
450         return;
451     ASSERT(!reachedTerminalState());
452     ASSERT(!m_resource->resourceToRevalidate());
453     // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion.
454     ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError);
455     LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
456     logResourceLoaded(m_frame.get(), m_resource->type());
457
458     Ref<SubresourceLoader> protectedThis(*this);
459     CachedResourceHandle<CachedResource> protectResource(m_resource);
460
461     m_state = Finishing;
462     m_resource->setLoadFinishTime(finishTime);
463     m_resource->finishLoading(resourceData());
464
465     if (wasCancelled())
466         return;
467     m_resource->finish();
468     ASSERT(!reachedTerminalState());
469     didFinishLoadingOnePart(finishTime);
470     notifyDone();
471     if (reachedTerminalState())
472         return;
473     releaseResources();
474 }
475
476 void SubresourceLoader::didFail(const ResourceError& error)
477 {
478     if (m_state != Initialized)
479         return;
480     ASSERT(!reachedTerminalState());
481     LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
482
483     Ref<SubresourceLoader> protectedThis(*this);
484     CachedResourceHandle<CachedResource> protectResource(m_resource);
485     m_state = Finishing;
486     if (m_resource->resourceToRevalidate())
487         MemoryCache::singleton().revalidationFailed(*m_resource);
488     m_resource->setResourceError(error);
489     if (!m_resource->isPreloaded())
490         MemoryCache::singleton().remove(*m_resource);
491     m_resource->error(CachedResource::LoadError);
492     cleanupForError(error);
493     notifyDone();
494     if (reachedTerminalState())
495         return;
496     releaseResources();
497 }
498
499 void SubresourceLoader::willCancel(const ResourceError& error)
500 {
501 #if PLATFORM(IOS)
502     // Since we defer initialization to scheduling time on iOS but
503     // CachedResourceLoader stores resources in the memory cache immediately,
504     // m_resource might be cached despite its loader not being initialized.
505     if (m_state != Initialized && m_state != Uninitialized)
506 #else
507     if (m_state != Initialized)
508 #endif
509         return;
510     ASSERT(!reachedTerminalState());
511     LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
512
513     Ref<SubresourceLoader> protectedThis(*this);
514 #if PLATFORM(IOS)
515     m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing;
516 #else
517     m_state = Finishing;
518 #endif
519     auto& memoryCache = MemoryCache::singleton();
520     if (m_resource->resourceToRevalidate())
521         memoryCache.revalidationFailed(*m_resource);
522     m_resource->setResourceError(error);
523     memoryCache.remove(*m_resource);
524 }
525
526 void SubresourceLoader::didCancel(const ResourceError&)
527 {
528     if (m_state == Uninitialized)
529         return;
530
531     m_resource->cancelLoad();
532     notifyDone();
533 }
534
535 void SubresourceLoader::notifyDone()
536 {
537     if (reachedTerminalState())
538         return;
539
540     m_requestCountTracker = Nullopt;
541 #if PLATFORM(IOS)
542     m_documentLoader->cachedResourceLoader().loadDone(m_resource, m_state != CancelledWhileInitializing);
543 #else
544     m_documentLoader->cachedResourceLoader().loadDone(m_resource);
545 #endif
546     if (reachedTerminalState())
547         return;
548     m_documentLoader->removeSubresourceLoader(this);
549 }
550
551 void SubresourceLoader::releaseResources()
552 {
553     ASSERT(!reachedTerminalState());
554 #if PLATFORM(IOS)
555     if (m_state != Uninitialized && m_state != CancelledWhileInitializing)
556 #else
557     if (m_state != Uninitialized)
558 #endif
559         m_resource->clearLoader();
560     m_resource = nullptr;
561     ResourceLoader::releaseResources();
562 }
563
564 }