1/*
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6 Copyright (C) 2004-2011, 2014, 2018 Apple Inc. All rights reserved.
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22*/
23
24#include "config.h"
25#include "CachedResource.h"
26
27#include "CachedResourceClient.h"
28#include "CachedResourceClientWalker.h"
29#include "CachedResourceHandle.h"
30#include "CachedResourceLoader.h"
31#include "CookieJar.h"
32#include "CrossOriginAccessControl.h"
33#include "DiagnosticLoggingClient.h"
34#include "DiagnosticLoggingKeys.h"
35#include "Document.h"
36#include "DocumentLoader.h"
37#include "Frame.h"
38#include "FrameLoader.h"
39#include "FrameLoaderClient.h"
40#include "HTTPHeaderNames.h"
41#include "InspectorInstrumentation.h"
42#include "LoaderStrategy.h"
43#include "Logging.h"
44#include "MemoryCache.h"
45#include "PlatformStrategies.h"
46#include "ProgressTracker.h"
47#include "ResourceHandle.h"
48#include "SchemeRegistry.h"
49#include "SecurityOrigin.h"
50#include "SubresourceLoader.h"
51#include <wtf/CompletionHandler.h>
52#include <wtf/MathExtras.h>
53#include <wtf/RefCountedLeakCounter.h>
54#include <wtf/StdLibExtras.h>
55#include <wtf/URL.h>
56#include <wtf/Vector.h>
57#include <wtf/text/CString.h>
58
59#if USE(QUICK_LOOK)
60#include "QuickLook.h"
61#endif
62
63#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(cachedResourceLoader.isAlwaysOnLoggingAllowed(), Network, "%p - CachedResource::" fmt, this, ##__VA_ARGS__)
64
65namespace WebCore {
66
67ResourceLoadPriority CachedResource::defaultPriorityForResourceType(Type type)
68{
69 switch (type) {
70 case Type::MainResource:
71 return ResourceLoadPriority::VeryHigh;
72 case Type::CSSStyleSheet:
73 case Type::Script:
74 return ResourceLoadPriority::High;
75#if ENABLE(SVG_FONTS)
76 case Type::SVGFontResource:
77#endif
78 case Type::MediaResource:
79 case Type::FontResource:
80 case Type::RawResource:
81 case Type::Icon:
82 return ResourceLoadPriority::Medium;
83 case Type::ImageResource:
84 return ResourceLoadPriority::Low;
85#if ENABLE(XSLT)
86 case Type::XSLStyleSheet:
87 return ResourceLoadPriority::High;
88#endif
89 case Type::SVGDocumentResource:
90 return ResourceLoadPriority::Low;
91 case Type::Beacon:
92 case Type::Ping:
93 return ResourceLoadPriority::VeryLow;
94 case Type::LinkPrefetch:
95 return ResourceLoadPriority::VeryLow;
96#if ENABLE(VIDEO_TRACK)
97 case Type::TextTrackResource:
98 return ResourceLoadPriority::Low;
99#endif
100#if ENABLE(APPLICATION_MANIFEST)
101 case Type::ApplicationManifest:
102 return ResourceLoadPriority::Low;
103#endif
104 }
105 ASSERT_NOT_REACHED();
106 return ResourceLoadPriority::Low;
107}
108
109static Seconds deadDecodedDataDeletionIntervalForResourceType(CachedResource::Type type)
110{
111 if (type == CachedResource::Type::Script)
112 return 0_s;
113
114 return MemoryCache::singleton().deadDecodedDataDeletionInterval();
115}
116
117DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, cachedResourceLeakCounter, ("CachedResource"));
118
119CachedResource::CachedResource(CachedResourceRequest&& request, Type type, const PAL::SessionID& sessionID, const CookieJar* cookieJar)
120 : m_options(request.options())
121 , m_resourceRequest(request.releaseResourceRequest())
122 , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type))
123 , m_sessionID(sessionID)
124 , m_cookieJar(cookieJar)
125 , m_responseTimestamp(WallTime::now())
126 , m_fragmentIdentifierForRequest(request.releaseFragmentIdentifier())
127 , m_origin(request.releaseOrigin())
128 , m_initiatorName(request.initiatorName())
129 , m_loadPriority(defaultPriorityForResourceType(type))
130 , m_type(type)
131 , m_isLinkPreload(request.isLinkPreload())
132 , m_hasUnknownEncoding(request.isLinkPreload())
133 , m_ignoreForRequestCount(request.ignoreForRequestCount())
134{
135 ASSERT(m_sessionID.isValid());
136
137 setLoadPriority(request.priority());
138#ifndef NDEBUG
139 cachedResourceLeakCounter.increment();
140#endif
141
142 // FIXME: We should have a better way of checking for Navigation loads, maybe FetchMode::Options::Navigate.
143 ASSERT(m_origin || m_type == Type::MainResource);
144
145 if (isRequestCrossOrigin(m_origin.get(), m_resourceRequest.url(), m_options))
146 setCrossOrigin();
147}
148
149// FIXME: For this constructor, we should probably mandate that the URL has no fragment identifier.
150CachedResource::CachedResource(const URL& url, Type type, const PAL::SessionID& sessionID, const CookieJar* cookieJar)
151 : m_resourceRequest(url)
152 , m_decodedDataDeletionTimer(*this, &CachedResource::destroyDecodedData, deadDecodedDataDeletionIntervalForResourceType(type))
153 , m_sessionID(sessionID)
154 , m_cookieJar(cookieJar)
155 , m_responseTimestamp(WallTime::now())
156 , m_fragmentIdentifierForRequest(CachedResourceRequest::splitFragmentIdentifierFromRequestURL(m_resourceRequest))
157 , m_status(Cached)
158 , m_type(type)
159{
160 ASSERT(m_sessionID.isValid());
161#ifndef NDEBUG
162 cachedResourceLeakCounter.increment();
163#endif
164}
165
166CachedResource::~CachedResource()
167{
168 ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this.
169 ASSERT(canDelete());
170 ASSERT(!inCache());
171 ASSERT(!m_deleted);
172 ASSERT(url().isNull() || !allowsCaching() || MemoryCache::singleton().resourceForRequest(resourceRequest(), sessionID()) != this);
173
174#ifndef NDEBUG
175 m_deleted = true;
176 cachedResourceLeakCounter.decrement();
177#endif
178}
179
180void CachedResource::failBeforeStarting()
181{
182 // FIXME: What if resources in other frames were waiting for this revalidation?
183 LOG(ResourceLoading, "Cannot start loading '%s'", url().string().latin1().data());
184 if (allowsCaching() && m_resourceToRevalidate)
185 MemoryCache::singleton().revalidationFailed(*this);
186 error(CachedResource::LoadError);
187}
188
189void CachedResource::load(CachedResourceLoader& cachedResourceLoader)
190{
191 if (!cachedResourceLoader.frame()) {
192 RELEASE_LOG_IF_ALLOWED("load: No associated frame");
193 failBeforeStarting();
194 return;
195 }
196 Frame& frame = *cachedResourceLoader.frame();
197
198 // Prevent new loads if we are in the PageCache or being added to the PageCache.
199 // We query the top document because new frames may be created in pagehide event handlers
200 // and their pageCacheState will not reflect the fact that they are about to enter page
201 // cache.
202 if (auto* topDocument = frame.mainFrame().document()) {
203 switch (topDocument->pageCacheState()) {
204 case Document::NotInPageCache:
205 break;
206 case Document::AboutToEnterPageCache:
207 // Beacons are allowed to go through in 'pagehide' event handlers.
208 if (shouldUsePingLoad(type()))
209 break;
210 RELEASE_LOG_IF_ALLOWED("load: About to enter page cache (frame = %p)", &frame);
211 failBeforeStarting();
212 return;
213 case Document::InPageCache:
214 RELEASE_LOG_IF_ALLOWED("load: Already in page cache (frame = %p)", &frame);
215 failBeforeStarting();
216 return;
217 }
218 }
219
220 FrameLoader& frameLoader = frame.loader();
221 if (m_options.securityCheck == SecurityCheckPolicy::DoSecurityCheck && !shouldUsePingLoad(type())) {
222 while (true) {
223 if (frameLoader.state() == FrameStateProvisional)
224 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- state is provisional (frame = %p)", &frame);
225 else if (!frameLoader.activeDocumentLoader())
226 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- not active document (frame = %p)", &frame);
227 else if (frameLoader.activeDocumentLoader()->isStopping())
228 RELEASE_LOG_IF_ALLOWED("load: Failed security check -- active loader is stopping (frame = %p)", &frame);
229 else
230 break;
231 failBeforeStarting();
232 return;
233 }
234 }
235
236 m_loading = true;
237
238 if (isCacheValidator()) {
239 CachedResource* resourceToRevalidate = m_resourceToRevalidate;
240 ASSERT(resourceToRevalidate->canUseCacheValidator());
241 ASSERT(resourceToRevalidate->isLoaded());
242 const String& lastModified = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::LastModified);
243 const String& eTag = resourceToRevalidate->response().httpHeaderField(HTTPHeaderName::ETag);
244 if (!lastModified.isEmpty() || !eTag.isEmpty()) {
245 ASSERT(cachedResourceLoader.cachePolicy(type(), url()) != CachePolicyReload);
246 if (cachedResourceLoader.cachePolicy(type(), url()) == CachePolicyRevalidate)
247 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::CacheControl, "max-age=0");
248 if (!lastModified.isEmpty())
249 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfModifiedSince, lastModified);
250 if (!eTag.isEmpty())
251 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::IfNoneMatch, eTag);
252 }
253 }
254
255 if (type() == Type::LinkPrefetch)
256 m_resourceRequest.setHTTPHeaderField(HTTPHeaderName::Purpose, "prefetch");
257 m_resourceRequest.setPriority(loadPriority());
258
259 // Navigation algorithm is setting up the request before sending it to CachedResourceLoader?CachedResource.
260 // So no need for extra fields for MainResource.
261 if (type() != Type::MainResource)
262 frameLoader.addExtraFieldsToSubresourceRequest(m_resourceRequest);
263
264
265 // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers.
266 // We should look into removing the expectation of that knowledge from the platform network stacks.
267 ResourceRequest request(m_resourceRequest);
268 if (!m_fragmentIdentifierForRequest.isNull()) {
269 URL url = request.url();
270 url.setFragmentIdentifier(m_fragmentIdentifierForRequest);
271 request.setURL(url);
272 m_fragmentIdentifierForRequest = String();
273 }
274
275 if (m_options.keepAlive && type() != Type::Ping && !cachedResourceLoader.keepaliveRequestTracker().tryRegisterRequest(*this)) {
276 setResourceError({ errorDomainWebKitInternal, 0, request.url(), "Reached maximum amount of queued data of 64Kb for keepalive requests"_s, ResourceError::Type::AccessControl });
277 failBeforeStarting();
278 return;
279 }
280
281 // FIXME: Deprecate that code path.
282 if (m_options.keepAlive && shouldUsePingLoad(type()) && platformStrategies()->loaderStrategy()->usePingLoad()) {
283 ASSERT(m_originalRequest);
284 CachedResourceHandle<CachedResource> protectedThis(this);
285
286 unsigned long identifier = frame.page()->progress().createUniqueIdentifier();
287 InspectorInstrumentation::willSendRequestOfType(&frame, identifier, frameLoader.activeDocumentLoader(), request, InspectorInstrumentation::LoadType::Beacon);
288
289 platformStrategies()->loaderStrategy()->startPingLoad(frame, request, m_originalRequest->httpHeaderFields(), m_options, m_options.contentSecurityPolicyImposition, [this, protectedThis = WTFMove(protectedThis), protectedFrame = makeRef(frame), identifier] (const ResourceError& error, const ResourceResponse& response) {
290 if (!response.isNull())
291 InspectorInstrumentation::didReceiveResourceResponse(protectedFrame, identifier, protectedFrame->loader().activeDocumentLoader(), response, nullptr);
292 if (!error.isNull()) {
293 setResourceError(error);
294 this->error(LoadError);
295 InspectorInstrumentation::didFailLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, error);
296 return;
297 }
298 finishLoading(nullptr);
299 NetworkLoadMetrics emptyMetrics;
300 InspectorInstrumentation::didFinishLoading(protectedFrame.ptr(), protectedFrame->loader().activeDocumentLoader(), identifier, emptyMetrics, nullptr);
301 });
302 return;
303 }
304
305 platformStrategies()->loaderStrategy()->loadResource(frame, *this, WTFMove(request), m_options, [this, protectedThis = CachedResourceHandle<CachedResource>(this), frame = makeRef(frame), loggingAllowed = cachedResourceLoader.isAlwaysOnLoggingAllowed()] (RefPtr<SubresourceLoader>&& loader) {
306 m_loader = WTFMove(loader);
307 if (!m_loader) {
308 RELEASE_LOG_IF(loggingAllowed, Network, "%p - CachedResource::load: Unable to create SubresourceLoader (frame = %p)", this, frame.ptr());
309 failBeforeStarting();
310 return;
311 }
312 m_status = Pending;
313 });
314}
315
316void CachedResource::loadFrom(const CachedResource& resource)
317{
318 ASSERT(url() == resource.url());
319 ASSERT(type() == resource.type());
320 ASSERT(resource.status() == Status::Cached);
321
322 if (isCrossOrigin() && m_options.mode == FetchOptions::Mode::Cors) {
323 ASSERT(m_origin);
324 String errorMessage;
325 if (!WebCore::passesAccessControlCheck(resource.response(), m_options.storedCredentialsPolicy, *m_origin, errorMessage)) {
326 setResourceError(ResourceError(String(), 0, url(), errorMessage, ResourceError::Type::AccessControl));
327 return;
328 }
329 }
330
331 setBodyDataFrom(resource);
332 setStatus(Status::Cached);
333 setLoading(false);
334}
335
336void CachedResource::setBodyDataFrom(const CachedResource& resource)
337{
338 m_data = resource.m_data;
339 m_response = resource.m_response;
340 m_response.setTainting(m_responseTainting);
341 setDecodedSize(resource.decodedSize());
342 setEncodedSize(resource.encodedSize());
343}
344
345void CachedResource::checkNotify()
346{
347 if (isLoading() || stillNeedsLoad())
348 return;
349
350 CachedResourceClientWalker<CachedResourceClient> walker(m_clients);
351 while (CachedResourceClient* client = walker.next())
352 client->notifyFinished(*this);
353}
354
355void CachedResource::updateBuffer(SharedBuffer&)
356{
357 ASSERT(dataBufferingPolicy() == DataBufferingPolicy::BufferData);
358}
359
360void CachedResource::updateData(const char*, unsigned)
361{
362 ASSERT(dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData);
363}
364
365void CachedResource::finishLoading(SharedBuffer*)
366{
367 setLoading(false);
368 checkNotify();
369}
370
371void CachedResource::error(CachedResource::Status status)
372{
373 setStatus(status);
374 ASSERT(errorOccurred());
375 m_data = nullptr;
376
377 setLoading(false);
378 checkNotify();
379}
380
381void CachedResource::cancelLoad()
382{
383 if (!isLoading() && !stillNeedsLoad())
384 return;
385
386 auto* documentLoader = (m_loader && m_loader->frame()) ? m_loader->frame()->loader().activeDocumentLoader() : nullptr;
387 if (m_options.keepAlive && (!documentLoader || documentLoader->isStopping()))
388 m_error = { };
389 else
390 setStatus(LoadError);
391
392 setLoading(false);
393 checkNotify();
394}
395
396void CachedResource::finish()
397{
398 if (!errorOccurred())
399 m_status = Cached;
400}
401
402void CachedResource::setCrossOrigin()
403{
404 ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin);
405 m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque;
406}
407
408bool CachedResource::isCrossOrigin() const
409{
410 return m_responseTainting != ResourceResponse::Tainting::Basic;
411}
412
413bool CachedResource::isCORSSameOrigin() const
414{
415 // Following resource types do not use CORS
416 ASSERT(type() != Type::FontResource);
417#if ENABLE(SVG_FONTS)
418 ASSERT(type() != Type::SVGFontResource);
419#endif
420#if ENABLE(XSLT)
421 ASSERT(type() != Type::XSLStyleSheet);
422#endif
423
424 // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin
425 return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque;
426}
427
428bool CachedResource::isExpired() const
429{
430 if (m_response.isNull())
431 return false;
432
433 return computeCurrentAge(m_response, m_responseTimestamp) > freshnessLifetime(m_response);
434}
435
436static inline bool shouldCacheSchemeIndefinitely(StringView scheme)
437{
438#if PLATFORM(COCOA)
439 if (equalLettersIgnoringASCIICase(scheme, "applewebdata"))
440 return true;
441#endif
442#if USE(SOUP)
443 if (equalLettersIgnoringASCIICase(scheme, "resource"))
444 return true;
445#endif
446 return equalLettersIgnoringASCIICase(scheme, "data");
447}
448
449Seconds CachedResource::freshnessLifetime(const ResourceResponse& response) const
450{
451 if (!response.url().protocolIsInHTTPFamily()) {
452 StringView protocol = response.url().protocol();
453 if (!shouldCacheSchemeIndefinitely(protocol)) {
454 // Don't cache non-HTTP main resources since we can't check for freshness.
455 // FIXME: We should not cache subresources either, but when we tried this
456 // it caused performance and flakiness issues in our test infrastructure.
457 if (m_type == Type::MainResource || SchemeRegistry::shouldAlwaysRevalidateURLScheme(protocol.toStringWithoutCopying()))
458 return 0_us;
459 }
460
461 return Seconds::infinity();
462 }
463
464 return computeFreshnessLifetimeForHTTPFamily(response, m_responseTimestamp);
465}
466
467void CachedResource::redirectReceived(ResourceRequest&& request, const ResourceResponse& response, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
468{
469 m_requestedFromNetworkingLayer = true;
470 if (response.isNull())
471 return completionHandler(WTFMove(request));
472
473 updateRedirectChainStatus(m_redirectChainCacheStatus, response);
474 completionHandler(WTFMove(request));
475}
476
477void CachedResource::setResponse(const ResourceResponse& response)
478{
479 ASSERT(m_response.type() == ResourceResponse::Type::Default);
480 m_response = response;
481 m_varyingHeaderValues = collectVaryingRequestHeaders(cookieJar(), m_resourceRequest, m_response, sessionID());
482
483#if ENABLE(SERVICE_WORKER)
484 if (m_response.source() == ResourceResponse::Source::ServiceWorker) {
485 m_responseTainting = m_response.tainting();
486 return;
487 }
488#endif
489 m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::Status::NoRedirection);
490 if (m_response.tainting() == ResourceResponse::Tainting::Basic || m_response.tainting() == ResourceResponse::Tainting::Cors)
491 m_response.setTainting(m_responseTainting);
492}
493
494void CachedResource::responseReceived(const ResourceResponse& response)
495{
496 setResponse(response);
497 m_responseTimestamp = WallTime::now();
498 String encoding = response.textEncodingName();
499 if (!encoding.isNull())
500 setEncoding(encoding);
501}
502
503void CachedResource::clearLoader()
504{
505 ASSERT(m_loader);
506 m_identifierForLoadWithoutResourceLoader = m_loader->identifier();
507 m_loader = nullptr;
508 deleteIfPossible();
509}
510
511void CachedResource::addClient(CachedResourceClient& client)
512{
513 if (addClientToSet(client))
514 didAddClient(client);
515}
516
517void CachedResource::didAddClient(CachedResourceClient& client)
518{
519 if (m_decodedDataDeletionTimer.isActive())
520 m_decodedDataDeletionTimer.stop();
521
522 if (m_clientsAwaitingCallback.remove(&client))
523 m_clients.add(&client);
524
525 // FIXME: Make calls to notifyFinished async
526 if (!isLoading() && !stillNeedsLoad())
527 client.notifyFinished(*this);
528}
529
530bool CachedResource::addClientToSet(CachedResourceClient& client)
531{
532 if (m_preloadResult == PreloadResult::PreloadNotReferenced && client.shouldMarkAsReferenced()) {
533 if (isLoaded())
534 m_preloadResult = PreloadResult::PreloadReferencedWhileComplete;
535 else if (m_requestedFromNetworkingLayer)
536 m_preloadResult = PreloadResult::PreloadReferencedWhileLoading;
537 else
538 m_preloadResult = PreloadResult::PreloadReferenced;
539 }
540 if (allowsCaching() && !hasClients() && inCache())
541 MemoryCache::singleton().addToLiveResourcesSize(*this);
542
543 if ((m_type == Type::RawResource || m_type == Type::MainResource) && !m_response.isNull() && !m_proxyResource) {
544 // Certain resources (especially XHRs and main resources) do crazy things if an asynchronous load returns
545 // synchronously (e.g., scripts may not have set all the state they need to handle the load).
546 // Therefore, rather than immediately sending callbacks on a cache hit like other CachedResources,
547 // we schedule the callbacks and ensure we never finish synchronously.
548 ASSERT(!m_clientsAwaitingCallback.contains(&client));
549 m_clientsAwaitingCallback.add(&client, std::make_unique<Callback>(*this, client));
550 return false;
551 }
552
553 m_clients.add(&client);
554 return true;
555}
556
557void CachedResource::removeClient(CachedResourceClient& client)
558{
559 auto callback = m_clientsAwaitingCallback.take(&client);
560 if (callback) {
561 ASSERT(!m_clients.contains(&client));
562 callback->cancel();
563 callback = nullptr;
564 } else {
565 ASSERT(m_clients.contains(&client));
566 m_clients.remove(&client);
567 didRemoveClient(client);
568 }
569
570 if (deleteIfPossible()) {
571 // `this` object is dead here.
572 return;
573 }
574
575 if (hasClients())
576 return;
577
578 auto& memoryCache = MemoryCache::singleton();
579 if (allowsCaching() && inCache()) {
580 memoryCache.removeFromLiveResourcesSize(*this);
581 memoryCache.removeFromLiveDecodedResourcesList(*this);
582 }
583 if (!m_switchingClientsToRevalidatedResource)
584 allClientsRemoved();
585 destroyDecodedDataIfNeeded();
586
587 if (!allowsCaching())
588 return;
589
590 if (response().cacheControlContainsNoStore() && url().protocolIs("https")) {
591 // RFC2616 14.9.2:
592 // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible"
593 // "... History buffers MAY store such responses as part of their normal operation."
594 // We allow non-secure content to be reused in history, but we do not allow secure content to be reused.
595 memoryCache.remove(*this);
596 }
597 memoryCache.pruneSoon();
598}
599
600void CachedResource::allClientsRemoved()
601{
602 if (isLinkPreload() && m_loader)
603 m_loader->cancelIfNotFinishing();
604}
605
606void CachedResource::destroyDecodedDataIfNeeded()
607{
608 if (!m_decodedSize)
609 return;
610 if (!MemoryCache::singleton().deadDecodedDataDeletionInterval())
611 return;
612 m_decodedDataDeletionTimer.restart();
613}
614
615void CachedResource::decodedDataDeletionTimerFired()
616{
617 destroyDecodedData();
618}
619
620bool CachedResource::deleteIfPossible()
621{
622 if (canDelete()) {
623 LOG(ResourceLoading, "CachedResource %p deleteIfPossible - can delete, in cache %d", this, inCache());
624 if (!inCache()) {
625 InspectorInstrumentation::willDestroyCachedResource(*this);
626 delete this;
627 return true;
628 }
629 if (m_data)
630 m_data->hintMemoryNotNeededSoon();
631 }
632
633 LOG(ResourceLoading, "CachedResource %p deleteIfPossible - can't delete (hasClients %d loader %p preloadCount %u handleCount %u resourceToRevalidate %p proxyResource %p)", this, hasClients(), m_loader.get(), m_preloadCount, m_handleCount, m_resourceToRevalidate, m_proxyResource);
634 return false;
635}
636
637void CachedResource::setDecodedSize(unsigned size)
638{
639 if (size == m_decodedSize)
640 return;
641
642 long long delta = static_cast<long long>(size) - m_decodedSize;
643
644 // The object must be moved to a different queue, since its size has been changed.
645 // Remove before updating m_decodedSize, so we find the resource in the correct LRU list.
646 if (allowsCaching() && inCache())
647 MemoryCache::singleton().removeFromLRUList(*this);
648
649 m_decodedSize = size;
650
651 if (allowsCaching() && inCache()) {
652 auto& memoryCache = MemoryCache::singleton();
653 // Now insert into the new LRU list.
654 memoryCache.insertInLRUList(*this);
655
656 // Insert into or remove from the live decoded list if necessary.
657 // When inserting into the LiveDecodedResourcesList it is possible
658 // that the m_lastDecodedAccessTime is still zero or smaller than
659 // the m_lastDecodedAccessTime of the current list head. This is a
660 // violation of the invariant that the list is to be kept sorted
661 // by access time. The weakening of the invariant does not pose
662 // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209
663 bool inLiveDecodedResourcesList = memoryCache.inLiveDecodedResourcesList(*this);
664 if (m_decodedSize && !inLiveDecodedResourcesList && hasClients())
665 memoryCache.insertInLiveDecodedResourcesList(*this);
666 else if (!m_decodedSize && inLiveDecodedResourcesList)
667 memoryCache.removeFromLiveDecodedResourcesList(*this);
668
669 // Update the cache's size totals.
670 memoryCache.adjustSize(hasClients(), delta);
671 }
672}
673
674void CachedResource::setEncodedSize(unsigned size)
675{
676 if (size == m_encodedSize)
677 return;
678
679 long long delta = static_cast<long long>(size) - m_encodedSize;
680
681 // The object must be moved to a different queue, since its size has been changed.
682 // Remove before updating m_encodedSize, so we find the resource in the correct LRU list.
683 if (allowsCaching() && inCache())
684 MemoryCache::singleton().removeFromLRUList(*this);
685
686 m_encodedSize = size;
687
688 if (allowsCaching() && inCache()) {
689 auto& memoryCache = MemoryCache::singleton();
690 memoryCache.insertInLRUList(*this);
691 memoryCache.adjustSize(hasClients(), delta);
692 }
693}
694
695void CachedResource::didAccessDecodedData(MonotonicTime timeStamp)
696{
697 m_lastDecodedAccessTime = timeStamp;
698
699 if (allowsCaching() && inCache()) {
700 auto& memoryCache = MemoryCache::singleton();
701 if (memoryCache.inLiveDecodedResourcesList(*this)) {
702 memoryCache.removeFromLiveDecodedResourcesList(*this);
703 memoryCache.insertInLiveDecodedResourcesList(*this);
704 }
705 memoryCache.pruneSoon();
706 }
707}
708
709void CachedResource::setResourceToRevalidate(CachedResource* resource)
710{
711 ASSERT(resource);
712 ASSERT(!m_resourceToRevalidate);
713 ASSERT(resource != this);
714 ASSERT(m_handlesToRevalidate.isEmpty());
715 ASSERT(resource->type() == type());
716 ASSERT(!resource->m_proxyResource);
717
718 LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource);
719
720 resource->m_proxyResource = this;
721 m_resourceToRevalidate = resource;
722}
723
724void CachedResource::clearResourceToRevalidate()
725{
726 ASSERT(m_resourceToRevalidate);
727 ASSERT(m_resourceToRevalidate->m_proxyResource == this);
728
729 if (m_switchingClientsToRevalidatedResource)
730 return;
731
732 m_resourceToRevalidate->m_proxyResource = nullptr;
733 m_resourceToRevalidate->deleteIfPossible();
734
735 m_handlesToRevalidate.clear();
736 m_resourceToRevalidate = nullptr;
737 deleteIfPossible();
738}
739
740void CachedResource::switchClientsToRevalidatedResource()
741{
742 ASSERT(m_resourceToRevalidate);
743 ASSERT(m_resourceToRevalidate->inCache());
744 ASSERT(!inCache());
745
746 LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate);
747
748 m_switchingClientsToRevalidatedResource = true;
749 for (auto& handle : m_handlesToRevalidate) {
750 handle->m_resource = m_resourceToRevalidate;
751 m_resourceToRevalidate->registerHandle(handle);
752 --m_handleCount;
753 }
754 ASSERT(!m_handleCount);
755 m_handlesToRevalidate.clear();
756
757 Vector<CachedResourceClient*> clientsToMove;
758 for (auto& entry : m_clients) {
759 CachedResourceClient* client = entry.key;
760 unsigned count = entry.value;
761 while (count) {
762 clientsToMove.append(client);
763 --count;
764 }
765 }
766
767 for (auto& client : clientsToMove)
768 removeClient(*client);
769 ASSERT(m_clients.isEmpty());
770
771 for (auto& client : clientsToMove)
772 m_resourceToRevalidate->addClientToSet(*client);
773 for (auto& client : clientsToMove) {
774 // Calling didAddClient may do anything, including trying to cancel revalidation.
775 // Assert that it didn't succeed.
776 ASSERT(m_resourceToRevalidate);
777 // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore.
778 if (m_resourceToRevalidate->m_clients.contains(client))
779 m_resourceToRevalidate->didAddClient(*client);
780 }
781 m_switchingClientsToRevalidatedResource = false;
782}
783
784void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse)
785{
786 m_responseTimestamp = WallTime::now();
787
788 updateResponseHeadersAfterRevalidation(m_response, validatingResponse);
789}
790
791void CachedResource::registerHandle(CachedResourceHandleBase* h)
792{
793 ++m_handleCount;
794 if (m_resourceToRevalidate)
795 m_handlesToRevalidate.add(h);
796}
797
798void CachedResource::unregisterHandle(CachedResourceHandleBase* h)
799{
800 ASSERT(m_handleCount > 0);
801 --m_handleCount;
802
803 if (m_resourceToRevalidate)
804 m_handlesToRevalidate.remove(h);
805
806 if (!m_handleCount)
807 deleteIfPossible();
808}
809
810bool CachedResource::canUseCacheValidator() const
811{
812 if (m_loading || errorOccurred())
813 return false;
814
815 if (m_response.cacheControlContainsNoStore())
816 return false;
817 return m_response.hasCacheValidatorFields();
818}
819
820CachedResource::RevalidationDecision CachedResource::makeRevalidationDecision(CachePolicy cachePolicy) const
821{
822 switch (cachePolicy) {
823 case CachePolicyHistoryBuffer:
824 return RevalidationDecision::No;
825
826 case CachePolicyReload:
827 return RevalidationDecision::YesDueToCachePolicy;
828
829 case CachePolicyRevalidate:
830 if (m_response.cacheControlContainsImmutable() && m_response.url().protocolIs("https")) {
831 if (isExpired())
832 return RevalidationDecision::YesDueToExpired;
833 return RevalidationDecision::No;
834 }
835 return RevalidationDecision::YesDueToCachePolicy;
836
837 case CachePolicyVerify:
838 if (m_response.cacheControlContainsNoCache())
839 return RevalidationDecision::YesDueToNoCache;
840 // FIXME: Cache-Control:no-store should prevent storing, not reuse.
841 if (m_response.cacheControlContainsNoStore())
842 return RevalidationDecision::YesDueToNoStore;
843
844 if (isExpired())
845 return RevalidationDecision::YesDueToExpired;
846
847 return RevalidationDecision::No;
848 };
849 ASSERT_NOT_REACHED();
850 return RevalidationDecision::No;
851}
852
853bool CachedResource::redirectChainAllowsReuse(ReuseExpiredRedirectionOrNot reuseExpiredRedirection) const
854{
855 return WebCore::redirectChainAllowsReuse(m_redirectChainCacheStatus, reuseExpiredRedirection);
856}
857
858bool CachedResource::varyHeaderValuesMatch(const ResourceRequest& request)
859{
860 if (m_varyingHeaderValues.isEmpty())
861 return true;
862
863 return verifyVaryingRequestHeaders(cookieJar(), m_varyingHeaderValues, request, sessionID());
864}
865
866unsigned CachedResource::overheadSize() const
867{
868 static const int kAverageClientsHashMapSize = 384;
869 return sizeof(CachedResource) + m_response.memoryUsage() + kAverageClientsHashMapSize + m_resourceRequest.url().string().length() * 2;
870}
871
872bool CachedResource::areAllClientsXMLHttpRequests() const
873{
874 if (type() != Type::RawResource)
875 return false;
876
877 for (auto& client : m_clients) {
878 if (!client.key->isXMLHttpRequest())
879 return false;
880 }
881 return true;
882}
883
884void CachedResource::setLoadPriority(const Optional<ResourceLoadPriority>& loadPriority)
885{
886 if (loadPriority)
887 m_loadPriority = loadPriority.value();
888 else
889 m_loadPriority = defaultPriorityForResourceType(type());
890}
891
892inline CachedResource::Callback::Callback(CachedResource& resource, CachedResourceClient& client)
893 : m_resource(resource)
894 , m_client(client)
895 , m_timer(*this, &Callback::timerFired)
896{
897 m_timer.startOneShot(0_s);
898}
899
900inline void CachedResource::Callback::cancel()
901{
902 if (m_timer.isActive())
903 m_timer.stop();
904}
905
906void CachedResource::Callback::timerFired()
907{
908 m_resource.didAddClient(m_client);
909}
910
911#if USE(FOUNDATION) || USE(SOUP)
912
913void CachedResource::tryReplaceEncodedData(SharedBuffer& newBuffer)
914{
915 if (!m_data)
916 return;
917
918 if (!mayTryReplaceEncodedData())
919 return;
920
921 // We have to do the memcmp because we can't tell if the replacement file backed data is for the
922 // same resource or if we made a second request with the same URL which gave us a different
923 // resource. We have seen this happen for cached POST resources.
924 if (m_data->size() != newBuffer.size() || memcmp(m_data->data(), newBuffer.data(), m_data->size()))
925 return;
926
927 m_data->clear();
928 m_data->append(newBuffer);
929 didReplaceSharedBufferContents();
930}
931
932#endif
933
934}
935