Network Cache: Add thread-safe accessors for storage paths
[WebKit-https.git] / Source / WebKit2 / NetworkProcess / cache / NetworkCache.cpp
index b2f3661..52af645 100644 (file)
 #if ENABLE(NETWORK_CACHE)
 
 #include "Logging.h"
-#include "NetworkCacheCoders.h"
 #include "NetworkCacheStatistics.h"
 #include "NetworkCacheStorage.h"
-#include "NetworkResourceLoader.h"
-#include "WebCoreArgumentCoders.h"
-#include <JavaScriptCore/JSONObject.h>
 #include <WebCore/CacheValidation.h>
 #include <WebCore/FileSystem.h>
 #include <WebCore/HTTPHeaderNames.h>
 #include <WebCore/NetworkStorageSession.h>
 #include <WebCore/PlatformCookieJar.h>
+#include <WebCore/ResourceRequest.h>
 #include <WebCore/ResourceResponse.h>
 #include <WebCore/SharedBuffer.h>
 #include <wtf/NeverDestroyed.h>
-#include <wtf/StringHasher.h>
 #include <wtf/text/StringBuilder.h>
 
 #if PLATFORM(COCOA)
@@ -80,11 +76,11 @@ bool Cache::initialize(const String& cachePath, bool enableEfficacyLogging)
     return !!m_storage;
 }
 
-void Cache::setMaximumSize(size_t maximumSize)
+void Cache::setCapacity(size_t maximumSize)
 {
     if (!m_storage)
         return;
-    m_storage->setMaximumSize(maximumSize);
+    m_storage->setCapacity(maximumSize);
 }
 
 static Key makeCacheKey(const WebCore::ResourceRequest& request)
@@ -110,37 +106,21 @@ static String headerValueForVary(const WebCore::ResourceRequest& request, const
     return request.httpHeaderField(headerName);
 }
 
-static Storage::Entry encodeStorageEntry(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response, PassRefPtr<WebCore::SharedBuffer> responseData)
+static Vector<std::pair<String, String>> collectVaryingRequestHeaders(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response)
 {
-    Encoder encoder;
-    encoder << response;
-
     String varyValue = response.httpHeaderField(WebCore::HTTPHeaderName::Vary);
-    bool hasVaryingRequestHeaders = !varyValue.isEmpty();
-
-    encoder << hasVaryingRequestHeaders;
-
-    if (hasVaryingRequestHeaders) {
-        Vector<String> varyingHeaderNames;
-        varyValue.split(',', false, varyingHeaderNames);
-
-        Vector<std::pair<String, String>> varyingRequestHeaders;
-        for (auto& varyHeaderName : varyingHeaderNames) {
-            String headerName = varyHeaderName.stripWhiteSpace();
-            String headerValue = headerValueForVary(request, headerName);
-            varyingRequestHeaders.append(std::make_pair(headerName, headerValue));
-        }
-        encoder << varyingRequestHeaders;
+    if (varyValue.isEmpty())
+        return { };
+    Vector<String> varyingHeaderNames;
+    varyValue.split(',', /*allowEmptyEntries*/ false, varyingHeaderNames);
+    Vector<std::pair<String, String>> varyingRequestHeaders;
+    varyingRequestHeaders.reserveCapacity(varyingHeaderNames.size());
+    for (auto& varyHeaderName : varyingHeaderNames) {
+        String headerName = varyHeaderName.stripWhiteSpace();
+        String headerValue = headerValueForVary(request, headerName);
+        varyingRequestHeaders.append(std::make_pair(headerName, headerValue));
     }
-    encoder.encodeChecksum();
-
-    auto timeStamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
-    Data header(encoder.buffer(), encoder.bufferSize());
-    Data body;
-    if (responseData)
-        body = { reinterpret_cast<const uint8_t*>(responseData->data()), responseData->size() };
-
-    return { makeCacheKey(request), timeStamp, header, body };
+    return varyingRequestHeaders;
 }
 
 static bool verifyVaryingRequestHeaders(const Vector<std::pair<String, String>>& varyingRequestHeaders, const WebCore::ResourceRequest& request)
@@ -170,81 +150,58 @@ static bool cachePolicyAllowsExpired(WebCore::ResourceRequestCachePolicy policy)
     return false;
 }
 
