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