CacheStorage::Caches should clear m_caches when clearing its representation even...
[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 Caches::~Caches()
54 {
55     ASSERT(m_pendingWritingCachesToDiskCallbacks.isEmpty());
56 }
57
58 void Caches::retrieveOriginFromDirectory(const String& folderPath, WorkQueue& queue, WTF::CompletionHandler<void(std::optional<WebCore::ClientOrigin>&&)>&& completionHandler)
59 {
60     queue.dispatch([completionHandler = WTFMove(completionHandler), filename = cachesOriginFilename(folderPath)]() mutable {
61         if (!WebCore::FileSystem::fileExists(filename)) {
62             RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler)]() mutable {
63                 completionHandler(std::nullopt);
64             });
65             return;
66         }
67
68         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
69         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [completionHandler = WTFMove(completionHandler)](const Data& data, int error) mutable {
70             ASSERT(RunLoop::isMain());
71             if (error) {
72                 completionHandler(std::nullopt);
73                 return;
74             }
75             completionHandler(readOrigin(data));
76         });
77     });
78 }
79
80 Caches::Caches(Engine& engine, WebCore::ClientOrigin&& origin, String&& rootPath, uint64_t quota)
81     : m_engine(&engine)
82     , m_origin(WTFMove(origin))
83     , m_rootPath(WTFMove(rootPath))
84     , m_quota(quota)
85 {
86 }
87
88 void Caches::storeOrigin(CompletionCallback&& completionHandler)
89 {
90     WTF::Persistence::Encoder encoder;
91     encoder << m_origin.topOrigin.protocol;
92     encoder << m_origin.topOrigin.host;
93     encoder << m_origin.topOrigin.port;
94     encoder << m_origin.clientOrigin.protocol;
95     encoder << m_origin.clientOrigin.host;
96     encoder << m_origin.clientOrigin.port;
97     m_engine->writeFile(cachesOriginFilename(m_rootPath), Data { encoder.buffer(), encoder.bufferSize() }, [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (std::optional<Error>&& error) mutable {
98         completionHandler(WTFMove(error));
99     });
100 }
101
102 std::optional<WebCore::ClientOrigin> Caches::readOrigin(const Data& data)
103 {
104     // FIXME: We should be able to use modern decoders for persistent data.
105     WebCore::SecurityOriginData topOrigin, clientOrigin;
106     WTF::Persistence::Decoder decoder(data.data(), data.size());
107
108     if (!decoder.decode(topOrigin.protocol))
109         return std::nullopt;
110     if (!decoder.decode(topOrigin.host))
111         return std::nullopt;
112     if (!decoder.decode(topOrigin.port))
113         return std::nullopt;
114     if (!decoder.decode(clientOrigin.protocol))
115         return std::nullopt;
116     if (!decoder.decode(clientOrigin.host))
117         return std::nullopt;
118     if (!decoder.decode(clientOrigin.port))
119         return std::nullopt;
120     return WebCore::ClientOrigin { WTFMove(topOrigin), WTFMove(clientOrigin) };
121 }
122
123 void Caches::initialize(WebCore::DOMCacheEngine::CompletionCallback&& callback)
124 {
125     if (m_isInitialized) {
126         callback(std::nullopt);
127         return;
128     }
129
130     if (m_rootPath.isNull()) {
131         makeDirty();
132         m_isInitialized = true;
133         callback(std::nullopt);
134         return;
135     }
136
137     if (m_storage) {
138         m_pendingInitializationCallbacks.append(WTFMove(callback));
139         return;
140     }
141
142     auto storage = Storage::open(m_rootPath, Storage::Mode::Normal);
143     if (!storage) {
144         callback(Error::WriteDisk);
145         return;
146     }
147
148     m_pendingInitializationCallbacks.append(WTFMove(callback));
149     m_storage = storage.releaseNonNull();
150     m_storage->writeWithoutWaiting();
151
152     storeOrigin([this] (std::optional<Error>&& error) mutable {
153         if (error) {
154             auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
155             for (auto& callback : pendingCallbacks)
156                 callback(Error::WriteDisk);
157
158             m_storage = nullptr;
159             return;
160         }
161
162         readCachesFromDisk([this](Expected<Vector<Cache>, Error>&& result) mutable {
163             makeDirty();
164
165             if (!result.has_value()) {
166                 auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
167                 for (auto& callback : pendingCallbacks)
168                     callback(result.error());
169
170                 m_storage = nullptr;
171                 return;
172             }
173             m_caches = WTFMove(result.value());
174
175             initializeSize();
176         });
177     });
178 }
179
180 void Caches::initializeSize()
181 {
182     if (!m_storage) {
183         auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
184         for (auto& callback : pendingCallbacks)
185             callback(Error::Internal);
186         return;
187     }
188
189     uint64_t size = 0;
190     m_storage->traverse({ }, 0, [protectedThis = makeRef(*this), this, protectedStorage = makeRef(*m_storage), size](const auto* storage, const auto& information) mutable {
191         if (!storage) {
192             if (m_pendingInitializationCallbacks.isEmpty()) {
193                 // Caches was cleared so let's not get initialized.
194                 m_storage = nullptr;
195                 return;
196             }
197             m_size = size;
198             m_isInitialized = true;
199             auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
200             for (auto& callback : pendingCallbacks)
201                 callback(std::nullopt);
202
203             return;
204         }
205         auto decoded = Cache::decodeRecordHeader(*storage);
206         if (decoded)
207             size += decoded->size;
208     });
209 }
210
211 void Caches::detach()
212 {
213     m_engine = nullptr;
214     m_rootPath = { };
215     clearPendingWritingCachesToDiskCallbacks();
216 }
217
218 void Caches::clear(CompletionHandler<void()>&& completionHandler)
219 {
220     if (m_isWritingCachesToDisk) {
221         m_pendingWritingCachesToDiskCallbacks.append([this, completionHandler = WTFMove(completionHandler)] (auto&& error) mutable {
222             this->clear(WTFMove(completionHandler));
223         });
224         return;
225     }
226
227     auto pendingCallbacks = WTFMove(m_pendingInitializationCallbacks);
228     for (auto& callback : pendingCallbacks)
229         callback(Error::Internal);
230
231     if (m_engine)
232         m_engine->removeFile(cachesListFilename(m_rootPath));
233     if (m_storage) {
234         m_storage->clear(String { }, -WallTime::infinity(), [protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable {
235             ASSERT(RunLoop::isMain());
236             protectedThis->clearMemoryRepresentation();
237             completionHandler();
238         });
239         return;
240     }
241     clearMemoryRepresentation();
242     clearPendingWritingCachesToDiskCallbacks();
243     completionHandler();
244 }
245
246 void Caches::clearPendingWritingCachesToDiskCallbacks()
247 {
248     auto pendingWritingCachesToDiskCallbacks = WTFMove(m_pendingWritingCachesToDiskCallbacks);
249     for (auto& callback : pendingWritingCachesToDiskCallbacks)
250         callback(Error::Internal);
251 }
252
253 Cache* Caches::find(const String& name)
254 {
255     auto position = m_caches.findMatching([&](const auto& item) { return item.name() == name; });
256     return (position != notFound) ? &m_caches[position] : nullptr;
257 }
258
259 Cache* Caches::find(uint64_t identifier)
260 {
261     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
262     if (position != notFound)
263         return &m_caches[position];
264
265     position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
266     return (position != notFound) ? &m_removedCaches[position] : nullptr;
267 }
268
269 void Caches::open(const String& name, CacheIdentifierCallback&& callback)
270 {
271     ASSERT(m_isInitialized);
272     ASSERT(m_engine);
273
274     if (m_isWritingCachesToDisk) {
275         m_pendingWritingCachesToDiskCallbacks.append([this, name, callback = WTFMove(callback)] (auto&& error) mutable {
276             if (error) {
277                 callback(makeUnexpected(error.value()));
278                 return;
279             }
280             this->open(name, WTFMove(callback));
281         });
282         return;
283     }
284
285     if (auto* cache = find(name)) {
286         cache->open([cacheIdentifier = cache->identifier(), callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
287             if (error) {
288                 callback(makeUnexpected(error.value()));
289                 return;
290             }
291             callback(CacheIdentifierOperationResult { cacheIdentifier, false });
292         });
293         return;
294     }
295
296     makeDirty();
297
298     uint64_t cacheIdentifier = m_engine->nextCacheIdentifier();
299     m_caches.append(Cache { *this, cacheIdentifier, Cache::State::Open, String { name }, createCanonicalUUIDString() });
300
301     writeCachesToDisk([callback = WTFMove(callback), cacheIdentifier](std::optional<Error>&& error) mutable {
302         callback(CacheIdentifierOperationResult { cacheIdentifier, !!error });
303     });
304 }
305
306 void Caches::remove(uint64_t identifier, CacheIdentifierCallback&& callback)
307 {
308     ASSERT(m_isInitialized);
309     ASSERT(m_engine);
310
311     if (m_isWritingCachesToDisk) {
312         m_pendingWritingCachesToDiskCallbacks.append([this, identifier, callback = WTFMove(callback)] (auto&& error) mutable {
313             if (error) {
314                 callback(makeUnexpected(error.value()));
315                 return;
316             }
317             this->remove(identifier, WTFMove(callback));
318         });
319         return;
320     }
321
322     auto position = m_caches.findMatching([&](const auto& item) { return item.identifier() == identifier; });
323
324     if (position == notFound) {
325         ASSERT(m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == identifier; }) != notFound);
326         callback(CacheIdentifierOperationResult { 0, false });
327         return;
328     }
329
330     makeDirty();
331
332     m_removedCaches.append(WTFMove(m_caches[position]));
333     m_caches.remove(position);
334
335     writeCachesToDisk([callback = WTFMove(callback), identifier](std::optional<Error>&& error) mutable {
336         callback(CacheIdentifierOperationResult { identifier, !!error });
337     });
338 }
339
340 void Caches::dispose(Cache& cache)
341 {
342     auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); });
343     if (position != notFound) {
344         if (m_storage)
345             m_storage->remove(cache.keys(), { });
346
347         m_removedCaches.remove(position);
348         return;
349     }
350     ASSERT(m_caches.findMatching([&](const auto& item) { return item.identifier() == cache.identifier(); }) != notFound);
351     cache.clearMemoryRepresentation();
352
353     if (m_caches.findMatching([](const auto& item) { return item.isActive(); }) == notFound)
354         clearMemoryRepresentation();
355 }
356
357 static inline Data encodeCacheNames(const Vector<Cache>& caches)
358 {
359     WTF::Persistence::Encoder encoder;
360
361     uint64_t size = caches.size();
362     encoder << size;
363     for (auto& cache : caches) {
364         encoder << cache.name();
365         encoder << cache.uniqueName();
366     }
367
368     return Data { encoder.buffer(), encoder.bufferSize() };
369 }
370
371 static inline Expected<Vector<std::pair<String, String>>, Error> decodeCachesNames(const Data& data, int error)
372 {
373     if (error)
374         return makeUnexpected(Error::ReadDisk);
375
376     WTF::Persistence::Decoder decoder(data.data(), data.size());
377     uint64_t count;
378     if (!decoder.decode(count))
379         return makeUnexpected(Error::ReadDisk);
380
381     Vector<std::pair<String, String>> names;
382     names.reserveInitialCapacity(count);
383     for (size_t index = 0; index < count; ++index) {
384         String name;
385         if (!decoder.decode(name))
386             return makeUnexpected(Error::ReadDisk);
387         String uniqueName;
388         if (!decoder.decode(uniqueName))
389             return makeUnexpected(Error::ReadDisk);
390
391         names.uncheckedAppend(std::pair<String, String> { WTFMove(name), WTFMove(uniqueName) });
392     }
393     return names;
394 }
395
396 void Caches::readCachesFromDisk(WTF::Function<void(Expected<Vector<Cache>, Error>&&)>&& callback)
397 {
398     ASSERT(m_engine);
399     ASSERT(!m_isInitialized);
400     ASSERT(m_caches.isEmpty());
401
402     if (!shouldPersist()) {
403         callback(Vector<Cache> { });
404         return;
405     }
406
407     auto filename = cachesListFilename(m_rootPath);
408     if (!WebCore::FileSystem::fileExists(filename)) {
409         callback(Vector<Cache> { });
410         return;
411     }
412
413     m_engine->readFile(filename, [protectedThis = makeRef(*this), this, callback = WTFMove(callback)](const Data& data, int error) mutable {
414         if (!m_engine) {
415             callback(Vector<Cache> { });
416             return;
417         }
418
419         auto result = decodeCachesNames(data, error);
420         if (!result.has_value()) {
421             callback(makeUnexpected(result.error()));
422             return;
423         }
424         callback(WTF::map(WTFMove(result.value()), [this] (auto&& pair) {
425             return Cache { *this, m_engine->nextCacheIdentifier(), Cache::State::Uninitialized, WTFMove(pair.first), WTFMove(pair.second) };
426         }));
427     });
428 }
429
430 void Caches::writeCachesToDisk(CompletionCallback&& callback)
431 {
432     ASSERT(!m_isWritingCachesToDisk);
433     ASSERT(m_isInitialized);
434     if (!shouldPersist()) {
435         callback(std::nullopt);
436         return;
437     }
438
439     ASSERT(m_engine);
440
441     if (m_caches.isEmpty()) {
442         m_engine->removeFile(cachesListFilename(m_rootPath));
443         callback(std::nullopt);
444         return;
445     }
446
447     m_isWritingCachesToDisk = true;
448     m_engine->writeFile(cachesListFilename(m_rootPath), encodeCacheNames(m_caches), [this, protectedThis = makeRef(*this), callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
449         m_isWritingCachesToDisk = false;
450         callback(WTFMove(error));
451         while (!m_pendingWritingCachesToDiskCallbacks.isEmpty() && !m_isWritingCachesToDisk)
452             m_pendingWritingCachesToDiskCallbacks.takeFirst()(std::nullopt);
453     });
454 }
455
456 void Caches::readRecordsList(Cache& cache, NetworkCache::Storage::TraverseHandler&& callback)
457 {
458     ASSERT(m_isInitialized);
459
460     if (!m_storage) {
461         callback(nullptr, { });
462         return;
463     }
464     m_storage->traverse(cache.uniqueName(), 0, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](const auto* storage, const auto& information) {
465         callback(storage, information);
466     });
467 }
468
469 void Caches::requestSpace(uint64_t spaceRequired, WebCore::DOMCacheEngine::CompletionCallback&& callback)
470 {
471     // FIXME: Implement quota increase request.
472     ASSERT(m_quota < m_size + spaceRequired);
473     callback(Error::QuotaExceeded);
474 }
475
476 void Caches::writeRecord(const Cache& cache, const RecordInformation& recordInformation, Record&& record, uint64_t previousRecordSize, CompletionCallback&& callback)
477 {
478     ASSERT(m_isInitialized);
479
480     ASSERT(m_size >= previousRecordSize);
481     m_size += recordInformation.size;
482     m_size -= previousRecordSize;
483
484     ASSERT(m_size <= m_quota);
485
486     if (!shouldPersist()) {
487         m_volatileStorage.set(recordInformation.key, WTFMove(record));
488         callback(std::nullopt);
489         return;
490     }
491
492     m_storage->store(Cache::encode(recordInformation, record), { }, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)]() {
493         callback(std::nullopt);
494     });
495 }
496
497 void Caches::readRecord(const NetworkCache::Key& key, WTF::Function<void(Expected<Record, Error>&&)>&& callback)
498 {
499     ASSERT(m_isInitialized);
500
501     if (!shouldPersist()) {
502         auto iterator = m_volatileStorage.find(key);
503         if (iterator == m_volatileStorage.end()) {
504             callback(makeUnexpected(Error::Internal));
505             return;
506         }
507         callback(iterator->value.copy());
508         return;
509     }
510
511     m_storage->retrieve(key, 4, [protectedStorage = makeRef(*m_storage), callback = WTFMove(callback)](std::unique_ptr<Storage::Record> storage) mutable {
512         if (!storage) {
513             callback(makeUnexpected(Error::ReadDisk));
514             return false;
515         }
516
517         auto record = Cache::decode(*storage);
518         if (!record) {
519             callback(makeUnexpected(Error::ReadDisk));
520             return false;
521         }
522
523         callback(WTFMove(record.value()));
524         return true;
525     });
526 }
527
528 void Caches::removeRecord(const RecordInformation& record)
529 {
530     ASSERT(m_isInitialized);
531
532     ASSERT(m_size >= record.size);
533     m_size -= record.size;
534     removeCacheEntry(record.key);
535 }
536
537 void Caches::removeCacheEntry(const NetworkCache::Key& key)
538 {
539     ASSERT(m_isInitialized);
540
541     if (!shouldPersist()) {
542         m_volatileStorage.remove(key);
543         return;
544     }
545     m_storage->remove(key);
546 }
547
548 void Caches::clearMemoryRepresentation()
549 {
550     if (!m_isInitialized) {
551         ASSERT(m_caches.isEmpty() || !m_pendingInitializationCallbacks.isEmpty());
552         // m_storage might not be null in case Caches is being initialized. This is fine as nullify it below is a memory optimization.
553         m_caches.clear();
554         return;
555     }
556
557     makeDirty();
558     m_caches.clear();
559     m_isInitialized = false;
560
561     // Clear storages as a memory optimization.
562     m_storage = nullptr;
563     m_volatileStorage.clear();
564 }
565
566 bool Caches::isDirty(uint64_t updateCounter) const
567 {
568     ASSERT(m_updateCounter >= updateCounter);
569     return m_updateCounter != updateCounter;
570 }
571
572 const NetworkCache::Salt& Caches::salt() const
573 {
574     if (m_engine)
575         return m_engine->salt();
576
577     if (!m_volatileSalt)
578         m_volatileSalt = Salt { };
579
580     return m_volatileSalt.value();
581 }
582
583 void Caches::cacheInfos(uint64_t updateCounter, CacheInfosCallback&& callback)
584 {
585     if (m_isWritingCachesToDisk) {
586         m_pendingWritingCachesToDiskCallbacks.append([this, updateCounter, callback = WTFMove(callback)] (auto&& error) mutable {
587             if (error) {
588                 callback(makeUnexpected(error.value()));
589                 return;
590             }
591             this->cacheInfos(updateCounter, WTFMove(callback));
592         });
593         return;
594     }
595
596     Vector<CacheInfo> cacheInfos;
597     if (isDirty(updateCounter)) {
598         cacheInfos.reserveInitialCapacity(m_caches.size());
599         for (auto& cache : m_caches)
600             cacheInfos.uncheckedAppend(CacheInfo { cache.identifier(), cache.name() });
601     }
602     callback(CacheInfos { WTFMove(cacheInfos), m_updateCounter });
603 }
604
605 void Caches::appendRepresentation(StringBuilder& builder) const
606 {
607     builder.append("{ \"persistent\": [");
608
609     bool isFirst = true;
610     for (auto& cache : m_caches) {
611         if (!isFirst)
612             builder.append(", ");
613         isFirst = false;
614         builder.append("\"");
615         builder.append(cache.name());
616         builder.append("\"");
617     }
618
619     builder.append("], \"removed\": [");
620
621     isFirst = true;
622     for (auto& cache : m_removedCaches) {
623         if (!isFirst)
624             builder.append(", ");
625         isFirst = false;
626         builder.append("\"");
627         builder.append(cache.name());
628         builder.append("\"");
629     }
630     builder.append("]}\n");
631 }
632
633 uint64_t Caches::storageSize() const
634 {
635     ASSERT(m_isInitialized);
636     if (!shouldPersist())
637         return 0;
638     return m_storage->approximateSize();
639 }
640
641 } // namespace CacheStorage
642
643 } // namespace WebKit