-static std::unique_ptr<Entry> decodeStorageEntry(const Storage::Entry& storageEntry, const WebCore::ResourceRequest& request, CachedEntryReuseFailure& failure)
+static bool responseHasExpired(const WebCore::ResourceResponse& response, std::chrono::system_clock::time_point timestamp, Optional<std::chrono::microseconds> maxStale)
 {
-    Decoder decoder(storageEntry.header.data(), storageEntry.header.size());
+    if (response.cacheControlContainsNoCache())
+        return true;
 
-    WebCore::ResourceResponse cachedResponse;
-    if (!decoder.decode(cachedResponse)) {
-        LOG(NetworkCache, "(NetworkProcess) response decoding failed\n");
-        failure = CachedEntryReuseFailure::Other;
-        return nullptr;
-    }
+    auto age = WebCore::computeCurrentAge(response, timestamp);
+    auto lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(response, timestamp);
 
-    bool hasVaryingRequestHeaders;
-    if (!decoder.decode(hasVaryingRequestHeaders)) {
-        failure = CachedEntryReuseFailure::Other;
-        return nullptr;
-    }
+    auto maximumStaleness = maxStale ? maxStale.value() : 0_ms;
+    bool hasExpired = age - lifetime > maximumStaleness;
 
-    if (hasVaryingRequestHeaders) {
-        Vector<std::pair<String, String>> varyingRequestHeaders;
-        if (!decoder.decode(varyingRequestHeaders)) {
-            failure = CachedEntryReuseFailure::Other;
-            return nullptr;
-        }
+#ifndef LOG_DISABLED
+    if (hasExpired)
+        LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasExpired age=%f lifetime=%f max-stale=%g", age, lifetime, maxStale);
+#endif
 
-        if (!verifyVaryingRequestHeaders(varyingRequestHeaders, request)) {
-            LOG(NetworkCache, "(NetworkProcess) varying header mismatch\n");
-            failure = CachedEntryReuseFailure::VaryingHeaderMismatch;
-            return nullptr;
-        }
-    }
-    if (!decoder.verifyChecksum()) {
-        LOG(NetworkCache, "(NetworkProcess) checksum verification failure\n");
-        failure = CachedEntryReuseFailure::Other;
-        return nullptr;
-    }
+    return hasExpired;
+}
 
-    bool allowExpired = cachePolicyAllowsExpired(request.cachePolicy()) && !cachedResponse.cacheControlContainsMustRevalidate();
-    auto timeStamp = std::chrono::duration_cast<std::chrono::duration<double>>(storageEntry.timeStamp);
-    double age = WebCore::computeCurrentAge(cachedResponse, timeStamp.count());
-    double lifetime = WebCore::computeFreshnessLifetimeForHTTPFamily(cachedResponse, timeStamp.count());
-    bool isExpired = age > lifetime;
-    bool needsRevalidation = (isExpired && !allowExpired) || cachedResponse.cacheControlContainsNoCache();
-
-    if (needsRevalidation) {
-        bool hasValidatorFields = cachedResponse.hasCacheValidatorFields();
-        LOG(NetworkCache, "(NetworkProcess) needsRevalidation hasValidatorFields=%d isExpired=%d age=%f lifetime=%f", isExpired, hasValidatorFields, age, lifetime);
-        if (!hasValidatorFields) {
-            failure = CachedEntryReuseFailure::MissingValidatorFields;
-            return nullptr;
-        }
-    }
+static bool responseNeedsRevalidation(const WebCore::ResourceResponse& response, const WebCore::ResourceRequest& request, std::chrono::system_clock::time_point timestamp)
+{
+    auto requestDirectives = WebCore::parseCacheControlDirectives(request.httpHeaderFields());
+    if (requestDirectives.noCache)
+        return true;
+    // For requests we ignore max-age values other than zero.
+    if (requestDirectives.maxAge && requestDirectives.maxAge.value() == 0_ms)
+        return true;
 
-    auto entry = std::make_unique<Entry>();
-    entry->storageEntry = storageEntry;
-    entry->needsRevalidation = needsRevalidation;
+    return responseHasExpired(response, timestamp, requestDirectives.maxStale);
+}
 
