a69255295813ad48d7c1fdad827acc8f10bba664
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / NetworkCache.cpp
1 /*
2  * Copyright (C) 2014-2017 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkCache.h"
28
29 #include "Logging.h"
30 #include "NetworkCacheSpeculativeLoadManager.h"
31 #include "NetworkCacheStatistics.h"
32 #include "NetworkCacheStorage.h"
33 #include <WebCore/CacheValidation.h>
34 #include <WebCore/FileSystem.h>
35 #include <WebCore/HTTPHeaderNames.h>
36 #include <WebCore/LowPowerModeNotifier.h>
37 #include <WebCore/NetworkStorageSession.h>
38 #include <WebCore/PlatformCookieJar.h>
39 #include <WebCore/ResourceRequest.h>
40 #include <WebCore/ResourceResponse.h>
41 #include <WebCore/SharedBuffer.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/RunLoop.h>
45 #include <wtf/text/StringBuilder.h>
46
47 #if PLATFORM(COCOA)
48 #include <notify.h>
49 #endif
50
51 using namespace WebCore::FileSystem;
52
53 namespace WebKit {
54 namespace NetworkCache {
55
56 static const AtomicString& resourceType()
57 {
58     ASSERT(WTF::RunLoop::isMain());
59     static NeverDestroyed<const AtomicString> resource("Resource", AtomicString::ConstructFromLiteral);
60     return resource;
61 }
62
63 RefPtr<Cache> Cache::open(const String& cachePath, OptionSet<Option> options)
64 {
65     auto storage = Storage::open(cachePath, options.contains(Option::TestingMode) ? Storage::Mode::Testing : Storage::Mode::Normal);
66
67     LOG(NetworkCache, "(NetworkProcess) opened cache storage, success %d", !!storage);
68
69     if (!storage)
70         return nullptr;
71
72     return adoptRef(*new Cache(storage.releaseNonNull(), options));
73 }
74
75 #if PLATFORM(GTK)
76 static void dumpFileChanged(Cache* cache)
77 {
78     cache->dumpContentsToFile();
79 }
80 #endif
81
82 Cache::Cache(Ref<Storage>&& storage, OptionSet<Option> options)
83     : m_storage(WTFMove(storage))
84 {
85 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
86     if (options.contains(Option::SpeculativeRevalidation)) {
87         m_lowPowerModeNotifier = std::make_unique<WebCore::LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) {
88             ASSERT(WTF::RunLoop::isMain());
89             if (isLowPowerModeEnabled)
90                 m_speculativeLoadManager = nullptr;
91             else {
92                 ASSERT(!m_speculativeLoadManager);
93                 m_speculativeLoadManager = std::make_unique<SpeculativeLoadManager>(*this, m_storage.get());
94             }
95         });
96         if (!m_lowPowerModeNotifier->isLowPowerModeEnabled())
97             m_speculativeLoadManager = std::make_unique<SpeculativeLoadManager>(*this, m_storage.get());
98     }
99 #endif
100
101     if (options.contains(Option::EfficacyLogging))
102         m_statistics = Statistics::open(*this, m_storage->basePath());
103
104     if (options.contains(Option::RegisterNotify)) {
105 #if PLATFORM(COCOA)
106         // Triggers with "notifyutil -p com.apple.WebKit.Cache.dump".
107         int token;
108         notify_register_dispatch("com.apple.WebKit.Cache.dump", &token, dispatch_get_main_queue(), ^(int) {
109             dumpContentsToFile();
110         });
111 #endif
112 #if PLATFORM(GTK)
113         // Triggers with "touch $cachePath/dump".
114         CString dumpFilePath = fileSystemRepresentation(pathByAppendingComponent(m_storage->basePath(), "dump"));
115         GRefPtr<GFile> dumpFile = adoptGRef(g_file_new_for_path(dumpFilePath.data()));
116         GFileMonitor* monitor = g_file_monitor_file(dumpFile.get(), G_FILE_MONITOR_NONE, nullptr, nullptr);
117         g_signal_connect_swapped(monitor, "changed", G_CALLBACK(dumpFileChanged), this);
118 #endif
119     }
120 }
121
122 Cache::~Cache()
123 {
124 }
125
126 void Cache::setCapacity(size_t maximumSize)
127 {
128     m_storage->setCapacity(maximumSize);
129 }
130
131 Key Cache::makeCacheKey(const WebCore::ResourceRequest& request)
132 {
133     // FIXME: This implements minimal Range header disk cache support. We don't parse
134     // ranges so only the same exact range request will be served from the cache.
135     String range = request.httpHeaderField(WebCore::HTTPHeaderName::Range);
136     return { request.cachePartition(), resourceType(), range, request.url().string(), m_storage->salt() };
137 }
138
139 static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
140 {
141     switch (policy) {
142     case WebCore::ReturnCacheDataElseLoad:
143     case WebCore::ReturnCacheDataDontLoad:
144         return true;
145     case WebCore::UseProtocolCachePolicy:
146     case WebCore::ReloadIgnoringCacheData:
147     case WebCore::RefreshAnyCacheData:
148         return false;
149     case WebCore::DoNotUseAnyCache:
150         ASSERT_NOT_REACHED();
151         return false;
152     }
153     return false;
154 }
155
156 static bool responseHasExpired(const WebCore::ResourceResponse& response, WallTime timestamp, std::optional<Seconds> maxStale)
157 {
158     if (response.cacheControlContainsNoCache())
159         return true;
160
161     auto age = WebCore::computeCurrentAge(response, timestamp);
162     auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
163
164     auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;
165     bool hasExpired = age - lifetime > maximumStaleness;
166
167 #ifndef LOG_DISABLED
168     if (hasExpired)
169         LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
170 #endif
171
172     return hasExpired;
173 }
174
175 static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, WallTime timestamp)
176 {
177     auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
178     if (requestDirectives.noCache)
179         return true;
180     // For requests we ignore max-age values other than zero.
181     if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0_ms)
182         return true;
183
184     return responseHasExpired(response, timestamp, requestDirectives.maxStale);
185 }
186
187 static UseDecision makeUseDecision(const Entry& entry, const WebCore::ResourceRequest& request)
188 {
189     // The request is conditional so we force revalidation from the network. We merely check the disk cache
190     // so we can update the cache entry.
191     if (request.isConditional() && !entry.redirectRequest())
192         return UseDecision::Validate;
193
194     if (!WebCore::verifyVaryingRequestHeaders(entry.varyingRequestHeaders(), request))
195         return UseDecision::NoDueToVaryingHeaderMismatch;
196
197     // We never revalidate in the case of a history navigation.
198     if (cachePolicyAllowsExpired(request.cachePolicy()))
199         return UseDecision::Use;
200
201     if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
202         return UseDecision::Use;
203
204     if (!entry.response().hasCacheValidatorFields())
205         return UseDecision::NoDueToMissingValidatorFields;
206
207     return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate;
208 }
209
210 static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request)
211 {
212     ASSERT(request.cachePolicy() != WebCore::DoNotUseAnyCache);
213
214     // FIXME: Support HEAD requests.
215     if (request.httpMethod() != "GET")
216         return RetrieveDecision::NoDueToHTTPMethod;
217     if (request.cachePolicy() == WebCore::ReloadIgnoringCacheData && !request.isConditional())
218         return RetrieveDecision::NoDueToReloadIgnoringCache;
219
220     return RetrieveDecision::Yes;
221 }
222
223 static bool isMediaMIMEType(const String& type)
224 {
225     return startsWithLettersIgnoringASCIICase(type, "video/") || startsWithLettersIgnoringASCIICase(type, "audio/");
226 }
227
228 static std::optional<size_t> expectedTotalResourceSizeFromContentRange(const WebCore::ResourceResponse& response)
229 {
230     ASSERT(response.httpStatusCode() == 206);
231
232     auto contentRange = response.httpHeaderField(WebCore::HTTPHeaderName::ContentRange);
233     if (contentRange.isNull())
234         return { };
235
236     if (!contentRange.startsWith("bytes "))
237         return { };
238
239     auto slashPosition = contentRange.find('/');
240     if (slashPosition == notFound)
241         return { };
242
243     auto sizeStringLength = contentRange.length() - slashPosition - 1;
244     if (!sizeStringLength)
245         return { };
246
247     bool isValid;
248     auto size = StringView(contentRange).right(sizeStringLength).toIntStrict(isValid);
249     if (!isValid)
250         return { };
251
252     return size;
253 }
254
255 static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, size_t bodySize)
256 {
257     if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP())
258         return StoreDecision::NoDueToProtocol;
259
260     if (originalRequest.httpMethod() != "GET")
261         return StoreDecision::NoDueToHTTPMethod;
262
263     auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());
264     if (requestDirectives.noStore)
265         return StoreDecision::NoDueToNoStoreRequest;
266
267     if (response.cacheControlContainsNoStore())
268         return StoreDecision::NoDueToNoStoreResponse;
269
270     if (!WebCore::isStatusCodeCacheableByDefault(response.httpStatusCode())) {
271         // http://tools.ietf.org/html/rfc7234#section-4.3.2
272         bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();
273         bool expirationHeadersAllowCaching = WebCore::isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;
274         if (!expirationHeadersAllowCaching)
275             return StoreDecision::NoDueToHTTPStatusCode;
276     }
277
278     bool isMainResource = originalRequest.requester() == WebCore::ResourceRequest::Requester::Main;
279     bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;
280     if (!storeUnconditionallyForHistoryNavigation) {
281         auto now = WallTime::now();
282         bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms;
283
284         bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
285         if (!possiblyReusable)
286             return StoreDecision::NoDueToUnlikelyToReuse;
287     }
288
289     // Streaming media fills the cache quickly and is unlikely to be reused.
290     // FIXME: We should introduce a separate media cache partition that doesn't affect other resources.
291     // FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.
292     auto requester = originalRequest.requester();
293     bool isDefinitelyMedia = requester == WebCore::ResourceRequest::Requester::Media;
294     if (isDefinitelyMedia) {
295         // Allow caching of smaller media files if we know the total size.
296         const size_t maximumCacheableMediaSize = 5 * 1024 * 1024;
297         auto totalSize = response.httpStatusCode() == 206 ? expectedTotalResourceSizeFromContentRange(response) : bodySize;
298         if (!totalSize || *totalSize > maximumCacheableMediaSize)
299             return StoreDecision::NoDueToStreamingMedia;
300     }
301
302     bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType());
303     if (isLikelyStreamingMedia) {
304         // Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).
305         // We have no way of knowing the total media size so disallow caching.
306         return StoreDecision::NoDueToStreamingMedia;
307     }
308
309     return StoreDecision::Yes;
310 }
311
312 void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameID& frameID, RetrieveCompletionHandler&& completionHandler)
313 {
314     ASSERT(request.url().protocolIsInHTTPFamily());
315
316     LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %d", request.url().string().ascii().data(), static_cast<int>(request.priority()));
317
318     if (m_statistics)
319         m_statistics->recordRetrievalRequest(frameID.first);
320
321     Key storageKey = makeCacheKey(request);
322     auto priority = static_cast<unsigned>(request.priority());
323
324     RetrieveInfo info;
325     info.startTime = MonotonicTime::now();
326     info.priority = priority;
327
328 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
329     bool canUseSpeculativeRevalidation = m_speculativeLoadManager && !request.isConditional() && !cachePolicyAllowsExpired(request.cachePolicy());
330     if (canUseSpeculativeRevalidation)
331         m_speculativeLoadManager->registerLoad(frameID, request, storageKey);
332 #endif
333
334     auto retrieveDecision = makeRetrieveDecision(request);
335     if (retrieveDecision != RetrieveDecision::Yes) {
336         if (m_statistics)
337             m_statistics->recordNotUsingCacheForRequest(frameID.first, storageKey, request, retrieveDecision);
338
339         completeRetrieve(WTFMove(completionHandler), nullptr, info);
340         return;
341     }
342
343 #if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
344     if (canUseSpeculativeRevalidation && m_speculativeLoadManager->canRetrieve(storageKey, request, frameID)) {
345         m_speculativeLoadManager->retrieve(storageKey, [request, completionHandler = WTFMove(completionHandler), info = WTFMove(info)](std::unique_ptr<Entry> entry) mutable {
346             info.wasSpeculativeLoad = true;
347             if (entry && WebCore::verifyVaryingRequestHeaders(entry->varyingRequestHeaders(), request))
348                 completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
349             else
350                 completeRetrieve(WTFMove(completionHandler), nullptr, info);
351         });
352         return;
353     }
354 #endif
355
356     m_storage->retrieve(storageKey, priority, [this, protectedThis = makeRef(*this), request, completionHandler = WTFMove(completionHandler), info = WTFMove(info), storageKey, frameID](auto record, auto timings) mutable {
357         info.storageTimings = timings;
358
359         if (!record) {
360             LOG(NetworkCache, "(NetworkProcess) not found in storage");
361
362             if (m_statistics)
363                 m_statistics->recordRetrievalFailure(frameID.first, storageKey, request);
364
365             completeRetrieve(WTFMove(completionHandler), nullptr, info);
366             return false;
367         }
368
369         ASSERT(record->key == storageKey);
370
371         auto entry = Entry::decodeStorageRecord(*record);
372
373         auto useDecision = entry ? makeUseDecision(*entry, request) : UseDecision::NoDueToDecodeFailure;
374         switch (useDecision) {
375         case UseDecision::Use:
376             break;
377         case UseDecision::Validate:
378             entry->setNeedsValidation(true);
379             break;
380         default:
381             entry = nullptr;
382         };
383
384 #if !LOG_DISABLED
385         auto elapsed = MonotonicTime::now() - info.startTime;
386         LOG(NetworkCache, "(NetworkProcess) retrieve complete useDecision=%d priority=%d time=%" PRIi64 "ms", static_cast<int>(useDecision), static_cast<int>(request.priority()), elapsed.millisecondsAs<int64_t>());
387 #endif
388         completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
389
390         if (m_statistics)
391             m_statistics->recordRetrievedCachedEntry(frameID.first, storageKey, request, useDecision);
392         return useDecision != UseDecision::NoDueToDecodeFailure;
393     });
394 }
395
396 void Cache::completeRetrieve(RetrieveCompletionHandler&& handler, std::unique_ptr<Entry> entry, RetrieveInfo& info)
397 {
398     info.completionTime = MonotonicTime::now();
399     handler(WTFMove(entry), info);
400 }
401     
402 std::unique_ptr<Entry> Cache::makeEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData)
403 {
404     return std::make_unique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(request, response));
405 }
406
407 std::unique_ptr<Entry> Cache::makeRedirectEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest)
408 {
409     return std::make_unique<Entry>(makeCacheKey(request), response, redirectRequest, WebCore::collectVaryingRequestHeaders(request, response));
410 }
411
412 std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, Function<void (MappedBody&)>&& completionHandler)
413 {
414     ASSERT(responseData);
415
416     LOG(NetworkCache, "(NetworkProcess) storing %s, partition %s", request.url().string().latin1().data(), makeCacheKey(request).partition().latin1().data());
417
418     StoreDecision storeDecision = makeStoreDecision(request, response, responseData ? responseData->size() : 0);
419     if (storeDecision != StoreDecision::Yes) {
420         LOG(NetworkCache, "(NetworkProcess) didn't store, storeDecision=%d", static_cast<int>(storeDecision));
421         auto key = makeCacheKey(request);
422
423         auto isSuccessfulRevalidation = response.httpStatusCode() == 304;
424         if (!isSuccessfulRevalidation) {
425             // Make sure we don't keep a stale entry in the cache.
426             remove(key);
427         }
428
429         if (m_statistics)
430             m_statistics->recordNotCachingResponse(key, storeDecision);
431
432         return nullptr;
433     }
434
435     auto cacheEntry = makeEntry(request, response, WTFMove(responseData));
436     auto record = cacheEntry->encodeAsStorageRecord();
437
438     m_storage->store(record, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](const Data& bodyData) {
439         MappedBody mappedBody;
440 #if ENABLE(SHAREABLE_RESOURCE)
441         if (auto sharedMemory = bodyData.tryCreateSharedMemory()) {
442             mappedBody.shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, bodyData.size());
443             ASSERT(mappedBody.shareableResource);
444             mappedBody.shareableResource->createHandle(mappedBody.shareableResourceHandle);
445         }
446 #endif
447         completionHandler(mappedBody);
448         LOG(NetworkCache, "(NetworkProcess) stored");
449     });
450
451     return cacheEntry;
452 }
453
454 std::unique_ptr<Entry> Cache::storeRedirect(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest)
455 {
456     LOG(NetworkCache, "(NetworkProcess) storing redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
457
458     StoreDecision storeDecision = makeStoreDecision(request, response, 0);
459     if (storeDecision != StoreDecision::Yes) {
460         LOG(NetworkCache, "(NetworkProcess) didn't store redirect, storeDecision=%d", static_cast<int>(storeDecision));
461         auto key = makeCacheKey(request);
462         if (m_statistics)
463             m_statistics->recordNotCachingResponse(key, storeDecision);
464
465         return nullptr;
466     }
467
468     auto cacheEntry = makeRedirectEntry(request, response, redirectRequest);
469     auto record = cacheEntry->encodeAsStorageRecord();
470
471     m_storage->store(record, nullptr);
472     
473     return cacheEntry;
474 }
475
476 std::unique_ptr<Entry> Cache::update(const WebCore::ResourceRequest& originalRequest, const GlobalFrameID& frameID, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
477 {
478     LOG(NetworkCache, "(NetworkProcess) updating %s", originalRequest.url().string().latin1().data());
479
480     WebCore::ResourceResponse response = existingEntry.response();
481     WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse);
482
483     auto updateEntry = std::make_unique<Entry>(existingEntry.key(), response, existingEntry.buffer(), WebCore::collectVaryingRequestHeaders(originalRequest, response));
484     auto updateRecord = updateEntry->encodeAsStorageRecord();
485
486     m_storage->store(updateRecord, { });
487
488     if (m_statistics)
489         m_statistics->recordRevalidationSuccess(frameID.first, existingEntry.key(), originalRequest);
490
491     return updateEntry;
492 }
493
494 void Cache::remove(const Key& key)
495 {
496     m_storage->remove(key);
497 }
498
499 void Cache::remove(const WebCore::ResourceRequest& request)
500 {
501     remove(makeCacheKey(request));
502 }
503
504 void Cache::remove(const Vector<Key>& keys, Function<void ()>&& completionHandler)
505 {
506     m_storage->remove(keys, WTFMove(completionHandler));
507 }
508
509 void Cache::traverse(Function<void (const TraversalEntry*)>&& traverseHandler)
510 {
511     // Protect against clients making excessive traversal requests.
512     const unsigned maximumTraverseCount = 3;
513     if (m_traverseCount >= maximumTraverseCount) {
514         WTFLogAlways("Maximum parallel cache traverse count exceeded. Ignoring traversal request.");
515
516         RunLoop::main().dispatch([traverseHandler = WTFMove(traverseHandler)] {
517             traverseHandler(nullptr);
518         });
519         return;
520     }
521
522     ++m_traverseCount;
523
524     m_storage->traverse(resourceType(), 0, [this, protectedThis = makeRef(*this), traverseHandler = WTFMove(traverseHandler)](const Storage::Record* record, const Storage::RecordInfo& recordInfo) {
525         if (!record) {
526             --m_traverseCount;
527             traverseHandler(nullptr);
528             return;
529         }
530
531         auto entry = Entry::decodeStorageRecord(*record);
532         if (!entry)
533             return;
534
535         TraversalEntry traversalEntry { *entry, recordInfo };
536         traverseHandler(&traversalEntry);
537     });
538 }
539
540 String Cache::dumpFilePath() const
541 {
542     return pathByAppendingComponent(m_storage->versionPath(), "dump.json");
543 }
544
545 void Cache::dumpContentsToFile()
546 {
547     auto fd = openFile(dumpFilePath(), FileOpenMode::Write);
548     if (!isHandleValid(fd))
549         return;
550     auto prologue = String("{\n\"entries\": [\n").utf8();
551     writeToFile(fd, prologue.data(), prologue.length());
552
553     struct Totals {
554         unsigned count { 0 };
555         double worth { 0 };
556         size_t bodySize { 0 };
557     };
558     Totals totals;
559     auto flags = Storage::TraverseFlag::ComputeWorth | Storage::TraverseFlag::ShareCount;
560     size_t capacity = m_storage->capacity();
561     m_storage->traverse(resourceType(), flags, [fd, totals, capacity](const Storage::Record* record, const Storage::RecordInfo& info) mutable {
562         if (!record) {
563             StringBuilder epilogue;
564             epilogue.appendLiteral("{}\n],\n");
565             epilogue.appendLiteral("\"totals\": {\n");
566             epilogue.appendLiteral("\"capacity\": ");
567             epilogue.appendNumber(capacity);
568             epilogue.appendLiteral(",\n");
569             epilogue.appendLiteral("\"count\": ");
570             epilogue.appendNumber(totals.count);
571             epilogue.appendLiteral(",\n");
572             epilogue.appendLiteral("\"bodySize\": ");
573             epilogue.appendNumber(totals.bodySize);
574             epilogue.appendLiteral(",\n");
575             epilogue.appendLiteral("\"averageWorth\": ");
576             epilogue.appendNumber(totals.count ? totals.worth / totals.count : 0);
577             epilogue.appendLiteral("\n");
578             epilogue.appendLiteral("}\n}\n");
579             auto writeData = epilogue.toString().utf8();
580             writeToFile(fd, writeData.data(), writeData.length());
581             closeFile(fd);
582             return;
583         }
584         auto entry = Entry::decodeStorageRecord(*record);
585         if (!entry)
586             return;
587         ++totals.count;
588         totals.worth += info.worth;
589         totals.bodySize += info.bodySize;
590
591         StringBuilder json;
592         entry->asJSON(json, info);
593         json.appendLiteral(",\n");
594         auto writeData = json.toString().utf8();
595         writeToFile(fd, writeData.data(), writeData.length());
596     });
597 }
598
599 void Cache::deleteDumpFile()
600 {
601     WorkQueue::create("com.apple.WebKit.Cache.delete")->dispatch([path = dumpFilePath().isolatedCopy()] {
602         deleteFile(path);
603     });
604 }
605
606 void Cache::clear(WallTime modifiedSince, Function<void ()>&& completionHandler)
607 {
608     LOG(NetworkCache, "(NetworkProcess) clearing cache");
609
610     if (m_statistics)
611         m_statistics->clear();
612
613     String anyType;
614     m_storage->clear(anyType, modifiedSince, WTFMove(completionHandler));
615
616     deleteDumpFile();
617 }
618
619 void Cache::clear()
620 {
621     clear(-WallTime::infinity(), nullptr);
622 }
623
624 String Cache::recordsPath() const
625 {
626     return m_storage->recordsPath();
627 }
628
629 void Cache::retrieveData(const DataKey& dataKey, Function<void (const uint8_t* data, size_t size)> completionHandler)
630 {
631     Key key { dataKey, m_storage->salt() };
632     m_storage->retrieve(key, 4, [completionHandler = WTFMove(completionHandler)] (auto record, auto) {
633         if (!record || !record->body.size()) {
634             completionHandler(nullptr, 0);
635             return true;
636         }
637         completionHandler(record->body.data(), record->body.size());
638         return true;
639     });
640 }
641
642 void Cache::storeData(const DataKey& dataKey, const uint8_t* data, size_t size)
643 {
644     Key key { dataKey, m_storage->salt() };
645     Storage::Record record { key, WallTime::now(), { }, Data { data, size }, { } };
646     m_storage->store(record, { });
647 }
648
649 }
650 }