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 "NetworkCacheStorage.h"
32#include "NetworkProcess.h"
33#include <WebCore/CacheValidation.h>
34#include <WebCore/HTTPHeaderNames.h>
35#include <WebCore/LowPowerModeNotifier.h>
36#include <WebCore/NetworkStorageSession.h>
37#include <WebCore/ResourceRequest.h>
38#include <WebCore/ResourceResponse.h>
39#include <WebCore/SharedBuffer.h>
40#include <wtf/FileSystem.h>
41#include <wtf/MainThread.h>
42#include <wtf/NeverDestroyed.h>
43#include <wtf/RunLoop.h>
44#include <wtf/text/StringBuilder.h>
45
46#if PLATFORM(COCOA)
47#include <notify.h>
48#endif
49
50namespace WebKit {
51namespace NetworkCache {
52
53using namespace FileSystem;
54
55static const AtomString& resourceType()
56{
57 ASSERT(WTF::RunLoop::isMain());
58 static NeverDestroyed<const AtomString> resource("Resource", AtomString::ConstructFromLiteral);
59 return resource;
60}
61
62RefPtr<Cache> Cache::open(NetworkProcess& networkProcess, const String& cachePath, OptionSet<Option> options)
63{
64 auto storage = Storage::open(cachePath, options.contains(Option::TestingMode) ? Storage::Mode::AvoidRandomness : Storage::Mode::Normal);
65
66 LOG(NetworkCache, "(NetworkProcess) opened cache storage, success %d", !!storage);
67
68 if (!storage)
69 return nullptr;
70
71 return adoptRef(*new Cache(networkProcess, storage.releaseNonNull(), options));
72}
73
74#if PLATFORM(GTK)
75static void dumpFileChanged(Cache* cache)
76{
77 cache->dumpContentsToFile();
78}
79#endif
80
81Cache::Cache(NetworkProcess& networkProcess, Ref<Storage>&& storage, OptionSet<Option> options)
82 : m_storage(WTFMove(storage))
83 , m_networkProcess(networkProcess)
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::RegisterNotify)) {
102#if PLATFORM(COCOA)
103 // Triggers with "notifyutil -p com.apple.WebKit.Cache.dump".
104 int token;
105 notify_register_dispatch("com.apple.WebKit.Cache.dump", &token, dispatch_get_main_queue(), ^(int) {
106 dumpContentsToFile();
107 });
108#endif
109#if PLATFORM(GTK)
110 // Triggers with "touch $cachePath/dump".
111 CString dumpFilePath = fileSystemRepresentation(pathByAppendingComponent(m_storage->basePath(), "dump"));
112 GRefPtr<GFile> dumpFile = adoptGRef(g_file_new_for_path(dumpFilePath.data()));
113 GFileMonitor* monitor = g_file_monitor_file(dumpFile.get(), G_FILE_MONITOR_NONE, nullptr, nullptr);
114 g_signal_connect_swapped(monitor, "changed", G_CALLBACK(dumpFileChanged), this);
115#endif
116 }
117}
118
119Cache::~Cache()
120{
121}
122
123void Cache::setCapacity(size_t maximumSize)
124{
125 m_storage->setCapacity(maximumSize);
126}
127
128Key Cache::makeCacheKey(const WebCore::ResourceRequest& request)
129{
130 // FIXME: This implements minimal Range header disk cache support. We don't parse
131 // ranges so only the same exact range request will be served from the cache.
132 String range = request.httpHeaderField(WebCore::HTTPHeaderName::Range);
133 return { request.cachePartition(), resourceType(), range, request.url().string(), m_storage->salt() };
134}
135
136static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
137{
138 switch (policy) {
139 case WebCore::ResourceRequestCachePolicy::ReturnCacheDataElseLoad:
140 case WebCore::ResourceRequestCachePolicy::ReturnCacheDataDontLoad:
141 return true;
142 case WebCore::ResourceRequestCachePolicy::UseProtocolCachePolicy:
143 case WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData:
144 case WebCore::ResourceRequestCachePolicy::RefreshAnyCacheData:
145 return false;
146 case WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache:
147 ASSERT_NOT_REACHED();
148 return false;
149 }
150 return false;
151}
152
153static bool responseHasExpired(const WebCore::ResourceResponse& response, WallTime timestamp, Optional<Seconds> maxStale)
154{
155 if (response.cacheControlContainsNoCache())
156 return true;
157
158 auto age = WebCore::computeCurrentAge(response, timestamp);
159 auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
160
161 auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;
162 bool hasExpired = age - lifetime > maximumStaleness;
163
164#ifndef LOG_DISABLED
165 if (hasExpired)
166 LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
167#endif
168
169 return hasExpired;
170}
171
172static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, WallTime timestamp)
173{
174 auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
175 if (requestDirectives.noCache)
176 return true;
177 // For requests we ignore max-age values other than zero.
178 if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0_ms)
179 return true;
180
181 return responseHasExpired(response, timestamp, requestDirectives.maxStale);
182}
183
184static UseDecision makeUseDecision(NetworkProcess& networkProcess, const Entry& entry, const WebCore::ResourceRequest& request)
185{
186 // The request is conditional so we force revalidation from the network. We merely check the disk cache
187 // so we can update the cache entry.
188 if (request.isConditional() && !entry.redirectRequest())
189 return UseDecision::Validate;
190
191 if (!WebCore::verifyVaryingRequestHeaders(networkProcess.defaultStorageSession(), entry.varyingRequestHeaders(), request))
192 return UseDecision::NoDueToVaryingHeaderMismatch;
193
194 // We never revalidate in the case of a history navigation.
195 if (cachePolicyAllowsExpired(request.cachePolicy()))
196 return UseDecision::Use;
197
198 if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
199 return UseDecision::Use;
200
201 if (!entry.response().hasCacheValidatorFields())
202 return UseDecision::NoDueToMissingValidatorFields;
203
204 return entry.redirectRequest() ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate;
205}
206
207static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request)
208{
209 ASSERT(request.cachePolicy() != WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache);
210
211 // FIXME: Support HEAD requests.
212 if (request.httpMethod() != "GET")
213 return RetrieveDecision::NoDueToHTTPMethod;
214 if (request.cachePolicy() == WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData && !request.isConditional())
215 return RetrieveDecision::NoDueToReloadIgnoringCache;
216
217 return RetrieveDecision::Yes;
218}
219
220static bool isMediaMIMEType(const String& type)
221{
222 return startsWithLettersIgnoringASCIICase(type, "video/") || startsWithLettersIgnoringASCIICase(type, "audio/");
223}
224
225static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, size_t bodySize)
226{
227 if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP())
228 return StoreDecision::NoDueToProtocol;
229
230 if (originalRequest.httpMethod() != "GET")
231 return StoreDecision::NoDueToHTTPMethod;
232
233 auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());
234 if (requestDirectives.noStore)
235 return StoreDecision::NoDueToNoStoreRequest;
236
237 if (response.cacheControlContainsNoStore())
238 return StoreDecision::NoDueToNoStoreResponse;
239
240 if (!WebCore::isStatusCodeCacheableByDefault(response.httpStatusCode())) {
241 // http://tools.ietf.org/html/rfc7234#section-4.3.2
242 bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();
243 bool expirationHeadersAllowCaching = WebCore::isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;
244 if (!expirationHeadersAllowCaching)
245 return StoreDecision::NoDueToHTTPStatusCode;
246 }
247
248 bool isMainResource = originalRequest.requester() == WebCore::ResourceRequest::Requester::Main;
249 bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority() == WebCore::ResourceLoadPriority::VeryHigh;
250 if (!storeUnconditionallyForHistoryNavigation) {
251 auto now = WallTime::now();
252 bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms;
253
254 bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
255 if (!possiblyReusable)
256 return StoreDecision::NoDueToUnlikelyToReuse;
257 }
258
259 // Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).
260 // Streaming media fills the cache quickly and is unlikely to be reused.
261 // FIXME: We should introduce a separate media cache partition that doesn't affect other resources.
262 // FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.
263 auto requester = originalRequest.requester();
264 bool isDefinitelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::Media;
265 bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType());
266 if (isLikelyStreamingMedia || isDefinitelyStreamingMedia)
267 return StoreDecision::NoDueToStreamingMedia;
268
269 return StoreDecision::Yes;
270}
271
272void Cache::retrieve(const WebCore::ResourceRequest& request, const GlobalFrameID& frameID, RetrieveCompletionHandler&& completionHandler)
273{
274 ASSERT(request.url().protocolIsInHTTPFamily());
275
276 LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %d", request.url().string().ascii().data(), static_cast<int>(request.priority()));
277
278 Key storageKey = makeCacheKey(request);
279 auto priority = static_cast<unsigned>(request.priority());
280
281 RetrieveInfo info;
282 info.startTime = MonotonicTime::now();
283 info.priority = priority;
284
285#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
286 bool canUseSpeculativeRevalidation = m_speculativeLoadManager && !request.isConditional() && !cachePolicyAllowsExpired(request.cachePolicy());
287 if (canUseSpeculativeRevalidation)
288 m_speculativeLoadManager->registerLoad(frameID, request, storageKey);
289#endif
290
291 auto retrieveDecision = makeRetrieveDecision(request);
292 if (retrieveDecision != RetrieveDecision::Yes) {
293 completeRetrieve(WTFMove(completionHandler), nullptr, info);
294 return;
295 }
296
297#if ENABLE(NETWORK_CACHE_SPECULATIVE_REVALIDATION)
298 if (canUseSpeculativeRevalidation && m_speculativeLoadManager->canRetrieve(storageKey, request, frameID)) {
299 m_speculativeLoadManager->retrieve(storageKey, [networkProcess = makeRef(networkProcess()), request, completionHandler = WTFMove(completionHandler), info = WTFMove(info)](std::unique_ptr<Entry> entry) mutable {
300 info.wasSpeculativeLoad = true;
301 if (entry && WebCore::verifyVaryingRequestHeaders(networkProcess->defaultStorageSession(), entry->varyingRequestHeaders(), request))
302 completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
303 else
304 completeRetrieve(WTFMove(completionHandler), nullptr, info);
305 });
306 return;
307 }
308#endif
309
310 m_storage->retrieve(storageKey, priority, [request, completionHandler = WTFMove(completionHandler), info = WTFMove(info), storageKey, networkProcess = makeRef(networkProcess())](auto record, auto timings) mutable {
311 info.storageTimings = timings;
312
313 if (!record) {
314 LOG(NetworkCache, "(NetworkProcess) not found in storage");
315
316 completeRetrieve(WTFMove(completionHandler), nullptr, info);
317 return false;
318 }
319
320 ASSERT(record->key == storageKey);
321
322 auto entry = Entry::decodeStorageRecord(*record);
323
324 auto useDecision = entry ? makeUseDecision(networkProcess, *entry, request) : UseDecision::NoDueToDecodeFailure;
325 switch (useDecision) {
326 case UseDecision::Use:
327 break;
328 case UseDecision::Validate:
329 entry->setNeedsValidation(true);
330 break;
331 default:
332 entry = nullptr;
333 };
334
335#if !LOG_DISABLED
336 auto elapsed = MonotonicTime::now() - info.startTime;
337 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>());
338#endif
339 completeRetrieve(WTFMove(completionHandler), WTFMove(entry), info);
340
341 return useDecision != UseDecision::NoDueToDecodeFailure;
342 });
343}
344
345void Cache::completeRetrieve(RetrieveCompletionHandler&& handler, std::unique_ptr<Entry> entry, RetrieveInfo& info)
346{
347 info.completionTime = MonotonicTime::now();
348 handler(WTFMove(entry), info);
349}
350
351std::unique_ptr<Entry> Cache::makeEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData)
352{
353 return std::make_unique<Entry>(makeCacheKey(request), response, WTFMove(responseData), WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), request, response));
354}
355
356std::unique_ptr<Entry> Cache::makeRedirectEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest)
357{
358 return std::make_unique<Entry>(makeCacheKey(request), response, redirectRequest, WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), request, response));
359}
360
361std::unique_ptr<Entry> Cache::store(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, Function<void(MappedBody&)>&& completionHandler)
362{
363 ASSERT(responseData);
364
365 LOG(NetworkCache, "(NetworkProcess) storing %s, partition %s", request.url().string().latin1().data(), makeCacheKey(request).partition().latin1().data());
366
367 StoreDecision storeDecision = makeStoreDecision(request, response, responseData ? responseData->size() : 0);
368 if (storeDecision != StoreDecision::Yes) {
369 LOG(NetworkCache, "(NetworkProcess) didn't store, storeDecision=%d", static_cast<int>(storeDecision));
370 auto key = makeCacheKey(request);
371
372 auto isSuccessfulRevalidation = response.httpStatusCode() == 304;
373 if (!isSuccessfulRevalidation) {
374 // Make sure we don't keep a stale entry in the cache.
375 remove(key);
376 }
377
378 return nullptr;
379 }
380
381 auto cacheEntry = makeEntry(request, response, WTFMove(responseData));
382 auto record = cacheEntry->encodeAsStorageRecord();
383
384 m_storage->store(record, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)](const Data& bodyData) mutable {
385 MappedBody mappedBody;
386#if ENABLE(SHAREABLE_RESOURCE)
387 if (auto sharedMemory = bodyData.tryCreateSharedMemory()) {
388 mappedBody.shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, bodyData.size());
389 ASSERT(mappedBody.shareableResource);
390 mappedBody.shareableResource->createHandle(mappedBody.shareableResourceHandle);
391 }
392#endif
393 completionHandler(mappedBody);
394 LOG(NetworkCache, "(NetworkProcess) stored");
395 });
396
397 return cacheEntry;
398}
399
400std::unique_ptr<Entry> Cache::storeRedirect(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& redirectRequest, Optional<Seconds> maxAgeCap)
401{
402 LOG(NetworkCache, "(NetworkProcess) storing redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
403
404 StoreDecision storeDecision = makeStoreDecision(request, response, 0);
405 if (storeDecision != StoreDecision::Yes) {
406 LOG(NetworkCache, "(NetworkProcess) didn't store redirect, storeDecision=%d", static_cast<int>(storeDecision));
407 return nullptr;
408 }
409
410 auto cacheEntry = makeRedirectEntry(request, response, redirectRequest);
411
412#if ENABLE(RESOURCE_LOAD_STATISTICS)
413 if (maxAgeCap) {
414 LOG(NetworkCache, "(NetworkProcess) capping max age for redirect %s -> %s", request.url().string().latin1().data(), redirectRequest.url().string().latin1().data());
415 cacheEntry->capMaxAge(maxAgeCap.value());
416 }
417#else
418 UNUSED_PARAM(maxAgeCap);
419#endif
420
421 auto record = cacheEntry->encodeAsStorageRecord();
422
423 m_storage->store(record, nullptr);
424
425 return cacheEntry;
426}
427
428std::unique_ptr<Entry> Cache::update(const WebCore::ResourceRequest& originalRequest, const GlobalFrameID& frameID, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
429{
430 LOG(NetworkCache, "(NetworkProcess) updating %s", originalRequest.url().string().latin1().data());
431
432 WebCore::ResourceResponse response = existingEntry.response();
433 WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse);
434
435 auto updateEntry = std::make_unique<Entry>(existingEntry.key(), response, existingEntry.buffer(), WebCore::collectVaryingRequestHeaders(networkProcess().defaultStorageSession(), originalRequest, response));
436 auto updateRecord = updateEntry->encodeAsStorageRecord();
437
438 m_storage->store(updateRecord, { });
439
440 return updateEntry;
441}
442
443void Cache::remove(const Key& key)
444{
445 m_storage->remove(key);
446}
447
448void Cache::remove(const WebCore::ResourceRequest& request)
449{
450 remove(makeCacheKey(request));
451}
452
453void Cache::remove(const Vector<Key>& keys, Function<void()>&& completionHandler)
454{
455 m_storage->remove(keys, WTFMove(completionHandler));
456}
457
458void Cache::traverse(Function<void(const TraversalEntry*)>&& traverseHandler)
459{
460 // Protect against clients making excessive traversal requests.
461 const unsigned maximumTraverseCount = 3;
462 if (m_traverseCount >= maximumTraverseCount) {
463 WTFLogAlways("Maximum parallel cache traverse count exceeded. Ignoring traversal request.");
464
465 RunLoop::main().dispatch([traverseHandler = WTFMove(traverseHandler)] () mutable {
466 traverseHandler(nullptr);
467 });
468 return;
469 }
470
471 ++m_traverseCount;
472
473 m_storage->traverse(resourceType(), { }, [this, protectedThis = makeRef(*this), traverseHandler = WTFMove(traverseHandler)] (const Storage::Record* record, const Storage::RecordInfo& recordInfo) mutable {
474 if (!record) {
475 --m_traverseCount;
476 traverseHandler(nullptr);
477 return;
478 }
479
480 auto entry = Entry::decodeStorageRecord(*record);
481 if (!entry)
482 return;
483
484 TraversalEntry traversalEntry { *entry, recordInfo };
485 traverseHandler(&traversalEntry);
486 });
487}
488
489String Cache::dumpFilePath() const
490{
491 return pathByAppendingComponent(m_storage->versionPath(), "dump.json");
492}
493
494void Cache::dumpContentsToFile()
495{
496 auto fd = openFile(dumpFilePath(), FileOpenMode::Write);
497 if (!isHandleValid(fd))
498 return;
499 auto prologue = String("{\n\"entries\": [\n").utf8();
500 writeToFile(fd, prologue.data(), prologue.length());
501
502 struct Totals {
503 unsigned count { 0 };
504 double worth { 0 };
505 size_t bodySize { 0 };
506 };
507 Totals totals;
508 auto flags = { Storage::TraverseFlag::ComputeWorth, Storage::TraverseFlag::ShareCount };
509 size_t capacity = m_storage->capacity();
510 m_storage->traverse(resourceType(), flags, [fd, totals, capacity](const Storage::Record* record, const Storage::RecordInfo& info) mutable {
511 if (!record) {
512 StringBuilder epilogue;
513 epilogue.appendLiteral("{}\n],\n");
514 epilogue.appendLiteral("\"totals\": {\n");
515 epilogue.appendLiteral("\"capacity\": ");
516 epilogue.appendNumber(capacity);
517 epilogue.appendLiteral(",\n");
518 epilogue.appendLiteral("\"count\": ");
519 epilogue.appendNumber(totals.count);
520 epilogue.appendLiteral(",\n");
521 epilogue.appendLiteral("\"bodySize\": ");
522 epilogue.appendNumber(totals.bodySize);
523 epilogue.appendLiteral(",\n");
524 epilogue.appendLiteral("\"averageWorth\": ");
525 epilogue.appendFixedPrecisionNumber(totals.count ? totals.worth / totals.count : 0);
526 epilogue.appendLiteral("\n");
527 epilogue.appendLiteral("}\n}\n");
528 auto writeData = epilogue.toString().utf8();
529 writeToFile(fd, writeData.data(), writeData.length());
530 closeFile(fd);
531 return;
532 }
533 auto entry = Entry::decodeStorageRecord(*record);
534 if (!entry)
535 return;
536 ++totals.count;
537 totals.worth += info.worth;
538 totals.bodySize += info.bodySize;
539
540 StringBuilder json;
541 entry->asJSON(json, info);
542 json.appendLiteral(",\n");
543 auto writeData = json.toString().utf8();
544 writeToFile(fd, writeData.data(), writeData.length());
545 });
546}
547
548void Cache::deleteDumpFile()
549{
550 WorkQueue::create("com.apple.WebKit.Cache.delete")->dispatch([path = dumpFilePath().isolatedCopy()] {
551 deleteFile(path);
552 });
553}
554
555void Cache::clear(WallTime modifiedSince, Function<void()>&& completionHandler)
556{
557 LOG(NetworkCache, "(NetworkProcess) clearing cache");
558
559 String anyType;
560 m_storage->clear(anyType, modifiedSince, WTFMove(completionHandler));
561
562 deleteDumpFile();
563}
564
565void Cache::clear()
566{
567 clear(-WallTime::infinity(), nullptr);
568}
569
570String Cache::recordsPath() const
571{
572 return m_storage->recordsPath();
573}
574
575void Cache::retrieveData(const DataKey& dataKey, Function<void(const uint8_t*, size_t)> completionHandler)
576{
577 Key key { dataKey, m_storage->salt() };
578 m_storage->retrieve(key, 4, [completionHandler = WTFMove(completionHandler)] (auto record, auto) mutable {
579 if (!record || !record->body.size()) {
580 completionHandler(nullptr, 0);
581 return true;
582 }
583 completionHandler(record->body.data(), record->body.size());
584 return true;
585 });
586}
587
588void Cache::storeData(const DataKey& dataKey, const uint8_t* data, size_t size)
589{
590 Key key { dataKey, m_storage->salt() };
591 Storage::Record record { key, WallTime::now(), { }, Data { data, size }, { } };
592 m_storage->store(record, { });
593}
594
595}
596}
597