-    cachedResponse.setSource(needsRevalidation ? WebCore::ResourceResponse::Source::DiskCacheAfterValidation : WebCore::ResourceResponse::Source::DiskCache);
-    entry->response = cachedResponse;
+static UseDecision makeUseDecision(const Entry& entry, const WebCore::ResourceRequest& request)
+{
+    if (!verifyVaryingRequestHeaders(entry.varyingRequestHeaders(), request))
+        return UseDecision::NoDueToVaryingHeaderMismatch;
 
-#if ENABLE(SHAREABLE_RESOURCE)
-    RefPtr<SharedMemory> sharedMemory = storageEntry.body.isMap() ? SharedMemory::createFromVMBuffer(const_cast<uint8_t*>(storageEntry.body.data()), storageEntry.body.size()) : nullptr;
-    RefPtr<ShareableResource> shareableResource = sharedMemory ? ShareableResource::create(sharedMemory.release(), 0, storageEntry.body.size()) : nullptr;
+    // We never revalidate in the case of a history navigation.
+    if (cachePolicyAllowsExpired(request.cachePolicy()))
+        return UseDecision::Use;
 
-    if (shareableResource && shareableResource->createHandle(entry->shareableResourceHandle))
-        entry->buffer = entry->shareableResourceHandle.tryWrapInSharedBuffer();
-    else
-#endif
-        entry->buffer = WebCore::SharedBuffer::create(storageEntry.body.data(), storageEntry.body.size());
+    if (!responseNeedsRevalidation(entry.response(), request, entry.timeStamp()))
+        return UseDecision::Use;
 
-    return entry;
+    if (!entry.response().hasCacheValidatorFields())
+        return UseDecision::NoDueToMissingValidatorFields;
+
+    return UseDecision::Validate;
 }
 
-static RetrieveDecision canRetrieve(const WebCore::ResourceRequest& request)
+static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request)
 {
-    // FIXME: Support HEAD and OPTIONS requests.
+    // FIXME: Support HEAD requests.
     if (request.httpMethod() != "GET")
         return RetrieveDecision::NoDueToHTTPMethod;
     // FIXME: We should be able to validate conditional requests using cache.
@@ -256,6 +213,81 @@ static RetrieveDecision canRetrieve(const WebCore::ResourceRequest& request)
     return RetrieveDecision::Yes;
 }
 
