Implement Cache API partitioning based on ClientOrigin
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / CacheStorageEngineCaches.cpp
1 /*
2  * Copyright (C) 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CacheStorageEngine.h"
28
29 #include "NetworkCacheCoders.h"
30 #include "NetworkCacheIOChannel.h"
31 #include <WebCore/SecurityOrigin.h>
32 #include <wtf/RunLoop.h>
33 #include <wtf/UUID.h>
34 #include <wtf/text/StringBuilder.h>
35
36 using namespace WebCore::DOMCacheEngine;
37 using namespace WebKit::NetworkCache;
38
39 namespace WebKit {
40
41 namespace CacheStorage {
42
43 static inline String cachesListFilename(const String& cachesRootPath)
44 {
45     return WebCore::FileSystem::pathByAppendingComponent(cachesRootPath, ASCIILiteral("cacheslist"));
46 }
47
48 static inline String cachesOriginFilename(const String& cachesRootPath)
49 {
50     return WebCore::FileSystem::pathByAppendingComponent(cachesRootPath, ASCIILiteral("origin"));
51 }
52
53 void Caches::retrieveOriginFromDirectory(const String& folderPath, WorkQueue& queue, WTF::CompletionHandler<void(std::optional<WebCore::ClientOrigin>&&)>&& completionHandler)
54 {
55     queue.dispatch([completionHandler = WTFMove(completionHandler), folderPath = folderPath.isolatedCopy()]() mutable {
56         if (!WebCore::FileSystem::fileExists(cachesListFilename(folderPath))) {
57             RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
58                 completionHandler(std::nullopt);
59             });
60             return;
61         }
62
63         auto channel = IOChannel::open(cachesOriginFilename(folderPath), IOChannel::Type::Read);
64         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [completionHandler = WTFMove(completionHandler)](const Data& data, int error) mutable {
65             ASSERT(RunLoop::isMain());
66             if (error) {
67                 completionHandler(std::nullopt);
68                 return;
69             }
70             completionHandler(readOrigin(data));
71         });
72     });
73 }
74
75 Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, uint64_t quota)
76     : m_engine(&engine)
77     , m_origin(WTFMove(origin))
78     , m_rootPath(WTFMove(rootPath))
79     , m_quota(quota)
80 {
81 }
82
83 void Caches::storeOrigin(CompletionCallback&& completionHandler)
84 {
85     WTF::Persistence::Encoder encoder;
86     encoder << m_origin.topOrigin.protocol;
87     encoder << m_origin.topOrigin.host;
88     encoder << m_origin.topOrigin.port;
89     encoder << m_origin.clientOrigin.protocol;
90     encoder << m_origin.clientOrigin.host;
91     encoder << m_origin.clientOrigin.port;
92     m_engine->writeFile(cachesOriginFilename(m_rootPath), Data { encoder.buffer(), encoder.bufferSize() }, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (std::optional<Error>&& error) mutable {
93         completionHandler(WTFMove(error));
94     });
95 }
96
97 std::optional<WebCore::ClientOrigin> Caches::readOrigin(const Data& data)
98 {
99     // FIXME: We should be able to use modern decoders for persistent data.
100     WebCore::SecurityOriginData topOrigin, clientOrigin;
101     WTF::Persistence::Decoder decoder(data.data(), data.size());
102
103     if (!decoder.decode(topOrigin.protocol))
104         return std::nullopt;
105     if (!decoder.decode(topOrigin.host))
106         return std::nullopt;
107     if (!decoder.decode(topOrigin.port))
108         return std::nullopt;
109     if (!decoder.decode(clientOrigin.protocol))
110         return std::nullopt;
111     if (!decoder.decode(clientOrigin.host))
112         return std::nullopt;
113     if (!decoder.decode(clientOrigin.port))
114         return std::nullopt;
115     return WebCore::ClientOrigin { WTFMove(topOrigin), WTFMove(clientOrigin) };
116 }
117
118 void Caches::initialize(WebCore::DOMCacheEngine::CompletionCallback&& callback)
119 {
120     if (m_isInitialized) {
121         callback(std::nullopt);
122         return;
123     }
124
125     if (m_rootPath.isNull()) {
126         makeDirty();
127         m_isInitialized = true;
128         callback(std::nullopt);
129         return;
130     }
131
132     if (m_storage) {
133         m_pendingInitializationCallbacks.append(WTFMove(callback));
134         return;
135     }
136
137     auto storage = Storage::open(m_rootPath, Storage::Mode::Normal);
138     if (!storage) {
139         callback(Error::WriteDisk);
140         return;
141     }
142     m_storage = storage.releaseNonNull();
143     m_storage->writeWithoutWaiting();
144
145     storeOrigin([this, callback = WTFMove(callback)] (std::optional<Error>&& error) mutable {
146         if (error) {
147             callback(Error::WriteDisk);
148             return;
149         }
150
151         readCachesFromDisk([this, callback = WTFMove(callback)](Expected<Vector<Cache>, Error>&& result) mutable {
152             makeDirty();
153
154             if (!result.has_value()) {
155                 callback(result.error());
156
157                 auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
158                 for (auto& callback : pendingCallbacks)
159                     callback(result.error());
160                 return;
161             }
162             m_caches = WTFMove(result.value());
163
164             initializeSize(WTFMove(callback));
165         });
166     });
167 }
168
169 void Caches::initializeSize(WebCore::DOMCacheEngine::CompletionCallback&& callback)
170 {
171     if (!m_storage) {
172         callback(Error::Internal);
173         return;
174     }
175
176     uint64_t size = 0;
177     m_storage->traverse({ }, 0, [protectedThis = makeRef(*this), this, protectedStorage = makeRef(*m_storage), callback = WTFMove(callback), size](const auto* storage, const auto& information) mutable {
178         if (!storage) {
179             m_size = size;
180             m_isInitialized = true;
181             callback(std::nullopt);
182
183             auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
184             for (auto& callback : pendingCallbacks)
185                 callback(std::nullopt);
186
187             return;
188         }
189         auto decoded = Cache::decodeRecordHeader(*storage);
190         if (decoded)
191             size += decoded->size;
192     });
193 }
194
195 void Caches::detach()
196 {
197     m_engine = nullptr;
198     m_rootPath = { };
199 }
200
201 void Caches::clear(CompletionHandler<void()>&& completionHandler)
202 {
203     if (m_engine)
204         m_engine->removeFile(cachesListFilename(m_rootPath));
205     if (m_storage) {
206         m_storage->clear(String { }, -WallTime::infinity(), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable {
207             ASSERT(RunLoop::isMain());
208             protectedThis->clearMemoryRepresentation();
209             completionHandler();
210         });
211         return;
212     }
213     clearMemoryRepresentation();
214     completionHandler();
215 }
216
217 Cache* Caches::find(const String& name)
218 {
219     auto position = m_caches.findMatching([&](const auto& item) { return item.name() == name; });
220     return (position != notFound) ? &m_caches[position] : nullptr;
221 }
222
223 Cache* Caches::find(uint64_t identifier)
224 {
225     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
226     if (position != notFound)
227         return &m_caches[position];
228
229     position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
230     return (position != notFound) ? &m_removedCaches[position] : nullptr;
231 }
232
233 void Caches::open(const String& name, CacheIdentifierCallback&& callback)
234 {
235     ASSERT(m_isInitialized);
236     ASSERT(m_engine);
237
238     if (auto* cache = find(name)) {
239         cache->open([cacheIdentifier = cache->identifier(), callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
240             if (error) {
241                 callback(makeUnexpected(error.value()));
242                 return;
243             }
244             callback(CacheIdentifierOperationResult { cacheIdentifier, false });
245         });
246         return;
247     }
248
249     makeDirty();
250
251     uint64_t cacheIdentifier = m_engine->nextCacheIdentifier();
252     m_caches.append(Cache { *this, cacheIdentifier, Cache::State::Open, String { name }, createCanonicalUUIDString() });
253
254     writeCachesToDisk([callback = WTFMove(callback), cacheIdentifier](std::optional<Error>&& error) mutable {
255         callback(CacheIdentifierOperationResult { cacheIdentifier, !!error });
256     });
257 }
258
259 void Caches::remove(uint64_t identifier, CacheIdentifierCallback&& callback)
260 {
261     ASSERT(m_isInitialized);
262     ASSERT(m_engine);
263
264     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
265
266     if (position == notFound) {
267         ASSERT(m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }) != notFound);
268         callback(CacheIdentifierOperationResult { identifier, false });
269         return;
270     }
271
272     makeDirty();
273
274     m_removedCaches.append(WTFMove(m_caches[position]));
275     m_caches.remove(position);
276
277     writeCachesToDisk([callback = WTFMove(callback), identifier](std::optional<Error>&& error) mutable {
278         callback(CacheIdentifierOperationResult { identifier, !!error });
279     });
280 }
281
282 void Caches::dispose(Cache& cache)
283 {
284     auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); });
285     if (position != notFound) {
286         if (m_storage)
287             m_storage->remove(cache.keys(), { });
288
289         m_removedCaches.remove(position);
290         return;
291     }
292     ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound);
293     cache.clearMemoryRepresentation();
294
295     if (m_caches.findMatching([](const auto& item) { return item.isActive(); }) == notFound)
296         clearMemoryRepresentation();
297 }
298
299 static inline Data encodeCacheNames(const Vector<Cache>& caches)
300 {
301     WTF::Persistence::Encoder encoder;
302
303     uint64_t size = caches.size();
304     encoder << size;
305     for (auto& cache : caches) {
306         encoder << cache.name();
307         encoder << cache.uniqueName();
308     }
309
310     return Data { encoder.buffer(), encoder.bufferSize() };
311 }
312
313 static inline Expected<Vector<std::pair<String, String>>, Error> decodeCachesNames(const Data& data, int error)
314 {
315     if (error)
316         return makeUnexpected(Error::ReadDisk);
317
318     WTF::Persistence::Decoder decoder(data.data(), data.size());
319     uint64_t count;
320     if (!decoder.decode(count))
321         return makeUnexpected(Error::ReadDisk);
322
323     Vector<std::pair<String, String>> names;
324     names.reserveInitialCapacity(count);
325     for (size_t index = 0; index < count; ++index) {
326         String name;
327         if (!decoder.decode(name))
328             return makeUnexpected(Error::ReadDisk);
329         String uniqueName;
330         if (!decoder.decode(uniqueName))
331             return makeUnexpected(Error::ReadDisk);
332
333         names.uncheckedAppend(std::pair<String, String> { WTFMove(name), WTFMove(uniqueName) });
334     }
335     return names;
336 }
337
338 void Caches::readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, Error>&&)>&& callback)
339 {
340     ASSERT(m_engine);
341     ASSERT(!m_isInitialized);
342     ASSERT(m_caches.isEmpty());
343
344     if (!shouldPersist()) {
345         callback(Vector<Cache> { });
346         return;
347     }
348
349     auto filename = cachesListFilename(m_rootPath);
350     if (!WebCore::FileSystem::fileExists(filename)) {
351         callback(Vector<Cache> { });
352         return;
353     }
354
355     m_engine->readFile(filename, [protectedThis = makeRef(*this), this, callback = WTFMove(callback)](const Data& data, int error) mutable {
356         if (!m_engine) {
357             callback(Vector<Cache> { });
358             return;
359         }
360
361         auto result = decodeCachesNames(data, error);
362         if (!result.has_value()) {
363             callback(makeUnexpected(result.error()));
364             return;
365         }
366         callback(WTF::map(WTFMove(result.value()), [this] (auto&& pair) {
367             return Cache { *this, m_engine->nextCacheIdentifier(), Cache::State::Uninitialized, WTFMove(pair.first), WTFMove(pair.second) };
368         }));
369     });
370 }
371
372 void Caches::writeCachesToDisk(CompletionCallback&& callback)
373 {
374     if (!shouldPersist()) {
375         callback(std::nullopt);
376         return;
377     }
378
379     ASSERT(m_engine);
380
381     if (m_caches.isEmpty()) {
382         m_engine->removeFile(cachesListFilename(m_rootPath));
383         callback(std::nullopt);
384         return;
385     }
386
387     m_engine->writeFile(cachesListFilename(m_rootPath), encodeCacheNames(m_caches), [callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
388         callback(WTFMove(error));
389     });
390 }
391
392 void Caches::readRecordsList(Cache& cache, NetworkCache::Storage::TraverseHandler&& callback)
393 {
394     ASSERT(m_isInitialized);
395
396     if (!m_storage) {
397         callback(nullptr, { });
398         return;
399     }
400     m_storage->traverse(cache.uniqueName(), 0, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const auto* storage, const auto& information) {
401         callback(storage, information);
402     });
403 }
404
405 void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::CompletionCallback&& callback)
406 {
407     // FIXME: Implement quota increase request.
408     ASSERT(m_quota < m_size + spaceRequired);
409     callback(Error::QuotaExceeded);
410 }
411
412 void Caches::writeRecord(const Cache& cache, const RecordInformation& recordInformation, Record&& record, uint64_t previousRecordSize, CompletionCallback&& callback)
413 {
414     ASSERT(m_isInitialized);
415
416     ASSERT(m_size >= previousRecordSize);
417     m_size += recordInformation.size;
418     m_size -= previousRecordSize;
419
420     ASSERT(m_size <= m_quota);
421
422     if (!shouldPersist()) {
423         m_volatileStorage.set(recordInformation.key, WTFMove(record));
424         return;
425     }
426
427     m_storage->store(Cache::encode(recordInformation, record), [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const Data&) {
428         callback(std::nullopt);
429     });
430 }
431
432 void Caches::readRecord(const NetworkCache::Key& key, WTF::Function<void(Expected<Record, Error>&&)>&& callback)
433 {
434     ASSERT(m_isInitialized);
435
436     if (!shouldPersist()) {
437         auto iterator = m_volatileStorage.find(key);
438         if (iterator == m_volatileStorage.end()) {
439             callback(makeUnexpected(Error::Internal));
440             return;
441         }
442         callback(iterator->value.copy());
443         return;
444     }
445
446     m_storage->retrieve(key, 4, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](std::unique_ptr<Storage::Record> storage) mutable {
447         if (!storage) {
448             callback(makeUnexpected(Error::ReadDisk));
449             return false;
450         }
451
452         auto record = Cache::decode(*storage);
453         if (!record) {
454             callback(makeUnexpected(Error::ReadDisk));
455             return false;
456         }
457
458         callback(WTFMove(record.value()));
459         return true;
460     });
461 }
462
463 void Caches::removeRecord(const RecordInformation& record)
464 {
465     ASSERT(m_isInitialized);
466
467     ASSERT(m_size >= record.size);
468     m_size -= record.size;
469     removeCacheEntry(record.key);
470 }
471
472 void Caches::removeCacheEntry(const NetworkCache::Key& key)
473 {
474     ASSERT(m_isInitialized);
475
476     if (!shouldPersist()) {
477         m_volatileStorage.remove(key);
478         return;
479     }
480     m_storage->remove(key);
481 }
482
483 void Caches::clearMemoryRepresentation()
484 {
485     makeDirty();
486     m_caches.clear();
487     m_isInitialized = false;
488     m_storage = nullptr;
489 }
490
491 bool Caches::isDirty(uint64_t updateCounter) const
492 {
493     ASSERT(m_updateCounter >= updateCounter);
494     return m_updateCounter != updateCounter;
495 }
496
497 const NetworkCache::Salt& Caches::salt() const
498 {
499     if (m_engine)
500         return m_engine->salt();
501
502     if (!m_volatileSalt)
503         m_volatileSalt = Salt { };
504
505     return m_volatileSalt.value();
506 }
507
508 CacheInfos Caches::cacheInfos(uint64_t updateCounter) const
509 {
510     Vector<CacheInfo> cacheInfos;
511     if (isDirty(updateCounter)) {
512         cacheInfos.reserveInitialCapacity(m_caches.size());
513         for (auto& cache : m_caches)
514             cacheInfos.uncheckedAppend(CacheInfo { cache.identifier(), cache.name() });
515     }
516     return { WTFMove(cacheInfos), m_updateCounter };
517 }
518
519 void Caches::appendRepresentation(StringBuilder& builder) const
520 {
521     builder.append("{ \"persistent\": [");
522
523     bool isFirst = true;
524     for (auto& cache : m_caches) {
525         if (!isFirst)
526             builder.append(", ");
527         isFirst = false;
528         builder.append("\"");
529         builder.append(cache.name());
530         builder.append("\"");
531     }
532
533     builder.append("], \"removed\": [");
534
535     isFirst = true;
536     for (auto& cache : m_removedCaches) {
537         if (!isFirst)
538             builder.append(", ");
539         isFirst = false;
540         builder.append("\"");
541         builder.append(cache.name());
542         builder.append("\"");
543     }
544     builder.append("]}\n");
545 }
546
547 uint64_t Caches::storageSize() const
548 {
549     ASSERT(m_isInitialized);
550     if (!shouldPersist())
551         return 0;
552     return m_storage->approximateSize();
553 }
554
555 } // namespace CacheStorage
556
557 } // namespace WebKit