+// http://tools.ietf.org/html/rfc7231#page-48
+static bool isStatusCodeCacheableByDefault(int statusCode)
+{
+    switch (statusCode) {
+    case 200: // OK
+    case 203: // Non-Authoritative Information
+    case 204: // No Content
+    case 300: // Multiple Choices
+    case 301: // Moved Permanently
+    case 404: // Not Found
+    case 405: // Method Not Allowed
+    case 410: // Gone
+    case 414: // URI Too Long
+    case 501: // Not Implemented
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool isStatusCodePotentiallyCacheable(int statusCode)
+{
+    switch (statusCode) {
+    case 201: // Created
+    case 202: // Accepted
+    case 205: // Reset Content
+    case 302: // Found
+    case 303: // See Other
+    case 307: // Temporary redirect
+    case 403: // Forbidden
+    case 406: // Not Acceptable
+    case 415: // Unsupported Media Type
+        return true;
+    default:
+        return false;
+    }
+}
+
+static StoreDecision makeStoreDecision(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response)
+{
+    if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP())
+        return StoreDecision::NoDueToProtocol;
+
+    if (originalRequest.httpMethod() != "GET")
+        return StoreDecision::NoDueToHTTPMethod;
+
+    auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields());
+    if (requestDirectives.noStore)
+        return StoreDecision::NoDueToNoStoreRequest;
+
+    if (response.cacheControlContainsNoStore())
+        return StoreDecision::NoDueToNoStoreResponse;
+
+    if (!isStatusCodeCacheableByDefault(response.httpStatusCode())) {
+        // http://tools.ietf.org/html/rfc7234#section-4.3.2
+        bool hasExpirationHeaders = response.expires() || response.cacheControlMaxAge();
+        bool expirationHeadersAllowCaching = isStatusCodePotentiallyCacheable(response.httpStatusCode()) && hasExpirationHeaders;
+        if (!expirationHeadersAllowCaching)
+            return StoreDecision::NoDueToHTTPStatusCode;
+    }
+
+    // Main resource has ResourceLoadPriorityVeryHigh.
+    bool storeUnconditionallyForHistoryNavigation = originalRequest.priority() == WebCore::ResourceLoadPriorityVeryHigh;
+    if (!storeUnconditionallyForHistoryNavigation) {
+        auto now = std::chrono::system_clock::now();
+        bool hasNonZeroLifetime = !response.cacheControlContainsNoCache() && WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms;
+
+        bool possiblyReusable = response.hasCacheValidatorFields() || hasNonZeroLifetime;
+        if (!possiblyReusable)
+            return StoreDecision::NoDueToUnlikelyToReuse;
+    }
+
+    return StoreDecision::Yes;
+}
+
 void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t webPageID, std::function<void (std::unique_ptr<Entry>)> completionHandler)
 {
     ASSERT(isEnabled());
@@ -263,8 +295,11 @@ void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t w
 
     LOG(NetworkCache, "(NetworkProcess) retrieving %s priority %u", originalRequest.url().string().ascii().data(), originalRequest.priority());
 
+    if (m_statistics)
+        m_statistics->recordRetrievalRequest(webPageID);
+
     Key storageKey = makeCacheKey(originalRequest);
-    RetrieveDecision retrieveDecision = canRetrieve(originalRequest);
+    auto retrieveDecision = makeRetrieveDecision(originalRequest);
     if (retrieveDecision != RetrieveDecision::Yes) {
         if (m_statistics)
             m_statistics->recordNotUsingCacheForRequest(webPageID, storageKey, originalRequest, retrieveDecision);
@@ -276,8 +311,8 @@ void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t w
     auto startTime = std::chrono::system_clock::now();
     unsigned priority = originalRequest.priority();
 
-    m_storage->retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime, storageKey, webPageID](std::unique_ptr<Storage::Entry> entry) {
-        if (!entry) {
+    m_storage->retrieve(storageKey, priority, [this, originalRequest, completionHandler, startTime, storageKey, webPageID](std::unique_ptr<Storage::Record> record) {
+        if (!record) {
             LOG(NetworkCache, "(NetworkProcess) not found in storage");
 
             if (m_statistics)
@@ -286,56 +321,32 @@ void Cache::retrieve(const WebCore::ResourceRequest& originalRequest, uint64_t w
             completionHandler(nullptr);
             return false;
         }
-        ASSERT(entry->key == storageKey);
 
-        CachedEntryReuseFailure failure = CachedEntryReuseFailure::None;
-        auto decodedEntry = decodeStorageEntry(*entry, originalRequest, failure);
-        bool success = !!decodedEntry;
-        if (m_statistics)
-            m_statistics->recordRetrievedCachedEntry(webPageID, storageKey, originalRequest, failure);
+        ASSERT(record->key == storageKey);
+
+        auto entry = Entry::decodeStorageRecord(*record);
+
+        auto useDecision = entry ? makeUseDecision(*entry, originalRequest) : UseDecision::NoDueToDecodeFailure;
+        switch (useDecision) {
+        case UseDecision::Use:
+            break;
+        case UseDecision::Validate:
+            entry->setNeedsValidation();
+            break;
+        default:
+            entry = nullptr;
+        };
 
 #if !LOG_DISABLED
         auto elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - startTime).count();
+        LOG(NetworkCache, "(NetworkProcess) retrieve complete useDecision=%d priority=%u time=%lldms", useDecision, originalRequest.priority(), elapsedMS);
 #endif
-        LOG(NetworkCache, "(NetworkProcess) retrieve complete success=%d priority=%u time=%lldms", success, originalRequest.priority(), elapsedMS);
-        completionHandler(WTF::move(decodedEntry));
-        return success;
-    });
-}
-
-static StoreDecision canStore(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response)
-{
-    if (!originalRequest.url().protocolIsInHTTPFamily() || !response.isHTTP()) {
-        LOG(NetworkCache, "(NetworkProcess) not HTTP");
-        return StoreDecision::NoDueToProtocol;
-    }
-    if (originalRequest.httpMethod() != "GET") {
-        LOG(NetworkCache, "(NetworkProcess) method %s", originalRequest.httpMethod().utf8().data());
-        return StoreDecision::NoDueToHTTPMethod;
-    }
-    if (response.isAttachment()) {
-        LOG(NetworkCache, "(NetworkProcess) attachment");
-        return StoreDecision::NoDueToAttachmentResponse;
-    }
+        completionHandler(WTF::move(entry));
 
-    switch (response.httpStatusCode()) {
-    case 200: // OK
-    case 203: // Non-Authoritative Information
-    case 300: // Multiple Choices
-    case 301: // Moved Permanently
-    case 302: // Found
-    case 307: // Temporary Redirect
-    case 410: // Gone
-        if (response.cacheControlContainsNoStore()) {
-            LOG(NetworkCache, "(NetworkProcess) Cache-control:no-store");
-            return StoreDecision::NoDueToNoStoreResponse;
-        }
-        return StoreDecision::Yes;
-    default:
-        LOG(NetworkCache, "(NetworkProcess) status code %d", response.httpStatusCode());
-    }
-
-    return StoreDecision::NoDueToHTTPStatusCode;
+        if (m_statistics)
+            m_statistics->recordRetrievedCachedEntry(webPageID, storageKey, originalRequest, useDecision);
+        return useDecision != UseDecision::NoDueToDecodeFailure;
+    });
 }
 
 void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore::ResourceResponse& response, RefPtr<WebCore::SharedBuffer>&& responseData, std::function<void (MappedBody&)> completionHandler)
@@ -345,9 +356,9 @@ void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore
 
     LOG(NetworkCache, "(NetworkProcess) storing %s, partition %s", originalRequest.url().string().latin1().data(), originalRequest.cachePartition().latin1().data());
 
-    StoreDecision storeDecision = canStore(originalRequest, response);
+    StoreDecision storeDecision = makeStoreDecision(originalRequest, response);
     if (storeDecision != StoreDecision::Yes) {
-        LOG(NetworkCache, "(NetworkProcess) didn't store");
+        LOG(NetworkCache, "(NetworkProcess) didn't store, storeDecision=%d", storeDecision);
         if (m_statistics) {
             auto key = makeCacheKey(originalRequest);
             m_statistics->recordNotCachingResponse(key, storeDecision);
@@ -355,126 +366,118 @@ void Cache::store(const WebCore::ResourceRequest& originalRequest, const WebCore
         return;
     }
 
-    auto storageEntry = encodeStorageEntry(originalRequest, response, WTF::move(responseData));
+    Entry cacheEntry(makeCacheKey(originalRequest), response, WTF::move(responseData), collectVaryingRequestHeaders(originalRequest, response));
+
+    auto record = cacheEntry.encodeAsStorageRecord();
 
-    m_storage->store(storageEntry, [completionHandler](bool success, const Data& bodyData) {
+    m_storage->store(record, [completionHandler](const Data& bodyData) {
         MappedBody mappedBody;
 #if ENABLE(SHAREABLE_RESOURCE)
         if (bodyData.isMap()) {
-            RefPtr<SharedMemory> sharedMemory = SharedMemory::createFromVMBuffer(const_cast<uint8_t*>(bodyData.data()), bodyData.size());
+            RefPtr<SharedMemory> sharedMemory = SharedMemory::create(const_cast<uint8_t*>(bodyData.data()), bodyData.size(), SharedMemory::Protection::ReadOnly);
             mappedBody.shareableResource = sharedMemory ? ShareableResource::create(WTF::move(sharedMemory), 0, bodyData.size()) : nullptr;
             if (mappedBody.shareableResource)
                 mappedBody.shareableResource->createHandle(mappedBody.shareableResourceHandle);
         }
 #endif
         completionHandler(mappedBody);
-        LOG(NetworkCache, "(NetworkProcess) store success=%d", success);
+        LOG(NetworkCache, "(NetworkProcess) stored");
     });
 }
 
-void Cache::update(const WebCore::ResourceRequest& originalRequest, const Entry& entry, const WebCore::ResourceResponse& validatingResponse)
+void Cache::update(const WebCore::ResourceRequest& originalRequest, const Entry& existingEntry, const WebCore::ResourceResponse& validatingResponse)
 {
     LOG(NetworkCache, "(NetworkProcess) updating %s", originalRequest.url().string().latin1().data());
 
-    WebCore::ResourceResponse response = entry.response;
+    WebCore::ResourceResponse response = existingEntry.response();
     WebCore::updateResponseHeadersAfterRevalidation(response, validatingResponse);
 
-    auto updateEntry = encodeStorageEntry(originalRequest, response, entry.buffer);
+    Entry updateEntry(existingEntry.key(), response, existingEntry.buffer(), collectVaryingRequestHeaders(originalRequest, response));
 
-    m_storage->update(updateEntry, entry.storageEntry, [](bool success, const Data&) {
-        LOG(NetworkCache, "(NetworkProcess) updated, success=%d", success);
-    });
+    auto updateRecord = updateEntry.encodeAsStorageRecord();
+
+    m_storage->store(updateRecord, { });
 }
 
-void Cache::remove(const Entry& entry)
+void Cache::remove(const Key& key)
 {
     ASSERT(isEnabled());
 
-    m_storage->remove(entry.storageEntry.key);
+    m_storage->remove(key);
 }
 
 void Cache::traverse(std::function<void (const Entry*)>&& traverseHandler)
 {
     ASSERT(isEnabled());
 
-    m_storage->traverse([traverseHandler](const Storage::Entry* entry) {
-        if (!entry) {
+    m_storage->traverse(0, [traverseHandler](const Storage::Record* record, const Storage::RecordInfo&) {
+        if (!record) {
             traverseHandler(nullptr);
             return;
         }
 
-        Entry cacheEntry;
-        cacheEntry.storageEntry = *entry;
-
-        Decoder decoder(cacheEntry.storageEntry.header.data(), cacheEntry.storageEntry.header.size());
-        if (!decoder.decode(cacheEntry.response))
+        auto entry = Entry::decodeStorageRecord(*record);
+        if (!entry)
             return;
 
-        traverseHandler(&cacheEntry);
+        traverseHandler(entry.get());
     });
 }
 
 String Cache::dumpFilePath() const
 {
-    return WebCore::pathByAppendingComponent(m_storage->baseDirectoryPath(), "dump.json");
-}
-
-static bool entryAsJSON(StringBuilder& json, const Storage::Entry& entry)
-{
-    Decoder decoder(entry.header.data(), entry.header.size());
-    WebCore::ResourceResponse cachedResponse;
-    if (!decoder.decode(cachedResponse))
-        return false;
-    json.append("{\n");
-    json.append("\"hash\": ");
-    JSC::appendQuotedJSONStringToBuilder(json, entry.key.hashAsString());
-    json.append(",\n");
-    json.append("\"partition\": ");
-    JSC::appendQuotedJSONStringToBuilder(json, entry.key.partition());
-    json.append(",\n");
-    json.append("\"timestamp\": ");
-    json.appendNumber(entry.timeStamp.count());
-    json.append(",\n");
-    json.append("\"URL\": ");
-    JSC::appendQuotedJSONStringToBuilder(json, cachedResponse.url().string());
-    json.append(",\n");
-    json.append("\"headers\": {\n");
-    bool firstHeader = true;
-    for (auto& header : cachedResponse.httpHeaderFields()) {
-        if (!firstHeader)
-            json.append(",\n");
-        firstHeader = false;
-        json.append("    ");
-        JSC::appendQuotedJSONStringToBuilder(json, header.key);
-        json.append(": ");
-        JSC::appendQuotedJSONStringToBuilder(json, header.value);
-    }
-    json.append("\n}\n");
-    json.append("}");
-    return true;
+    return WebCore::pathByAppendingComponent(m_storage->versionPath(), "dump.json");
 }
 
 void Cache::dumpContentsToFile()
 {
     if (!m_storage)
         return;
-    auto dumpFileHandle = WebCore::openFile(dumpFilePath(), WebCore::OpenForWrite);
-    if (!dumpFileHandle)
+    auto fd = WebCore::openFile(dumpFilePath(), WebCore::OpenForWrite);
+    if (!fd)
         return;
-    WebCore::writeToFile(dumpFileHandle, "[\n", 2);
-    m_storage->traverse([dumpFileHandle](const Storage::Entry* entry) {
-        if (!entry) {
-            WebCore::writeToFile(dumpFileHandle, "{}\n]\n", 5);
-            auto handle = dumpFileHandle;
-            WebCore::closeFile(handle);
+    auto prologue = String("{\n\"entries\": [\n").utf8();
+    WebCore::writeToFile(fd, prologue.data(), prologue.length());
+
+    struct Totals {
+        unsigned count { 0 };
+        double worth { 0 };
+        size_t bodySize { 0 };
+    };
+    Totals totals;
+    auto flags = Storage::TraverseFlag::ComputeWorth | Storage::TraverseFlag::ShareCount;
+    m_storage->traverse(flags, [fd, totals](const Storage::Record* record, const Storage::RecordInfo& info) mutable {
+        if (!record) {
+            StringBuilder epilogue;
+            epilogue.appendLiteral("{}\n],\n");
+            epilogue.appendLiteral("\"totals\": {\n");
+            epilogue.appendLiteral("\"count\": ");
+            epilogue.appendNumber(totals.count);
+            epilogue.appendLiteral(",\n");
+            epilogue.appendLiteral("\"bodySize\": ");
+            epilogue.appendNumber(totals.bodySize);
+            epilogue.appendLiteral(",\n");
+            epilogue.appendLiteral("\"averageWorth\": ");
+            epilogue.appendNumber(totals.count ? totals.worth / totals.count : 0);
+            epilogue.appendLiteral("\n");
+            epilogue.appendLiteral("}\n}\n");
+            auto writeData = epilogue.toString().utf8();
+            WebCore::writeToFile(fd, writeData.data(), writeData.length());
+            WebCore::closeFile(fd);
             return;
         }
-        StringBuilder json;
-        if (!entryAsJSON(json, *entry))
+        auto entry = Entry::decodeStorageRecord(*record);
+        if (!entry)
             return;
-        json.append(",\n");
+        ++totals.count;
+        totals.worth += info.worth;
+        totals.bodySize += info.bodySize;
+
+        StringBuilder json;
+        entry->asJSON(json, info);
+        json.appendLiteral(",\n");
         auto writeData = json.toString().utf8();
-        WebCore::writeToFile(dumpFileHandle, writeData.data(), writeData.length());
+        WebCore::writeToFile(fd, writeData.data(), writeData.length());
     });
 }
 
@@ -496,7 +499,7 @@ void Cache::clear()
 
 String Cache::storagePath() const
 {
-    return m_storage ? m_storage->directoryPath() : String();
+    return m_storage ? m_storage->versionPath() : String();
 }
 
 }