1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ActorsParent.h"
8 
9 // Local includes
10 #include "Flatten.h"
11 #include "FirstInitializationAttemptsImpl.h"
12 #include "OriginScope.h"
13 #include "QuotaCommon.h"
14 #include "QuotaManager.h"
15 #include "QuotaObject.h"
16 #include "UsageInfo.h"
17 
18 // Global includes
19 #include <cinttypes>
20 #include <cstdlib>
21 #include <cstring>
22 #include <algorithm>
23 #include <cstdint>
24 #include <functional>
25 #include <new>
26 #include <numeric>
27 #include <tuple>
28 #include <type_traits>
29 #include <utility>
30 #include "DirectoryLockImpl.h"
31 #include "ErrorList.h"
32 #include "MainThreadUtils.h"
33 #include "mozIStorageAsyncConnection.h"
34 #include "mozIStorageConnection.h"
35 #include "mozIStorageService.h"
36 #include "mozIStorageStatement.h"
37 #include "mozStorageCID.h"
38 #include "mozStorageHelper.h"
39 #include "mozilla/AlreadyAddRefed.h"
40 #include "mozilla/Assertions.h"
41 #include "mozilla/Atomics.h"
42 #include "mozilla/Attributes.h"
43 #include "mozilla/AutoRestore.h"
44 #include "mozilla/BasePrincipal.h"
45 #include "mozilla/CheckedInt.h"
46 #include "mozilla/CondVar.h"
47 #include "mozilla/InitializedOnce.h"
48 #include "mozilla/Logging.h"
49 #include "mozilla/MacroForEach.h"
50 #include "mozilla/Maybe.h"
51 #include "mozilla/Mutex.h"
52 #include "mozilla/NotNull.h"
53 #include "mozilla/OriginAttributes.h"
54 #include "mozilla/Preferences.h"
55 #include "mozilla/ProfilerLabels.h"
56 #include "mozilla/RefPtr.h"
57 #include "mozilla/Result.h"
58 #include "mozilla/ResultExtensions.h"
59 #include "mozilla/ScopeExit.h"
60 #include "mozilla/Services.h"
61 #include "mozilla/SpinEventLoopUntil.h"
62 #include "mozilla/StaticPrefs_dom.h"
63 #include "mozilla/StaticPtr.h"
64 #include "mozilla/Telemetry.h"
65 #include "mozilla/TelemetryHistogramEnums.h"
66 #include "mozilla/TextUtils.h"
67 #include "mozilla/TimeStamp.h"
68 #include "mozilla/UniquePtr.h"
69 #include "mozilla/Unused.h"
70 #include "mozilla/Variant.h"
71 #include "mozilla/dom/FlippedOnce.h"
72 #include "mozilla/dom/LocalStorageCommon.h"
73 #include "mozilla/dom/StorageActivityService.h"
74 #include "mozilla/dom/StorageDBUpdater.h"
75 #include "mozilla/dom/StorageTypeBinding.h"
76 #include "mozilla/dom/cache/QuotaClient.h"
77 #include "mozilla/dom/indexedDB/ActorsParent.h"
78 #include "mozilla/dom/ipc/IdType.h"
79 #include "mozilla/dom/localstorage/ActorsParent.h"
80 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
81 #include "mozilla/dom/quota/Client.h"
82 #include "mozilla/dom/quota/DirectoryLock.h"
83 #include "mozilla/dom/quota/PersistenceType.h"
84 #include "mozilla/dom/quota/PQuota.h"
85 #include "mozilla/dom/quota/PQuotaParent.h"
86 #include "mozilla/dom/quota/PQuotaRequest.h"
87 #include "mozilla/dom/quota/PQuotaRequestParent.h"
88 #include "mozilla/dom/quota/PQuotaUsageRequest.h"
89 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
90 #include "mozilla/dom/quota/QuotaManagerImpl.h"
91 #include "mozilla/dom/quota/ResultExtensions.h"
92 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
93 #include "mozilla/dom/simpledb/ActorsParent.h"
94 #include "mozilla/fallible.h"
95 #include "mozilla/ipc/BackgroundChild.h"
96 #include "mozilla/ipc/BackgroundParent.h"
97 #include "mozilla/ipc/PBackgroundChild.h"
98 #include "mozilla/ipc/PBackgroundSharedTypes.h"
99 #include "mozilla/ipc/ProtocolUtils.h"
100 #include "mozilla/net/MozURL.h"
101 #include "nsAppDirectoryServiceDefs.h"
102 #include "nsBaseHashtable.h"
103 #include "nsCOMPtr.h"
104 #include "nsCRTGlue.h"
105 #include "nsCharSeparatedTokenizer.h"
106 #include "nsClassHashtable.h"
107 #include "nsComponentManagerUtils.h"
108 #include "nsContentUtils.h"
109 #include "nsTHashMap.h"
110 #include "nsDebug.h"
111 #include "nsDirectoryServiceUtils.h"
112 #include "nsError.h"
113 #include "nsHashKeys.h"
114 #include "nsIBinaryInputStream.h"
115 #include "nsIBinaryOutputStream.h"
116 #include "nsIConsoleService.h"
117 #include "nsIDirectoryEnumerator.h"
118 #include "nsIEventTarget.h"
119 #include "nsIFile.h"
120 #include "nsIFileStreams.h"
121 #include "nsIInputStream.h"
122 #include "nsIObjectInputStream.h"
123 #include "nsIObjectOutputStream.h"
124 #include "nsIObserver.h"
125 #include "nsIObserverService.h"
126 #include "nsIOutputStream.h"
127 #include "nsIPlatformInfo.h"
128 #include "nsIPrincipal.h"
129 #include "nsIRunnable.h"
130 #include "nsIScriptObjectPrincipal.h"
131 #include "nsISupports.h"
132 #include "nsISupportsPrimitives.h"
133 #include "nsIThread.h"
134 #include "nsITimer.h"
135 #include "nsIURI.h"
136 #include "nsIWidget.h"
137 #include "nsLiteralString.h"
138 #include "nsNetUtil.h"
139 #include "nsPIDOMWindow.h"
140 #include "nsPrintfCString.h"
141 #include "nsServiceManagerUtils.h"
142 #include "nsString.h"
143 #include "nsStringFlags.h"
144 #include "nsStringFwd.h"
145 #include "nsTArray.h"
146 #include "nsTHashtable.h"
147 #include "nsTLiteralString.h"
148 #include "nsTPromiseFlatString.h"
149 #include "nsTStringRepr.h"
150 #include "nsThreadUtils.h"
151 #include "nsURLHelper.h"
152 #include "nsXPCOM.h"
153 #include "nsXPCOMCID.h"
154 #include "nsXULAppAPI.h"
155 #include "prinrval.h"
156 #include "prio.h"
157 #include "prthread.h"
158 #include "prtime.h"
159 
160 // As part of bug 1536596 in order to identify the remaining sources of
161 // principal info inconsistencies, we have added anonymized crash logging and
162 // are temporarily making these checks occur on both debug and optimized
163 // nightly, dev-edition, and early beta builds through use of
164 // EARLY_BETA_OR_EARLIER during Firefox 82.  The plan is to return this
165 // condition to MOZ_DIAGNOSTIC_ASSERT_ENABLED during Firefox 84 at the latest.
166 // The analysis and disabling is tracked by bug 1536596.
167 
168 #ifdef EARLY_BETA_OR_EARLIER
169 #  define QM_PRINCIPALINFO_VERIFICATION_ENABLED
170 #endif
171 
172 // The amount of time, in milliseconds, that our IO thread will stay alive
173 // after the last event it processes.
174 #define DEFAULT_THREAD_TIMEOUT_MS 30000
175 
176 /**
177  * If shutdown takes this long, kill actors of a quota client, to avoid reaching
178  * the crash timeout.
179  */
180 #define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
181 
182 /**
183  * Automatically crash the browser if shutdown of a quota client takes this
184  * long. We've chosen a value that is long enough that it is unlikely for the
185  * problem to be falsely triggered by slow system I/O.  We've also chosen a
186  * value long enough so that automated tests should time out and fail if
187  * shutdown of a quota client takes too long.  Also, this value is long enough
188  * so that testers can notice the timeout; we want to know about the timeouts,
189  * not hide them. On the other hand this value is less than 60 seconds which is
190  * used by nsTerminator to crash a hung main process.
191  */
192 #define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
193 
194 // profile-before-change, when we need to shut down quota manager
195 #define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
196 
197 #define KB *1024ULL
198 #define MB *1024ULL KB
199 #define GB *1024ULL MB
200 
201 namespace mozilla::dom::quota {
202 
203 using namespace mozilla::ipc;
204 using mozilla::net::MozURL;
205 
206 // We want profiles to be platform-independent so we always need to replace
207 // the same characters on every platform. Windows has the most extensive set
208 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
209 // FILE_PATH_SEPARATOR.
210 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
211 
212 namespace {
213 
214 template <typename T>
215 void AssertNoOverflow(uint64_t aDest, T aArg);
216 
217 /*******************************************************************************
218  * Constants
219  ******************************************************************************/
220 
221 const uint32_t kSQLitePageSizeOverride = 512;
222 
223 // Important version history:
224 // - Bug 1290481 bumped our schema from major.minor 2.0 to 3.0 in Firefox 57
225 //   which caused Firefox 57 release concerns because the major schema upgrade
226 //   means anyone downgrading to Firefox 56 will experience a non-operational
227 //   QuotaManager and all of its clients.
228 // - Bug 1404344 got very concerned about that and so we decided to effectively
229 //   rename 3.0 to 2.1, effective in Firefox 57.  This works because post
230 //   storage.sqlite v1.0, QuotaManager doesn't care about minor storage version
231 //   increases.  It also works because all the upgrade did was give the DOM
232 //   Cache API QuotaClient an opportunity to create its newly added .padding
233 //   files during initialization/upgrade, which isn't functionally necessary as
234 //   that can be done on demand.
235 
236 // Major storage version. Bump for backwards-incompatible changes.
237 // (The next major version should be 4 to distinguish from the Bug 1290481
238 // downgrade snafu.)
239 const uint32_t kMajorStorageVersion = 2;
240 
241 // Minor storage version. Bump for backwards-compatible changes.
242 const uint32_t kMinorStorageVersion = 3;
243 
244 // The storage version we store in the SQLite database is a (signed) 32-bit
245 // integer. The major version is left-shifted 16 bits so the max value is
246 // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF.
247 static_assert(kMajorStorageVersion <= 0xFFFF,
248               "Major version needs to fit in 16 bits.");
249 static_assert(kMinorStorageVersion <= 0xFFFF,
250               "Minor version needs to fit in 16 bits.");
251 
252 const int32_t kStorageVersion =
253     int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion);
254 
255 // See comments above about why these are a thing.
256 const int32_t kHackyPreDowngradeStorageVersion = int32_t((3 << 16) + 0);
257 const int32_t kHackyPostDowngradeStorageVersion = int32_t((2 << 16) + 1);
258 
259 static_assert(static_cast<uint32_t>(StorageType::Persistent) ==
260                   static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
261               "Enum values should match.");
262 
263 static_assert(static_cast<uint32_t>(StorageType::Temporary) ==
264                   static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
265               "Enum values should match.");
266 
267 static_assert(static_cast<uint32_t>(StorageType::Default) ==
268                   static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
269               "Enum values should match.");
270 
271 const char kChromeOrigin[] = "chrome";
272 const char kAboutHomeOriginPrefix[] = "moz-safe-about:home";
273 const char kIndexedDBOriginPrefix[] = "indexeddb://";
274 const char kResourceOriginPrefix[] = "resource://";
275 
276 constexpr auto kStorageName = u"storage"_ns;
277 constexpr auto kSQLiteSuffix = u".sqlite"_ns;
278 
279 #define INDEXEDDB_DIRECTORY_NAME u"indexedDB"
280 #define ARCHIVES_DIRECTORY_NAME u"archives"
281 #define PERSISTENT_DIRECTORY_NAME u"persistent"
282 #define PERMANENT_DIRECTORY_NAME u"permanent"
283 #define TEMPORARY_DIRECTORY_NAME u"temporary"
284 #define DEFAULT_DIRECTORY_NAME u"default"
285 
286 // The name of the file that we use to load/save the last access time of an
287 // origin.
288 // XXX We should get rid of old metadata files at some point, bug 1343576.
289 #define METADATA_FILE_NAME u".metadata"
290 #define METADATA_TMP_FILE_NAME u".metadata-tmp"
291 #define METADATA_V2_FILE_NAME u".metadata-v2"
292 #define METADATA_V2_TMP_FILE_NAME u".metadata-v2-tmp"
293 
294 #define WEB_APPS_STORE_FILE_NAME u"webappsstore.sqlite"
295 #define LS_ARCHIVE_FILE_NAME u"ls-archive.sqlite"
296 #define LS_ARCHIVE_TMP_FILE_NAME u"ls-archive-tmp.sqlite"
297 
298 const int32_t kLocalStorageArchiveVersion = 4;
299 
300 const char kProfileDoChangeTopic[] = "profile-do-change";
301 
302 const int32_t kCacheVersion = 2;
303 
304 /******************************************************************************
305  * SQLite functions
306  ******************************************************************************/
307 
MakeStorageVersion(uint32_t aMajorStorageVersion,uint32_t aMinorStorageVersion)308 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
309                            uint32_t aMinorStorageVersion) {
310   return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
311 }
312 
GetMajorStorageVersion(int32_t aStorageVersion)313 uint32_t GetMajorStorageVersion(int32_t aStorageVersion) {
314   return uint32_t(aStorageVersion >> 16);
315 }
316 
CreateTables(mozIStorageConnection * aConnection)317 nsresult CreateTables(mozIStorageConnection* aConnection) {
318   AssertIsOnIOThread();
319   MOZ_ASSERT(aConnection);
320 
321   // Table `database`
322   QM_TRY(MOZ_TO_RESULT(
323       aConnection->ExecuteSimpleSQL("CREATE TABLE database"
324                                     "( cache_version INTEGER NOT NULL DEFAULT 0"
325                                     ");"_ns)));
326 
327 #ifdef DEBUG
328   {
329     QM_TRY_INSPECT(const int32_t& storageVersion,
330                    MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
331 
332     MOZ_ASSERT(storageVersion == 0);
333   }
334 #endif
335 
336   QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(kStorageVersion)));
337 
338   return NS_OK;
339 }
340 
LoadCacheVersion(mozIStorageConnection & aConnection)341 Result<int32_t, nsresult> LoadCacheVersion(mozIStorageConnection& aConnection) {
342   AssertIsOnIOThread();
343 
344   QM_TRY_INSPECT(const auto& stmt,
345                  CreateAndExecuteSingleStepStatement<
346                      SingleStepResult::ReturnNullIfNoResult>(
347                      aConnection, "SELECT cache_version FROM database"_ns));
348 
349   QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
350 
351   QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
352 }
353 
SaveCacheVersion(mozIStorageConnection & aConnection,int32_t aVersion)354 nsresult SaveCacheVersion(mozIStorageConnection& aConnection,
355                           int32_t aVersion) {
356   AssertIsOnIOThread();
357 
358   QM_TRY_INSPECT(
359       const auto& stmt,
360       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
361           nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
362           "UPDATE database SET cache_version = :version;"_ns));
363 
364   QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, aVersion)));
365 
366   QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
367 
368   return NS_OK;
369 }
370 
CreateCacheTables(mozIStorageConnection & aConnection)371 nsresult CreateCacheTables(mozIStorageConnection& aConnection) {
372   AssertIsOnIOThread();
373 
374   // Table `cache`
375   QM_TRY(MOZ_TO_RESULT(
376       aConnection.ExecuteSimpleSQL("CREATE TABLE cache"
377                                    "( valid INTEGER NOT NULL DEFAULT 0"
378                                    ", build_id TEXT NOT NULL DEFAULT ''"
379                                    ");"_ns)));
380 
381   // Table `repository`
382   QM_TRY(
383       MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL("CREATE TABLE repository"
384                                                  "( id INTEGER PRIMARY KEY"
385                                                  ", name TEXT NOT NULL"
386                                                  ");"_ns)));
387 
388   // Table `origin`
389   QM_TRY(MOZ_TO_RESULT(
390       aConnection.ExecuteSimpleSQL("CREATE TABLE origin"
391                                    "( repository_id INTEGER NOT NULL"
392                                    ", suffix TEXT"
393                                    ", group_ TEXT NOT NULL"
394                                    ", origin TEXT NOT NULL"
395                                    ", client_usages TEXT NOT NULL"
396                                    ", usage INTEGER NOT NULL"
397                                    ", last_access_time INTEGER NOT NULL"
398                                    ", accessed INTEGER NOT NULL"
399                                    ", persisted INTEGER NOT NULL"
400                                    ", PRIMARY KEY (repository_id, origin)"
401                                    ", FOREIGN KEY (repository_id) "
402                                    "REFERENCES repository(id) "
403                                    ");"_ns)));
404 
405 #ifdef DEBUG
406   {
407     QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
408     MOZ_ASSERT(cacheVersion == 0);
409   }
410 #endif
411 
412   QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, kCacheVersion)));
413 
414   return NS_OK;
415 }
416 
InvalidateCache(mozIStorageConnection & aConnection)417 OkOrErr InvalidateCache(mozIStorageConnection& aConnection) {
418   AssertIsOnIOThread();
419 
420   static constexpr auto kDeleteCacheQuery = "DELETE FROM origin;"_ns;
421   static constexpr auto kSetInvalidFlagQuery = "UPDATE cache SET valid = 0"_ns;
422 
423   QM_TRY(QM_OR_ELSE_WARN(
424       // Expression.
425       ([&]() -> OkOrErr {
426         mozStorageTransaction transaction(&aConnection,
427                                           /*aCommitOnComplete */ false);
428 
429         QM_TRY(QM_TO_RESULT(transaction.Start()));
430         QM_TRY(QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kDeleteCacheQuery)));
431         QM_TRY(
432             QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
433         QM_TRY(QM_TO_RESULT(transaction.Commit()));
434 
435         return Ok{};
436       }()),
437       // Fallback.
438       ([&](const QMResult& rv) -> OkOrErr {
439         QM_TRY(
440             QM_TO_RESULT(aConnection.ExecuteSimpleSQL(kSetInvalidFlagQuery)));
441 
442         return Ok{};
443       })));
444 
445   return Ok{};
446 }
447 
UpgradeCacheFrom1To2(mozIStorageConnection & aConnection)448 nsresult UpgradeCacheFrom1To2(mozIStorageConnection& aConnection) {
449   AssertIsOnIOThread();
450 
451   QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
452       "ALTER TABLE origin ADD COLUMN suffix TEXT"_ns)));
453 
454   QM_TRY(InvalidateCache(aConnection));
455 
456 #ifdef DEBUG
457   {
458     QM_TRY_INSPECT(const int32_t& cacheVersion, LoadCacheVersion(aConnection));
459 
460     MOZ_ASSERT(cacheVersion == 1);
461   }
462 #endif
463 
464   QM_TRY(MOZ_TO_RESULT(SaveCacheVersion(aConnection, 2)));
465 
466   return NS_OK;
467 }
468 
MaybeCreateOrUpgradeCache(mozIStorageConnection & aConnection)469 Result<bool, nsresult> MaybeCreateOrUpgradeCache(
470     mozIStorageConnection& aConnection) {
471   bool cacheUsable = true;
472 
473   QM_TRY_UNWRAP(int32_t cacheVersion, LoadCacheVersion(aConnection));
474 
475   if (cacheVersion > kCacheVersion) {
476     cacheUsable = false;
477   } else if (cacheVersion != kCacheVersion) {
478     const bool newCache = !cacheVersion;
479 
480     mozStorageTransaction transaction(
481         &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
482 
483     QM_TRY(MOZ_TO_RESULT(transaction.Start()));
484 
485     if (newCache) {
486       QM_TRY(MOZ_TO_RESULT(CreateCacheTables(aConnection)));
487 
488 #ifdef DEBUG
489       {
490         QM_TRY_INSPECT(const int32_t& cacheVersion,
491                        LoadCacheVersion(aConnection));
492         MOZ_ASSERT(cacheVersion == kCacheVersion);
493       }
494 #endif
495 
496       QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
497           nsLiteralCString("INSERT INTO cache (valid, build_id) "
498                            "VALUES (0, '')"))));
499 
500       nsCOMPtr<mozIStorageStatement> insertStmt;
501 
502       for (const PersistenceType persistenceType : kAllPersistenceTypes) {
503         if (insertStmt) {
504           MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
505         } else {
506           QM_TRY_UNWRAP(insertStmt, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
507                                         nsCOMPtr<mozIStorageStatement>,
508                                         aConnection, CreateStatement,
509                                         "INSERT INTO repository (id, name) "
510                                         "VALUES (:id, :name)"_ns));
511         }
512 
513         QM_TRY(MOZ_TO_RESULT(
514             insertStmt->BindInt32ByName("id"_ns, persistenceType)));
515 
516         QM_TRY(MOZ_TO_RESULT(insertStmt->BindUTF8StringByName(
517             "name"_ns, PersistenceTypeToString(persistenceType))));
518 
519         QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()));
520       }
521     } else {
522       // This logic needs to change next time we change the cache!
523       static_assert(kCacheVersion == 2,
524                     "Upgrade function needed due to cache version increase.");
525 
526       while (cacheVersion != kCacheVersion) {
527         if (cacheVersion == 1) {
528           QM_TRY(MOZ_TO_RESULT(UpgradeCacheFrom1To2(aConnection)));
529         } else {
530           QM_FAIL(Err(NS_ERROR_FAILURE), []() {
531             QM_WARNING(
532                 "Unable to initialize cache, no upgrade path is "
533                 "available!");
534           });
535         }
536 
537         QM_TRY_UNWRAP(cacheVersion, LoadCacheVersion(aConnection));
538       }
539 
540       MOZ_ASSERT(cacheVersion == kCacheVersion);
541     }
542 
543     QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
544   }
545 
546   return cacheUsable;
547 }
548 
CreateWebAppsStoreConnection(nsIFile & aWebAppsStoreFile,mozIStorageService & aStorageService)549 Result<nsCOMPtr<mozIStorageConnection>, nsresult> CreateWebAppsStoreConnection(
550     nsIFile& aWebAppsStoreFile, mozIStorageService& aStorageService) {
551   AssertIsOnIOThread();
552 
553   // Check if the old database exists at all.
554   QM_TRY_INSPECT(const bool& exists,
555                  MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, Exists));
556 
557   if (!exists) {
558     // webappsstore.sqlite doesn't exist, return a null connection.
559     return nsCOMPtr<mozIStorageConnection>{};
560   }
561 
562   QM_TRY_INSPECT(const bool& isDirectory,
563                  MOZ_TO_RESULT_INVOKE_MEMBER(aWebAppsStoreFile, IsDirectory));
564 
565   if (isDirectory) {
566     QM_WARNING("webappsstore.sqlite is not a file!");
567     return nsCOMPtr<mozIStorageConnection>{};
568   }
569 
570   QM_TRY_INSPECT(const auto& connection,
571                  QM_OR_ELSE_WARN_IF(
572                      // Expression.
573                      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
574                          nsCOMPtr<mozIStorageConnection>, aStorageService,
575                          OpenUnsharedDatabase, &aWebAppsStoreFile,
576                          mozIStorageService::CONNECTION_DEFAULT),
577                      // Predicate.
578                      IsDatabaseCorruptionError,
579                      // Fallback. Don't throw an error, leave a corrupted
580                      // webappsstore database as it is.
581                      ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
582 
583   if (connection) {
584     // Don't propagate an error, leave a non-updateable webappsstore database as
585     // it is.
586     QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
587            nsCOMPtr<mozIStorageConnection>{});
588   }
589 
590   return connection;
591 }
592 
GetLocalStorageArchiveFile(const nsAString & aDirectoryPath)593 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveFile(
594     const nsAString& aDirectoryPath) {
595   AssertIsOnIOThread();
596   MOZ_ASSERT(!aDirectoryPath.IsEmpty());
597 
598   QM_TRY_UNWRAP(auto lsArchiveFile, QM_NewLocalFile(aDirectoryPath));
599 
600   QM_TRY(MOZ_TO_RESULT(
601       lsArchiveFile->Append(nsLiteralString(LS_ARCHIVE_FILE_NAME))));
602 
603   return lsArchiveFile;
604 }
605 
GetLocalStorageArchiveTmpFile(const nsAString & aDirectoryPath)606 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveTmpFile(
607     const nsAString& aDirectoryPath) {
608   AssertIsOnIOThread();
609   MOZ_ASSERT(!aDirectoryPath.IsEmpty());
610 
611   QM_TRY_UNWRAP(auto lsArchiveTmpFile, QM_NewLocalFile(aDirectoryPath));
612 
613   QM_TRY(MOZ_TO_RESULT(
614       lsArchiveTmpFile->Append(nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
615 
616   return lsArchiveTmpFile;
617 }
618 
IsLocalStorageArchiveInitialized(mozIStorageConnection & aConnection)619 Result<bool, nsresult> IsLocalStorageArchiveInitialized(
620     mozIStorageConnection& aConnection) {
621   AssertIsOnIOThread();
622 
623   QM_TRY_RETURN(
624       MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, TableExists, "database"_ns));
625 }
626 
InitializeLocalStorageArchive(mozIStorageConnection * aConnection)627 nsresult InitializeLocalStorageArchive(mozIStorageConnection* aConnection) {
628   AssertIsOnIOThread();
629   MOZ_ASSERT(aConnection);
630 
631 #ifdef DEBUG
632   {
633     QM_TRY_INSPECT(const auto& initialized,
634                    IsLocalStorageArchiveInitialized(*aConnection));
635     MOZ_ASSERT(!initialized);
636   }
637 #endif
638 
639   QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
640       "CREATE TABLE database(version INTEGER NOT NULL DEFAULT 0);"_ns)));
641 
642   QM_TRY_INSPECT(
643       const auto& stmt,
644       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
645           nsCOMPtr<mozIStorageStatement>, aConnection, CreateStatement,
646           "INSERT INTO database (version) VALUES (:version)"_ns));
647 
648   QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("version"_ns, 0)));
649   QM_TRY(MOZ_TO_RESULT(stmt->Execute()));
650 
651   return NS_OK;
652 }
653 
LoadLocalStorageArchiveVersion(mozIStorageConnection & aConnection)654 Result<int32_t, nsresult> LoadLocalStorageArchiveVersion(
655     mozIStorageConnection& aConnection) {
656   AssertIsOnIOThread();
657 
658   QM_TRY_INSPECT(const auto& stmt,
659                  CreateAndExecuteSingleStepStatement<
660                      SingleStepResult::ReturnNullIfNoResult>(
661                      aConnection, "SELECT version FROM database"_ns));
662 
663   QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
664 
665   QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
666 }
667 
SaveLocalStorageArchiveVersion(mozIStorageConnection * aConnection,int32_t aVersion)668 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
669                                         int32_t aVersion) {
670   AssertIsOnIOThread();
671   MOZ_ASSERT(aConnection);
672 
673   nsCOMPtr<mozIStorageStatement> stmt;
674   nsresult rv = aConnection->CreateStatement(
675       "UPDATE database SET version = :version;"_ns, getter_AddRefs(stmt));
676   if (NS_WARN_IF(NS_FAILED(rv))) {
677     return rv;
678   }
679 
680   rv = stmt->BindInt32ByName("version"_ns, aVersion);
681   if (NS_WARN_IF(NS_FAILED(rv))) {
682     return rv;
683   }
684 
685   rv = stmt->Execute();
686   if (NS_WARN_IF(NS_FAILED(rv))) {
687     return rv;
688   }
689 
690   return NS_OK;
691 }
692 
693 template <typename FileFunc, typename DirectoryFunc>
CollectEachFileEntry(nsIFile & aDirectory,const FileFunc & aFileFunc,const DirectoryFunc & aDirectoryFunc)694 Result<mozilla::Ok, nsresult> CollectEachFileEntry(
695     nsIFile& aDirectory, const FileFunc& aFileFunc,
696     const DirectoryFunc& aDirectoryFunc) {
697   AssertIsOnIOThread();
698 
699   return CollectEachFile(
700       aDirectory,
701       [&aFileFunc, &aDirectoryFunc](
702           const nsCOMPtr<nsIFile>& file) -> Result<mozilla::Ok, nsresult> {
703         QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
704 
705         switch (dirEntryKind) {
706           case nsIFileKind::ExistsAsDirectory:
707             return aDirectoryFunc(file);
708 
709           case nsIFileKind::ExistsAsFile:
710             return aFileFunc(file);
711 
712           case nsIFileKind::DoesNotExist:
713             // Ignore files that got removed externally while iterating.
714             break;
715         }
716 
717         return Ok{};
718       });
719 }
720 
721 /******************************************************************************
722  * Quota manager class declarations
723  ******************************************************************************/
724 
725 }  // namespace
726 
727 class QuotaManager::Observer final : public nsIObserver {
728   static Observer* sInstance;
729 
730   bool mPendingProfileChange;
731   bool mShutdownComplete;
732 
733  public:
734   static nsresult Initialize();
735 
736   static void ShutdownCompleted();
737 
738  private:
Observer()739   Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
740     MOZ_ASSERT(NS_IsMainThread());
741   }
742 
~Observer()743   ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
744 
745   nsresult Init();
746 
747   nsresult Shutdown();
748 
749   NS_DECL_ISUPPORTS
750   NS_DECL_NSIOBSERVER
751 };
752 
753 namespace {
754 
755 /*******************************************************************************
756  * Local class declarations
757  ******************************************************************************/
758 
759 }  // namespace
760 
761 // XXX Change this not to derive from AutoTArray.
762 class ClientUsageArray final
763     : public AutoTArray<Maybe<uint64_t>, Client::TYPE_MAX> {
764  public:
ClientUsageArray()765   ClientUsageArray() { SetLength(Client::TypeMax()); }
766 
767   void Serialize(nsACString& aText) const;
768 
769   nsresult Deserialize(const nsACString& aText);
770 
Clone() const771   ClientUsageArray Clone() const {
772     ClientUsageArray res;
773     res.Assign(*this);
774     return res;
775   }
776 };
777 
778 class OriginInfo final {
779   friend class GroupInfo;
780   friend class QuotaManager;
781   friend class QuotaObject;
782 
783  public:
784   OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
785              const ClientUsageArray& aClientUsages, uint64_t aUsage,
786              int64_t aAccessTime, bool aPersisted, bool aDirectoryExists);
787 
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)788   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
789 
790   GroupInfo* GetGroupInfo() const { return mGroupInfo; }
791 
Origin() const792   const nsCString& Origin() const { return mOrigin; }
793 
LockedUsage() const794   int64_t LockedUsage() const {
795     AssertCurrentThreadOwnsQuotaMutex();
796 
797 #ifdef DEBUG
798     QuotaManager* quotaManager = QuotaManager::Get();
799     MOZ_ASSERT(quotaManager);
800 
801     uint64_t usage = 0;
802     for (Client::Type type : quotaManager->AllClientTypes()) {
803       AssertNoOverflow(usage, mClientUsages[type].valueOr(0));
804       usage += mClientUsages[type].valueOr(0);
805     }
806     MOZ_ASSERT(mUsage == usage);
807 #endif
808 
809     return mUsage;
810   }
811 
LockedAccessTime() const812   int64_t LockedAccessTime() const {
813     AssertCurrentThreadOwnsQuotaMutex();
814 
815     return mAccessTime;
816   }
817 
LockedPersisted() const818   bool LockedPersisted() const {
819     AssertCurrentThreadOwnsQuotaMutex();
820 
821     return mPersisted;
822   }
823 
824   OriginMetadata FlattenToOriginMetadata() const;
825 
826   FullOriginMetadata LockedFlattenToFullOriginMetadata() const;
827 
828   nsresult LockedBindToStatement(mozIStorageStatement* aStatement) const;
829 
830  private:
831   // Private destructor, to discourage deletion outside of Release():
~OriginInfo()832   ~OriginInfo() {
833     MOZ_COUNT_DTOR(OriginInfo);
834 
835     MOZ_ASSERT(!mQuotaObjects.Count());
836   }
837 
838   void LockedDecreaseUsage(Client::Type aClientType, int64_t aSize);
839 
840   void LockedResetUsageForClient(Client::Type aClientType);
841 
842   UsageInfo LockedGetUsageForClient(Client::Type aClientType);
843 
LockedUpdateAccessTime(int64_t aAccessTime)844   void LockedUpdateAccessTime(int64_t aAccessTime) {
845     AssertCurrentThreadOwnsQuotaMutex();
846 
847     mAccessTime = aAccessTime;
848     if (!mAccessed) {
849       mAccessed = true;
850     }
851   }
852 
853   void LockedPersist();
854 
IsExtensionOrigin()855   bool IsExtensionOrigin() { return mIsExtension; }
856 
857   nsTHashMap<nsStringHashKey, NotNull<QuotaObject*>> mQuotaObjects;
858   ClientUsageArray mClientUsages;
859   GroupInfo* mGroupInfo;
860   const nsCString mOrigin;
861   bool mIsExtension;
862   uint64_t mUsage;
863   int64_t mAccessTime;
864   bool mAccessed;
865   bool mPersisted;
866   /**
867    * In some special cases like the LocalStorage client where it's possible to
868    * create a Quota-using representation but not actually write any data, we
869    * want to be able to track quota for an origin without creating its origin
870    * directory or the per-client files until they are actually needed to store
871    * data. In those cases, the OriginInfo will be created by
872    * EnsureQuotaForOrigin and the resulting mDirectoryExists will be false until
873    * the origin actually needs to be created. It is possible for mUsage to be
874    * greater than zero while mDirectoryExists is false, representing a state
875    * where a client like LocalStorage has reserved quota for disk writes, but
876    * has not yet flushed the data to disk.
877    */
878   bool mDirectoryExists;
879 };
880 
881 class OriginInfoAccessTimeComparator {
882  public:
Equals(const NotNull<RefPtr<const OriginInfo>> & a,const NotNull<RefPtr<const OriginInfo>> & b) const883   bool Equals(const NotNull<RefPtr<const OriginInfo>>& a,
884               const NotNull<RefPtr<const OriginInfo>>& b) const {
885     return a->LockedAccessTime() == b->LockedAccessTime();
886   }
887 
LessThan(const NotNull<RefPtr<const OriginInfo>> & a,const NotNull<RefPtr<const OriginInfo>> & b) const888   bool LessThan(const NotNull<RefPtr<const OriginInfo>>& a,
889                 const NotNull<RefPtr<const OriginInfo>>& b) const {
890     return a->LockedAccessTime() < b->LockedAccessTime();
891   }
892 };
893 
894 class GroupInfo final {
895   friend class GroupInfoPair;
896   friend class OriginInfo;
897   friend class QuotaManager;
898   friend class QuotaObject;
899 
900  public:
GroupInfo(GroupInfoPair * aGroupInfoPair,PersistenceType aPersistenceType)901   GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType)
902       : mGroupInfoPair(aGroupInfoPair),
903         mPersistenceType(aPersistenceType),
904         mUsage(0) {
905     MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
906 
907     MOZ_COUNT_CTOR(GroupInfo);
908   }
909 
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)910   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
911 
912   PersistenceType GetPersistenceType() const { return mPersistenceType; }
913 
914  private:
915   // Private destructor, to discourage deletion outside of Release():
916   MOZ_COUNTED_DTOR(GroupInfo)
917 
918   already_AddRefed<OriginInfo> LockedGetOriginInfo(const nsACString& aOrigin);
919 
920   void LockedAddOriginInfo(NotNull<RefPtr<OriginInfo>>&& aOriginInfo);
921 
922   void LockedAdjustUsageForRemovedOriginInfo(const OriginInfo& aOriginInfo);
923 
924   void LockedRemoveOriginInfo(const nsACString& aOrigin);
925 
926   void LockedRemoveOriginInfos();
927 
LockedHasOriginInfos()928   bool LockedHasOriginInfos() {
929     AssertCurrentThreadOwnsQuotaMutex();
930 
931     return !mOriginInfos.IsEmpty();
932   }
933 
934   nsTArray<NotNull<RefPtr<OriginInfo>>> mOriginInfos;
935 
936   GroupInfoPair* mGroupInfoPair;
937   PersistenceType mPersistenceType;
938   uint64_t mUsage;
939 };
940 
941 // XXX Consider a new name for this class, it has other data members now
942 // (besides two GroupInfo objects).
943 class GroupInfoPair {
944  public:
GroupInfoPair(const nsACString & aSuffix,const nsACString & aGroup)945   GroupInfoPair(const nsACString& aSuffix, const nsACString& aGroup)
946       : mSuffix(aSuffix), mGroup(aGroup) {
947     MOZ_COUNT_CTOR(GroupInfoPair);
948   }
949 
MOZ_COUNTED_DTOR(GroupInfoPair) const950   MOZ_COUNTED_DTOR(GroupInfoPair)
951 
952   const nsCString& Suffix() const { return mSuffix; }
953 
Group() const954   const nsCString& Group() const { return mGroup; }
955 
LockedGetGroupInfo(PersistenceType aPersistenceType)956   RefPtr<GroupInfo> LockedGetGroupInfo(PersistenceType aPersistenceType) {
957     AssertCurrentThreadOwnsQuotaMutex();
958     MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
959 
960     return GetGroupInfoForPersistenceType(aPersistenceType);
961   }
962 
LockedSetGroupInfo(PersistenceType aPersistenceType,GroupInfo * aGroupInfo)963   void LockedSetGroupInfo(PersistenceType aPersistenceType,
964                           GroupInfo* aGroupInfo) {
965     AssertCurrentThreadOwnsQuotaMutex();
966     MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
967 
968     RefPtr<GroupInfo>& groupInfo =
969         GetGroupInfoForPersistenceType(aPersistenceType);
970     groupInfo = aGroupInfo;
971   }
972 
LockedClearGroupInfo(PersistenceType aPersistenceType)973   void LockedClearGroupInfo(PersistenceType aPersistenceType) {
974     AssertCurrentThreadOwnsQuotaMutex();
975     MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
976 
977     RefPtr<GroupInfo>& groupInfo =
978         GetGroupInfoForPersistenceType(aPersistenceType);
979     groupInfo = nullptr;
980   }
981 
LockedHasGroupInfos()982   bool LockedHasGroupInfos() {
983     AssertCurrentThreadOwnsQuotaMutex();
984 
985     return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo;
986   }
987 
988  private:
989   RefPtr<GroupInfo>& GetGroupInfoForPersistenceType(
990       PersistenceType aPersistenceType);
991 
992   const nsCString mSuffix;
993   const nsCString mGroup;
994   RefPtr<GroupInfo> mTemporaryStorageGroupInfo;
995   RefPtr<GroupInfo> mDefaultStorageGroupInfo;
996 };
997 
998 namespace {
999 
1000 class CollectOriginsHelper final : public Runnable {
1001   uint64_t mMinSizeToBeFreed;
1002 
1003   Mutex& mMutex;
1004   CondVar mCondVar;
1005 
1006   // The members below are protected by mMutex.
1007   nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
1008   uint64_t mSizeToBeFreed;
1009   bool mWaiting;
1010 
1011  public:
1012   CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
1013 
1014   // Blocks the current thread until origins are collected on the main thread.
1015   // The returned value contains an aggregate size of those origins.
1016   int64_t BlockAndReturnOriginsForEviction(
1017       nsTArray<RefPtr<OriginDirectoryLock>>& aLocks);
1018 
1019  private:
1020   ~CollectOriginsHelper() = default;
1021 
1022   NS_IMETHOD
1023   Run() override;
1024 };
1025 
1026 class OriginOperationBase : public BackgroundThreadObject, public Runnable {
1027  protected:
1028   nsresult mResultCode;
1029 
1030   enum State {
1031     // Not yet run.
1032     State_Initial,
1033 
1034     // Running on the owning thread in the listener for OpenDirectory.
1035     State_DirectoryOpenPending,
1036 
1037     // Running on the IO thread.
1038     State_DirectoryWorkOpen,
1039 
1040     // Running on the owning thread after all work is done.
1041     State_UnblockingOpen,
1042 
1043     // All done.
1044     State_Complete
1045   };
1046 
1047  private:
1048   State mState;
1049   bool mActorDestroyed;
1050 
1051  protected:
1052   bool mNeedsQuotaManagerInit;
1053   bool mNeedsStorageInit;
1054 
1055  public:
NoteActorDestroyed()1056   void NoteActorDestroyed() {
1057     AssertIsOnOwningThread();
1058 
1059     mActorDestroyed = true;
1060   }
1061 
IsActorDestroyed() const1062   bool IsActorDestroyed() const {
1063     AssertIsOnOwningThread();
1064 
1065     return mActorDestroyed;
1066   }
1067 
1068  protected:
OriginOperationBase(nsIEventTarget * aOwningThread=GetCurrentEventTarget ())1069   explicit OriginOperationBase(
1070       nsIEventTarget* aOwningThread = GetCurrentEventTarget())
1071       : BackgroundThreadObject(aOwningThread),
1072         Runnable("dom::quota::OriginOperationBase"),
1073         mResultCode(NS_OK),
1074         mState(State_Initial),
1075         mActorDestroyed(false),
1076         mNeedsQuotaManagerInit(false),
1077         mNeedsStorageInit(false) {}
1078 
1079   // Reference counted.
~OriginOperationBase()1080   virtual ~OriginOperationBase() {
1081     MOZ_ASSERT(mState == State_Complete);
1082     MOZ_ASSERT(mActorDestroyed);
1083   }
1084 
1085 #ifdef DEBUG
GetState() const1086   State GetState() const { return mState; }
1087 #endif
1088 
SetState(State aState)1089   void SetState(State aState) {
1090     MOZ_ASSERT(mState == State_Initial);
1091     mState = aState;
1092   }
1093 
AdvanceState()1094   void AdvanceState() {
1095     switch (mState) {
1096       case State_Initial:
1097         mState = State_DirectoryOpenPending;
1098         return;
1099       case State_DirectoryOpenPending:
1100         mState = State_DirectoryWorkOpen;
1101         return;
1102       case State_DirectoryWorkOpen:
1103         mState = State_UnblockingOpen;
1104         return;
1105       case State_UnblockingOpen:
1106         mState = State_Complete;
1107         return;
1108       default:
1109         MOZ_CRASH("Bad state!");
1110     }
1111   }
1112 
1113   NS_IMETHOD
1114   Run() override;
1115 
1116   virtual void Open() = 0;
1117 
1118   nsresult DirectoryOpen();
1119 
1120   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) = 0;
1121 
1122   void Finish(nsresult aResult);
1123 
1124   virtual void UnblockOpen() = 0;
1125 
1126  private:
1127   nsresult Init();
1128 
1129   nsresult FinishInit();
1130 
1131   nsresult DirectoryWork();
1132 };
1133 
1134 class FinalizeOriginEvictionOp : public OriginOperationBase {
1135   nsTArray<RefPtr<OriginDirectoryLock>> mLocks;
1136 
1137  public:
FinalizeOriginEvictionOp(nsIEventTarget * aBackgroundThread,nsTArray<RefPtr<OriginDirectoryLock>> && aLocks)1138   FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread,
1139                            nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks)
1140       : OriginOperationBase(aBackgroundThread), mLocks(std::move(aLocks)) {
1141     MOZ_ASSERT(!NS_IsMainThread());
1142   }
1143 
1144   void Dispatch();
1145 
1146   void RunOnIOThreadImmediately();
1147 
1148  private:
1149   ~FinalizeOriginEvictionOp() = default;
1150 
1151   virtual void Open() override;
1152 
1153   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1154 
1155   virtual void UnblockOpen() override;
1156 };
1157 
1158 class NormalOriginOperationBase
1159     : public OriginOperationBase,
1160       public OpenDirectoryListener,
1161       public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
1162   RefPtr<DirectoryLock> mDirectoryLock;
1163 
1164  protected:
1165   Nullable<PersistenceType> mPersistenceType;
1166   OriginScope mOriginScope;
1167   Nullable<Client::Type> mClientType;
1168   mozilla::Atomic<bool> mCanceled;
1169   const bool mExclusive;
1170 
1171  public:
RunImmediately()1172   void RunImmediately() {
1173     MOZ_ASSERT(GetState() == State_Initial);
1174 
1175     MOZ_ALWAYS_SUCCEEDS(this->Run());
1176   }
1177 
1178  protected:
NormalOriginOperationBase(const Nullable<PersistenceType> & aPersistenceType,const OriginScope & aOriginScope,bool aExclusive)1179   NormalOriginOperationBase(const Nullable<PersistenceType>& aPersistenceType,
1180                             const OriginScope& aOriginScope, bool aExclusive)
1181       : mPersistenceType(aPersistenceType),
1182         mOriginScope(aOriginScope),
1183         mExclusive(aExclusive) {
1184     AssertIsOnOwningThread();
1185   }
1186 
1187   ~NormalOriginOperationBase() = default;
1188 
1189   virtual RefPtr<DirectoryLock> CreateDirectoryLock();
1190 
1191  private:
1192   // Need to declare refcounting unconditionally, because
1193   // OpenDirectoryListener has pure-virtual refcounting.
1194   NS_DECL_ISUPPORTS_INHERITED
1195 
1196   virtual void Open() override;
1197 
1198   virtual void UnblockOpen() override;
1199 
1200   // OpenDirectoryListener overrides.
1201   virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
1202 
1203   virtual void DirectoryLockFailed() override;
1204 
1205   // Used to send results before unblocking open.
1206   virtual void SendResults() = 0;
1207 };
1208 
1209 class SaveOriginAccessTimeOp : public NormalOriginOperationBase {
1210   int64_t mTimestamp;
1211 
1212  public:
SaveOriginAccessTimeOp(PersistenceType aPersistenceType,const nsACString & aOrigin,int64_t aTimestamp)1213   SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
1214                          const nsACString& aOrigin, int64_t aTimestamp)
1215       : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
1216                                   OriginScope::FromOrigin(aOrigin),
1217                                   /* aExclusive */ false),
1218         mTimestamp(aTimestamp) {
1219     AssertIsOnOwningThread();
1220   }
1221 
1222  private:
1223   ~SaveOriginAccessTimeOp() = default;
1224 
1225   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1226 
1227   virtual void SendResults() override;
1228 };
1229 
1230 /*******************************************************************************
1231  * Actor class declarations
1232  ******************************************************************************/
1233 
1234 class Quota final : public PQuotaParent {
1235 #ifdef DEBUG
1236   bool mActorDestroyed;
1237 #endif
1238 
1239  public:
1240   Quota();
1241 
1242   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
1243 
1244  private:
1245   ~Quota();
1246 
1247   bool VerifyRequestParams(const UsageRequestParams& aParams) const;
1248 
1249   bool VerifyRequestParams(const RequestParams& aParams) const;
1250 
1251   // IPDL methods.
1252   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
1253 
1254   virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(
1255       const UsageRequestParams& aParams) override;
1256 
1257   virtual mozilla::ipc::IPCResult RecvPQuotaUsageRequestConstructor(
1258       PQuotaUsageRequestParent* aActor,
1259       const UsageRequestParams& aParams) override;
1260 
1261   virtual bool DeallocPQuotaUsageRequestParent(
1262       PQuotaUsageRequestParent* aActor) override;
1263 
1264   virtual PQuotaRequestParent* AllocPQuotaRequestParent(
1265       const RequestParams& aParams) override;
1266 
1267   virtual mozilla::ipc::IPCResult RecvPQuotaRequestConstructor(
1268       PQuotaRequestParent* aActor, const RequestParams& aParams) override;
1269 
1270   virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
1271 
1272   virtual mozilla::ipc::IPCResult RecvStartIdleMaintenance() override;
1273 
1274   virtual mozilla::ipc::IPCResult RecvStopIdleMaintenance() override;
1275 
1276   virtual mozilla::ipc::IPCResult RecvAbortOperationsForProcess(
1277       const ContentParentId& aContentParentId) override;
1278 };
1279 
1280 class QuotaUsageRequestBase : public NormalOriginOperationBase,
1281                               public PQuotaUsageRequestParent {
1282  public:
1283   // May be overridden by subclasses if they need to perform work on the
1284   // background thread before being run.
1285   virtual void Init(Quota& aQuota);
1286 
1287  protected:
QuotaUsageRequestBase()1288   QuotaUsageRequestBase()
1289       : NormalOriginOperationBase(Nullable<PersistenceType>(),
1290                                   OriginScope::FromNull(),
1291                                   /* aExclusive */ false) {}
1292 
1293   mozilla::Result<UsageInfo, nsresult> GetUsageForOrigin(
1294       QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
1295       const OriginMetadata& aOriginMetadata);
1296 
1297   // Subclasses use this override to set the IPDL response value.
1298   virtual void GetResponse(UsageRequestResponse& aResponse) = 0;
1299 
1300  private:
1301   mozilla::Result<UsageInfo, nsresult> GetUsageForOriginEntries(
1302       QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
1303       const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
1304       bool aInitialized);
1305 
1306   void SendResults() override;
1307 
1308   // IPDL methods.
1309   void ActorDestroy(ActorDestroyReason aWhy) override;
1310 
1311   mozilla::ipc::IPCResult RecvCancel() final;
1312 };
1313 
1314 // A mix-in class to simplify operations that need to process every origin in
1315 // one or more repositories. Sub-classes should call TraverseRepository in their
1316 // DoDirectoryWork and implement a ProcessOrigin method for their per-origin
1317 // logic.
1318 class TraverseRepositoryHelper {
1319  public:
1320   TraverseRepositoryHelper() = default;
1321 
1322  protected:
1323   virtual ~TraverseRepositoryHelper() = default;
1324 
1325   // If ProcessOrigin returns an error, TraverseRepository will immediately
1326   // terminate and return the received error code to its caller.
1327   nsresult TraverseRepository(QuotaManager& aQuotaManager,
1328                               PersistenceType aPersistenceType);
1329 
1330  private:
1331   virtual const Atomic<bool>& GetIsCanceledFlag() = 0;
1332 
1333   virtual nsresult ProcessOrigin(QuotaManager& aQuotaManager,
1334                                  nsIFile& aOriginDir, const bool aPersistent,
1335                                  const PersistenceType aPersistenceType) = 0;
1336 };
1337 
1338 class GetUsageOp final : public QuotaUsageRequestBase,
1339                          public TraverseRepositoryHelper {
1340   nsTArray<OriginUsage> mOriginUsages;
1341   nsTHashMap<nsCStringHashKey, uint32_t> mOriginUsagesIndex;
1342 
1343   bool mGetAll;
1344 
1345  public:
1346   explicit GetUsageOp(const UsageRequestParams& aParams);
1347 
1348  private:
1349   ~GetUsageOp() = default;
1350 
1351   void ProcessOriginInternal(QuotaManager* aQuotaManager,
1352                              const PersistenceType aPersistenceType,
1353                              const nsACString& aOrigin,
1354                              const int64_t aTimestamp, const bool aPersisted,
1355                              const uint64_t aUsage);
1356 
1357   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1358 
1359   const Atomic<bool>& GetIsCanceledFlag() override;
1360 
1361   nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
1362                          const bool aPersistent,
1363                          const PersistenceType aPersistenceType) override;
1364 
1365   void GetResponse(UsageRequestResponse& aResponse) override;
1366 };
1367 
1368 class GetOriginUsageOp final : public QuotaUsageRequestBase {
1369   nsCString mSuffix;
1370   nsCString mGroup;
1371   uint64_t mUsage;
1372   uint64_t mFileUsage;
1373   bool mFromMemory;
1374 
1375  public:
1376   explicit GetOriginUsageOp(const UsageRequestParams& aParams);
1377 
1378  private:
1379   ~GetOriginUsageOp() = default;
1380 
1381   RefPtr<DirectoryLock> CreateDirectoryLock() override;
1382 
1383   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1384 
1385   void GetResponse(UsageRequestResponse& aResponse) override;
1386 };
1387 
1388 class QuotaRequestBase : public NormalOriginOperationBase,
1389                          public PQuotaRequestParent {
1390  public:
1391   // May be overridden by subclasses if they need to perform work on the
1392   // background thread before being run.
1393   virtual void Init(Quota& aQuota);
1394 
1395  protected:
QuotaRequestBase(bool aExclusive)1396   explicit QuotaRequestBase(bool aExclusive)
1397       : NormalOriginOperationBase(Nullable<PersistenceType>(),
1398                                   OriginScope::FromNull(), aExclusive) {}
1399 
1400   // Subclasses use this override to set the IPDL response value.
1401   virtual void GetResponse(RequestResponse& aResponse) = 0;
1402 
1403  private:
1404   virtual void SendResults() override;
1405 
1406   // IPDL methods.
1407   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
1408 };
1409 
1410 class StorageNameOp final : public QuotaRequestBase {
1411   nsString mName;
1412 
1413  public:
1414   StorageNameOp();
1415 
1416   void Init(Quota& aQuota) override;
1417 
1418  private:
1419   ~StorageNameOp() = default;
1420 
1421   RefPtr<DirectoryLock> CreateDirectoryLock() override;
1422 
1423   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1424 
1425   void GetResponse(RequestResponse& aResponse) override;
1426 };
1427 
1428 class InitializedRequestBase : public QuotaRequestBase {
1429  protected:
1430   bool mInitialized;
1431 
1432  public:
1433   void Init(Quota& aQuota) override;
1434 
1435  protected:
1436   InitializedRequestBase();
1437 
1438  private:
1439   RefPtr<DirectoryLock> CreateDirectoryLock() override;
1440 };
1441 
1442 class StorageInitializedOp final : public InitializedRequestBase {
1443  private:
1444   ~StorageInitializedOp() = default;
1445 
1446   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1447 
1448   void GetResponse(RequestResponse& aResponse) override;
1449 };
1450 
1451 class TemporaryStorageInitializedOp final : public InitializedRequestBase {
1452  private:
1453   ~TemporaryStorageInitializedOp() = default;
1454 
1455   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1456 
1457   void GetResponse(RequestResponse& aResponse) override;
1458 };
1459 
1460 class InitOp final : public QuotaRequestBase {
1461  public:
1462   InitOp();
1463 
1464   void Init(Quota& aQuota) override;
1465 
1466  private:
1467   ~InitOp() = default;
1468 
1469   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1470 
1471   void GetResponse(RequestResponse& aResponse) override;
1472 };
1473 
1474 class InitTemporaryStorageOp final : public QuotaRequestBase {
1475  public:
1476   InitTemporaryStorageOp();
1477 
1478   void Init(Quota& aQuota) override;
1479 
1480  private:
1481   ~InitTemporaryStorageOp() = default;
1482 
1483   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1484 
1485   void GetResponse(RequestResponse& aResponse) override;
1486 };
1487 
1488 class InitializeOriginRequestBase : public QuotaRequestBase {
1489  protected:
1490   nsCString mSuffix;
1491   nsCString mGroup;
1492   bool mCreated;
1493 
1494  public:
1495   void Init(Quota& aQuota) override;
1496 
1497  protected:
1498   InitializeOriginRequestBase(PersistenceType aPersistenceType,
1499                               const PrincipalInfo& aPrincipalInfo);
1500 };
1501 
1502 class InitializePersistentOriginOp final : public InitializeOriginRequestBase {
1503  public:
1504   explicit InitializePersistentOriginOp(const RequestParams& aParams);
1505 
1506  private:
1507   ~InitializePersistentOriginOp() = default;
1508 
1509   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1510 
1511   void GetResponse(RequestResponse& aResponse) override;
1512 };
1513 
1514 class InitializeTemporaryOriginOp final : public InitializeOriginRequestBase {
1515  public:
1516   explicit InitializeTemporaryOriginOp(const RequestParams& aParams);
1517 
1518  private:
1519   ~InitializeTemporaryOriginOp() = default;
1520 
1521   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1522 
1523   void GetResponse(RequestResponse& aResponse) override;
1524 };
1525 
1526 class GetFullOriginMetadataOp : public QuotaRequestBase {
1527   const OriginMetadata mOriginMetadata;
1528   Maybe<FullOriginMetadata> mMaybeFullOriginMetadata;
1529 
1530  public:
1531   explicit GetFullOriginMetadataOp(const GetFullOriginMetadataParams& aParams);
1532 
1533  private:
1534   RefPtr<DirectoryLock> CreateDirectoryLock() override;
1535 
1536   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1537 
1538   void GetResponse(RequestResponse& aResponse) override;
1539 };
1540 
1541 class ResetOrClearOp final : public QuotaRequestBase {
1542   const bool mClear;
1543 
1544  public:
1545   explicit ResetOrClearOp(bool aClear);
1546 
1547   void Init(Quota& aQuota) override;
1548 
1549  private:
1550   ~ResetOrClearOp() = default;
1551 
1552   void DeleteFiles(QuotaManager& aQuotaManager);
1553 
1554   void DeleteStorageFile(QuotaManager& aQuotaManager);
1555 
1556   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1557 
1558   virtual void GetResponse(RequestResponse& aResponse) override;
1559 };
1560 
1561 class ClearRequestBase : public QuotaRequestBase {
1562  protected:
ClearRequestBase(bool aExclusive)1563   explicit ClearRequestBase(bool aExclusive) : QuotaRequestBase(aExclusive) {
1564     AssertIsOnOwningThread();
1565   }
1566 
1567   void DeleteFiles(QuotaManager& aQuotaManager,
1568                    PersistenceType aPersistenceType);
1569 
1570   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1571 };
1572 
1573 class ClearOriginOp final : public ClearRequestBase {
1574   const ClearResetOriginParams mParams;
1575   const bool mMatchAll;
1576 
1577  public:
1578   explicit ClearOriginOp(const RequestParams& aParams);
1579 
1580   void Init(Quota& aQuota) override;
1581 
1582  private:
1583   ~ClearOriginOp() = default;
1584 
1585   void GetResponse(RequestResponse& aResponse) override;
1586 };
1587 
1588 class ClearDataOp final : public ClearRequestBase {
1589   const ClearDataParams mParams;
1590 
1591  public:
1592   explicit ClearDataOp(const RequestParams& aParams);
1593 
1594   void Init(Quota& aQuota) override;
1595 
1596  private:
1597   ~ClearDataOp() = default;
1598 
1599   void GetResponse(RequestResponse& aResponse) override;
1600 };
1601 
1602 class ResetOriginOp final : public QuotaRequestBase {
1603  public:
1604   explicit ResetOriginOp(const RequestParams& aParams);
1605 
1606   void Init(Quota& aQuota) override;
1607 
1608  private:
1609   ~ResetOriginOp() = default;
1610 
1611   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1612 
1613   void GetResponse(RequestResponse& aResponse) override;
1614 };
1615 
1616 class PersistRequestBase : public QuotaRequestBase {
1617   const PrincipalInfo mPrincipalInfo;
1618 
1619  protected:
1620   nsCString mSuffix;
1621   nsCString mGroup;
1622 
1623  public:
1624   void Init(Quota& aQuota) override;
1625 
1626  protected:
1627   explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
1628 };
1629 
1630 class PersistedOp final : public PersistRequestBase {
1631   bool mPersisted;
1632 
1633  public:
1634   explicit PersistedOp(const RequestParams& aParams);
1635 
1636  private:
1637   ~PersistedOp() = default;
1638 
1639   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1640 
1641   void GetResponse(RequestResponse& aResponse) override;
1642 };
1643 
1644 class PersistOp final : public PersistRequestBase {
1645  public:
1646   explicit PersistOp(const RequestParams& aParams);
1647 
1648  private:
1649   ~PersistOp() = default;
1650 
1651   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1652 
1653   void GetResponse(RequestResponse& aResponse) override;
1654 };
1655 
1656 class EstimateOp final : public QuotaRequestBase {
1657   const OriginMetadata mOriginMetadata;
1658   std::pair<uint64_t, uint64_t> mUsageAndLimit;
1659 
1660  public:
1661   explicit EstimateOp(const EstimateParams& aParams);
1662 
1663  private:
1664   ~EstimateOp() = default;
1665 
1666   RefPtr<DirectoryLock> CreateDirectoryLock() override;
1667 
1668   virtual nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1669 
1670   void GetResponse(RequestResponse& aResponse) override;
1671 };
1672 
1673 class ListOriginsOp final : public QuotaRequestBase,
1674                             public TraverseRepositoryHelper {
1675   // XXX Bug 1521541 will make each origin has it's own state.
1676   nsTArray<nsCString> mOrigins;
1677 
1678  public:
1679   ListOriginsOp();
1680 
1681   void Init(Quota& aQuota) override;
1682 
1683  private:
1684   ~ListOriginsOp() = default;
1685 
1686   nsresult DoDirectoryWork(QuotaManager& aQuotaManager) override;
1687 
1688   const Atomic<bool>& GetIsCanceledFlag() override;
1689 
1690   nsresult ProcessOrigin(QuotaManager& aQuotaManager, nsIFile& aOriginDir,
1691                          const bool aPersistent,
1692                          const PersistenceType aPersistenceType) override;
1693 
1694   void GetResponse(RequestResponse& aResponse) override;
1695 };
1696 
1697 /*******************************************************************************
1698  * Other class declarations
1699  ******************************************************************************/
1700 
1701 class StoragePressureRunnable final : public Runnable {
1702   const uint64_t mUsage;
1703 
1704  public:
StoragePressureRunnable(uint64_t aUsage)1705   explicit StoragePressureRunnable(uint64_t aUsage)
1706       : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
1707         mUsage(aUsage) {}
1708 
1709  private:
1710   ~StoragePressureRunnable() = default;
1711 
1712   NS_DECL_NSIRUNNABLE
1713 };
1714 
1715 class RecordQuotaInfoLoadTimeHelper final : public Runnable {
1716   // TimeStamps that are set on the IO thread.
1717   LazyInitializedOnceNotNull<const TimeStamp> mStartTime;
1718   LazyInitializedOnceNotNull<const TimeStamp> mEndTime;
1719 
1720   // A TimeStamp that is set on the main thread.
1721   LazyInitializedOnceNotNull<const TimeStamp> mInitializedTime;
1722 
1723  public:
RecordQuotaInfoLoadTimeHelper()1724   RecordQuotaInfoLoadTimeHelper()
1725       : Runnable("dom::quota::RecordQuotaInfoLoadTimeHelper") {}
1726 
1727   TimeStamp Start();
1728 
1729   TimeStamp End();
1730 
1731  private:
1732   ~RecordQuotaInfoLoadTimeHelper() = default;
1733 
1734   NS_DECL_NSIRUNNABLE
1735 };
1736 
1737 /*******************************************************************************
1738  * Helper classes
1739  ******************************************************************************/
1740 
1741 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1742 
1743 class PrincipalVerifier final : public Runnable {
1744   nsTArray<PrincipalInfo> mPrincipalInfos;
1745 
1746  public:
1747   static already_AddRefed<PrincipalVerifier> CreateAndDispatch(
1748       nsTArray<PrincipalInfo>&& aPrincipalInfos);
1749 
1750  private:
PrincipalVerifier(nsTArray<PrincipalInfo> && aPrincipalInfos)1751   explicit PrincipalVerifier(nsTArray<PrincipalInfo>&& aPrincipalInfos)
1752       : Runnable("dom::quota::PrincipalVerifier"),
1753         mPrincipalInfos(std::move(aPrincipalInfos)) {
1754     AssertIsOnIOThread();
1755   }
1756 
1757   virtual ~PrincipalVerifier() = default;
1758 
1759   Result<Ok, nsCString> CheckPrincipalInfoValidity(
1760       const PrincipalInfo& aPrincipalInfo);
1761 
1762   NS_DECL_NSIRUNNABLE
1763 };
1764 
1765 #endif
1766 
1767 /*******************************************************************************
1768  * Helper Functions
1769  ******************************************************************************/
1770 
1771 template <typename T, bool = std::is_unsigned_v<T>>
1772 struct IntChecker {
Assertmozilla::dom::quota::__anoneea2a16e0711::IntChecker1773   static void Assert(T aInt) {
1774     static_assert(std::is_integral_v<T>, "Not an integer!");
1775     MOZ_ASSERT(aInt >= 0);
1776   }
1777 };
1778 
1779 template <typename T>
1780 struct IntChecker<T, true> {
Assertmozilla::dom::quota::__anoneea2a16e0711::IntChecker1781   static void Assert(T aInt) {
1782     static_assert(std::is_integral_v<T>, "Not an integer!");
1783   }
1784 };
1785 
1786 template <typename T>
AssertNoOverflow(uint64_t aDest,T aArg)1787 void AssertNoOverflow(uint64_t aDest, T aArg) {
1788   IntChecker<T>::Assert(aDest);
1789   IntChecker<T>::Assert(aArg);
1790   MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg));
1791 }
1792 
1793 template <typename T, typename U>
AssertNoUnderflow(T aDest,U aArg)1794 void AssertNoUnderflow(T aDest, U aArg) {
1795   IntChecker<T>::Assert(aDest);
1796   IntChecker<T>::Assert(aArg);
1797   MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg));
1798 }
1799 
IsDotFile(const nsAString & aFileName)1800 inline bool IsDotFile(const nsAString& aFileName) {
1801   return QuotaManager::IsDotFile(aFileName);
1802 }
1803 
IsOSMetadata(const nsAString & aFileName)1804 inline bool IsOSMetadata(const nsAString& aFileName) {
1805   return QuotaManager::IsOSMetadata(aFileName);
1806 }
1807 
IsOriginMetadata(const nsAString & aFileName)1808 bool IsOriginMetadata(const nsAString& aFileName) {
1809   return aFileName.EqualsLiteral(METADATA_FILE_NAME) ||
1810          aFileName.EqualsLiteral(METADATA_V2_FILE_NAME) ||
1811          IsOSMetadata(aFileName);
1812 }
1813 
IsTempMetadata(const nsAString & aFileName)1814 bool IsTempMetadata(const nsAString& aFileName) {
1815   return aFileName.EqualsLiteral(METADATA_TMP_FILE_NAME) ||
1816          aFileName.EqualsLiteral(METADATA_V2_TMP_FILE_NAME);
1817 }
1818 
1819 // Return whether the group was actually updated.
MaybeUpdateGroupForOrigin(OriginMetadata & aOriginMetadata)1820 Result<bool, nsresult> MaybeUpdateGroupForOrigin(
1821     OriginMetadata& aOriginMetadata) {
1822   MOZ_ASSERT(!NS_IsMainThread());
1823 
1824   bool updated = false;
1825 
1826   if (aOriginMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
1827     if (!aOriginMetadata.mGroup.EqualsLiteral(kChromeOrigin)) {
1828       aOriginMetadata.mGroup.AssignLiteral(kChromeOrigin);
1829       updated = true;
1830     }
1831   } else {
1832     OriginAttributes originAttributes;
1833     nsCString originNoSuffix;
1834     QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
1835                                                     originNoSuffix)),
1836            Err(NS_ERROR_FAILURE));
1837 
1838     RefPtr<MozURL> url;
1839     QM_TRY(MOZ_TO_RESULT(MozURL::Init(getter_AddRefs(url), originNoSuffix)),
1840            QM_PROPAGATE, [&originNoSuffix](const nsresult) {
1841              QM_WARNING("A URL %s is not recognized by MozURL",
1842                         originNoSuffix.get());
1843            });
1844 
1845     QM_TRY_INSPECT(
1846         const auto& baseDomain,
1847         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *url, BaseDomain));
1848 
1849     const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
1850 
1851     if (aOriginMetadata.mGroup != upToDateGroup) {
1852       aOriginMetadata.mGroup = upToDateGroup;
1853       updated = true;
1854 
1855 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
1856       ContentPrincipalInfo contentPrincipalInfo;
1857       contentPrincipalInfo.attrs() = originAttributes;
1858       contentPrincipalInfo.originNoSuffix() = originNoSuffix;
1859       contentPrincipalInfo.spec() = originNoSuffix;
1860       contentPrincipalInfo.baseDomain() = baseDomain;
1861 
1862       PrincipalInfo principalInfo(contentPrincipalInfo);
1863 
1864       nsTArray<PrincipalInfo> principalInfos;
1865       principalInfos.AppendElement(principalInfo);
1866 
1867       RefPtr<PrincipalVerifier> principalVerifier =
1868           PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
1869 #endif
1870     }
1871   }
1872 
1873   return updated;
1874 }
1875 
MaybeUpdateLastAccessTimeForOrigin(FullOriginMetadata & aFullOriginMetadata)1876 Result<bool, nsresult> MaybeUpdateLastAccessTimeForOrigin(
1877     FullOriginMetadata& aFullOriginMetadata) {
1878   MOZ_ASSERT(!NS_IsMainThread());
1879 
1880   if (aFullOriginMetadata.mLastAccessTime == INT64_MIN) {
1881     QuotaManager* quotaManager = QuotaManager::Get();
1882     MOZ_ASSERT(quotaManager);
1883 
1884     QM_TRY_INSPECT(
1885         const auto& metadataFile,
1886         quotaManager->GetDirectoryForOrigin(
1887             aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mOrigin));
1888 
1889     QM_TRY(MOZ_TO_RESULT(
1890         metadataFile->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
1891 
1892     QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
1893                                          metadataFile, GetLastModifiedTime));
1894 
1895     // Need to convert from milliseconds to microseconds.
1896     MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
1897     timestamp *= int64_t(PR_USEC_PER_MSEC);
1898 
1899     aFullOriginMetadata.mLastAccessTime = timestamp;
1900 
1901     return true;
1902   }
1903 
1904   return false;
1905 }
1906 
1907 }  // namespace
1908 
BackgroundThreadObject()1909 BackgroundThreadObject::BackgroundThreadObject()
1910     : mOwningThread(GetCurrentEventTarget()) {
1911   AssertIsOnOwningThread();
1912 }
1913 
BackgroundThreadObject(nsIEventTarget * aOwningThread)1914 BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread)
1915     : mOwningThread(aOwningThread) {}
1916 
1917 #ifdef DEBUG
1918 
AssertIsOnOwningThread() const1919 void BackgroundThreadObject::AssertIsOnOwningThread() const {
1920   AssertIsOnBackgroundThread();
1921   MOZ_ASSERT(mOwningThread);
1922   bool current;
1923   MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
1924   MOZ_ASSERT(current);
1925 }
1926 
1927 #endif  // DEBUG
1928 
OwningThread() const1929 nsIEventTarget* BackgroundThreadObject::OwningThread() const {
1930   MOZ_ASSERT(mOwningThread);
1931   return mOwningThread;
1932 }
1933 
IsOnIOThread()1934 bool IsOnIOThread() {
1935   QuotaManager* quotaManager = QuotaManager::Get();
1936   NS_ASSERTION(quotaManager, "Must have a manager here!");
1937 
1938   bool currentThread;
1939   return NS_SUCCEEDED(
1940              quotaManager->IOThread()->IsOnCurrentThread(&currentThread)) &&
1941          currentThread;
1942 }
1943 
AssertIsOnIOThread()1944 void AssertIsOnIOThread() {
1945   NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
1946 }
1947 
DiagnosticAssertIsOnIOThread()1948 void DiagnosticAssertIsOnIOThread() { MOZ_DIAGNOSTIC_ASSERT(IsOnIOThread()); }
1949 
AssertCurrentThreadOwnsQuotaMutex()1950 void AssertCurrentThreadOwnsQuotaMutex() {
1951 #ifdef DEBUG
1952   QuotaManager* quotaManager = QuotaManager::Get();
1953   NS_ASSERTION(quotaManager, "Must have a manager here!");
1954 
1955   quotaManager->AssertCurrentThreadOwnsQuotaMutex();
1956 #endif
1957 }
1958 
ReportInternalError(const char * aFile,uint32_t aLine,const char * aStr)1959 void ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) {
1960   // Get leaf of file path
1961   for (const char* p = aFile; *p; ++p) {
1962     if (*p == '/' && *(p + 1)) {
1963       aFile = p + 1;
1964     }
1965   }
1966 
1967   nsContentUtils::LogSimpleConsoleError(
1968       NS_ConvertUTF8toUTF16(
1969           nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
1970       "quota", false /* Quota Manager is not active in private browsing mode */,
1971       true /* Quota Manager runs always in a chrome context */);
1972 }
1973 
1974 namespace {
1975 
1976 bool gInvalidateQuotaCache = false;
1977 StaticAutoPtr<nsString> gBasePath;
1978 StaticAutoPtr<nsString> gStorageName;
1979 StaticAutoPtr<nsCString> gBuildId;
1980 
1981 #ifdef DEBUG
1982 bool gQuotaManagerInitialized = false;
1983 #endif
1984 
1985 StaticRefPtr<QuotaManager> gInstance;
1986 mozilla::Atomic<bool> gShutdown(false);
1987 
1988 // A time stamp that can only be accessed on the main thread.
1989 TimeStamp gLastOSWake;
1990 
1991 using NormalOriginOpArray =
1992     nsTArray<CheckedUnsafePtr<NormalOriginOperationBase>>;
1993 StaticAutoPtr<NormalOriginOpArray> gNormalOriginOps;
1994 
RegisterNormalOriginOp(NormalOriginOperationBase & aNormalOriginOp)1995 void RegisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
1996   AssertIsOnBackgroundThread();
1997 
1998   if (!gNormalOriginOps) {
1999     gNormalOriginOps = new NormalOriginOpArray();
2000   }
2001 
2002   gNormalOriginOps->AppendElement(&aNormalOriginOp);
2003 }
2004 
UnregisterNormalOriginOp(NormalOriginOperationBase & aNormalOriginOp)2005 void UnregisterNormalOriginOp(NormalOriginOperationBase& aNormalOriginOp) {
2006   AssertIsOnBackgroundThread();
2007   MOZ_ASSERT(gNormalOriginOps);
2008 
2009   gNormalOriginOps->RemoveElement(&aNormalOriginOp);
2010 
2011   if (gNormalOriginOps->IsEmpty()) {
2012     gNormalOriginOps = nullptr;
2013   }
2014 }
2015 
2016 class StorageOperationBase {
2017  protected:
2018   struct OriginProps {
2019     enum Type { eChrome, eContent, eObsolete, eInvalid };
2020 
2021     NotNull<nsCOMPtr<nsIFile>> mDirectory;
2022     nsString mLeafName;
2023     nsCString mSpec;
2024     OriginAttributes mAttrs;
2025     int64_t mTimestamp;
2026     OriginMetadata mOriginMetadata;
2027     nsCString mOriginalSuffix;
2028 
2029     LazyInitializedOnceEarlyDestructible<const PersistenceType>
2030         mPersistenceType;
2031     Type mType;
2032     bool mNeedsRestore;
2033     bool mNeedsRestore2;
2034     bool mIgnore;
2035 
2036    public:
OriginPropsmozilla::dom::quota::__anoneea2a16e0911::StorageOperationBase::OriginProps2037     explicit OriginProps(MovingNotNull<nsCOMPtr<nsIFile>> aDirectory)
2038         : mDirectory(std::move(aDirectory)),
2039           mTimestamp(0),
2040           mType(eContent),
2041           mNeedsRestore(false),
2042           mNeedsRestore2(false),
2043           mIgnore(false) {}
2044 
2045     template <typename PersistenceTypeFunc>
2046     nsresult Init(PersistenceTypeFunc&& aPersistenceTypeFunc);
2047   };
2048 
2049   nsTArray<OriginProps> mOriginProps;
2050 
2051   nsCOMPtr<nsIFile> mDirectory;
2052 
2053  public:
StorageOperationBase(nsIFile * aDirectory)2054   explicit StorageOperationBase(nsIFile* aDirectory) : mDirectory(aDirectory) {
2055     AssertIsOnIOThread();
2056   }
2057 
2058   NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
2059 
2060  protected:
2061   virtual ~StorageOperationBase() = default;
2062 
2063   nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
2064                                 nsACString& aGroup, nsACString& aOrigin,
2065                                 Nullable<bool>& aIsApp);
2066 
2067   // Upgrade helper to load the contents of ".metadata-v2" files from previous
2068   // schema versions.  Although QuotaManager has a similar GetDirectoryMetadata2
2069   // method, it is only intended to read current version ".metadata-v2" files.
2070   // And unlike the old ".metadata" files, the ".metadata-v2" format can evolve
2071   // because our "storage.sqlite" lets us track the overall version of the
2072   // storage directory.
2073   nsresult GetDirectoryMetadata2(nsIFile* aDirectory, int64_t& aTimestamp,
2074                                  nsACString& aSuffix, nsACString& aGroup,
2075                                  nsACString& aOrigin, bool& aIsApp);
2076 
2077   int64_t GetOriginLastModifiedTime(const OriginProps& aOriginProps);
2078 
2079   nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
2080 
2081   /**
2082    * Rename the origin if the origin string generation from nsIPrincipal
2083    * changed. This consists of renaming the origin in the metadata files and
2084    * renaming the origin directory itself. For simplicity, the origin in
2085    * metadata files is not actually updated, but the metadata files are
2086    * recreated instead.
2087    *
2088    * @param  aOriginProps the properties of the origin to check.
2089    *
2090    * @return whether origin was renamed.
2091    */
2092   Result<bool, nsresult> MaybeRenameOrigin(const OriginProps& aOriginProps);
2093 
2094   nsresult ProcessOriginDirectories();
2095 
2096   virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
2097 };
2098 
2099 class MOZ_STACK_CLASS OriginParser final {
2100  public:
2101   enum ResultType { InvalidOrigin, ObsoleteOrigin, ValidOrigin };
2102 
2103  private:
2104   using Tokenizer =
2105       nsCCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>;
2106 
2107   enum SchemeType { eNone, eFile, eAbout, eChrome };
2108 
2109   enum State {
2110     eExpectingAppIdOrScheme,
2111     eExpectingInMozBrowser,
2112     eExpectingScheme,
2113     eExpectingEmptyToken1,
2114     eExpectingEmptyToken2,
2115     eExpectingEmptyTokenOrUniversalFileOrigin,
2116     eExpectingHost,
2117     eExpectingPort,
2118     eExpectingEmptyTokenOrDriveLetterOrPathnameComponent,
2119     eExpectingEmptyTokenOrPathnameComponent,
2120     eExpectingEmptyToken1OrHost,
2121 
2122     // We transit from eExpectingHost to this state when we encounter a host
2123     // beginning with "[" which indicates an IPv6 literal. Because we mangle the
2124     // IPv6 ":" delimiter to be a "+", we will receive separate tokens for each
2125     // portion of the IPv6 address, including a final token that ends with "]".
2126     // (Note that we do not mangle "[" or "]".) Note that the URL spec
2127     // explicitly disclaims support for "<zone_id>" and so we don't have to deal
2128     // with that.
2129     eExpectingIPV6Token,
2130     eComplete,
2131     eHandledTrailingSeparator
2132   };
2133 
2134   const nsCString mOrigin;
2135   Tokenizer mTokenizer;
2136 
2137   nsCString mScheme;
2138   nsCString mHost;
2139   Nullable<uint32_t> mPort;
2140   nsTArray<nsCString> mPathnameComponents;
2141   nsCString mHandledTokens;
2142 
2143   SchemeType mSchemeType;
2144   State mState;
2145   bool mInIsolatedMozBrowser;
2146   bool mUniversalFileOrigin;
2147   bool mMaybeDriveLetter;
2148   bool mError;
2149   bool mMaybeObsolete;
2150 
2151   // Number of group which a IPv6 address has. Should be less than 9.
2152   uint8_t mIPGroup;
2153 
2154  public:
OriginParser(const nsACString & aOrigin)2155   explicit OriginParser(const nsACString& aOrigin)
2156       : mOrigin(aOrigin),
2157         mTokenizer(aOrigin, '+'),
2158         mPort(),
2159         mSchemeType(eNone),
2160         mState(eExpectingAppIdOrScheme),
2161         mInIsolatedMozBrowser(false),
2162         mUniversalFileOrigin(false),
2163         mMaybeDriveLetter(false),
2164         mError(false),
2165         mMaybeObsolete(false),
2166         mIPGroup(0) {}
2167 
2168   static ResultType ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
2169                                 OriginAttributes* aAttrs,
2170                                 nsCString& aOriginalSuffix);
2171 
2172   ResultType Parse(nsACString& aSpec);
2173 
2174  private:
2175   void HandleScheme(const nsDependentCSubstring& aToken);
2176 
2177   void HandlePathnameComponent(const nsDependentCSubstring& aToken);
2178 
2179   void HandleToken(const nsDependentCSubstring& aToken);
2180 
2181   void HandleTrailingSeparator();
2182 };
2183 
2184 class RepositoryOperationBase : public StorageOperationBase {
2185  public:
RepositoryOperationBase(nsIFile * aDirectory)2186   explicit RepositoryOperationBase(nsIFile* aDirectory)
2187       : StorageOperationBase(aDirectory) {}
2188 
2189   nsresult ProcessRepository();
2190 
2191  protected:
2192   virtual ~RepositoryOperationBase() = default;
2193 
2194   template <typename UpgradeMethod>
2195   nsresult MaybeUpgradeClients(const OriginProps& aOriginsProps,
2196                                UpgradeMethod aMethod);
2197 
2198  private:
2199   virtual PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) = 0;
2200 
2201   virtual nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2202                                           bool* aRemoved) = 0;
2203 
2204   virtual nsresult PrepareClientDirectory(nsIFile* aFile,
2205                                           const nsAString& aLeafName,
2206                                           bool& aRemoved);
2207 };
2208 
2209 class CreateOrUpgradeDirectoryMetadataHelper final
2210     : public RepositoryOperationBase {
2211   nsCOMPtr<nsIFile> mPermanentStorageDir;
2212 
2213   // The legacy PersistenceType, before the default repository introduction.
2214   enum class LegacyPersistenceType {
2215     Persistent = 0,
2216     Temporary
2217     // The PersistenceType had also PERSISTENCE_TYPE_INVALID, but we don't need
2218     // it here.
2219   };
2220 
2221   LazyInitializedOnce<const LegacyPersistenceType> mLegacyPersistenceType;
2222 
2223  public:
CreateOrUpgradeDirectoryMetadataHelper(nsIFile * aDirectory)2224   explicit CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory)
2225       : RepositoryOperationBase(aDirectory) {}
2226 
2227   nsresult Init();
2228 
2229  private:
2230   Maybe<LegacyPersistenceType> LegacyPersistenceTypeFromFile(nsIFile& aFile,
2231                                                              const fallible_t&);
2232 
2233   PersistenceType PersistenceTypeFromLegacyPersistentSpec(
2234       const nsCString& aSpec);
2235 
2236   PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
2237 
2238   nsresult MaybeUpgradeOriginDirectory(nsIFile* aDirectory);
2239 
2240   nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2241                                   bool* aRemoved) override;
2242 
2243   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2244 };
2245 
2246 class UpgradeStorageHelperBase : public RepositoryOperationBase {
2247   LazyInitializedOnce<const PersistenceType> mPersistenceType;
2248 
2249  public:
UpgradeStorageHelperBase(nsIFile * aDirectory)2250   explicit UpgradeStorageHelperBase(nsIFile* aDirectory)
2251       : RepositoryOperationBase(aDirectory) {}
2252 
2253   nsresult Init();
2254 
2255  private:
2256   PersistenceType PersistenceTypeFromSpec(const nsCString& aSpec) override;
2257 };
2258 
2259 class UpgradeStorageFrom0_0To1_0Helper final : public UpgradeStorageHelperBase {
2260  public:
UpgradeStorageFrom0_0To1_0Helper(nsIFile * aDirectory)2261   explicit UpgradeStorageFrom0_0To1_0Helper(nsIFile* aDirectory)
2262       : UpgradeStorageHelperBase(aDirectory) {}
2263 
2264  private:
2265   nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2266                                   bool* aRemoved) override;
2267 
2268   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2269 };
2270 
2271 class UpgradeStorageFrom1_0To2_0Helper final : public UpgradeStorageHelperBase {
2272  public:
UpgradeStorageFrom1_0To2_0Helper(nsIFile * aDirectory)2273   explicit UpgradeStorageFrom1_0To2_0Helper(nsIFile* aDirectory)
2274       : UpgradeStorageHelperBase(aDirectory) {}
2275 
2276  private:
2277   nsresult MaybeRemoveMorgueDirectory(const OriginProps& aOriginProps);
2278 
2279   /**
2280    * Remove the origin directory if appId is present in origin attributes.
2281    *
2282    * @param aOriginProps the properties of the origin to check.
2283    *
2284    * @return whether the origin directory was removed.
2285    */
2286   Result<bool, nsresult> MaybeRemoveAppsData(const OriginProps& aOriginProps);
2287 
2288   nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2289                                   bool* aRemoved) override;
2290 
2291   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2292 };
2293 
2294 class UpgradeStorageFrom2_0To2_1Helper final : public UpgradeStorageHelperBase {
2295  public:
UpgradeStorageFrom2_0To2_1Helper(nsIFile * aDirectory)2296   explicit UpgradeStorageFrom2_0To2_1Helper(nsIFile* aDirectory)
2297       : UpgradeStorageHelperBase(aDirectory) {}
2298 
2299  private:
2300   nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2301                                   bool* aRemoved) override;
2302 
2303   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2304 };
2305 
2306 class UpgradeStorageFrom2_1To2_2Helper final : public UpgradeStorageHelperBase {
2307  public:
UpgradeStorageFrom2_1To2_2Helper(nsIFile * aDirectory)2308   explicit UpgradeStorageFrom2_1To2_2Helper(nsIFile* aDirectory)
2309       : UpgradeStorageHelperBase(aDirectory) {}
2310 
2311  private:
2312   nsresult PrepareOriginDirectory(OriginProps& aOriginProps,
2313                                   bool* aRemoved) override;
2314 
2315   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2316 
2317   nsresult PrepareClientDirectory(nsIFile* aFile, const nsAString& aLeafName,
2318                                   bool& aRemoved) override;
2319 };
2320 
2321 class RestoreDirectoryMetadata2Helper final : public StorageOperationBase {
2322   LazyInitializedOnce<const PersistenceType> mPersistenceType;
2323 
2324  public:
RestoreDirectoryMetadata2Helper(nsIFile * aDirectory)2325   explicit RestoreDirectoryMetadata2Helper(nsIFile* aDirectory)
2326       : StorageOperationBase(aDirectory) {}
2327 
2328   nsresult Init();
2329 
2330   nsresult RestoreMetadata2File();
2331 
2332  private:
2333   nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override;
2334 };
2335 
MakeSanitizedOriginCString(const nsACString & aOrigin)2336 auto MakeSanitizedOriginCString(const nsACString& aOrigin) {
2337 #ifdef XP_WIN
2338   NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars,
2339                        FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
2340                "Illegal file characters have changed!");
2341 #endif
2342 
2343   nsAutoCString res{aOrigin};
2344 
2345   res.ReplaceChar(QuotaManager::kReplaceChars, '+');
2346 
2347   return res;
2348 }
2349 
MakeSanitizedOriginString(const nsACString & aOrigin)2350 auto MakeSanitizedOriginString(const nsACString& aOrigin) {
2351   // An origin string is ASCII-only, since it is obtained via
2352   // nsIPrincipal::GetOrigin, which returns an ACString.
2353   return NS_ConvertASCIItoUTF16(MakeSanitizedOriginCString(aOrigin));
2354 }
2355 
GetPathForStorage(nsIFile & aBaseDir,const nsAString & aStorageName)2356 Result<nsAutoString, nsresult> GetPathForStorage(
2357     nsIFile& aBaseDir, const nsAString& aStorageName) {
2358   QM_TRY_INSPECT(const auto& storageDir,
2359                  CloneFileAndAppend(aBaseDir, aStorageName));
2360 
2361   QM_TRY_RETURN(
2362       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, storageDir, GetPath));
2363 }
2364 
GetLastModifiedTime(PersistenceType aPersistenceType,nsIFile & aFile)2365 int64_t GetLastModifiedTime(PersistenceType aPersistenceType, nsIFile& aFile) {
2366   AssertIsOnIOThread();
2367 
2368   class MOZ_STACK_CLASS Helper final {
2369    public:
2370     static nsresult GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) {
2371       AssertIsOnIOThread();
2372       MOZ_ASSERT(aFile);
2373       MOZ_ASSERT(aTimestamp);
2374 
2375       QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*aFile));
2376 
2377       switch (dirEntryKind) {
2378         case nsIFileKind::ExistsAsDirectory:
2379           QM_TRY(CollectEachFile(
2380               *aFile,
2381               [&aTimestamp](const nsCOMPtr<nsIFile>& file)
2382                   -> Result<mozilla::Ok, nsresult> {
2383                 QM_TRY(MOZ_TO_RESULT(GetLastModifiedTime(file, aTimestamp)));
2384 
2385                 return Ok{};
2386               }));
2387           break;
2388 
2389         case nsIFileKind::ExistsAsFile: {
2390           QM_TRY_INSPECT(const auto& leafName,
2391                          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, aFile,
2392                                                            GetLeafName));
2393 
2394           // Bug 1595445 will handle unknown files here.
2395 
2396           if (IsOriginMetadata(leafName) || IsTempMetadata(leafName) ||
2397               IsDotFile(leafName)) {
2398             return NS_OK;
2399           }
2400 
2401           QM_TRY_UNWRAP(int64_t timestamp, MOZ_TO_RESULT_INVOKE_MEMBER(
2402                                                aFile, GetLastModifiedTime));
2403 
2404           // Need to convert from milliseconds to microseconds.
2405           MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp);
2406           timestamp *= int64_t(PR_USEC_PER_MSEC);
2407 
2408           if (timestamp > *aTimestamp) {
2409             *aTimestamp = timestamp;
2410           }
2411           break;
2412         }
2413 
2414         case nsIFileKind::DoesNotExist:
2415           // Ignore files that got removed externally while iterating.
2416           break;
2417       }
2418 
2419       return NS_OK;
2420     }
2421   };
2422 
2423   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
2424     return PR_Now();
2425   }
2426 
2427   int64_t timestamp = INT64_MIN;
2428   nsresult rv = Helper::GetLastModifiedTime(&aFile, &timestamp);
2429   if (NS_FAILED(rv)) {
2430     timestamp = PR_Now();
2431   }
2432 
2433   // XXX if there were no suitable files for getting last modified time
2434   // (timestamp is still set to INT64_MIN), we should return the current time
2435   // instead of returning INT64_MIN.
2436 
2437   return timestamp;
2438 }
2439 
2440 // Returns a bool indicating whether the directory was newly created.
EnsureDirectory(nsIFile & aDirectory)2441 Result<bool, nsresult> EnsureDirectory(nsIFile& aDirectory) {
2442   AssertIsOnIOThread();
2443 
2444   // Callers call this function without checking if the directory already
2445   // exists (idempotent usage). QM_OR_ELSE_WARN_IF is not used here since we
2446   // just want to log NS_ERROR_FILE_ALREADY_EXISTS result and not spam the
2447   // reports.
2448   QM_TRY_INSPECT(const auto& exists,
2449                  QM_OR_ELSE_LOG_VERBOSE_IF(
2450                      // Expression.
2451                      MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Create,
2452                                                  nsIFile::DIRECTORY_TYPE, 0755,
2453                                                  /* aSkipAncestors = */ false)
2454                          .map([](Ok) { return false; }),
2455                      // Predicate.
2456                      IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
2457                      // Fallback.
2458                      ErrToOk<true>));
2459 
2460   if (exists) {
2461     QM_TRY_INSPECT(const bool& isDirectory,
2462                    MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, IsDirectory));
2463     QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
2464   }
2465 
2466   return !exists;
2467 }
2468 
2469 enum FileFlag { Truncate, Update, Append };
2470 
GetOutputStream(nsIFile & aFile,FileFlag aFileFlag)2471 Result<nsCOMPtr<nsIOutputStream>, nsresult> GetOutputStream(
2472     nsIFile& aFile, FileFlag aFileFlag) {
2473   AssertIsOnIOThread();
2474 
2475   switch (aFileFlag) {
2476     case FileFlag::Truncate:
2477       QM_TRY_RETURN(NS_NewLocalFileOutputStream(&aFile));
2478 
2479     case FileFlag::Update: {
2480       QM_TRY_INSPECT(const bool& exists,
2481                      MOZ_TO_RESULT_INVOKE_MEMBER(&aFile, Exists));
2482 
2483       if (!exists) {
2484         return nsCOMPtr<nsIOutputStream>();
2485       }
2486 
2487       QM_TRY_INSPECT(const auto& stream, NS_NewLocalFileStream(&aFile));
2488 
2489       nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(stream);
2490       QM_TRY(OkIf(outputStream), Err(NS_ERROR_FAILURE));
2491 
2492       return outputStream;
2493     }
2494 
2495     case FileFlag::Append:
2496       QM_TRY_RETURN(NS_NewLocalFileOutputStream(
2497           &aFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND));
2498 
2499     default:
2500       MOZ_CRASH("Should never get here!");
2501   }
2502 }
2503 
GetBinaryOutputStream(nsIFile & aFile,FileFlag aFileFlag)2504 Result<nsCOMPtr<nsIBinaryOutputStream>, nsresult> GetBinaryOutputStream(
2505     nsIFile& aFile, FileFlag aFileFlag) {
2506   QM_TRY_UNWRAP(auto outputStream, GetOutputStream(aFile, aFileFlag));
2507 
2508   QM_TRY(OkIf(outputStream), Err(NS_ERROR_UNEXPECTED));
2509 
2510   return nsCOMPtr<nsIBinaryOutputStream>(
2511       NS_NewObjectOutputStream(outputStream));
2512 }
2513 
GetJarPrefix(bool aInIsolatedMozBrowser,nsACString & aJarPrefix)2514 void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
2515   aJarPrefix.Truncate();
2516 
2517   // Fallback.
2518   if (!aInIsolatedMozBrowser) {
2519     return;
2520   }
2521 
2522   // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
2523   // 1320404).
2524   // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
2525   aJarPrefix.AppendInt(0);  // TODO: this is the appId, to be removed.
2526   aJarPrefix.Append('+');
2527   aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
2528   aJarPrefix.Append('+');
2529 }
2530 
CreateDirectoryMetadata(nsIFile & aDirectory,int64_t aTimestamp,const OriginMetadata & aOriginMetadata)2531 nsresult CreateDirectoryMetadata(nsIFile& aDirectory, int64_t aTimestamp,
2532                                  const OriginMetadata& aOriginMetadata) {
2533   AssertIsOnIOThread();
2534 
2535   OriginAttributes groupAttributes;
2536 
2537   nsCString groupNoSuffix;
2538   QM_TRY(OkIf(groupAttributes.PopulateFromOrigin(aOriginMetadata.mGroup,
2539                                                  groupNoSuffix)),
2540          NS_ERROR_FAILURE);
2541 
2542   nsCString groupPrefix;
2543   GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
2544 
2545   nsCString group = groupPrefix + groupNoSuffix;
2546 
2547   OriginAttributes originAttributes;
2548 
2549   nsCString originNoSuffix;
2550   QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
2551                                                   originNoSuffix)),
2552          NS_ERROR_FAILURE);
2553 
2554   nsCString originPrefix;
2555   GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
2556 
2557   nsCString origin = originPrefix + originNoSuffix;
2558 
2559   MOZ_ASSERT(groupPrefix == originPrefix);
2560 
2561   QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2562                                        nsCOMPtr<nsIFile>, aDirectory, Clone));
2563 
2564   QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_TMP_FILE_NAME))));
2565 
2566   QM_TRY_INSPECT(const auto& stream,
2567                  GetBinaryOutputStream(*file, FileFlag::Truncate));
2568   MOZ_ASSERT(stream);
2569 
2570   QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
2571 
2572   QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(group.get())));
2573 
2574   QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(origin.get())));
2575 
2576   // Currently unused (used to be isApp).
2577   QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
2578 
2579   QM_TRY(MOZ_TO_RESULT(stream->Flush()));
2580 
2581   QM_TRY(MOZ_TO_RESULT(stream->Close()));
2582 
2583   QM_TRY(MOZ_TO_RESULT(
2584       file->RenameTo(nullptr, nsLiteralString(METADATA_FILE_NAME))));
2585 
2586   return NS_OK;
2587 }
2588 
CreateDirectoryMetadata2(nsIFile & aDirectory,int64_t aTimestamp,bool aPersisted,const OriginMetadata & aOriginMetadata)2589 nsresult CreateDirectoryMetadata2(nsIFile& aDirectory, int64_t aTimestamp,
2590                                   bool aPersisted,
2591                                   const OriginMetadata& aOriginMetadata) {
2592   AssertIsOnIOThread();
2593 
2594   QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2595                                        nsCOMPtr<nsIFile>, aDirectory, Clone));
2596 
2597   QM_TRY(
2598       MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_TMP_FILE_NAME))));
2599 
2600   QM_TRY_INSPECT(const auto& stream,
2601                  GetBinaryOutputStream(*file, FileFlag::Truncate));
2602   MOZ_ASSERT(stream);
2603 
2604   QM_TRY(MOZ_TO_RESULT(stream->Write64(aTimestamp)));
2605 
2606   QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(aPersisted)));
2607 
2608   // Reserved data 1
2609   QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
2610 
2611   // Reserved data 2
2612   QM_TRY(MOZ_TO_RESULT(stream->Write32(0)));
2613 
2614   // The suffix isn't used right now, but we might need it in future. It's
2615   // a bit of redundancy we can live with given how painful is to upgrade
2616   // metadata files.
2617   QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mSuffix.get())));
2618 
2619   QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mGroup.get())));
2620 
2621   QM_TRY(MOZ_TO_RESULT(stream->WriteStringZ(aOriginMetadata.mOrigin.get())));
2622 
2623   // Currently unused (used to be isApp).
2624   QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
2625 
2626   QM_TRY(MOZ_TO_RESULT(stream->Flush()));
2627 
2628   QM_TRY(MOZ_TO_RESULT(stream->Close()));
2629 
2630   QM_TRY(MOZ_TO_RESULT(
2631       file->RenameTo(nullptr, nsLiteralString(METADATA_V2_FILE_NAME))));
2632 
2633   return NS_OK;
2634 }
2635 
GetBinaryInputStream(nsIFile & aDirectory,const nsAString & aFilename)2636 Result<nsCOMPtr<nsIBinaryInputStream>, nsresult> GetBinaryInputStream(
2637     nsIFile& aDirectory, const nsAString& aFilename) {
2638   MOZ_ASSERT(!NS_IsMainThread());
2639 
2640   QM_TRY_INSPECT(const auto& file, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
2641                                        nsCOMPtr<nsIFile>, aDirectory, Clone));
2642 
2643   QM_TRY(MOZ_TO_RESULT(file->Append(aFilename)));
2644 
2645   QM_TRY_UNWRAP(auto stream, NS_NewLocalFileInputStream(file));
2646 
2647   QM_TRY_INSPECT(const auto& bufferedStream,
2648                  NS_NewBufferedInputStream(stream.forget(), 512));
2649 
2650   QM_TRY(OkIf(bufferedStream), Err(NS_ERROR_FAILURE));
2651 
2652   return nsCOMPtr<nsIBinaryInputStream>(
2653       NS_NewObjectInputStream(bufferedStream));
2654 }
2655 
2656 // This method computes and returns our best guess for the temporary storage
2657 // limit (in bytes), based on disk capacity.
GetTemporaryStorageLimit(nsIFile & aStorageDir)2658 Result<uint64_t, nsresult> GetTemporaryStorageLimit(nsIFile& aStorageDir) {
2659   // The fixed limit pref can be used to override temporary storage limit
2660   // calculation.
2661   if (StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit() >= 0) {
2662     return static_cast<uint64_t>(
2663                StaticPrefs::dom_quotaManager_temporaryStorage_fixedLimit()) *
2664            1024;
2665   }
2666 
2667   // Check for disk capacity of user's device on which storage directory lives.
2668   QM_TRY_INSPECT(const int64_t& diskCapacity,
2669                  MOZ_TO_RESULT_INVOKE_MEMBER(aStorageDir, GetDiskCapacity));
2670 
2671   MOZ_ASSERT(diskCapacity >= 0);
2672 
2673   // Allow temporary storage to consume up to 50% of disk capacity.
2674   return diskCapacity / 2u;
2675 }
2676 
IsOriginUnaccessed(const FullOriginMetadata & aFullOriginMetadata,const int64_t aRecentTime)2677 bool IsOriginUnaccessed(const FullOriginMetadata& aFullOriginMetadata,
2678                         const int64_t aRecentTime) {
2679   if (aFullOriginMetadata.mLastAccessTime > aRecentTime) {
2680     return false;
2681   }
2682 
2683   return (aRecentTime - aFullOriginMetadata.mLastAccessTime) / PR_USEC_PER_SEC >
2684          StaticPrefs::dom_quotaManager_unaccessedForLongTimeThresholdSec();
2685 }
2686 
2687 }  // namespace
2688 
2689 /*******************************************************************************
2690  * Exported functions
2691  ******************************************************************************/
2692 
InitializeQuotaManager()2693 void InitializeQuotaManager() {
2694   MOZ_ASSERT(XRE_IsParentProcess());
2695   MOZ_ASSERT(NS_IsMainThread());
2696   MOZ_ASSERT(!gQuotaManagerInitialized);
2697 
2698 #ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
2699   ScopedLogExtraInfo::Initialize();
2700 #endif
2701 
2702   if (!QuotaManager::IsRunningGTests()) {
2703     // This service has to be started on the main thread currently.
2704     const nsCOMPtr<mozIStorageService> ss =
2705         do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
2706 
2707     QM_WARNONLY_TRY(OkIf(ss));
2708   }
2709 
2710   QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
2711 
2712 #ifdef DEBUG
2713   gQuotaManagerInitialized = true;
2714 #endif
2715 }
2716 
AllocPQuotaParent()2717 PQuotaParent* AllocPQuotaParent() {
2718   AssertIsOnBackgroundThread();
2719 
2720   if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
2721     return nullptr;
2722   }
2723 
2724   auto actor = MakeRefPtr<Quota>();
2725 
2726   return actor.forget().take();
2727 }
2728 
DeallocPQuotaParent(PQuotaParent * aActor)2729 bool DeallocPQuotaParent(PQuotaParent* aActor) {
2730   AssertIsOnBackgroundThread();
2731   MOZ_ASSERT(aActor);
2732 
2733   RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
2734   return true;
2735 }
2736 
RecvShutdownQuotaManager()2737 bool RecvShutdownQuotaManager() {
2738   AssertIsOnBackgroundThread();
2739 
2740   // If we are already in shutdown, don't call ShutdownInstance()
2741   // again and return true immediately. We shall see this incident
2742   // in Telemetry.
2743   // XXX todo: Make QM_TRY stacks thread-aware (Bug 1735124)
2744   // XXX todo: Active QM_TRY context for shutdown (Bug 1735170)
2745   QM_TRY(OkIf(!gShutdown), true);
2746 
2747   QuotaManager::ShutdownInstance();
2748 
2749   return true;
2750 }
2751 
2752 QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
2753 
2754 // static
Initialize()2755 nsresult QuotaManager::Observer::Initialize() {
2756   MOZ_ASSERT(NS_IsMainThread());
2757 
2758   RefPtr<Observer> observer = new Observer();
2759 
2760   nsresult rv = observer->Init();
2761   if (NS_WARN_IF(NS_FAILED(rv))) {
2762     return rv;
2763   }
2764 
2765   sInstance = observer;
2766 
2767   return NS_OK;
2768 }
2769 
2770 // static
ShutdownCompleted()2771 void QuotaManager::Observer::ShutdownCompleted() {
2772   MOZ_ASSERT(NS_IsMainThread());
2773   MOZ_ASSERT(sInstance);
2774 
2775   sInstance->mShutdownComplete = true;
2776 }
2777 
Init()2778 nsresult QuotaManager::Observer::Init() {
2779   MOZ_ASSERT(NS_IsMainThread());
2780 
2781   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2782   if (NS_WARN_IF(!obs)) {
2783     return NS_ERROR_FAILURE;
2784   }
2785 
2786   // XXX: Improve the way that we remove observer in failure cases.
2787   nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
2788   if (NS_WARN_IF(NS_FAILED(rv))) {
2789     return rv;
2790   }
2791 
2792   rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
2793   if (NS_WARN_IF(NS_FAILED(rv))) {
2794     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2795     return rv;
2796   }
2797 
2798   rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
2799   if (NS_WARN_IF(NS_FAILED(rv))) {
2800     obs->RemoveObserver(this, kProfileDoChangeTopic);
2801     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2802     return rv;
2803   }
2804 
2805   rv = obs->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
2806   if (NS_WARN_IF(NS_FAILED(rv))) {
2807     obs->RemoveObserver(this, kProfileDoChangeTopic);
2808     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
2809     obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
2810     return rv;
2811   }
2812 
2813   return NS_OK;
2814 }
2815 
Shutdown()2816 nsresult QuotaManager::Observer::Shutdown() {
2817   MOZ_ASSERT(NS_IsMainThread());
2818 
2819   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2820   if (NS_WARN_IF(!obs)) {
2821     return NS_ERROR_FAILURE;
2822   }
2823 
2824   MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC));
2825   MOZ_ALWAYS_SUCCEEDS(
2826       obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
2827   MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
2828   MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
2829 
2830   sInstance = nullptr;
2831 
2832   // In general, the instance will have died after the latter removal call, so
2833   // it's not safe to do anything after that point.
2834   // However, Shutdown is currently called from Observe which is called by the
2835   // Observer Service which holds a strong reference to the observer while the
2836   // Observe method is being called.
2837 
2838   return NS_OK;
2839 }
2840 
NS_IMPL_ISUPPORTS(QuotaManager::Observer,nsIObserver)2841 NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
2842 
2843 NS_IMETHODIMP
2844 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
2845                                 const char16_t* aData) {
2846   MOZ_ASSERT(NS_IsMainThread());
2847 
2848   nsresult rv;
2849 
2850   if (!strcmp(aTopic, kProfileDoChangeTopic)) {
2851     if (NS_WARN_IF(gBasePath)) {
2852       NS_WARNING(
2853           "profile-before-change-qm must precede repeated "
2854           "profile-do-change!");
2855       return NS_OK;
2856     }
2857 
2858     Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, true);
2859 
2860     gBasePath = new nsString();
2861 
2862     nsCOMPtr<nsIFile> baseDir;
2863     rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
2864                                 getter_AddRefs(baseDir));
2865     if (NS_FAILED(rv)) {
2866       rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
2867                                   getter_AddRefs(baseDir));
2868     }
2869     if (NS_WARN_IF(NS_FAILED(rv))) {
2870       return rv;
2871     }
2872 
2873     rv = baseDir->GetPath(*gBasePath);
2874     if (NS_WARN_IF(NS_FAILED(rv))) {
2875       return rv;
2876     }
2877 
2878     gStorageName = new nsString();
2879 
2880     rv = Preferences::GetString("dom.quotaManager.storageName", *gStorageName);
2881     if (NS_FAILED(rv)) {
2882       *gStorageName = kStorageName;
2883     }
2884 
2885     gBuildId = new nsCString();
2886 
2887     nsCOMPtr<nsIPlatformInfo> platformInfo =
2888         do_GetService("@mozilla.org/xre/app-info;1");
2889     if (NS_WARN_IF(!platformInfo)) {
2890       return NS_ERROR_FAILURE;
2891     }
2892 
2893     rv = platformInfo->GetPlatformBuildID(*gBuildId);
2894     if (NS_WARN_IF(NS_FAILED(rv))) {
2895       return rv;
2896     }
2897 
2898     MaybeEnableNextGenLocalStorage();
2899 
2900     return NS_OK;
2901   }
2902 
2903   if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
2904     if (NS_WARN_IF(!gBasePath)) {
2905       NS_WARNING("profile-do-change must precede profile-before-change-qm!");
2906       return NS_OK;
2907     }
2908 
2909     // mPendingProfileChange is our re-entrancy guard (the nested event loop
2910     // below may cause re-entrancy).
2911     if (mPendingProfileChange) {
2912       return NS_OK;
2913     }
2914 
2915     AutoRestore<bool> pending(mPendingProfileChange);
2916     mPendingProfileChange = true;
2917 
2918     mShutdownComplete = false;
2919 
2920     PBackgroundChild* backgroundActor =
2921         BackgroundChild::GetOrCreateForCurrentThread();
2922     if (NS_WARN_IF(!backgroundActor)) {
2923       return NS_ERROR_FAILURE;
2924     }
2925 
2926     if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
2927       return NS_ERROR_FAILURE;
2928     }
2929 
2930     MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
2931         "QuotaManager::Observer::Observe profile-before-change-qm"_ns,
2932         [&]() { return mShutdownComplete; }));
2933 
2934     gBasePath = nullptr;
2935 
2936     gStorageName = nullptr;
2937 
2938     gBuildId = nullptr;
2939 
2940     Telemetry::SetEventRecordingEnabled("dom.quota.try"_ns, false);
2941 
2942     return NS_OK;
2943   }
2944 
2945   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
2946     rv = Shutdown();
2947     if (NS_WARN_IF(NS_FAILED(rv))) {
2948       return rv;
2949     }
2950 
2951     return NS_OK;
2952   }
2953 
2954   if (!strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
2955     gLastOSWake = TimeStamp::Now();
2956 
2957     return NS_OK;
2958   }
2959 
2960   NS_WARNING("Unknown observer topic!");
2961   return NS_OK;
2962 }
2963 
2964 /*******************************************************************************
2965  * Quota object
2966  ******************************************************************************/
2967 
AddRef()2968 void QuotaObject::AddRef() {
2969   QuotaManager* quotaManager = QuotaManager::Get();
2970   if (!quotaManager) {
2971     NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2972 
2973     ++mRefCnt;
2974 
2975     return;
2976   }
2977 
2978   MutexAutoLock lock(quotaManager->mQuotaMutex);
2979 
2980   ++mRefCnt;
2981 }
2982 
Release()2983 void QuotaObject::Release() {
2984   QuotaManager* quotaManager = QuotaManager::Get();
2985   if (!quotaManager) {
2986     NS_ERROR("Null quota manager, this shouldn't happen, possible leak!");
2987 
2988     nsrefcnt count = --mRefCnt;
2989     if (count == 0) {
2990       mRefCnt = 1;
2991       delete this;
2992     }
2993 
2994     return;
2995   }
2996 
2997   {
2998     MutexAutoLock lock(quotaManager->mQuotaMutex);
2999 
3000     --mRefCnt;
3001 
3002     if (mRefCnt > 0) {
3003       return;
3004     }
3005 
3006     if (mOriginInfo) {
3007       mOriginInfo->mQuotaObjects.Remove(mPath);
3008     }
3009   }
3010 
3011   delete this;
3012 }
3013 
MaybeUpdateSize(int64_t aSize,bool aTruncate)3014 bool QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) {
3015   QuotaManager* quotaManager = QuotaManager::Get();
3016   MOZ_ASSERT(quotaManager);
3017 
3018   MutexAutoLock lock(quotaManager->mQuotaMutex);
3019 
3020   return LockedMaybeUpdateSize(aSize, aTruncate);
3021 }
3022 
IncreaseSize(int64_t aDelta)3023 bool QuotaObject::IncreaseSize(int64_t aDelta) {
3024   MOZ_ASSERT(aDelta >= 0);
3025 
3026   QuotaManager* quotaManager = QuotaManager::Get();
3027   MOZ_ASSERT(quotaManager);
3028 
3029   MutexAutoLock lock(quotaManager->mQuotaMutex);
3030 
3031   AssertNoOverflow(mSize, aDelta);
3032   int64_t size = mSize + aDelta;
3033 
3034   return LockedMaybeUpdateSize(size, /* aTruncate */ false);
3035 }
3036 
DisableQuotaCheck()3037 void QuotaObject::DisableQuotaCheck() {
3038   QuotaManager* quotaManager = QuotaManager::Get();
3039   MOZ_ASSERT(quotaManager);
3040 
3041   MutexAutoLock lock(quotaManager->mQuotaMutex);
3042 
3043   mQuotaCheckDisabled = true;
3044 }
3045 
EnableQuotaCheck()3046 void QuotaObject::EnableQuotaCheck() {
3047   QuotaManager* quotaManager = QuotaManager::Get();
3048   MOZ_ASSERT(quotaManager);
3049 
3050   MutexAutoLock lock(quotaManager->mQuotaMutex);
3051 
3052   mQuotaCheckDisabled = false;
3053 }
3054 
LockedMaybeUpdateSize(int64_t aSize,bool aTruncate)3055 bool QuotaObject::LockedMaybeUpdateSize(int64_t aSize, bool aTruncate) {
3056   QuotaManager* quotaManager = QuotaManager::Get();
3057   MOZ_ASSERT(quotaManager);
3058 
3059   quotaManager->mQuotaMutex.AssertCurrentThreadOwns();
3060 
3061   if (mWritingDone == false && mOriginInfo) {
3062     mWritingDone = true;
3063     StorageActivityService::SendActivity(mOriginInfo->mOrigin);
3064   }
3065 
3066   if (mQuotaCheckDisabled) {
3067     return true;
3068   }
3069 
3070   if (mSize == aSize) {
3071     return true;
3072   }
3073 
3074   if (!mOriginInfo) {
3075     mSize = aSize;
3076     return true;
3077   }
3078 
3079   GroupInfo* groupInfo = mOriginInfo->mGroupInfo;
3080   MOZ_ASSERT(groupInfo);
3081 
3082   if (mSize > aSize) {
3083     if (aTruncate) {
3084       const int64_t delta = mSize - aSize;
3085 
3086       AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta);
3087       quotaManager->mTemporaryStorageUsage -= delta;
3088 
3089       if (!mOriginInfo->LockedPersisted()) {
3090         AssertNoUnderflow(groupInfo->mUsage, delta);
3091         groupInfo->mUsage -= delta;
3092       }
3093 
3094       AssertNoUnderflow(mOriginInfo->mUsage, delta);
3095       mOriginInfo->mUsage -= delta;
3096 
3097       MOZ_ASSERT(mOriginInfo->mClientUsages[mClientType].isSome());
3098       AssertNoUnderflow(mOriginInfo->mClientUsages[mClientType].value(), delta);
3099       mOriginInfo->mClientUsages[mClientType] =
3100           Some(mOriginInfo->mClientUsages[mClientType].value() - delta);
3101 
3102       mSize = aSize;
3103     }
3104     return true;
3105   }
3106 
3107   MOZ_ASSERT(mSize < aSize);
3108 
3109   RefPtr<GroupInfo> complementaryGroupInfo =
3110       groupInfo->mGroupInfoPair->LockedGetGroupInfo(
3111           ComplementaryPersistenceType(groupInfo->mPersistenceType));
3112 
3113   uint64_t delta = aSize - mSize;
3114 
3115   AssertNoOverflow(mOriginInfo->mClientUsages[mClientType].valueOr(0), delta);
3116   uint64_t newClientUsage =
3117       mOriginInfo->mClientUsages[mClientType].valueOr(0) + delta;
3118 
3119   AssertNoOverflow(mOriginInfo->mUsage, delta);
3120   uint64_t newUsage = mOriginInfo->mUsage + delta;
3121 
3122   // Temporary storage has no limit for origin usage (there's a group and the
3123   // global limit though).
3124 
3125   uint64_t newGroupUsage = groupInfo->mUsage;
3126   if (!mOriginInfo->LockedPersisted()) {
3127     AssertNoOverflow(groupInfo->mUsage, delta);
3128     newGroupUsage += delta;
3129 
3130     uint64_t groupUsage = groupInfo->mUsage;
3131     if (complementaryGroupInfo) {
3132       AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
3133       groupUsage += complementaryGroupInfo->mUsage;
3134     }
3135 
3136     // Temporary storage has a hard limit for group usage (20 % of the global
3137     // limit).
3138     AssertNoOverflow(groupUsage, delta);
3139     if (groupUsage + delta > quotaManager->GetGroupLimit()) {
3140       return false;
3141     }
3142   }
3143 
3144   AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
3145   uint64_t newTemporaryStorageUsage =
3146       quotaManager->mTemporaryStorageUsage + delta;
3147 
3148   if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
3149     // This will block the thread without holding the lock while waitting.
3150 
3151     AutoTArray<RefPtr<OriginDirectoryLock>, 10> locks;
3152     uint64_t sizeToBeFreed;
3153 
3154     if (IsOnBackgroundThread()) {
3155       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3156 
3157       sizeToBeFreed = quotaManager->CollectOriginsForEviction(delta, locks);
3158     } else {
3159       sizeToBeFreed =
3160           quotaManager->LockedCollectOriginsForEviction(delta, locks);
3161     }
3162 
3163     if (!sizeToBeFreed) {
3164       uint64_t usage = quotaManager->mTemporaryStorageUsage;
3165 
3166       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3167 
3168       quotaManager->NotifyStoragePressure(usage);
3169 
3170       return false;
3171     }
3172 
3173     NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
3174 
3175     {
3176       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3177 
3178       for (const auto& lock : locks) {
3179         quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType(),
3180                                            lock->Origin());
3181       }
3182     }
3183 
3184     // Relocked.
3185 
3186     NS_ASSERTION(mOriginInfo, "How come?!");
3187 
3188     for (const auto& lock : locks) {
3189       MOZ_ASSERT(!(lock->GetPersistenceType() == groupInfo->mPersistenceType &&
3190                    lock->Origin() == mOriginInfo->mOrigin),
3191                  "Deleted itself!");
3192 
3193       quotaManager->LockedRemoveQuotaForOrigin(lock->GetPersistenceType(),
3194                                                lock->OriginMetadata());
3195     }
3196 
3197     // We unlocked and relocked several times so we need to recompute all the
3198     // essential variables and recheck the group limit.
3199 
3200     AssertNoUnderflow(aSize, mSize);
3201     delta = aSize - mSize;
3202 
3203     AssertNoOverflow(mOriginInfo->mClientUsages[mClientType].valueOr(0), delta);
3204     newClientUsage = mOriginInfo->mClientUsages[mClientType].valueOr(0) + delta;
3205 
3206     AssertNoOverflow(mOriginInfo->mUsage, delta);
3207     newUsage = mOriginInfo->mUsage + delta;
3208 
3209     newGroupUsage = groupInfo->mUsage;
3210     if (!mOriginInfo->LockedPersisted()) {
3211       AssertNoOverflow(groupInfo->mUsage, delta);
3212       newGroupUsage += delta;
3213 
3214       uint64_t groupUsage = groupInfo->mUsage;
3215       if (complementaryGroupInfo) {
3216         AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage);
3217         groupUsage += complementaryGroupInfo->mUsage;
3218       }
3219 
3220       AssertNoOverflow(groupUsage, delta);
3221       if (groupUsage + delta > quotaManager->GetGroupLimit()) {
3222         // Unfortunately some other thread increased the group usage in the
3223         // meantime and we are not below the group limit anymore.
3224 
3225         // However, the origin eviction must be finalized in this case too.
3226         MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3227 
3228         quotaManager->FinalizeOriginEviction(std::move(locks));
3229 
3230         return false;
3231       }
3232     }
3233 
3234     AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
3235     newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
3236 
3237     NS_ASSERTION(
3238         newTemporaryStorageUsage <= quotaManager->mTemporaryStorageLimit,
3239         "How come?!");
3240 
3241     // Ok, we successfully freed enough space and the operation can continue
3242     // without throwing the quota error.
3243     mOriginInfo->mClientUsages[mClientType] = Some(newClientUsage);
3244 
3245     mOriginInfo->mUsage = newUsage;
3246     if (!mOriginInfo->LockedPersisted()) {
3247       groupInfo->mUsage = newGroupUsage;
3248     }
3249     quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
3250     ;
3251 
3252     // Some other thread could increase the size in the meantime, but no more
3253     // than this one.
3254     MOZ_ASSERT(mSize < aSize);
3255     mSize = aSize;
3256 
3257     // Finally, release IO thread only objects and allow next synchronized
3258     // ops for the evicted origins.
3259     MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
3260 
3261     quotaManager->FinalizeOriginEviction(std::move(locks));
3262 
3263     return true;
3264   }
3265 
3266   mOriginInfo->mClientUsages[mClientType] = Some(newClientUsage);
3267 
3268   mOriginInfo->mUsage = newUsage;
3269   if (!mOriginInfo->LockedPersisted()) {
3270     groupInfo->mUsage = newGroupUsage;
3271   }
3272   quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
3273 
3274   mSize = aSize;
3275 
3276   return true;
3277 }
3278 
3279 /*******************************************************************************
3280  * Quota manager
3281  ******************************************************************************/
3282 
QuotaManager(const nsAString & aBasePath,const nsAString & aStorageName)3283 QuotaManager::QuotaManager(const nsAString& aBasePath,
3284                            const nsAString& aStorageName)
3285     : mQuotaMutex("QuotaManager.mQuotaMutex"),
3286       mBasePath(aBasePath),
3287       mStorageName(aStorageName),
3288       mTemporaryStorageUsage(0),
3289       mNextDirectoryLockId(0),
3290       mTemporaryStorageInitialized(false),
3291       mCacheUsable(false) {
3292   AssertIsOnOwningThread();
3293   MOZ_ASSERT(!gInstance);
3294 }
3295 
~QuotaManager()3296 QuotaManager::~QuotaManager() {
3297   AssertIsOnOwningThread();
3298   MOZ_ASSERT(!gInstance || gInstance == this);
3299 }
3300 
3301 // static
Initialize()3302 nsresult QuotaManager::Initialize() {
3303   MOZ_ASSERT(NS_IsMainThread());
3304 
3305   nsresult rv = Observer::Initialize();
3306   if (NS_WARN_IF(NS_FAILED(rv))) {
3307     return rv;
3308   }
3309 
3310   return NS_OK;
3311 }
3312 
3313 // static
3314 Result<MovingNotNull<RefPtr<QuotaManager>>, nsresult>
GetOrCreate()3315 QuotaManager::GetOrCreate() {
3316   AssertIsOnBackgroundThread();
3317 
3318   if (gInstance) {
3319     return WrapMovingNotNullUnchecked(RefPtr<QuotaManager>{gInstance});
3320   }
3321 
3322   QM_TRY(OkIf(gBasePath), Err(NS_ERROR_FAILURE), [](const auto&) {
3323     NS_WARNING(
3324         "Trying to create QuotaManager before profile-do-change! "
3325         "Forgot to call do_get_profile()?");
3326   });
3327 
3328   QM_TRY(OkIf(!IsShuttingDown()), Err(NS_ERROR_FAILURE), [](const auto&) {
3329     MOZ_ASSERT(false,
3330                "Trying to create QuotaManager after profile-before-change-qm!");
3331   });
3332 
3333   auto instance = MakeRefPtr<QuotaManager>(*gBasePath, *gStorageName);
3334 
3335   QM_TRY(MOZ_TO_RESULT(instance->Init()));
3336 
3337   gInstance = instance;
3338 
3339   return WrapMovingNotNullUnchecked(std::move(instance));
3340 }
3341 
EnsureCreated()3342 Result<Ok, nsresult> QuotaManager::EnsureCreated() {
3343   AssertIsOnBackgroundThread();
3344 
3345   QM_TRY_RETURN(GetOrCreate().map([](const auto& res) { return Ok{}; }))
3346 }
3347 
3348 // static
Get()3349 QuotaManager* QuotaManager::Get() {
3350   // Does not return an owning reference.
3351   return gInstance;
3352 }
3353 
3354 // static
IsShuttingDown()3355 bool QuotaManager::IsShuttingDown() { return gShutdown; }
3356 
3357 // static
ShutdownInstance()3358 void QuotaManager::ShutdownInstance() {
3359   AssertIsOnBackgroundThread();
3360 
3361   if (gInstance) {
3362     gInstance->Shutdown();
3363 
3364     gInstance = nullptr;
3365   }
3366 
3367   RefPtr<Runnable> runnable =
3368       NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
3369                              []() { Observer::ShutdownCompleted(); });
3370   MOZ_ASSERT(runnable);
3371 
3372   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
3373 }
3374 
3375 // static
IsOSMetadata(const nsAString & aFileName)3376 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
3377   return aFileName.EqualsLiteral(DSSTORE_FILE_NAME) ||
3378          aFileName.EqualsLiteral(DESKTOP_FILE_NAME) ||
3379          aFileName.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME) ||
3380          aFileName.LowerCaseEqualsLiteral(THUMBS_DB_FILE_NAME);
3381 }
3382 
3383 // static
IsDotFile(const nsAString & aFileName)3384 bool QuotaManager::IsDotFile(const nsAString& aFileName) {
3385   return aFileName.First() == char16_t('.');
3386 }
3387 
RegisterDirectoryLock(DirectoryLockImpl & aLock)3388 void QuotaManager::RegisterDirectoryLock(DirectoryLockImpl& aLock) {
3389   AssertIsOnOwningThread();
3390 
3391   mDirectoryLocks.AppendElement(WrapNotNullUnchecked(&aLock));
3392 
3393   if (aLock.ShouldUpdateLockIdTable()) {
3394     MutexAutoLock lock(mQuotaMutex);
3395 
3396     MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLockIdTable.Contains(aLock.Id()));
3397     mDirectoryLockIdTable.InsertOrUpdate(aLock.Id(),
3398                                          WrapNotNullUnchecked(&aLock));
3399   }
3400 
3401   if (aLock.ShouldUpdateLockTable()) {
3402     DirectoryLockTable& directoryLockTable =
3403         GetDirectoryLockTable(aLock.GetPersistenceType());
3404 
3405     // XXX It seems that the contents of the array are never actually used, we
3406     // just use that like an inefficient use counter. Can't we just change
3407     // DirectoryLockTable to a nsTHashMap<nsCStringHashKey, uint32_t>?
3408     directoryLockTable
3409         .LookupOrInsertWith(
3410             aLock.Origin(),
3411             [this, &aLock] {
3412               if (!IsShuttingDown()) {
3413                 UpdateOriginAccessTime(aLock.GetPersistenceType(),
3414                                        aLock.OriginMetadata());
3415               }
3416               return MakeUnique<nsTArray<NotNull<DirectoryLockImpl*>>>();
3417             })
3418         ->AppendElement(WrapNotNullUnchecked(&aLock));
3419   }
3420 
3421   aLock.SetRegistered(true);
3422 }
3423 
UnregisterDirectoryLock(DirectoryLockImpl & aLock)3424 void QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl& aLock) {
3425   AssertIsOnOwningThread();
3426 
3427   MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(&aLock));
3428 
3429   if (aLock.ShouldUpdateLockIdTable()) {
3430     MutexAutoLock lock(mQuotaMutex);
3431 
3432     MOZ_DIAGNOSTIC_ASSERT(mDirectoryLockIdTable.Contains(aLock.Id()));
3433     mDirectoryLockIdTable.Remove(aLock.Id());
3434   }
3435 
3436   if (aLock.ShouldUpdateLockTable()) {
3437     DirectoryLockTable& directoryLockTable =
3438         GetDirectoryLockTable(aLock.GetPersistenceType());
3439 
3440     nsTArray<NotNull<DirectoryLockImpl*>>* array;
3441     MOZ_ALWAYS_TRUE(directoryLockTable.Get(aLock.Origin(), &array));
3442 
3443     MOZ_ALWAYS_TRUE(array->RemoveElement(&aLock));
3444     if (array->IsEmpty()) {
3445       directoryLockTable.Remove(aLock.Origin());
3446 
3447       if (!IsShuttingDown()) {
3448         UpdateOriginAccessTime(aLock.GetPersistenceType(),
3449                                aLock.OriginMetadata());
3450       }
3451     }
3452   }
3453 
3454   aLock.SetRegistered(false);
3455 }
3456 
AddPendingDirectoryLock(DirectoryLockImpl & aLock)3457 void QuotaManager::AddPendingDirectoryLock(DirectoryLockImpl& aLock) {
3458   AssertIsOnOwningThread();
3459 
3460   mPendingDirectoryLocks.AppendElement(&aLock);
3461 }
3462 
RemovePendingDirectoryLock(DirectoryLockImpl & aLock)3463 void QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl& aLock) {
3464   AssertIsOnOwningThread();
3465 
3466   MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(&aLock));
3467 }
3468 
CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,nsTArray<RefPtr<OriginDirectoryLock>> & aLocks)3469 uint64_t QuotaManager::CollectOriginsForEviction(
3470     uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
3471   AssertIsOnOwningThread();
3472   MOZ_ASSERT(aLocks.IsEmpty());
3473 
3474   // XXX This looks as if this could/should also use CollectLRUOriginInfosUntil,
3475   // or maybe a generalization if that.
3476 
3477   struct MOZ_STACK_CLASS Helper final {
3478     static void GetInactiveOriginInfos(
3479         const nsTArray<NotNull<RefPtr<OriginInfo>>>& aOriginInfos,
3480         const nsTArray<NotNull<const DirectoryLockImpl*>>& aLocks,
3481         OriginInfosFlatTraversable& aInactiveOriginInfos) {
3482       for (const auto& originInfo : aOriginInfos) {
3483         MOZ_ASSERT(originInfo->mGroupInfo->mPersistenceType !=
3484                    PERSISTENCE_TYPE_PERSISTENT);
3485 
3486         if (originInfo->LockedPersisted()) {
3487           continue;
3488         }
3489 
3490         // Never evict PERSISTENCE_TYPE_DEFAULT data associated to a
3491         // moz-extension origin, unlike websites (which may more likely using
3492         // the local data as a cache but still able to retrieve the same data
3493         // from the server side) extensions do not have the same data stored
3494         // anywhere else and evicting the data would result into potential data
3495         // loss for the users.
3496         //
3497         // Also, unlike a website the extensions are explicitly installed and
3498         // uninstalled by the user and all data associated to the extension
3499         // principal will be completely removed once the addon is uninstalled.
3500         if (originInfo->mGroupInfo->mPersistenceType !=
3501                 PERSISTENCE_TYPE_TEMPORARY &&
3502             originInfo->IsExtensionOrigin()) {
3503           continue;
3504         }
3505 
3506         const auto originScope = OriginScope::FromOrigin(originInfo->mOrigin);
3507 
3508         const bool match =
3509             std::any_of(aLocks.begin(), aLocks.end(),
3510                         [&originScope](const DirectoryLockImpl* const lock) {
3511                           return originScope.Matches(lock->GetOriginScope());
3512                         });
3513 
3514         if (!match) {
3515           MOZ_ASSERT(!originInfo->mQuotaObjects.Count(),
3516                      "Inactive origin shouldn't have open files!");
3517           aInactiveOriginInfos.InsertElementSorted(
3518               originInfo, OriginInfoAccessTimeComparator());
3519         }
3520       }
3521     }
3522   };
3523 
3524   // Split locks into separate arrays and filter out locks for persistent
3525   // storage, they can't block us.
3526   const auto [temporaryStorageLocks, defaultStorageLocks] = [this] {
3527     nsTArray<NotNull<const DirectoryLockImpl*>> temporaryStorageLocks;
3528     nsTArray<NotNull<const DirectoryLockImpl*>> defaultStorageLocks;
3529     for (NotNull<const DirectoryLockImpl*> const lock : mDirectoryLocks) {
3530       const Nullable<PersistenceType>& persistenceType =
3531           lock->NullablePersistenceType();
3532 
3533       if (persistenceType.IsNull()) {
3534         temporaryStorageLocks.AppendElement(lock);
3535         defaultStorageLocks.AppendElement(lock);
3536       } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3537         temporaryStorageLocks.AppendElement(lock);
3538       } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
3539         defaultStorageLocks.AppendElement(lock);
3540       } else {
3541         MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
3542 
3543         // Do nothing here, persistent origins don't need to be collected ever.
3544       }
3545     }
3546 
3547     return std::pair(std::move(temporaryStorageLocks),
3548                      std::move(defaultStorageLocks));
3549   }();
3550 
3551   // Enumerate and process inactive origins. This must be protected by the
3552   // mutex.
3553   MutexAutoLock lock(mQuotaMutex);
3554 
3555   const auto [inactiveOrigins, sizeToBeFreed] =
3556       [this, &temporaryStorageLocks = temporaryStorageLocks,
3557        &defaultStorageLocks = defaultStorageLocks, aMinSizeToBeFreed] {
3558         nsTArray<NotNull<RefPtr<const OriginInfo>>> inactiveOrigins;
3559         for (const auto& entry : mGroupInfoPairs) {
3560           const auto& pair = entry.GetData();
3561 
3562           MOZ_ASSERT(!entry.GetKey().IsEmpty());
3563           MOZ_ASSERT(pair);
3564 
3565           RefPtr<GroupInfo> groupInfo =
3566               pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3567           if (groupInfo) {
3568             Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos,
3569                                            temporaryStorageLocks,
3570                                            inactiveOrigins);
3571           }
3572 
3573           groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3574           if (groupInfo) {
3575             Helper::GetInactiveOriginInfos(
3576                 groupInfo->mOriginInfos, defaultStorageLocks, inactiveOrigins);
3577           }
3578         }
3579 
3580 #ifdef DEBUG
3581         // Make sure the array is sorted correctly.
3582         const bool inactiveOriginsSorted =
3583             std::is_sorted(inactiveOrigins.cbegin(), inactiveOrigins.cend(),
3584                            [](const auto& lhs, const auto& rhs) {
3585                              return lhs->mAccessTime < rhs->mAccessTime;
3586                            });
3587         MOZ_ASSERT(inactiveOriginsSorted);
3588 #endif
3589 
3590         // Create a list of inactive and the least recently used origins
3591         // whose aggregate size is greater or equals the minimal size to be
3592         // freed.
3593         uint64_t sizeToBeFreed = 0;
3594         for (uint32_t count = inactiveOrigins.Length(), index = 0;
3595              index < count; index++) {
3596           if (sizeToBeFreed >= aMinSizeToBeFreed) {
3597             inactiveOrigins.TruncateLength(index);
3598             break;
3599           }
3600 
3601           sizeToBeFreed += inactiveOrigins[index]->LockedUsage();
3602         }
3603 
3604         return std::pair(std::move(inactiveOrigins), sizeToBeFreed);
3605       }();
3606 
3607   if (sizeToBeFreed >= aMinSizeToBeFreed) {
3608     // Success, add directory locks for these origins, so any other
3609     // operations for them will be delayed (until origin eviction is finalized).
3610 
3611     for (const auto& originInfo : inactiveOrigins) {
3612       auto lock = DirectoryLockImpl::CreateForEviction(
3613           WrapNotNullUnchecked(this), originInfo->mGroupInfo->mPersistenceType,
3614           originInfo->FlattenToOriginMetadata());
3615 
3616       lock->AcquireImmediately();
3617 
3618       aLocks.AppendElement(lock.forget());
3619     }
3620 
3621     return sizeToBeFreed;
3622   }
3623 
3624   return 0;
3625 }
3626 
3627 template <typename P>
CollectPendingOriginsForListing(P aPredicate)3628 void QuotaManager::CollectPendingOriginsForListing(P aPredicate) {
3629   MutexAutoLock lock(mQuotaMutex);
3630 
3631   for (const auto& entry : mGroupInfoPairs) {
3632     const auto& pair = entry.GetData();
3633 
3634     MOZ_ASSERT(!entry.GetKey().IsEmpty());
3635     MOZ_ASSERT(pair);
3636 
3637     RefPtr<GroupInfo> groupInfo =
3638         pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
3639     if (groupInfo) {
3640       for (const auto& originInfo : groupInfo->mOriginInfos) {
3641         if (!originInfo->mDirectoryExists) {
3642           aPredicate(originInfo);
3643         }
3644       }
3645     }
3646   }
3647 }
3648 
Init()3649 nsresult QuotaManager::Init() {
3650   AssertIsOnOwningThread();
3651 
3652 #ifdef XP_WIN
3653   CacheUseDOSDevicePathSyntaxPrefValue();
3654 #endif
3655 
3656   QM_TRY_INSPECT(const auto& baseDir, QM_NewLocalFile(mBasePath));
3657 
3658   QM_TRY_UNWRAP(
3659       do_Init(mIndexedDBPath),
3660       GetPathForStorage(*baseDir, nsLiteralString(INDEXEDDB_DIRECTORY_NAME)));
3661 
3662   QM_TRY(MOZ_TO_RESULT(baseDir->Append(mStorageName)));
3663 
3664   QM_TRY_UNWRAP(do_Init(mStoragePath),
3665                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, baseDir, GetPath));
3666 
3667   QM_TRY_UNWRAP(
3668       do_Init(mStorageArchivesPath),
3669       GetPathForStorage(*baseDir, nsLiteralString(ARCHIVES_DIRECTORY_NAME)));
3670 
3671   QM_TRY_UNWRAP(
3672       do_Init(mPermanentStoragePath),
3673       GetPathForStorage(*baseDir, nsLiteralString(PERMANENT_DIRECTORY_NAME)));
3674 
3675   QM_TRY_UNWRAP(
3676       do_Init(mTemporaryStoragePath),
3677       GetPathForStorage(*baseDir, nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
3678 
3679   QM_TRY_UNWRAP(
3680       do_Init(mDefaultStoragePath),
3681       GetPathForStorage(*baseDir, nsLiteralString(DEFAULT_DIRECTORY_NAME)));
3682 
3683   QM_TRY_UNWRAP(do_Init(mIOThread),
3684                 MOZ_TO_RESULT_INVOKE_TYPED(
3685                     nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
3686                     "QuotaManager IO"));
3687 
3688   // Make a timer here to avoid potential failures later. We don't actually
3689   // initialize the timer until shutdown.
3690   nsCOMPtr shutdownTimer = NS_NewTimer();
3691   QM_TRY(OkIf(shutdownTimer), Err(NS_ERROR_FAILURE));
3692 
3693   mShutdownTimer.init(WrapNotNullUnchecked(std::move(shutdownTimer)));
3694 
3695   static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
3696                     Client::LS == 3 && Client::TYPE_MAX == 4,
3697                 "Fix the registration!");
3698 
3699   // Register clients.
3700   auto clients = decltype(mClients)::ValueType{};
3701   clients.AppendElement(indexedDB::CreateQuotaClient());
3702   clients.AppendElement(cache::CreateQuotaClient());
3703   clients.AppendElement(simpledb::CreateQuotaClient());
3704   if (NextGenLocalStorageEnabled()) {
3705     clients.AppendElement(localstorage::CreateQuotaClient());
3706   } else {
3707     clients.SetLength(Client::TypeMax());
3708   }
3709 
3710   mClients.init(std::move(clients));
3711 
3712   MOZ_ASSERT(mClients->Capacity() == Client::TYPE_MAX,
3713              "Should be using an auto array with correct capacity!");
3714 
3715   mAllClientTypes.init(ClientTypesArray{Client::Type::IDB,
3716                                         Client::Type::DOMCACHE,
3717                                         Client::Type::SDB, Client::Type::LS});
3718   mAllClientTypesExceptLS.init(ClientTypesArray{
3719       Client::Type::IDB, Client::Type::DOMCACHE, Client::Type::SDB});
3720 
3721   return NS_OK;
3722 }
3723 
3724 // static
MaybeRecordQuotaClientShutdownStep(const Client::Type aClientType,const nsACString & aStepDescription)3725 void QuotaManager::MaybeRecordQuotaClientShutdownStep(
3726     const Client::Type aClientType, const nsACString& aStepDescription) {
3727   // Callable on any thread.
3728 
3729   auto* const quotaManager = QuotaManager::Get();
3730   MOZ_DIAGNOSTIC_ASSERT(quotaManager);
3731 
3732   if (quotaManager->ShutdownStarted()) {
3733     quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
3734   }
3735 }
3736 
3737 // static
SafeMaybeRecordQuotaClientShutdownStep(const Client::Type aClientType,const nsACString & aStepDescription)3738 void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
3739     const Client::Type aClientType, const nsACString& aStepDescription) {
3740   // Callable on any thread.
3741 
3742   auto* const quotaManager = QuotaManager::Get();
3743 
3744   if (quotaManager && quotaManager->ShutdownStarted()) {
3745     quotaManager->RecordShutdownStep(Some(aClientType), aStepDescription);
3746   }
3747 }
3748 
MaybeRecordQuotaManagerShutdownStep(const nsACString & aStepDescription)3749 void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
3750     const nsACString& aStepDescription) {
3751   // Callable on any thread.
3752 
3753   if (ShutdownStarted()) {
3754     RecordShutdownStep(Nothing{}, aStepDescription);
3755   }
3756 }
3757 
ShutdownStarted() const3758 bool QuotaManager::ShutdownStarted() const { return mShutdownStarted; }
3759 
RecordShutdownStep(const Maybe<Client::Type> aClientType,const nsACString & aStepDescription)3760 void QuotaManager::RecordShutdownStep(const Maybe<Client::Type> aClientType,
3761                                       const nsACString& aStepDescription) {
3762   MOZ_ASSERT(mShutdownStarted);
3763 
3764   const TimeDuration elapsedSinceShutdownStart =
3765       TimeStamp::NowLoRes() - *mShutdownStartedAt;
3766 
3767   const auto stepString =
3768       nsPrintfCString("%fs: %s", elapsedSinceShutdownStart.ToSeconds(),
3769                       nsPromiseFlatCString(aStepDescription).get());
3770 
3771   if (aClientType) {
3772     AssertIsOnBackgroundThread();
3773 
3774     mShutdownSteps[*aClientType].Append(stepString + "\n"_ns);
3775   } else {
3776     // Callable on any thread.
3777     MutexAutoLock lock(mQuotaMutex);
3778 
3779     mQuotaManagerShutdownSteps.Append(stepString + "\n"_ns);
3780   }
3781 
3782 #ifdef DEBUG
3783   // XXX Probably this isn't the mechanism that should be used here.
3784 
3785   NS_DebugBreak(
3786       NS_DEBUG_WARNING,
3787       nsAutoCString(aClientType ? Client::TypeToText(*aClientType)
3788                                 : "quota manager"_ns + " shutdown step"_ns)
3789           .get(),
3790       stepString.get(), __FILE__, __LINE__);
3791 #endif
3792 }
3793 
Shutdown()3794 void QuotaManager::Shutdown() {
3795   AssertIsOnOwningThread();
3796   MOZ_ASSERT(!mShutdownStarted);
3797   MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
3798 
3799   // Setting this flag prevents the service from being recreated and prevents
3800   // further storagess from being created.
3801   gShutdown = true;
3802 
3803   StopIdleMaintenance();
3804 
3805   mShutdownStartedAt.init(TimeStamp::NowLoRes());
3806   mShutdownStarted = true;
3807 
3808   const auto& allClientTypes = AllClientTypes();
3809 
3810   bool needsToWait = false;
3811   for (Client::Type type : allClientTypes) {
3812     needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
3813   }
3814   needsToWait |= static_cast<bool>(gNormalOriginOps);
3815 
3816   // If any clients cannot shutdown immediately, spin the event loop while we
3817   // wait on all the threads to close. Our timer may fire during that loop.
3818   if (needsToWait) {
3819     MOZ_ALWAYS_SUCCEEDS(
3820         (*mShutdownTimer)
3821             ->InitWithNamedFuncCallback(
3822                 [](nsITimer* aTimer, void* aClosure) {
3823                   auto* const quotaManager =
3824                       static_cast<QuotaManager*>(aClosure);
3825 
3826                   for (Client::Type type : quotaManager->AllClientTypes()) {
3827                     // XXX This is a workaround to unblock shutdown, which ought
3828                     // to be removed by Bug 1682326.
3829                     if (type == Client::IDB) {
3830                       (*quotaManager->mClients)[type]->AbortAllOperations();
3831                     }
3832 
3833                     (*quotaManager->mClients)[type]->ForceKillActors();
3834                   }
3835 
3836                   MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
3837                       [](nsITimer* aTimer, void* aClosure) {
3838                         auto* const quotaManager =
3839                             static_cast<QuotaManager*>(aClosure);
3840 
3841                         nsCString annotation;
3842 
3843                         {
3844                           for (Client::Type type :
3845                                quotaManager->AllClientTypes()) {
3846                             auto& quotaClient =
3847                                 *(*quotaManager->mClients)[type];
3848 
3849                             if (!quotaClient.IsShutdownCompleted()) {
3850                               annotation.AppendPrintf(
3851                                   "%s: %s\nIntermediate steps:\n%s\n\n",
3852                                   Client::TypeToText(type).get(),
3853                                   quotaClient.GetShutdownStatus().get(),
3854                                   quotaManager->mShutdownSteps[type].get());
3855                             }
3856                           }
3857 
3858                           if (gNormalOriginOps) {
3859                             MutexAutoLock lock(quotaManager->mQuotaMutex);
3860 
3861                             annotation.AppendPrintf(
3862                                 "QM: %zu normal origin ops "
3863                                 "pending\nIntermediate "
3864                                 "steps:\n%s\n",
3865                                 gNormalOriginOps->Length(),
3866                                 quotaManager->mQuotaManagerShutdownSteps.get());
3867                           }
3868                         }
3869 
3870                         // We expect that at least one quota client didn't
3871                         // complete its shutdown.
3872                         MOZ_DIAGNOSTIC_ASSERT(!annotation.IsEmpty());
3873 
3874                         CrashReporter::AnnotateCrashReport(
3875                             CrashReporter::Annotation::
3876                                 QuotaManagerShutdownTimeout,
3877                             annotation);
3878 
3879                         MOZ_CRASH("Quota manager shutdown timed out");
3880                       },
3881                       aClosure, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS,
3882                       nsITimer::TYPE_ONE_SHOT,
3883                       "quota::QuotaManager::ForceCrashTimer"));
3884                 },
3885                 this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
3886                 "quota::QuotaManager::ForceKillTimer"));
3887 
3888     MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
3889         "QuotaManager::Shutdown"_ns, [this, &allClientTypes] {
3890           return !gNormalOriginOps &&
3891                  std::all_of(
3892                      allClientTypes.cbegin(), allClientTypes.cend(),
3893                      [&self = *this](const auto type) {
3894                        return (*self.mClients)[type]->IsShutdownCompleted();
3895                      });
3896         }));
3897   }
3898 
3899   for (Client::Type type : allClientTypes) {
3900     (*mClients)[type]->FinalizeShutdownWorkThreads();
3901   }
3902 
3903   // Cancel the timer regardless of whether it actually fired.
3904   QM_WARNONLY_TRY(QM_TO_RESULT((*mShutdownTimer)->Cancel()));
3905 
3906   // NB: It's very important that runnable is destroyed on this thread
3907   // (i.e. after we join the IO thread) because we can't release the
3908   // QuotaManager on the IO thread. This should probably use
3909   // NewNonOwningRunnableMethod ...
3910   RefPtr<Runnable> runnable =
3911       NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
3912                         &QuotaManager::ShutdownStorage);
3913   MOZ_ASSERT(runnable);
3914 
3915   // Give clients a chance to cleanup IO thread only objects.
3916   QM_WARNONLY_TRY(
3917       QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
3918 
3919   // Make sure to join with our IO thread.
3920   QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
3921 
3922   for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
3923     lock->Invalidate();
3924   }
3925 }
3926 
InitQuotaForOrigin(const FullOriginMetadata & aFullOriginMetadata,const ClientUsageArray & aClientUsages,uint64_t aUsageBytes)3927 void QuotaManager::InitQuotaForOrigin(
3928     const FullOriginMetadata& aFullOriginMetadata,
3929     const ClientUsageArray& aClientUsages, uint64_t aUsageBytes) {
3930   AssertIsOnIOThread();
3931   MOZ_ASSERT(IsBestEffortPersistenceType(aFullOriginMetadata.mPersistenceType));
3932 
3933   MutexAutoLock lock(mQuotaMutex);
3934 
3935   RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3936       aFullOriginMetadata.mPersistenceType, aFullOriginMetadata.mSuffix,
3937       aFullOriginMetadata.mGroup);
3938 
3939   groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3940       groupInfo, aFullOriginMetadata.mOrigin, aClientUsages, aUsageBytes,
3941       aFullOriginMetadata.mLastAccessTime, aFullOriginMetadata.mPersisted,
3942       /* aDirectoryExists */ true));
3943 }
3944 
EnsureQuotaForOrigin(const OriginMetadata & aOriginMetadata)3945 void QuotaManager::EnsureQuotaForOrigin(const OriginMetadata& aOriginMetadata) {
3946   AssertIsOnIOThread();
3947   MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
3948 
3949   MutexAutoLock lock(mQuotaMutex);
3950 
3951   RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3952       aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
3953       aOriginMetadata.mGroup);
3954 
3955   RefPtr<OriginInfo> originInfo =
3956       groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3957   if (!originInfo) {
3958     groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3959         groupInfo, aOriginMetadata.mOrigin, ClientUsageArray(),
3960         /* aUsageBytes */ 0,
3961         /* aAccessTime */ PR_Now(), /* aPersisted */ false,
3962         /* aDirectoryExists */ false));
3963   }
3964 }
3965 
NoteOriginDirectoryCreated(const OriginMetadata & aOriginMetadata,bool aPersisted)3966 int64_t QuotaManager::NoteOriginDirectoryCreated(
3967     const OriginMetadata& aOriginMetadata, bool aPersisted) {
3968   AssertIsOnIOThread();
3969   MOZ_ASSERT(IsBestEffortPersistenceType(aOriginMetadata.mPersistenceType));
3970 
3971   int64_t timestamp;
3972 
3973   MutexAutoLock lock(mQuotaMutex);
3974 
3975   RefPtr<GroupInfo> groupInfo = LockedGetOrCreateGroupInfo(
3976       aOriginMetadata.mPersistenceType, aOriginMetadata.mSuffix,
3977       aOriginMetadata.mGroup);
3978 
3979   RefPtr<OriginInfo> originInfo =
3980       groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
3981   if (originInfo) {
3982     timestamp = originInfo->LockedAccessTime();
3983     originInfo->mPersisted = aPersisted;
3984     originInfo->mDirectoryExists = true;
3985   } else {
3986     timestamp = PR_Now();
3987     groupInfo->LockedAddOriginInfo(MakeNotNull<RefPtr<OriginInfo>>(
3988         groupInfo, aOriginMetadata.mOrigin, ClientUsageArray(),
3989         /* aUsageBytes */ 0,
3990         /* aAccessTime */ timestamp, aPersisted, /* aDirectoryExists */ true));
3991   }
3992 
3993   return timestamp;
3994 }
3995 
DecreaseUsageForClient(const ClientMetadata & aClientMetadata,int64_t aSize)3996 void QuotaManager::DecreaseUsageForClient(const ClientMetadata& aClientMetadata,
3997                                           int64_t aSize) {
3998   MOZ_ASSERT(!NS_IsMainThread());
3999   MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
4000 
4001   MutexAutoLock lock(mQuotaMutex);
4002 
4003   GroupInfoPair* pair;
4004   if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
4005     return;
4006   }
4007 
4008   RefPtr<GroupInfo> groupInfo =
4009       pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
4010   if (!groupInfo) {
4011     return;
4012   }
4013 
4014   RefPtr<OriginInfo> originInfo =
4015       groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
4016   if (originInfo) {
4017     originInfo->LockedDecreaseUsage(aClientMetadata.mClientType, aSize);
4018   }
4019 }
4020 
ResetUsageForClient(const ClientMetadata & aClientMetadata)4021 void QuotaManager::ResetUsageForClient(const ClientMetadata& aClientMetadata) {
4022   MOZ_ASSERT(!NS_IsMainThread());
4023   MOZ_ASSERT(IsBestEffortPersistenceType(aClientMetadata.mPersistenceType));
4024 
4025   MutexAutoLock lock(mQuotaMutex);
4026 
4027   GroupInfoPair* pair;
4028   if (!mGroupInfoPairs.Get(aClientMetadata.mGroup, &pair)) {
4029     return;
4030   }
4031 
4032   RefPtr<GroupInfo> groupInfo =
4033       pair->LockedGetGroupInfo(aClientMetadata.mPersistenceType);
4034   if (!groupInfo) {
4035     return;
4036   }
4037 
4038   RefPtr<OriginInfo> originInfo =
4039       groupInfo->LockedGetOriginInfo(aClientMetadata.mOrigin);
4040   if (originInfo) {
4041     originInfo->LockedResetUsageForClient(aClientMetadata.mClientType);
4042   }
4043 }
4044 
GetUsageForClient(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,Client::Type aClientType)4045 UsageInfo QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
4046                                           const OriginMetadata& aOriginMetadata,
4047                                           Client::Type aClientType) {
4048   MOZ_ASSERT(!NS_IsMainThread());
4049   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
4050 
4051   MutexAutoLock lock(mQuotaMutex);
4052 
4053   GroupInfoPair* pair;
4054   if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
4055     return UsageInfo{};
4056   }
4057 
4058   RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
4059   if (!groupInfo) {
4060     return UsageInfo{};
4061   }
4062 
4063   RefPtr<OriginInfo> originInfo =
4064       groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
4065   if (!originInfo) {
4066     return UsageInfo{};
4067   }
4068 
4069   return originInfo->LockedGetUsageForClient(aClientType);
4070 }
4071 
UpdateOriginAccessTime(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata)4072 void QuotaManager::UpdateOriginAccessTime(
4073     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
4074   AssertIsOnOwningThread();
4075   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
4076   MOZ_ASSERT(!IsShuttingDown());
4077 
4078   MutexAutoLock lock(mQuotaMutex);
4079 
4080   GroupInfoPair* pair;
4081   if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
4082     return;
4083   }
4084 
4085   RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
4086   if (!groupInfo) {
4087     return;
4088   }
4089 
4090   RefPtr<OriginInfo> originInfo =
4091       groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
4092   if (originInfo) {
4093     int64_t timestamp = PR_Now();
4094     originInfo->LockedUpdateAccessTime(timestamp);
4095 
4096     MutexAutoUnlock autoUnlock(mQuotaMutex);
4097 
4098     auto op = MakeRefPtr<SaveOriginAccessTimeOp>(
4099         aPersistenceType, aOriginMetadata.mOrigin, timestamp);
4100 
4101     RegisterNormalOriginOp(*op);
4102 
4103     op->RunImmediately();
4104   }
4105 }
4106 
RemoveQuota()4107 void QuotaManager::RemoveQuota() {
4108   AssertIsOnIOThread();
4109 
4110   MutexAutoLock lock(mQuotaMutex);
4111 
4112   for (const auto& entry : mGroupInfoPairs) {
4113     const auto& pair = entry.GetData();
4114 
4115     MOZ_ASSERT(!entry.GetKey().IsEmpty());
4116     MOZ_ASSERT(pair);
4117 
4118     RefPtr<GroupInfo> groupInfo =
4119         pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
4120     if (groupInfo) {
4121       groupInfo->LockedRemoveOriginInfos();
4122     }
4123 
4124     groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
4125     if (groupInfo) {
4126       groupInfo->LockedRemoveOriginInfos();
4127     }
4128   }
4129 
4130   mGroupInfoPairs.Clear();
4131 
4132   MOZ_ASSERT(mTemporaryStorageUsage == 0, "Should be zero!");
4133 }
4134 
LoadQuota()4135 nsresult QuotaManager::LoadQuota() {
4136   AssertIsOnIOThread();
4137   MOZ_ASSERT(mStorageConnection);
4138   MOZ_ASSERT(!mTemporaryStorageInitialized);
4139 
4140   // A list of all unaccessed default or temporary origins.
4141   nsTArray<FullOriginMetadata> unaccessedOrigins;
4142 
4143   auto MaybeCollectUnaccessedOrigin =
4144       [loadQuotaInfoStartTime = PR_Now(),
4145        &unaccessedOrigins](auto& fullOriginMetadata) {
4146         if (IsOriginUnaccessed(fullOriginMetadata, loadQuotaInfoStartTime)) {
4147           unaccessedOrigins.AppendElement(std::move(fullOriginMetadata));
4148         }
4149       };
4150 
4151   auto recordQuotaInfoLoadTimeHelper =
4152       MakeRefPtr<RecordQuotaInfoLoadTimeHelper>();
4153 
4154   const auto startTime = recordQuotaInfoLoadTimeHelper->Start();
4155 
4156   auto LoadQuotaFromCache = [&]() -> nsresult {
4157     QM_TRY_INSPECT(
4158         const auto& stmt,
4159         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4160             nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
4161             "SELECT repository_id, suffix, group_, "
4162             "origin, client_usages, usage, "
4163             "last_access_time, accessed, persisted "
4164             "FROM origin"_ns));
4165 
4166     auto autoRemoveQuota = MakeScopeExit([&] {
4167       RemoveQuota();
4168       unaccessedOrigins.Clear();
4169     });
4170 
4171     QM_TRY(quota::CollectWhileHasResult(
4172         *stmt,
4173         [this,
4174          &MaybeCollectUnaccessedOrigin](auto& stmt) -> Result<Ok, nsresult> {
4175           QM_TRY_INSPECT(const int32_t& repositoryId,
4176                          MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
4177 
4178           const auto maybePersistenceType =
4179               PersistenceTypeFromInt32(repositoryId, fallible);
4180           QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
4181 
4182           FullOriginMetadata fullOriginMetadata;
4183 
4184           fullOriginMetadata.mPersistenceType = maybePersistenceType.value();
4185 
4186           QM_TRY_UNWRAP(fullOriginMetadata.mSuffix,
4187                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
4188                                                           GetUTF8String, 1));
4189 
4190           QM_TRY_UNWRAP(fullOriginMetadata.mGroup,
4191                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
4192                                                           GetUTF8String, 2));
4193 
4194           QM_TRY_UNWRAP(fullOriginMetadata.mOrigin,
4195                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
4196                                                           GetUTF8String, 3));
4197 
4198           QM_TRY_INSPECT(const auto& clientUsagesText,
4199                          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
4200                                                            GetUTF8String, 4));
4201 
4202           ClientUsageArray clientUsages;
4203           QM_TRY(MOZ_TO_RESULT(clientUsages.Deserialize(clientUsagesText)));
4204 
4205           QM_TRY_INSPECT(const int64_t& usage,
4206                          MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 5));
4207           QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
4208                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 6));
4209           QM_TRY_INSPECT(const int64_t& accessed,
4210                          MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 7));
4211           QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
4212                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 8));
4213 
4214           QM_TRY_INSPECT(const bool& groupUpdated,
4215                          MaybeUpdateGroupForOrigin(fullOriginMetadata));
4216 
4217           Unused << groupUpdated;
4218 
4219           QM_TRY_INSPECT(
4220               const bool& lastAccessTimeUpdated,
4221               MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
4222 
4223           Unused << lastAccessTimeUpdated;
4224 
4225           // We don't need to update the .metadata-v2 file on disk here,
4226           // EnsureTemporaryOriginIsInitialized is responsible for doing that.
4227           // We just need to use correct group and last access time before
4228           // initializing quota for the given origin. (Note that calling
4229           // LoadFullOriginMetadataWithRestore below might update the group in
4230           // the metadata file, but only as a side-effect. The actual place we
4231           // ensure consistency is in EnsureTemporaryOriginIsInitialized.)
4232 
4233           if (accessed) {
4234             QM_TRY_INSPECT(
4235                 const auto& directory,
4236                 GetDirectoryForOrigin(fullOriginMetadata.mPersistenceType,
4237                                       fullOriginMetadata.mOrigin));
4238 
4239             QM_TRY_INSPECT(const bool& exists,
4240                            MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
4241 
4242             QM_TRY(OkIf(exists), Err(NS_ERROR_FAILURE));
4243 
4244             QM_TRY_INSPECT(const bool& isDirectory,
4245                            MOZ_TO_RESULT_INVOKE_MEMBER(directory, IsDirectory));
4246 
4247             QM_TRY(OkIf(isDirectory), Err(NS_ERROR_FAILURE));
4248 
4249             // Calling LoadFullOriginMetadataWithRestore might update the group
4250             // in the metadata file, but only as a side-effect. The actual place
4251             // we ensure consistency is in EnsureTemporaryOriginIsInitialized.
4252 
4253             QM_TRY_INSPECT(const auto& metadata,
4254                            LoadFullOriginMetadataWithRestore(directory));
4255 
4256             QM_TRY(OkIf(fullOriginMetadata.mLastAccessTime ==
4257                         metadata.mLastAccessTime),
4258                    Err(NS_ERROR_FAILURE));
4259 
4260             QM_TRY(OkIf(fullOriginMetadata.mPersisted == metadata.mPersisted),
4261                    Err(NS_ERROR_FAILURE));
4262 
4263             QM_TRY(OkIf(fullOriginMetadata.mPersistenceType ==
4264                         metadata.mPersistenceType),
4265                    Err(NS_ERROR_FAILURE));
4266 
4267             QM_TRY(OkIf(fullOriginMetadata.mSuffix == metadata.mSuffix),
4268                    Err(NS_ERROR_FAILURE));
4269 
4270             QM_TRY(OkIf(fullOriginMetadata.mGroup == metadata.mGroup),
4271                    Err(NS_ERROR_FAILURE));
4272 
4273             QM_TRY(OkIf(fullOriginMetadata.mOrigin == metadata.mOrigin),
4274                    Err(NS_ERROR_FAILURE));
4275 
4276             QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
4277                 fullOriginMetadata.mPersistenceType, fullOriginMetadata,
4278                 fullOriginMetadata.mLastAccessTime,
4279                 fullOriginMetadata.mPersisted, directory)));
4280           } else {
4281             InitQuotaForOrigin(fullOriginMetadata, clientUsages, usage);
4282           }
4283 
4284           MaybeCollectUnaccessedOrigin(fullOriginMetadata);
4285 
4286           return Ok{};
4287         }));
4288 
4289     autoRemoveQuota.release();
4290 
4291     return NS_OK;
4292   };
4293 
4294   QM_TRY_INSPECT(
4295       const bool& loadQuotaFromCache, ([this]() -> Result<bool, nsresult> {
4296         if (mCacheUsable) {
4297           QM_TRY_INSPECT(
4298               const auto& stmt,
4299               CreateAndExecuteSingleStepStatement<
4300                   SingleStepResult::ReturnNullIfNoResult>(
4301                   *mStorageConnection, "SELECT valid, build_id FROM cache"_ns));
4302 
4303           QM_TRY(OkIf(stmt), Err(NS_ERROR_FILE_CORRUPTED));
4304 
4305           QM_TRY_INSPECT(const int32_t& valid,
4306                          MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
4307 
4308           if (valid) {
4309             if (!StaticPrefs::dom_quotaManager_caching_checkBuildId()) {
4310               return true;
4311             }
4312 
4313             QM_TRY_INSPECT(const auto& buildId,
4314                            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4315                                nsAutoCString, stmt, GetUTF8String, 1));
4316 
4317             return buildId == *gBuildId;
4318           }
4319         }
4320 
4321         return false;
4322       }()));
4323 
4324   auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
4325 
4326   if (!loadQuotaFromCache ||
4327       !StaticPrefs::dom_quotaManager_loadQuotaFromCache() ||
4328       ![&LoadQuotaFromCache] {
4329         QM_WARNONLY_TRY_UNWRAP(auto res, MOZ_TO_RESULT(LoadQuotaFromCache()));
4330         return static_cast<bool>(res);
4331       }()) {
4332     // A keeper to defer the return only in Nightly, so that the telemetry data
4333     // for whole profile can be collected.
4334 #ifdef NIGHTLY_BUILD
4335     nsresult statusKeeper = NS_OK;
4336 #endif
4337 
4338     const auto statusKeeperFunc = [&](const nsresult rv) {
4339       RECORD_IN_NIGHTLY(statusKeeper, rv);
4340     };
4341 
4342     for (const PersistenceType type : kBestEffortPersistenceTypes) {
4343       if (NS_WARN_IF(IsShuttingDown())) {
4344         RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4345       }
4346 
4347       QM_TRY(([&]() -> Result<Ok, nsresult> {
4348         QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
4349                  const auto innerFunc = [&](const auto&) -> nsresult {
4350                    return InitializeRepository(type,
4351                                                MaybeCollectUnaccessedOrigin);
4352                  };
4353 
4354                  return ExecuteInitialization(
4355                      type == PERSISTENCE_TYPE_DEFAULT
4356                          ? Initialization::DefaultRepository
4357                          : Initialization::TemporaryRepository,
4358                      innerFunc);
4359                }())),
4360                OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4361 
4362         return Ok{};
4363       }()));
4364     }
4365 
4366 #ifdef NIGHTLY_BUILD
4367     if (NS_FAILED(statusKeeper)) {
4368       return statusKeeper;
4369     }
4370 #endif
4371   }
4372 
4373   autoRemoveQuota.release();
4374 
4375   const auto endTime = recordQuotaInfoLoadTimeHelper->End();
4376 
4377   if (StaticPrefs::dom_quotaManager_checkQuotaInfoLoadTime() &&
4378       static_cast<uint32_t>((endTime - startTime).ToMilliseconds()) >=
4379           StaticPrefs::dom_quotaManager_longQuotaInfoLoadTimeThresholdMs() &&
4380       !unaccessedOrigins.IsEmpty()) {
4381     QM_WARNONLY_TRY(ArchiveOrigins(unaccessedOrigins));
4382   }
4383 
4384   return NS_OK;
4385 }
4386 
UnloadQuota()4387 void QuotaManager::UnloadQuota() {
4388   AssertIsOnIOThread();
4389   MOZ_ASSERT(mStorageConnection);
4390   MOZ_ASSERT(mTemporaryStorageInitialized);
4391   MOZ_ASSERT(mCacheUsable);
4392 
4393   auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
4394 
4395   mozStorageTransaction transaction(
4396       mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
4397 
4398   QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
4399 
4400   QM_TRY(MOZ_TO_RESULT(
4401              mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
4402          QM_VOID);
4403 
4404   nsCOMPtr<mozIStorageStatement> insertStmt;
4405 
4406   {
4407     MutexAutoLock lock(mQuotaMutex);
4408 
4409     for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
4410       MOZ_ASSERT(!iter.Key().IsEmpty());
4411 
4412       GroupInfoPair* const pair = iter.UserData();
4413       MOZ_ASSERT(pair);
4414 
4415       for (const PersistenceType type : kBestEffortPersistenceTypes) {
4416         RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
4417         if (!groupInfo) {
4418           continue;
4419         }
4420 
4421         for (const auto& originInfo : groupInfo->mOriginInfos) {
4422           MOZ_ASSERT(!originInfo->mQuotaObjects.Count());
4423 
4424           if (!originInfo->mDirectoryExists) {
4425             continue;
4426           }
4427 
4428           if (insertStmt) {
4429             MOZ_ALWAYS_SUCCEEDS(insertStmt->Reset());
4430           } else {
4431             QM_TRY_UNWRAP(
4432                 insertStmt,
4433                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4434                     nsCOMPtr<mozIStorageStatement>, mStorageConnection,
4435                     CreateStatement,
4436                     "INSERT INTO origin (repository_id, suffix, group_, "
4437                     "origin, client_usages, usage, last_access_time, "
4438                     "accessed, persisted) "
4439                     "VALUES (:repository_id, :suffix, :group_, :origin, "
4440                     ":client_usages, :usage, :last_access_time, :accessed, "
4441                     ":persisted)"_ns),
4442                 QM_VOID);
4443           }
4444 
4445           QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
4446                  QM_VOID);
4447 
4448           QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
4449         }
4450 
4451         groupInfo->LockedRemoveOriginInfos();
4452       }
4453 
4454       iter.Remove();
4455     }
4456   }
4457 
4458   QM_TRY_INSPECT(
4459       const auto& stmt,
4460       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4461           nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
4462           "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
4463       QM_VOID);
4464 
4465   QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
4466   QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
4467          QM_VOID);
4468   QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
4469   QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
4470 }
4471 
GetQuotaObject(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,Client::Type aClientType,nsIFile * aFile,int64_t aFileSize,int64_t * aFileSizeOut)4472 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4473     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4474     Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
4475     int64_t* aFileSizeOut /* = nullptr */) {
4476   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4477 
4478   if (aFileSizeOut) {
4479     *aFileSizeOut = 0;
4480   }
4481 
4482   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
4483     return nullptr;
4484   }
4485 
4486   QM_TRY_INSPECT(const auto& path,
4487                  MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aFile, GetPath),
4488                  nullptr);
4489 
4490 #ifdef DEBUG
4491   {
4492     QM_TRY_INSPECT(
4493         const auto& directory,
4494         GetDirectoryForOrigin(aPersistenceType, aOriginMetadata.mOrigin),
4495         nullptr);
4496 
4497     nsAutoString clientType;
4498     QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
4499            nullptr);
4500 
4501     QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
4502 
4503     QM_TRY_INSPECT(
4504         const auto& directoryPath,
4505         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, directory, GetPath),
4506         nullptr);
4507 
4508     MOZ_ASSERT(StringBeginsWith(path, directoryPath));
4509   }
4510 #endif
4511 
4512   QM_TRY_INSPECT(
4513       const int64_t fileSize,
4514       ([&aFile, aFileSize]() -> Result<int64_t, nsresult> {
4515         if (aFileSize == -1) {
4516           QM_TRY_INSPECT(const bool& exists,
4517                          MOZ_TO_RESULT_INVOKE_MEMBER(aFile, Exists));
4518 
4519           if (exists) {
4520             QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(aFile, GetFileSize));
4521           }
4522 
4523           return 0;
4524         }
4525 
4526         return aFileSize;
4527       }()),
4528       nullptr);
4529 
4530   RefPtr<QuotaObject> result;
4531   {
4532     MutexAutoLock lock(mQuotaMutex);
4533 
4534     GroupInfoPair* pair;
4535     if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
4536       return nullptr;
4537     }
4538 
4539     RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
4540 
4541     if (!groupInfo) {
4542       return nullptr;
4543     }
4544 
4545     RefPtr<OriginInfo> originInfo =
4546         groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
4547 
4548     if (!originInfo) {
4549       return nullptr;
4550     }
4551 
4552     // We need this extra raw pointer because we can't assign to the smart
4553     // pointer directly since QuotaObject::AddRef would try to acquire the same
4554     // mutex.
4555     const NotNull<QuotaObject*> quotaObject =
4556         originInfo->mQuotaObjects.LookupOrInsertWith(path, [&] {
4557           // Create a new QuotaObject. The hashtable is not responsible to
4558           // delete the QuotaObject.
4559           return WrapNotNullUnchecked(
4560               new QuotaObject(originInfo, aClientType, path, fileSize));
4561         });
4562 
4563     // Addref the QuotaObject and move the ownership to the result. This must
4564     // happen before we unlock!
4565     result = quotaObject->LockedAddRef();
4566   }
4567 
4568   if (aFileSizeOut) {
4569     *aFileSizeOut = fileSize;
4570   }
4571 
4572   // The caller becomes the owner of the QuotaObject, that is, the caller is
4573   // is responsible to delete it when the last reference is removed.
4574   return result.forget();
4575 }
4576 
GetQuotaObject(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,Client::Type aClientType,const nsAString & aPath,int64_t aFileSize,int64_t * aFileSizeOut)4577 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4578     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
4579     Client::Type aClientType, const nsAString& aPath, int64_t aFileSize,
4580     int64_t* aFileSizeOut /* = nullptr */) {
4581   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4582 
4583   if (aFileSizeOut) {
4584     *aFileSizeOut = 0;
4585   }
4586 
4587   QM_TRY_INSPECT(const auto& file, QM_NewLocalFile(aPath), nullptr);
4588 
4589   return GetQuotaObject(aPersistenceType, aOriginMetadata, aClientType, file,
4590                         aFileSize, aFileSizeOut);
4591 }
4592 
GetQuotaObject(const int64_t aDirectoryLockId,const nsAString & aPath)4593 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
4594     const int64_t aDirectoryLockId, const nsAString& aPath) {
4595   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4596 
4597   Maybe<MutexAutoLock> lock;
4598 
4599   // See the comment for mDirectoryLockIdTable in QuotaManager.h
4600   if (!IsOnBackgroundThread()) {
4601     lock.emplace(mQuotaMutex);
4602   }
4603 
4604   if (auto maybeDirectoryLock =
4605           mDirectoryLockIdTable.MaybeGet(aDirectoryLockId)) {
4606     const auto& directoryLock = *maybeDirectoryLock;
4607     MOZ_DIAGNOSTIC_ASSERT(directoryLock->ShouldUpdateLockIdTable());
4608 
4609     const PersistenceType persistenceType = directoryLock->GetPersistenceType();
4610     const OriginMetadata& originMetadata = directoryLock->OriginMetadata();
4611     const Client::Type clientType = directoryLock->ClientType();
4612 
4613     lock.reset();
4614 
4615     return GetQuotaObject(persistenceType, originMetadata, clientType, aPath);
4616   }
4617 
4618   MOZ_CRASH("Getting quota object for an unregistered directory lock?");
4619 }
4620 
OriginPersisted(const OriginMetadata & aOriginMetadata)4621 Nullable<bool> QuotaManager::OriginPersisted(
4622     const OriginMetadata& aOriginMetadata) {
4623   AssertIsOnIOThread();
4624 
4625   MutexAutoLock lock(mQuotaMutex);
4626 
4627   RefPtr<OriginInfo> originInfo =
4628       LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
4629   if (originInfo) {
4630     return Nullable<bool>(originInfo->LockedPersisted());
4631   }
4632 
4633   return Nullable<bool>();
4634 }
4635 
PersistOrigin(const OriginMetadata & aOriginMetadata)4636 void QuotaManager::PersistOrigin(const OriginMetadata& aOriginMetadata) {
4637   AssertIsOnIOThread();
4638 
4639   MutexAutoLock lock(mQuotaMutex);
4640 
4641   RefPtr<OriginInfo> originInfo =
4642       LockedGetOriginInfo(PERSISTENCE_TYPE_DEFAULT, aOriginMetadata);
4643   if (originInfo && !originInfo->LockedPersisted()) {
4644     originInfo->LockedPersist();
4645   }
4646 }
4647 
AbortOperationsForLocks(const DirectoryLockIdTableArray & aLockIds)4648 void QuotaManager::AbortOperationsForLocks(
4649     const DirectoryLockIdTableArray& aLockIds) {
4650   for (Client::Type type : AllClientTypes()) {
4651     if (aLockIds[type].Filled()) {
4652       (*mClients)[type]->AbortOperationsForLocks(aLockIds[type]);
4653     }
4654   }
4655 }
4656 
AbortOperationsForProcess(ContentParentId aContentParentId)4657 void QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) {
4658   AssertIsOnOwningThread();
4659 
4660   for (const RefPtr<Client>& client : *mClients) {
4661     client->AbortOperationsForProcess(aContentParentId);
4662   }
4663 }
4664 
GetDirectoryForOrigin(PersistenceType aPersistenceType,const nsACString & aASCIIOrigin) const4665 Result<nsCOMPtr<nsIFile>, nsresult> QuotaManager::GetDirectoryForOrigin(
4666     PersistenceType aPersistenceType, const nsACString& aASCIIOrigin) const {
4667   QM_TRY_UNWRAP(auto directory,
4668                 QM_NewLocalFile(GetStoragePath(aPersistenceType)));
4669 
4670   QM_TRY(MOZ_TO_RESULT(
4671       directory->Append(MakeSanitizedOriginString(aASCIIOrigin))));
4672 
4673   return directory;
4674 }
4675 
RestoreDirectoryMetadata2(nsIFile * aDirectory)4676 nsresult QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory) {
4677   AssertIsOnIOThread();
4678   MOZ_ASSERT(aDirectory);
4679   MOZ_ASSERT(mStorageConnection);
4680 
4681   RefPtr<RestoreDirectoryMetadata2Helper> helper =
4682       new RestoreDirectoryMetadata2Helper(aDirectory);
4683 
4684   QM_TRY(MOZ_TO_RESULT(helper->Init()));
4685 
4686   QM_TRY(MOZ_TO_RESULT(helper->RestoreMetadata2File()));
4687 
4688   return NS_OK;
4689 }
4690 
LoadFullOriginMetadata(nsIFile * aDirectory,PersistenceType aPersistenceType)4691 Result<FullOriginMetadata, nsresult> QuotaManager::LoadFullOriginMetadata(
4692     nsIFile* aDirectory, PersistenceType aPersistenceType) {
4693   MOZ_ASSERT(!NS_IsMainThread());
4694   MOZ_ASSERT(aDirectory);
4695   MOZ_ASSERT(mStorageConnection);
4696 
4697   QM_TRY_INSPECT(const auto& binaryStream,
4698                  GetBinaryInputStream(*aDirectory,
4699                                       nsLiteralString(METADATA_V2_FILE_NAME)));
4700 
4701   FullOriginMetadata fullOriginMetadata;
4702 
4703   QM_TRY_UNWRAP(fullOriginMetadata.mLastAccessTime,
4704                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
4705 
4706   QM_TRY_UNWRAP(fullOriginMetadata.mPersisted,
4707                 MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
4708 
4709   QM_TRY_INSPECT(const bool& reservedData1,
4710                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
4711   Unused << reservedData1;
4712 
4713   // XXX Use for the persistence type.
4714   QM_TRY_INSPECT(const bool& reservedData2,
4715                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
4716   Unused << reservedData2;
4717 
4718   fullOriginMetadata.mPersistenceType = aPersistenceType;
4719 
4720   QM_TRY_UNWRAP(
4721       fullOriginMetadata.mSuffix,
4722       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
4723 
4724   QM_TRY_UNWRAP(
4725       fullOriginMetadata.mGroup,
4726       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
4727 
4728   QM_TRY_UNWRAP(
4729       fullOriginMetadata.mOrigin,
4730       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, binaryStream, ReadCString));
4731 
4732   // Currently unused (used to be isApp).
4733   QM_TRY_INSPECT(const bool& dummy,
4734                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
4735   Unused << dummy;
4736 
4737   QM_TRY(MOZ_TO_RESULT(binaryStream->Close()));
4738 
4739   QM_TRY_INSPECT(const bool& groupUpdated,
4740                  MaybeUpdateGroupForOrigin(fullOriginMetadata));
4741 
4742   // A workaround for a bug in GetLastModifiedTime implementation which should
4743   // have returned the current time instead of INT64_MIN when there were no
4744   // suitable files for getting last modified time.
4745   QM_TRY_INSPECT(const bool& lastAccessTimeUpdated,
4746                  MaybeUpdateLastAccessTimeForOrigin(fullOriginMetadata));
4747 
4748   if (groupUpdated || lastAccessTimeUpdated) {
4749     // Only overwriting .metadata-v2 (used to overwrite .metadata too) to reduce
4750     // I/O.
4751     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
4752         *aDirectory, fullOriginMetadata.mLastAccessTime,
4753         fullOriginMetadata.mPersisted, fullOriginMetadata)));
4754   }
4755 
4756   return fullOriginMetadata;
4757 }
4758 
4759 Result<FullOriginMetadata, nsresult>
LoadFullOriginMetadataWithRestore(nsIFile * aDirectory)4760 QuotaManager::LoadFullOriginMetadataWithRestore(nsIFile* aDirectory) {
4761   // XXX Once the persistence type is stored in the metadata file, this block
4762   // for getting the persistence type from the parent directory name can be
4763   // removed.
4764   nsCOMPtr<nsIFile> parentDir;
4765   QM_TRY(MOZ_TO_RESULT(aDirectory->GetParent(getter_AddRefs(parentDir))));
4766 
4767   const auto maybePersistenceType =
4768       PersistenceTypeFromFile(*parentDir, fallible);
4769   QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
4770 
4771   const auto& persistenceType = maybePersistenceType.value();
4772 
4773   QM_TRY_RETURN(QM_OR_ELSE_WARN(
4774       // Expression.
4775       LoadFullOriginMetadata(aDirectory, persistenceType),
4776       // Fallback.
4777       ([&aDirectory, &persistenceType,
4778         this](const nsresult rv) -> Result<FullOriginMetadata, nsresult> {
4779         QM_TRY(MOZ_TO_RESULT(RestoreDirectoryMetadata2(aDirectory)));
4780 
4781         QM_TRY_RETURN(LoadFullOriginMetadata(aDirectory, persistenceType));
4782       })));
4783 }
4784 
4785 template <typename OriginFunc>
InitializeRepository(PersistenceType aPersistenceType,OriginFunc && aOriginFunc)4786 nsresult QuotaManager::InitializeRepository(PersistenceType aPersistenceType,
4787                                             OriginFunc&& aOriginFunc) {
4788   AssertIsOnIOThread();
4789   MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY ||
4790              aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
4791 
4792   QM_TRY_INSPECT(const auto& directory,
4793                  QM_NewLocalFile(GetStoragePath(aPersistenceType)));
4794 
4795   QM_TRY_INSPECT(const bool& created, EnsureDirectory(*directory));
4796 
4797   Unused << created;
4798 
4799   // A keeper to defer the return only in Nightly, so that the telemetry data
4800   // for whole profile can be collected
4801 #ifdef NIGHTLY_BUILD
4802   nsresult statusKeeper = NS_OK;
4803 #endif
4804 
4805   const auto statusKeeperFunc = [&](const nsresult rv) {
4806     RECORD_IN_NIGHTLY(statusKeeper, rv);
4807   };
4808 
4809   struct RenameAndInitInfo {
4810     nsCOMPtr<nsIFile> mOriginDirectory;
4811     FullOriginMetadata mFullOriginMetadata;
4812     int64_t mTimestamp;
4813     bool mPersisted;
4814   };
4815   nsTArray<RenameAndInitInfo> renameAndInitInfos;
4816 
4817   QM_TRY(([&]() -> Result<Ok, nsresult> {
4818     QM_TRY(
4819         CollectEachFile(
4820             *directory,
4821             [&](nsCOMPtr<nsIFile>&& childDirectory) -> Result<Ok, nsresult> {
4822               if (NS_WARN_IF(IsShuttingDown())) {
4823                 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
4824               }
4825 
4826               QM_TRY(
4827                   ([this, &childDirectory, &renameAndInitInfos,
4828                     aPersistenceType, &aOriginFunc]() -> Result<Ok, nsresult> {
4829                     QM_TRY_INSPECT(
4830                         const auto& leafName,
4831                         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
4832                             nsAutoString, childDirectory, GetLeafName));
4833 
4834                     QM_TRY_INSPECT(const auto& dirEntryKind,
4835                                    GetDirEntryKind(*childDirectory));
4836 
4837                     switch (dirEntryKind) {
4838                       case nsIFileKind::ExistsAsDirectory: {
4839                         QM_TRY_UNWRAP(
4840                             auto metadata,
4841                             LoadFullOriginMetadataWithRestore(childDirectory));
4842 
4843                         MOZ_ASSERT(metadata.mPersistenceType ==
4844                                    aPersistenceType);
4845 
4846                         // FIXME(tt): The check for origin name consistency can
4847                         // be removed once we have an upgrade to traverse origin
4848                         // directories and check through the directory metadata
4849                         // files.
4850                         const auto originSanitized =
4851                             MakeSanitizedOriginCString(metadata.mOrigin);
4852 
4853                         NS_ConvertUTF16toUTF8 utf8LeafName(leafName);
4854                         if (!originSanitized.Equals(utf8LeafName)) {
4855                           QM_WARNING(
4856                               "The name of the origin directory (%s) doesn't "
4857                               "match the sanitized origin string (%s) in the "
4858                               "metadata file!",
4859                               utf8LeafName.get(), originSanitized.get());
4860 
4861                           // If it's the known case, we try to restore the
4862                           // origin directory name if it's possible.
4863                           if (originSanitized.Equals(utf8LeafName + "."_ns)) {
4864                             const int64_t lastAccessTime =
4865                                 metadata.mLastAccessTime;
4866                             const bool persisted = metadata.mPersisted;
4867                             renameAndInitInfos.AppendElement(RenameAndInitInfo{
4868                                 std::move(childDirectory), std::move(metadata),
4869                                 lastAccessTime, persisted});
4870                             break;
4871                           }
4872 
4873                           // XXXtt: Try to restore the unknown cases base on the
4874                           // content for their metadata files. Note that if the
4875                           // restore fails, QM should maintain a list and ensure
4876                           // they won't be accessed after initialization.
4877                         }
4878 
4879                         QM_TRY(QM_OR_ELSE_WARN_IF(
4880                             // Expression.
4881                             MOZ_TO_RESULT(InitializeOrigin(
4882                                 aPersistenceType, metadata,
4883                                 metadata.mLastAccessTime, metadata.mPersisted,
4884                                 childDirectory)),
4885                             // Predicate.
4886                             IsDatabaseCorruptionError,
4887                             // Fallback.
4888                             ([&childDirectory](
4889                                  const nsresult rv) -> Result<Ok, nsresult> {
4890                               // If the origin can't be initialized due to
4891                               // corruption, this is a permanent
4892                               // condition, and we need to remove all data
4893                               // for the origin on disk.
4894 
4895                               QM_TRY(
4896                                   MOZ_TO_RESULT(childDirectory->Remove(true)));
4897 
4898                               return Ok{};
4899                             })));
4900 
4901                         std::forward<OriginFunc>(aOriginFunc)(metadata);
4902 
4903                         break;
4904                       }
4905 
4906                       case nsIFileKind::ExistsAsFile:
4907                         if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
4908                           break;
4909                         }
4910 
4911                         // Unknown files during initialization are now allowed.
4912                         // Just warn if we find them.
4913                         UNKNOWN_FILE_WARNING(leafName);
4914                         break;
4915 
4916                       case nsIFileKind::DoesNotExist:
4917                         // Ignore files that got removed externally while
4918                         // iterating.
4919                         break;
4920                     }
4921 
4922                     return Ok{};
4923                   }()),
4924                   OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4925 
4926               return Ok{};
4927             }),
4928         OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4929 
4930     return Ok{};
4931   }()));
4932 
4933   for (auto& info : renameAndInitInfos) {
4934     QM_TRY(([&]() -> Result<Ok, nsresult> {
4935       QM_TRY(([&directory, &info, this, aPersistenceType,
4936                &aOriginFunc]() -> Result<Ok, nsresult> {
4937                const auto originDirName =
4938                    MakeSanitizedOriginString(info.mFullOriginMetadata.mOrigin);
4939 
4940                // Check if targetDirectory exist.
4941                QM_TRY_INSPECT(const auto& targetDirectory,
4942                               CloneFileAndAppend(*directory, originDirName));
4943 
4944                QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(
4945                                                       targetDirectory, Exists));
4946 
4947                if (exists) {
4948                  QM_TRY(MOZ_TO_RESULT(info.mOriginDirectory->Remove(true)));
4949 
4950                  return Ok{};
4951                }
4952 
4953                QM_TRY(MOZ_TO_RESULT(
4954                    info.mOriginDirectory->RenameTo(nullptr, originDirName)));
4955 
4956                // XXX We don't check corruption here ?
4957                QM_TRY(MOZ_TO_RESULT(InitializeOrigin(
4958                    aPersistenceType, info.mFullOriginMetadata, info.mTimestamp,
4959                    info.mPersisted, targetDirectory)));
4960 
4961                std::forward<OriginFunc>(aOriginFunc)(info.mFullOriginMetadata);
4962 
4963                return Ok{};
4964              }()),
4965              OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
4966 
4967       return Ok{};
4968     }()));
4969   }
4970 
4971 #ifdef NIGHTLY_BUILD
4972   if (NS_FAILED(statusKeeper)) {
4973     return statusKeeper;
4974   }
4975 #endif
4976 
4977   return NS_OK;
4978 }
4979 
InitializeOrigin(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,int64_t aAccessTime,bool aPersisted,nsIFile * aDirectory)4980 nsresult QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
4981                                         const OriginMetadata& aOriginMetadata,
4982                                         int64_t aAccessTime, bool aPersisted,
4983                                         nsIFile* aDirectory) {
4984   AssertIsOnIOThread();
4985 
4986   const bool trackQuota = aPersistenceType != PERSISTENCE_TYPE_PERSISTENT;
4987 
4988   // We need to initialize directories of all clients if they exists and also
4989   // get the total usage to initialize the quota.
4990 
4991   ClientUsageArray clientUsages;
4992 
4993   // A keeper to defer the return only in Nightly, so that the telemetry data
4994   // for whole profile can be collected
4995 #ifdef NIGHTLY_BUILD
4996   nsresult statusKeeper = NS_OK;
4997 #endif
4998 
4999   QM_TRY(([&, statusKeeperFunc = [&](const nsresult rv) {
5000             RECORD_IN_NIGHTLY(statusKeeper, rv);
5001           }]() -> Result<Ok, nsresult> {
5002     QM_TRY(
5003         CollectEachFile(
5004             *aDirectory,
5005             [&](const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
5006               if (NS_WARN_IF(IsShuttingDown())) {
5007                 RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
5008               }
5009 
5010               QM_TRY(
5011                   ([this, &file, trackQuota, aPersistenceType, &aOriginMetadata,
5012                     &clientUsages]() -> Result<Ok, nsresult> {
5013                     QM_TRY_INSPECT(const auto& leafName,
5014                                    MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5015                                        nsAutoString, file, GetLeafName));
5016 
5017                     QM_TRY_INSPECT(const auto& dirEntryKind,
5018                                    GetDirEntryKind(*file));
5019 
5020                     switch (dirEntryKind) {
5021                       case nsIFileKind::ExistsAsDirectory: {
5022                         Client::Type clientType;
5023                         const bool ok = Client::TypeFromText(
5024                             leafName, clientType, fallible);
5025                         if (!ok) {
5026                           // Unknown directories during initialization are now
5027                           // allowed. Just warn if we find them.
5028                           UNKNOWN_FILE_WARNING(leafName);
5029                           break;
5030                         }
5031 
5032                         if (trackQuota) {
5033                           QM_TRY_INSPECT(
5034                               const auto& usageInfo,
5035                               (*mClients)[clientType]->InitOrigin(
5036                                   aPersistenceType, aOriginMetadata,
5037                                   /* aCanceled */ Atomic<bool>(false)));
5038 
5039                           MOZ_ASSERT(!clientUsages[clientType]);
5040 
5041                           if (usageInfo.TotalUsage()) {
5042                             // XXX(Bug 1683863) Until we identify the root cause
5043                             // of seemingly converted-from-negative usage
5044                             // values, we will just treat them as unset here,
5045                             // but log a warning to the browser console.
5046                             if (static_cast<int64_t>(*usageInfo.TotalUsage()) >=
5047                                 0) {
5048                               clientUsages[clientType] = usageInfo.TotalUsage();
5049                             } else {
5050 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
5051                               const nsCOMPtr<nsIConsoleService> console =
5052                                   do_GetService(NS_CONSOLESERVICE_CONTRACTID);
5053                               if (console) {
5054                                 console->LogStringMessage(
5055                                     nsString(
5056                                         u"QuotaManager warning: client "_ns +
5057                                         leafName +
5058                                         u" reported negative usage for group "_ns +
5059                                         NS_ConvertUTF8toUTF16(
5060                                             aOriginMetadata.mGroup) +
5061                                         u", origin "_ns +
5062                                         NS_ConvertUTF8toUTF16(
5063                                             aOriginMetadata.mOrigin))
5064                                         .get());
5065                               }
5066 #endif
5067                             }
5068                           }
5069                         } else {
5070                           QM_TRY(MOZ_TO_RESULT(
5071                               (*mClients)[clientType]
5072                                   ->InitOriginWithoutTracking(
5073                                       aPersistenceType, aOriginMetadata,
5074                                       /* aCanceled */ Atomic<bool>(false))));
5075                         }
5076 
5077                         break;
5078                       }
5079 
5080                       case nsIFileKind::ExistsAsFile:
5081                         if (IsOriginMetadata(leafName)) {
5082                           break;
5083                         }
5084 
5085                         if (IsTempMetadata(leafName)) {
5086                           QM_TRY(MOZ_TO_RESULT(
5087                               file->Remove(/* recursive */ false)));
5088 
5089                           break;
5090                         }
5091 
5092                         if (IsOSMetadata(leafName) || IsDotFile(leafName)) {
5093                           break;
5094                         }
5095 
5096                         // Unknown files during initialization are now allowed.
5097                         // Just warn if we find them.
5098                         UNKNOWN_FILE_WARNING(leafName);
5099                         // Bug 1595448 will handle the case for unknown files
5100                         // like idb, cache, or ls.
5101                         break;
5102 
5103                       case nsIFileKind::DoesNotExist:
5104                         // Ignore files that got removed externally while
5105                         // iterating.
5106                         break;
5107                     }
5108 
5109                     return Ok{};
5110                   }()),
5111                   OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
5112 
5113               return Ok{};
5114             }),
5115         OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
5116 
5117     return Ok{};
5118   }()));
5119 
5120 #ifdef NIGHTLY_BUILD
5121   if (NS_FAILED(statusKeeper)) {
5122     return statusKeeper;
5123   }
5124 #endif
5125 
5126   if (trackQuota) {
5127     const auto usage = std::accumulate(
5128         clientUsages.cbegin(), clientUsages.cend(), CheckedUint64(0),
5129         [](CheckedUint64 value, const Maybe<uint64_t>& clientUsage) {
5130           return value + clientUsage.valueOr(0);
5131         });
5132 
5133     // XXX Should we log more information, i.e. the whole clientUsages array, in
5134     // case usage is not valid?
5135 
5136     QM_TRY(OkIf(usage.isValid()), NS_ERROR_FAILURE);
5137 
5138     InitQuotaForOrigin(
5139         FullOriginMetadata{aOriginMetadata, aPersisted, aAccessTime},
5140         clientUsages, usage.value());
5141   }
5142 
5143   return NS_OK;
5144 }
5145 
5146 nsresult
UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(nsIFile * aIndexedDBDir)5147 QuotaManager::UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
5148     nsIFile* aIndexedDBDir) {
5149   AssertIsOnIOThread();
5150   MOZ_ASSERT(aIndexedDBDir);
5151 
5152   const auto innerFunc = [this, &aIndexedDBDir](const auto&) -> nsresult {
5153     bool isDirectory;
5154     QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->IsDirectory(&isDirectory)));
5155 
5156     if (!isDirectory) {
5157       NS_WARNING("indexedDB entry is not a directory!");
5158       return NS_OK;
5159     }
5160 
5161     auto persistentStorageDirOrErr = QM_NewLocalFile(*mStoragePath);
5162     if (NS_WARN_IF(persistentStorageDirOrErr.isErr())) {
5163       return persistentStorageDirOrErr.unwrapErr();
5164     }
5165 
5166     nsCOMPtr<nsIFile> persistentStorageDir = persistentStorageDirOrErr.unwrap();
5167 
5168     QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
5169         nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
5170 
5171     bool exists;
5172     QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Exists(&exists)));
5173 
5174     if (exists) {
5175       QM_WARNING("Deleting old <profile>/indexedDB directory!");
5176 
5177       QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->Remove(/* aRecursive */ true)));
5178 
5179       return NS_OK;
5180     }
5181 
5182     nsCOMPtr<nsIFile> storageDir;
5183     QM_TRY(MOZ_TO_RESULT(
5184         persistentStorageDir->GetParent(getter_AddRefs(storageDir))));
5185 
5186     // MoveTo() is atomic if the move happens on the same volume which should
5187     // be our case, so even if we crash in the middle of the operation nothing
5188     // breaks next time we try to initialize.
5189     // However there's a theoretical possibility that the indexedDB directory
5190     // is on different volume, but it should be rare enough that we don't have
5191     // to worry about it.
5192     QM_TRY(MOZ_TO_RESULT(aIndexedDBDir->MoveTo(
5193         storageDir, nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
5194 
5195     return NS_OK;
5196   };
5197 
5198   return ExecuteInitialization(Initialization::UpgradeFromIndexedDBDirectory,
5199                                innerFunc);
5200 }
5201 
5202 nsresult
UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(nsIFile * aPersistentStorageDir)5203 QuotaManager::UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5204     nsIFile* aPersistentStorageDir) {
5205   AssertIsOnIOThread();
5206   MOZ_ASSERT(aPersistentStorageDir);
5207 
5208   const auto innerFunc = [this,
5209                           &aPersistentStorageDir](const auto&) -> nsresult {
5210     QM_TRY_INSPECT(
5211         const bool& isDirectory,
5212         MOZ_TO_RESULT_INVOKE_MEMBER(aPersistentStorageDir, IsDirectory));
5213 
5214     if (!isDirectory) {
5215       NS_WARNING("persistent entry is not a directory!");
5216       return NS_OK;
5217     }
5218 
5219     {
5220       QM_TRY_INSPECT(const auto& defaultStorageDir,
5221                      QM_NewLocalFile(*mDefaultStoragePath));
5222 
5223       QM_TRY_INSPECT(const bool& exists,
5224                      MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
5225 
5226       if (exists) {
5227         QM_WARNING("Deleting old <profile>/storage/persistent directory!");
5228 
5229         QM_TRY(MOZ_TO_RESULT(
5230             aPersistentStorageDir->Remove(/* aRecursive */ true)));
5231 
5232         return NS_OK;
5233       }
5234     }
5235 
5236     {
5237       // Create real metadata files for origin directories in persistent
5238       // storage.
5239       auto helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
5240           aPersistentStorageDir);
5241 
5242       QM_TRY(MOZ_TO_RESULT(helper->Init()));
5243 
5244       QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
5245 
5246       // Upgrade metadata files for origin directories in temporary storage.
5247       QM_TRY_INSPECT(const auto& temporaryStorageDir,
5248                      QM_NewLocalFile(*mTemporaryStoragePath));
5249 
5250       QM_TRY_INSPECT(const bool& exists,
5251                      MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, Exists));
5252 
5253       if (exists) {
5254         QM_TRY_INSPECT(
5255             const bool& isDirectory,
5256             MOZ_TO_RESULT_INVOKE_MEMBER(temporaryStorageDir, IsDirectory));
5257 
5258         if (!isDirectory) {
5259           NS_WARNING("temporary entry is not a directory!");
5260           return NS_OK;
5261         }
5262 
5263         helper = MakeRefPtr<CreateOrUpgradeDirectoryMetadataHelper>(
5264             temporaryStorageDir);
5265 
5266         QM_TRY(MOZ_TO_RESULT(helper->Init()));
5267 
5268         QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
5269       }
5270     }
5271 
5272     // And finally rename persistent to default.
5273     QM_TRY(MOZ_TO_RESULT(aPersistentStorageDir->RenameTo(
5274         nullptr, nsLiteralString(DEFAULT_DIRECTORY_NAME))));
5275 
5276     return NS_OK;
5277   };
5278 
5279   return ExecuteInitialization(
5280       Initialization::UpgradeFromPersistentStorageDirectory, innerFunc);
5281 }
5282 
5283 template <typename Helper>
UpgradeStorage(const int32_t aOldVersion,const int32_t aNewVersion,mozIStorageConnection * aConnection)5284 nsresult QuotaManager::UpgradeStorage(const int32_t aOldVersion,
5285                                       const int32_t aNewVersion,
5286                                       mozIStorageConnection* aConnection) {
5287   AssertIsOnIOThread();
5288   MOZ_ASSERT(aNewVersion > aOldVersion);
5289   MOZ_ASSERT(aNewVersion <= kStorageVersion);
5290   MOZ_ASSERT(aConnection);
5291 
5292   for (const PersistenceType persistenceType : kAllPersistenceTypes) {
5293     QM_TRY_UNWRAP(auto directory,
5294                   QM_NewLocalFile(GetStoragePath(persistenceType)));
5295 
5296     QM_TRY_INSPECT(const bool& exists,
5297                    MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
5298 
5299     if (!exists) {
5300       continue;
5301     }
5302 
5303     RefPtr<UpgradeStorageHelperBase> helper = new Helper(directory);
5304 
5305     QM_TRY(MOZ_TO_RESULT(helper->Init()));
5306 
5307     QM_TRY(MOZ_TO_RESULT(helper->ProcessRepository()));
5308   }
5309 
5310 #ifdef DEBUG
5311   {
5312     QM_TRY_INSPECT(const int32_t& storageVersion,
5313                    MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
5314 
5315     MOZ_ASSERT(storageVersion == aOldVersion);
5316   }
5317 #endif
5318 
5319   QM_TRY(MOZ_TO_RESULT(aConnection->SetSchemaVersion(aNewVersion)));
5320 
5321   return NS_OK;
5322 }
5323 
UpgradeStorageFrom0_0To1_0(mozIStorageConnection * aConnection)5324 nsresult QuotaManager::UpgradeStorageFrom0_0To1_0(
5325     mozIStorageConnection* aConnection) {
5326   AssertIsOnIOThread();
5327   MOZ_ASSERT(aConnection);
5328 
5329   const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
5330     QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom0_0To1_0Helper>(
5331         0, MakeStorageVersion(1, 0), aConnection)));
5332 
5333     return NS_OK;
5334   };
5335 
5336   return ExecuteInitialization(Initialization::UpgradeStorageFrom0_0To1_0,
5337                                innerFunc);
5338 }
5339 
UpgradeStorageFrom1_0To2_0(mozIStorageConnection * aConnection)5340 nsresult QuotaManager::UpgradeStorageFrom1_0To2_0(
5341     mozIStorageConnection* aConnection) {
5342   AssertIsOnIOThread();
5343   MOZ_ASSERT(aConnection);
5344 
5345   // The upgrade consists of a number of logically distinct bugs that
5346   // intentionally got fixed at the same time to trigger just one major
5347   // version bump.
5348   //
5349   //
5350   // Morgue directory cleanup
5351   // [Feature/Bug]:
5352   // The original bug that added "on demand" morgue cleanup is 1165119.
5353   //
5354   // [Mutations]:
5355   // Morgue directories are removed from all origin directories during the
5356   // upgrade process. Origin initialization and usage calculation doesn't try
5357   // to remove morgue directories anymore.
5358   //
5359   // [Downgrade-incompatible changes]:
5360   // Morgue directories can reappear if user runs an already upgraded profile
5361   // in an older version of Firefox. Morgue directories then prevent current
5362   // Firefox from initializing and using the storage.
5363   //
5364   //
5365   // App data removal
5366   // [Feature/Bug]:
5367   // The bug that removes isApp flags is 1311057.
5368   //
5369   // [Mutations]:
5370   // Origin directories with appIds are removed during the upgrade process.
5371   //
5372   // [Downgrade-incompatible changes]:
5373   // Origin directories with appIds can reappear if user runs an already
5374   // upgraded profile in an older version of Firefox. Origin directories with
5375   // appIds don't prevent current Firefox from initializing and using the
5376   // storage, but they wouldn't ever be removed again, potentially causing
5377   // problems once appId is removed from origin attributes.
5378   //
5379   //
5380   // Strip obsolete origin attributes
5381   // [Feature/Bug]:
5382   // The bug that strips obsolete origin attributes is 1314361.
5383   //
5384   // [Mutations]:
5385   // Origin directories with obsolete origin attributes are renamed and their
5386   // metadata files are updated during the upgrade process.
5387   //
5388   // [Downgrade-incompatible changes]:
5389   // Origin directories with obsolete origin attributes can reappear if user
5390   // runs an already upgraded profile in an older version of Firefox. Origin
5391   // directories with obsolete origin attributes don't prevent current Firefox
5392   // from initializing and using the storage, but they wouldn't ever be upgraded
5393   // again, potentially causing problems in future.
5394   //
5395   //
5396   // File manager directory renaming (client specific)
5397   // [Feature/Bug]:
5398   // The original bug that added "on demand" file manager directory renaming is
5399   // 1056939.
5400   //
5401   // [Mutations]:
5402   // All file manager directories are renamed to contain the ".files" suffix.
5403   //
5404   // [Downgrade-incompatible changes]:
5405   // File manager directories with the ".files" suffix prevent older versions of
5406   // Firefox from initializing and using the storage.
5407   // File manager directories without the ".files" suffix can appear if user
5408   // runs an already upgraded profile in an older version of Firefox. File
5409   // manager directories without the ".files" suffix then prevent current
5410   // Firefox from initializing and using the storage.
5411 
5412   const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
5413     QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom1_0To2_0Helper>(
5414         MakeStorageVersion(1, 0), MakeStorageVersion(2, 0), aConnection)));
5415 
5416     return NS_OK;
5417   };
5418 
5419   return ExecuteInitialization(Initialization::UpgradeStorageFrom1_0To2_0,
5420                                innerFunc);
5421 }
5422 
UpgradeStorageFrom2_0To2_1(mozIStorageConnection * aConnection)5423 nsresult QuotaManager::UpgradeStorageFrom2_0To2_1(
5424     mozIStorageConnection* aConnection) {
5425   AssertIsOnIOThread();
5426   MOZ_ASSERT(aConnection);
5427 
5428   // The upgrade is mainly to create a directory padding file in DOM Cache
5429   // directory to record the overall padding size of an origin.
5430 
5431   const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
5432     QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_0To2_1Helper>(
5433         MakeStorageVersion(2, 0), MakeStorageVersion(2, 1), aConnection)));
5434 
5435     return NS_OK;
5436   };
5437 
5438   return ExecuteInitialization(Initialization::UpgradeStorageFrom2_0To2_1,
5439                                innerFunc);
5440 }
5441 
UpgradeStorageFrom2_1To2_2(mozIStorageConnection * aConnection)5442 nsresult QuotaManager::UpgradeStorageFrom2_1To2_2(
5443     mozIStorageConnection* aConnection) {
5444   AssertIsOnIOThread();
5445   MOZ_ASSERT(aConnection);
5446 
5447   // The upgrade is mainly to clean obsolete origins in the repositoies, remove
5448   // asmjs client, and ".tmp" file in the idb folers.
5449 
5450   const auto innerFunc = [this, &aConnection](const auto&) -> nsresult {
5451     QM_TRY(MOZ_TO_RESULT(UpgradeStorage<UpgradeStorageFrom2_1To2_2Helper>(
5452         MakeStorageVersion(2, 1), MakeStorageVersion(2, 2), aConnection)));
5453 
5454     return NS_OK;
5455   };
5456 
5457   return ExecuteInitialization(Initialization::UpgradeStorageFrom2_1To2_2,
5458                                innerFunc);
5459 }
5460 
UpgradeStorageFrom2_2To2_3(mozIStorageConnection * aConnection)5461 nsresult QuotaManager::UpgradeStorageFrom2_2To2_3(
5462     mozIStorageConnection* aConnection) {
5463   AssertIsOnIOThread();
5464   MOZ_ASSERT(aConnection);
5465 
5466   const auto innerFunc = [&aConnection](const auto&) -> nsresult {
5467     // Table `database`
5468     QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
5469         nsLiteralCString("CREATE TABLE database"
5470                          "( cache_version INTEGER NOT NULL DEFAULT 0"
5471                          ");"))));
5472 
5473     QM_TRY(MOZ_TO_RESULT(aConnection->ExecuteSimpleSQL(
5474         nsLiteralCString("INSERT INTO database (cache_version) "
5475                          "VALUES (0)"))));
5476 
5477 #ifdef DEBUG
5478     {
5479       QM_TRY_INSPECT(
5480           const int32_t& storageVersion,
5481           MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
5482 
5483       MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 2));
5484     }
5485 #endif
5486 
5487     QM_TRY(
5488         MOZ_TO_RESULT(aConnection->SetSchemaVersion(MakeStorageVersion(2, 3))));
5489 
5490     return NS_OK;
5491   };
5492 
5493   return ExecuteInitialization(Initialization::UpgradeStorageFrom2_2To2_3,
5494                                innerFunc);
5495 }
5496 
MaybeRemoveLocalStorageDataAndArchive(nsIFile & aLsArchiveFile)5497 nsresult QuotaManager::MaybeRemoveLocalStorageDataAndArchive(
5498     nsIFile& aLsArchiveFile) {
5499   AssertIsOnIOThread();
5500   MOZ_ASSERT(!CachedNextGenLocalStorageEnabled());
5501 
5502   QM_TRY_INSPECT(const bool& exists,
5503                  MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5504 
5505   if (!exists) {
5506     // If the ls archive doesn't exist then ls directories can't exist either.
5507     return NS_OK;
5508   }
5509 
5510   QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
5511 
5512   InvalidateQuotaCache();
5513 
5514   // Finally remove the ls archive, so we don't have to check all origin
5515   // directories next time this method is called.
5516   QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
5517 
5518   return NS_OK;
5519 }
5520 
MaybeRemoveLocalStorageDirectories()5521 nsresult QuotaManager::MaybeRemoveLocalStorageDirectories() {
5522   AssertIsOnIOThread();
5523 
5524   QM_TRY_INSPECT(const auto& defaultStorageDir,
5525                  QM_NewLocalFile(*mDefaultStoragePath));
5526 
5527   QM_TRY_INSPECT(const bool& exists,
5528                  MOZ_TO_RESULT_INVOKE_MEMBER(defaultStorageDir, Exists));
5529 
5530   if (!exists) {
5531     return NS_OK;
5532   }
5533 
5534   QM_TRY(CollectEachFile(
5535       *defaultStorageDir,
5536       [](const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
5537 #ifdef DEBUG
5538         {
5539           QM_TRY_INSPECT(const bool& exists,
5540                          MOZ_TO_RESULT_INVOKE_MEMBER(originDir, Exists));
5541           MOZ_ASSERT(exists);
5542         }
5543 #endif
5544 
5545         QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
5546 
5547         switch (dirEntryKind) {
5548           case nsIFileKind::ExistsAsDirectory: {
5549             QM_TRY_INSPECT(
5550                 const auto& lsDir,
5551                 CloneFileAndAppend(*originDir, NS_LITERAL_STRING_FROM_CSTRING(
5552                                                    LS_DIRECTORY_NAME)));
5553 
5554             {
5555               QM_TRY_INSPECT(const bool& exists,
5556                              MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, Exists));
5557 
5558               if (!exists) {
5559                 return Ok{};
5560               }
5561             }
5562 
5563             {
5564               QM_TRY_INSPECT(const bool& isDirectory,
5565                              MOZ_TO_RESULT_INVOKE_MEMBER(lsDir, IsDirectory));
5566 
5567               if (!isDirectory) {
5568                 QM_WARNING("ls entry is not a directory!");
5569 
5570                 return Ok{};
5571               }
5572             }
5573 
5574             nsString path;
5575             QM_TRY(MOZ_TO_RESULT(lsDir->GetPath(path)));
5576 
5577             QM_WARNING("Deleting %s directory!",
5578                        NS_ConvertUTF16toUTF8(path).get());
5579 
5580             QM_TRY(MOZ_TO_RESULT(lsDir->Remove(/* aRecursive */ true)));
5581 
5582             break;
5583           }
5584 
5585           case nsIFileKind::ExistsAsFile: {
5586             QM_TRY_INSPECT(const auto& leafName,
5587                            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5588                                nsAutoString, originDir, GetLeafName));
5589 
5590             // Unknown files during upgrade are allowed. Just warn if we find
5591             // them.
5592             if (!IsOSMetadata(leafName)) {
5593               UNKNOWN_FILE_WARNING(leafName);
5594             }
5595 
5596             break;
5597           }
5598 
5599           case nsIFileKind::DoesNotExist:
5600             // Ignore files that got removed externally while iterating.
5601             break;
5602         }
5603         return Ok{};
5604       }));
5605 
5606   return NS_OK;
5607 }
5608 
CopyLocalStorageArchiveFromWebAppsStore(nsIFile & aLsArchiveFile) const5609 Result<Ok, nsresult> QuotaManager::CopyLocalStorageArchiveFromWebAppsStore(
5610     nsIFile& aLsArchiveFile) const {
5611   AssertIsOnIOThread();
5612   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5613 
5614 #ifdef DEBUG
5615   {
5616     QM_TRY_INSPECT(const bool& exists,
5617                    MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5618     MOZ_ASSERT(!exists);
5619   }
5620 #endif
5621 
5622   // Get the storage service first, we will need it at multiple places.
5623   QM_TRY_INSPECT(const auto& ss,
5624                  MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
5625                                          MOZ_SELECT_OVERLOAD(do_GetService),
5626                                          MOZ_STORAGE_SERVICE_CONTRACTID));
5627 
5628   // Get the web apps store file.
5629   QM_TRY_INSPECT(const auto& webAppsStoreFile, QM_NewLocalFile(mBasePath));
5630 
5631   QM_TRY(MOZ_TO_RESULT(
5632       webAppsStoreFile->Append(nsLiteralString(WEB_APPS_STORE_FILE_NAME))));
5633 
5634   // Now check if the web apps store is useable.
5635   QM_TRY_INSPECT(const auto& connection,
5636                  CreateWebAppsStoreConnection(*webAppsStoreFile, *ss));
5637 
5638   if (connection) {
5639     // Find out the journal mode.
5640     QM_TRY_INSPECT(const auto& stmt,
5641                    CreateAndExecuteSingleStepStatement(
5642                        *connection, "PRAGMA journal_mode;"_ns));
5643 
5644     QM_TRY_INSPECT(const auto& journalMode,
5645                    MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *stmt,
5646                                                      GetUTF8String, 0));
5647 
5648     QM_TRY(MOZ_TO_RESULT(stmt->Finalize()));
5649 
5650     if (journalMode.EqualsLiteral("wal")) {
5651       // We don't copy the WAL file, so make sure the old database is fully
5652       // checkpointed.
5653       QM_TRY(MOZ_TO_RESULT(
5654           connection->ExecuteSimpleSQL("PRAGMA wal_checkpoint(TRUNCATE);"_ns)));
5655     }
5656 
5657     // Explicitely close the connection before the old database is copied.
5658     QM_TRY(MOZ_TO_RESULT(connection->Close()));
5659 
5660     // Copy the old database. The database is copied from
5661     // <profile>/webappsstore.sqlite to
5662     // <profile>/storage/ls-archive-tmp.sqlite
5663     // We use a "-tmp" postfix since we are not done yet.
5664     QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
5665 
5666     QM_TRY(MOZ_TO_RESULT(webAppsStoreFile->CopyTo(
5667         storageDir, nsLiteralString(LS_ARCHIVE_TMP_FILE_NAME))));
5668 
5669     QM_TRY_INSPECT(const auto& lsArchiveTmpFile,
5670                    GetLocalStorageArchiveTmpFile(*mStoragePath));
5671 
5672     if (journalMode.EqualsLiteral("wal")) {
5673       QM_TRY_INSPECT(
5674           const auto& lsArchiveTmpConnection,
5675           MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5676               nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5677               lsArchiveTmpFile, mozIStorageService::CONNECTION_DEFAULT));
5678 
5679       // The archive will only be used for lazy data migration. There won't be
5680       // any concurrent readers and writers that could benefit from Write-Ahead
5681       // Logging. So switch to a standard rollback journal. The standard
5682       // rollback journal also provides atomicity across multiple attached
5683       // databases which is import for the lazy data migration to work safely.
5684       QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->ExecuteSimpleSQL(
5685           "PRAGMA journal_mode = DELETE;"_ns)));
5686 
5687       // Close the connection explicitly. We are going to rename the file below.
5688       QM_TRY(MOZ_TO_RESULT(lsArchiveTmpConnection->Close()));
5689     }
5690 
5691     // Finally, rename ls-archive-tmp.sqlite to ls-archive.sqlite
5692     QM_TRY(MOZ_TO_RESULT(lsArchiveTmpFile->MoveTo(
5693         nullptr, nsLiteralString(LS_ARCHIVE_FILE_NAME))));
5694 
5695     return Ok{};
5696   }
5697 
5698   // If webappsstore database is not useable, just create an empty archive.
5699   // XXX The code below should be removed and the caller should call us only
5700   // when webappstore.sqlite exists. CreateWebAppsStoreConnection should be
5701   // reworked to propagate database corruption instead of returning null
5702   // connection.
5703   // So, if there's no webappsstore.sqlite
5704   // MaybeCreateOrUpgradeLocalStorageArchive will call
5705   // CreateEmptyLocalStorageArchive instead of
5706   // CopyLocalStorageArchiveFromWebAppsStore.
5707   // If there's any corruption detected during
5708   // MaybeCreateOrUpgradeLocalStorageArchive (including nested calls like
5709   // CopyLocalStorageArchiveFromWebAppsStore and CreateWebAppsStoreConnection)
5710   // EnsureStorageIsInitialized will fallback to
5711   // CreateEmptyLocalStorageArchive.
5712 
5713   // Ensure the storage directory actually exists.
5714   QM_TRY_INSPECT(const auto& storageDirectory, QM_NewLocalFile(*mStoragePath));
5715 
5716   QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDirectory));
5717 
5718   Unused << created;
5719 
5720   QM_TRY_UNWRAP(auto lsArchiveConnection,
5721                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5722                     nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5723                     &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
5724 
5725   QM_TRY(MOZ_TO_RESULT(
5726       StorageDBUpdater::CreateCurrentSchema(lsArchiveConnection)));
5727 
5728   return Ok{};
5729 }
5730 
5731 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
CreateLocalStorageArchiveConnection(nsIFile & aLsArchiveFile) const5732 QuotaManager::CreateLocalStorageArchiveConnection(
5733     nsIFile& aLsArchiveFile) const {
5734   AssertIsOnIOThread();
5735   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5736 
5737 #ifdef DEBUG
5738   {
5739     QM_TRY_INSPECT(const bool& exists,
5740                    MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5741     MOZ_ASSERT(exists);
5742   }
5743 #endif
5744 
5745   QM_TRY_INSPECT(const bool& isDirectory,
5746                  MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, IsDirectory));
5747 
5748   // A directory with the name of the archive file is treated as corruption
5749   // (similarly as wrong content of the file).
5750   QM_TRY(OkIf(!isDirectory), Err(NS_ERROR_FILE_CORRUPTED));
5751 
5752   QM_TRY_INSPECT(const auto& ss,
5753                  MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
5754                                          MOZ_SELECT_OVERLOAD(do_GetService),
5755                                          MOZ_STORAGE_SERVICE_CONTRACTID));
5756 
5757   // This may return NS_ERROR_FILE_CORRUPTED too.
5758   QM_TRY_UNWRAP(auto connection,
5759                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
5760                     nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
5761                     &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
5762 
5763   // The legacy LS implementation removes the database and creates an empty one
5764   // when the schema can't be updated. The same effect can be achieved here by
5765   // mapping all errors to NS_ERROR_FILE_CORRUPTED. One such case is tested by
5766   // sub test case 3 of dom/localstorage/test/unit/test_archive.js
5767   QM_TRY(
5768       MOZ_TO_RESULT(StorageDBUpdater::Update(connection))
5769           .mapErr([](const nsresult rv) { return NS_ERROR_FILE_CORRUPTED; }));
5770 
5771   return connection;
5772 }
5773 
5774 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
RecopyLocalStorageArchiveFromWebAppsStore(nsIFile & aLsArchiveFile)5775 QuotaManager::RecopyLocalStorageArchiveFromWebAppsStore(
5776     nsIFile& aLsArchiveFile) {
5777   AssertIsOnIOThread();
5778   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5779 
5780   QM_TRY(MOZ_TO_RESULT(MaybeRemoveLocalStorageDirectories()));
5781 
5782 #ifdef DEBUG
5783   {
5784     QM_TRY_INSPECT(const bool& exists,
5785                    MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
5786 
5787     MOZ_ASSERT(exists);
5788   }
5789 #endif
5790 
5791   QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(false)));
5792 
5793   QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5794 
5795   QM_TRY_UNWRAP(auto connection,
5796                 CreateLocalStorageArchiveConnection(aLsArchiveFile));
5797 
5798   QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
5799 
5800   return connection;
5801 }
5802 
5803 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
DowngradeLocalStorageArchive(nsIFile & aLsArchiveFile)5804 QuotaManager::DowngradeLocalStorageArchive(nsIFile& aLsArchiveFile) {
5805   AssertIsOnIOThread();
5806   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5807 
5808   QM_TRY_UNWRAP(auto connection,
5809                 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5810 
5811   QM_TRY(MOZ_TO_RESULT(
5812       SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
5813 
5814   return connection;
5815 }
5816 
5817 Result<nsCOMPtr<mozIStorageConnection>, nsresult>
UpgradeLocalStorageArchiveFromLessThan4To4(nsIFile & aLsArchiveFile)5818 QuotaManager::UpgradeLocalStorageArchiveFromLessThan4To4(
5819     nsIFile& aLsArchiveFile) {
5820   AssertIsOnIOThread();
5821   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5822 
5823   QM_TRY_UNWRAP(auto connection,
5824                 RecopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
5825 
5826   QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(connection, 4)));
5827 
5828   return connection;
5829 }
5830 
5831 /*
5832 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom4To5(
5833     nsCOMPtr<mozIStorageConnection>& aConnection) {
5834   AssertIsOnIOThread();
5835   MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
5836 
5837   nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 5);
5838   if (NS_WARN_IF(NS_FAILED(rv))) {
5839     return rv;
5840   }
5841 
5842   return NS_OK;
5843 }
5844 */
5845 
5846 #ifdef DEBUG
5847 
AssertStorageIsInitialized() const5848 void QuotaManager::AssertStorageIsInitialized() const {
5849   AssertIsOnIOThread();
5850   MOZ_ASSERT(IsStorageInitialized());
5851 }
5852 
5853 #endif  // DEBUG
5854 
MaybeUpgradeToDefaultStorageDirectory(nsIFile & aStorageFile)5855 nsresult QuotaManager::MaybeUpgradeToDefaultStorageDirectory(
5856     nsIFile& aStorageFile) {
5857   AssertIsOnIOThread();
5858 
5859   QM_TRY_INSPECT(const auto& storageFileExists,
5860                  MOZ_TO_RESULT_INVOKE_MEMBER(aStorageFile, Exists));
5861 
5862   if (!storageFileExists) {
5863     QM_TRY_INSPECT(const auto& indexedDBDir, QM_NewLocalFile(*mIndexedDBPath));
5864 
5865     QM_TRY_INSPECT(const auto& indexedDBDirExists,
5866                    MOZ_TO_RESULT_INVOKE_MEMBER(indexedDBDir, Exists));
5867 
5868     if (indexedDBDirExists) {
5869       QM_TRY(MOZ_TO_RESULT(
5870           UpgradeFromIndexedDBDirectoryToPersistentStorageDirectory(
5871               indexedDBDir)));
5872     }
5873 
5874     QM_TRY_INSPECT(const auto& persistentStorageDir,
5875                    QM_NewLocalFile(*mStoragePath));
5876 
5877     QM_TRY(MOZ_TO_RESULT(persistentStorageDir->Append(
5878         nsLiteralString(PERSISTENT_DIRECTORY_NAME))));
5879 
5880     QM_TRY_INSPECT(const auto& persistentStorageDirExists,
5881                    MOZ_TO_RESULT_INVOKE_MEMBER(persistentStorageDir, Exists));
5882 
5883     if (persistentStorageDirExists) {
5884       QM_TRY(MOZ_TO_RESULT(
5885           UpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory(
5886               persistentStorageDir)));
5887     }
5888   }
5889 
5890   return NS_OK;
5891 }
5892 
MaybeCreateOrUpgradeStorage(mozIStorageConnection & aConnection)5893 nsresult QuotaManager::MaybeCreateOrUpgradeStorage(
5894     mozIStorageConnection& aConnection) {
5895   AssertIsOnIOThread();
5896 
5897   QM_TRY_UNWRAP(auto storageVersion,
5898                 MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
5899 
5900   // Hacky downgrade logic!
5901   // If we see major.minor of 3.0, downgrade it to be 2.1.
5902   if (storageVersion == kHackyPreDowngradeStorageVersion) {
5903     storageVersion = kHackyPostDowngradeStorageVersion;
5904     QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
5905            QM_PROPAGATE,
5906            [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
5907   }
5908 
5909   QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
5910          NS_ERROR_FAILURE, [](const auto&) {
5911            NS_WARNING("Unable to initialize storage, version is too high!");
5912          });
5913 
5914   if (storageVersion < kStorageVersion) {
5915     const bool newDatabase = !storageVersion;
5916 
5917     QM_TRY_INSPECT(const auto& storageDir, QM_NewLocalFile(*mStoragePath));
5918 
5919     QM_TRY_INSPECT(const auto& storageDirExists,
5920                    MOZ_TO_RESULT_INVOKE_MEMBER(storageDir, Exists));
5921 
5922     const bool newDirectory = !storageDirExists;
5923 
5924     if (newDatabase) {
5925       // Set the page size first.
5926       if (kSQLitePageSizeOverride) {
5927         QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(nsPrintfCString(
5928             "PRAGMA page_size = %" PRIu32 ";", kSQLitePageSizeOverride))));
5929       }
5930     }
5931 
5932     mozStorageTransaction transaction(
5933         &aConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
5934 
5935     QM_TRY(MOZ_TO_RESULT(transaction.Start()));
5936 
5937     // An upgrade method can upgrade the database, the storage or both.
5938     // The upgrade loop below can only be avoided when there's no database and
5939     // no storage yet (e.g. new profile).
5940     if (newDatabase && newDirectory) {
5941       QM_TRY(MOZ_TO_RESULT(CreateTables(&aConnection)));
5942 
5943 #ifdef DEBUG
5944       {
5945         QM_TRY_INSPECT(
5946             const int32_t& storageVersion,
5947             MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion),
5948             QM_ASSERT_UNREACHABLE);
5949         MOZ_ASSERT(storageVersion == kStorageVersion);
5950       }
5951 #endif
5952 
5953       QM_TRY(MOZ_TO_RESULT(aConnection.ExecuteSimpleSQL(
5954           nsLiteralCString("INSERT INTO database (cache_version) "
5955                            "VALUES (0)"))));
5956     } else {
5957       // This logic needs to change next time we change the storage!
5958       static_assert(kStorageVersion == int32_t((2 << 16) + 3),
5959                     "Upgrade function needed due to storage version increase.");
5960 
5961       while (storageVersion != kStorageVersion) {
5962         if (storageVersion == 0) {
5963           QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom0_0To1_0(&aConnection)));
5964         } else if (storageVersion == MakeStorageVersion(1, 0)) {
5965           QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom1_0To2_0(&aConnection)));
5966         } else if (storageVersion == MakeStorageVersion(2, 0)) {
5967           QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_0To2_1(&aConnection)));
5968         } else if (storageVersion == MakeStorageVersion(2, 1)) {
5969           QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_1To2_2(&aConnection)));
5970         } else if (storageVersion == MakeStorageVersion(2, 2)) {
5971           QM_TRY(MOZ_TO_RESULT(UpgradeStorageFrom2_2To2_3(&aConnection)));
5972         } else {
5973           QM_FAIL(NS_ERROR_FAILURE, []() {
5974             NS_WARNING(
5975                 "Unable to initialize storage, no upgrade path is "
5976                 "available!");
5977           });
5978         }
5979 
5980         QM_TRY_UNWRAP(storageVersion, MOZ_TO_RESULT_INVOKE_MEMBER(
5981                                           aConnection, GetSchemaVersion));
5982       }
5983 
5984       MOZ_ASSERT(storageVersion == kStorageVersion);
5985     }
5986 
5987     QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
5988   }
5989 
5990   return NS_OK;
5991 }
5992 
MaybeRemoveLocalStorageArchiveTmpFile()5993 OkOrErr QuotaManager::MaybeRemoveLocalStorageArchiveTmpFile() {
5994   AssertIsOnIOThread();
5995 
5996   QM_TRY_INSPECT(
5997       const auto& lsArchiveTmpFile,
5998       QM_TO_RESULT_TRANSFORM(GetLocalStorageArchiveTmpFile(*mStoragePath)));
5999 
6000   QM_TRY_INSPECT(const bool& exists,
6001                  QM_TO_RESULT_INVOKE_MEMBER(lsArchiveTmpFile, Exists));
6002 
6003   if (exists) {
6004     QM_TRY(QM_TO_RESULT(lsArchiveTmpFile->Remove(false)));
6005   }
6006 
6007   return Ok{};
6008 }
6009 
MaybeCreateOrUpgradeLocalStorageArchive(nsIFile & aLsArchiveFile)6010 Result<Ok, nsresult> QuotaManager::MaybeCreateOrUpgradeLocalStorageArchive(
6011     nsIFile& aLsArchiveFile) {
6012   AssertIsOnIOThread();
6013 
6014   QM_TRY_INSPECT(
6015       const bool& lsArchiveFileExisted,
6016       ([this, &aLsArchiveFile]() -> Result<bool, nsresult> {
6017         QM_TRY_INSPECT(const bool& exists,
6018                        MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
6019 
6020         if (!exists) {
6021           QM_TRY(CopyLocalStorageArchiveFromWebAppsStore(aLsArchiveFile));
6022         }
6023 
6024         return exists;
6025       }()));
6026 
6027   QM_TRY_UNWRAP(auto connection,
6028                 CreateLocalStorageArchiveConnection(aLsArchiveFile));
6029 
6030   QM_TRY_INSPECT(const auto& initialized,
6031                  IsLocalStorageArchiveInitialized(*connection));
6032 
6033   if (!initialized) {
6034     QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
6035   }
6036 
6037   QM_TRY_UNWRAP(int32_t version, LoadLocalStorageArchiveVersion(*connection));
6038 
6039   if (version > kLocalStorageArchiveVersion) {
6040     // Close local storage archive connection. We are going to remove underlying
6041     // file.
6042     QM_TRY(MOZ_TO_RESULT(connection->Close()));
6043 
6044     // This will wipe the archive and any migrated data and recopy the archive
6045     // from webappsstore.sqlite.
6046     QM_TRY_UNWRAP(connection, DowngradeLocalStorageArchive(aLsArchiveFile));
6047 
6048     QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
6049 
6050     MOZ_ASSERT(version == kLocalStorageArchiveVersion);
6051   } else if (version != kLocalStorageArchiveVersion) {
6052     // The version can be zero either when the archive didn't exist or it did
6053     // exist, but the archive was created without any version information.
6054     // We don't need to do any upgrades only if it didn't exist because existing
6055     // archives without version information must be recopied to really fix bug
6056     // 1542104. See also bug 1546305 which introduced archive versions.
6057     if (!lsArchiveFileExisted) {
6058       MOZ_ASSERT(version == 0);
6059 
6060       QM_TRY(MOZ_TO_RESULT(SaveLocalStorageArchiveVersion(
6061           connection, kLocalStorageArchiveVersion)));
6062     } else {
6063       static_assert(kLocalStorageArchiveVersion == 4,
6064                     "Upgrade function needed due to LocalStorage archive "
6065                     "version increase.");
6066 
6067       while (version != kLocalStorageArchiveVersion) {
6068         if (version < 4) {
6069           // Close local storage archive connection. We are going to remove
6070           // underlying file.
6071           QM_TRY(MOZ_TO_RESULT(connection->Close()));
6072 
6073           // This won't do an "upgrade" in a normal sense. It will wipe the
6074           // archive and any migrated data and recopy the archive from
6075           // webappsstore.sqlite
6076           QM_TRY_UNWRAP(connection, UpgradeLocalStorageArchiveFromLessThan4To4(
6077                                         aLsArchiveFile));
6078         } /* else if (version == 4) {
6079           QM_TRY(MOZ_TO_RESULT(UpgradeLocalStorageArchiveFrom4To5(connection)));
6080         } */
6081         else {
6082           QM_FAIL(Err(NS_ERROR_FAILURE), []() {
6083             QM_WARNING(
6084                 "Unable to initialize LocalStorage archive, no upgrade path "
6085                 "is available!");
6086           });
6087         }
6088 
6089         QM_TRY_UNWRAP(version, LoadLocalStorageArchiveVersion(*connection));
6090       }
6091 
6092       MOZ_ASSERT(version == kLocalStorageArchiveVersion);
6093     }
6094   }
6095 
6096   // At this point, we have finished initializing the local storage archive, and
6097   // can continue storage initialization. We don't know though if the actual
6098   // data in the archive file is readable. We can't do a PRAGMA integrity_check
6099   // here though, because that would be too heavyweight.
6100 
6101   return Ok{};
6102 }
6103 
CreateEmptyLocalStorageArchive(nsIFile & aLsArchiveFile) const6104 Result<Ok, nsresult> QuotaManager::CreateEmptyLocalStorageArchive(
6105     nsIFile& aLsArchiveFile) const {
6106   AssertIsOnIOThread();
6107 
6108   QM_TRY_INSPECT(const bool& exists,
6109                  MOZ_TO_RESULT_INVOKE_MEMBER(aLsArchiveFile, Exists));
6110 
6111   // If it exists, remove it. It might be a directory, so remove it recursively.
6112   if (exists) {
6113     QM_TRY(MOZ_TO_RESULT(aLsArchiveFile.Remove(true)));
6114 
6115     // XXX If we crash right here, the next session will copy the archive from
6116     // webappsstore.sqlite again!
6117     // XXX Create a marker file before removing the archive which can be
6118     // used in MaybeCreateOrUpgradeLocalStorageArchive to create an empty
6119     // archive instead of recopying it from webapppstore.sqlite (in other
6120     // words, finishing what was started here).
6121   }
6122 
6123   QM_TRY_INSPECT(const auto& ss,
6124                  MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
6125                                          MOZ_SELECT_OVERLOAD(do_GetService),
6126                                          MOZ_STORAGE_SERVICE_CONTRACTID));
6127 
6128   QM_TRY_UNWRAP(const auto connection,
6129                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6130                     nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
6131                     &aLsArchiveFile, mozIStorageService::CONNECTION_DEFAULT));
6132 
6133   QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::CreateCurrentSchema(connection)));
6134 
6135   QM_TRY(MOZ_TO_RESULT(InitializeLocalStorageArchive(connection)));
6136 
6137   QM_TRY(MOZ_TO_RESULT(
6138       SaveLocalStorageArchiveVersion(connection, kLocalStorageArchiveVersion)));
6139 
6140   return Ok{};
6141 }
6142 
EnsureStorageIsInitialized()6143 nsresult QuotaManager::EnsureStorageIsInitialized() {
6144   DiagnosticAssertIsOnIOThread();
6145 
6146   const auto innerFunc =
6147       [&](const auto& firstInitializationAttempt) -> nsresult {
6148     if (mStorageConnection) {
6149       MOZ_ASSERT(firstInitializationAttempt.Recorded());
6150       return NS_OK;
6151     }
6152 
6153     QM_TRY_INSPECT(const auto& storageFile, QM_NewLocalFile(mBasePath));
6154     QM_TRY(MOZ_TO_RESULT(storageFile->Append(mStorageName + kSQLiteSuffix)));
6155 
6156     QM_TRY(MOZ_TO_RESULT(MaybeUpgradeToDefaultStorageDirectory(*storageFile)));
6157 
6158     QM_TRY_INSPECT(const auto& ss,
6159                    MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
6160                                            MOZ_SELECT_OVERLOAD(do_GetService),
6161                                            MOZ_STORAGE_SERVICE_CONTRACTID));
6162 
6163     QM_TRY_UNWRAP(
6164         auto connection,
6165         QM_OR_ELSE_WARN_IF(
6166             // Expression.
6167             MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6168                 nsCOMPtr<mozIStorageConnection>, ss, OpenUnsharedDatabase,
6169                 storageFile, mozIStorageService::CONNECTION_DEFAULT),
6170             // Predicate.
6171             IsDatabaseCorruptionError,
6172             // Fallback.
6173             ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
6174 
6175     if (!connection) {
6176       // Nuke the database file.
6177       QM_TRY(MOZ_TO_RESULT(storageFile->Remove(false)));
6178 
6179       QM_TRY_UNWRAP(connection, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6180                                     nsCOMPtr<mozIStorageConnection>, ss,
6181                                     OpenUnsharedDatabase, storageFile,
6182                                     mozIStorageService::CONNECTION_DEFAULT));
6183     }
6184 
6185     // We want extra durability for this important file.
6186     QM_TRY(MOZ_TO_RESULT(
6187         connection->ExecuteSimpleSQL("PRAGMA synchronous = EXTRA;"_ns)));
6188 
6189     // Check to make sure that the storage version is correct.
6190     QM_TRY(MOZ_TO_RESULT(MaybeCreateOrUpgradeStorage(*connection)));
6191 
6192     QM_TRY(MaybeRemoveLocalStorageArchiveTmpFile());
6193 
6194     QM_TRY_INSPECT(const auto& lsArchiveFile,
6195                    GetLocalStorageArchiveFile(*mStoragePath));
6196 
6197     if (CachedNextGenLocalStorageEnabled()) {
6198       QM_TRY(QM_OR_ELSE_WARN_IF(
6199           // Expression.
6200           MaybeCreateOrUpgradeLocalStorageArchive(*lsArchiveFile),
6201           // Predicate.
6202           IsDatabaseCorruptionError,
6203           // Fallback.
6204           ([&](const nsresult rv) -> Result<Ok, nsresult> {
6205             QM_TRY_RETURN(CreateEmptyLocalStorageArchive(*lsArchiveFile));
6206           })));
6207     } else {
6208       QM_TRY(
6209           MOZ_TO_RESULT(MaybeRemoveLocalStorageDataAndArchive(*lsArchiveFile)));
6210     }
6211 
6212     QM_TRY_UNWRAP(mCacheUsable, MaybeCreateOrUpgradeCache(*connection));
6213 
6214     if (mCacheUsable && gInvalidateQuotaCache) {
6215       QM_TRY(InvalidateCache(*connection));
6216 
6217       gInvalidateQuotaCache = false;
6218     }
6219 
6220     mStorageConnection = std::move(connection);
6221 
6222     return NS_OK;
6223   };
6224 
6225   return ExecuteInitialization(
6226       Initialization::Storage,
6227       "dom::quota::FirstInitializationAttempt::Storage"_ns, innerFunc);
6228 }
6229 
CreateDirectoryLock(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,Client::Type aClientType,bool aExclusive)6230 RefPtr<ClientDirectoryLock> QuotaManager::CreateDirectoryLock(
6231     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
6232     Client::Type aClientType, bool aExclusive) {
6233   AssertIsOnOwningThread();
6234 
6235   return DirectoryLockImpl::Create(WrapNotNullUnchecked(this), aPersistenceType,
6236                                    aOriginMetadata, aClientType, aExclusive);
6237 }
6238 
CreateDirectoryLockInternal(const Nullable<PersistenceType> & aPersistenceType,const OriginScope & aOriginScope,const Nullable<Client::Type> & aClientType,bool aExclusive)6239 RefPtr<UniversalDirectoryLock> QuotaManager::CreateDirectoryLockInternal(
6240     const Nullable<PersistenceType>& aPersistenceType,
6241     const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
6242     bool aExclusive) {
6243   AssertIsOnOwningThread();
6244 
6245   return DirectoryLockImpl::CreateInternal(WrapNotNullUnchecked(this),
6246                                            aPersistenceType, aOriginScope,
6247                                            aClientType, aExclusive);
6248 }
6249 
6250 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
EnsurePersistentOriginIsInitialized(const OriginMetadata & aOriginMetadata)6251 QuotaManager::EnsurePersistentOriginIsInitialized(
6252     const OriginMetadata& aOriginMetadata) {
6253   AssertIsOnIOThread();
6254   MOZ_ASSERT(aOriginMetadata.mPersistenceType == PERSISTENCE_TYPE_PERSISTENT);
6255   MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6256 
6257   const auto innerFunc = [&aOriginMetadata,
6258                           this](const auto& firstInitializationAttempt)
6259       -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6260     QM_TRY_UNWRAP(auto directory,
6261                   GetDirectoryForOrigin(PERSISTENCE_TYPE_PERSISTENT,
6262                                         aOriginMetadata.mOrigin));
6263 
6264     if (mInitializedOrigins.Contains(aOriginMetadata.mOrigin)) {
6265       MOZ_ASSERT(firstInitializationAttempt.Recorded());
6266       return std::pair(std::move(directory), false);
6267     }
6268 
6269     QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6270 
6271     QM_TRY_INSPECT(
6272         const int64_t& timestamp,
6273         ([this, created, &directory,
6274           &aOriginMetadata]() -> Result<int64_t, nsresult> {
6275           if (created) {
6276             const int64_t timestamp = PR_Now();
6277 
6278             // Only creating .metadata-v2 to reduce IO.
6279             QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
6280                                                           /* aPersisted */ true,
6281                                                           aOriginMetadata)));
6282 
6283             return timestamp;
6284           }
6285 
6286           // Get the metadata. We only use the timestamp.
6287           QM_TRY_INSPECT(const auto& metadata,
6288                          LoadFullOriginMetadataWithRestore(directory));
6289 
6290           MOZ_ASSERT(metadata.mLastAccessTime <= PR_Now());
6291 
6292           return metadata.mLastAccessTime;
6293         }()));
6294 
6295     QM_TRY(MOZ_TO_RESULT(InitializeOrigin(PERSISTENCE_TYPE_PERSISTENT,
6296                                           aOriginMetadata, timestamp,
6297                                           /* aPersisted */ true, directory)));
6298 
6299     mInitializedOrigins.AppendElement(aOriginMetadata.mOrigin);
6300 
6301     return std::pair(std::move(directory), created);
6302   };
6303 
6304   return ExecuteOriginInitialization(
6305       aOriginMetadata.mOrigin, OriginInitialization::PersistentOrigin,
6306       "dom::quota::FirstOriginInitializationAttempt::PersistentOrigin"_ns,
6307       innerFunc);
6308 }
6309 
6310 Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult>
EnsureTemporaryOriginIsInitialized(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata)6311 QuotaManager::EnsureTemporaryOriginIsInitialized(
6312     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6313   AssertIsOnIOThread();
6314   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6315   MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6316   MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitialized);
6317 
6318   const auto innerFunc = [&aPersistenceType, &aOriginMetadata,
6319                           this](const auto&)
6320       -> mozilla::Result<std::pair<nsCOMPtr<nsIFile>, bool>, nsresult> {
6321     // Get directory for this origin and persistence type.
6322     QM_TRY_UNWRAP(
6323         auto directory,
6324         GetDirectoryForOrigin(aPersistenceType, aOriginMetadata.mOrigin));
6325 
6326     QM_TRY_INSPECT(const bool& created, EnsureOriginDirectory(*directory));
6327 
6328     if (created) {
6329       const int64_t timestamp =
6330           NoteOriginDirectoryCreated(aOriginMetadata, /* aPersisted */ false);
6331 
6332       // Only creating .metadata-v2 to reduce IO.
6333       QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
6334                                                     /* aPersisted */ false,
6335                                                     aOriginMetadata)));
6336     }
6337 
6338     // TODO: If the metadata file exists and we didn't call
6339     //       LoadFullOriginMetadataWithRestore for it (because the quota info
6340     //       was loaded from the cache), then the group in the metadata file
6341     //       may be wrong, so it should be checked and eventually updated.
6342     //       It's not a big deal that we are not doing it here, because the
6343     //       origin will be marked as "accessed", so
6344     //       LoadFullOriginMetadataWithRestore will be called for the metadata
6345     //       file in next session in LoadQuotaFromCache.
6346 
6347     return std::pair(std::move(directory), created);
6348   };
6349 
6350   return ExecuteOriginInitialization(
6351       aOriginMetadata.mOrigin, OriginInitialization::TemporaryOrigin,
6352       "dom::quota::FirstOriginInitializationAttempt::TemporaryOrigin"_ns,
6353       innerFunc);
6354 }
6355 
EnsureTemporaryStorageIsInitialized()6356 nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() {
6357   AssertIsOnIOThread();
6358   MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6359 
6360   const auto innerFunc =
6361       [&](const auto& firstInitializationAttempt) -> nsresult {
6362     if (mTemporaryStorageInitialized) {
6363       MOZ_ASSERT(firstInitializationAttempt.Recorded());
6364       return NS_OK;
6365     }
6366 
6367     QM_TRY_INSPECT(
6368         const auto& storageDir,
6369         MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFile>,
6370                                 MOZ_SELECT_OVERLOAD(do_CreateInstance),
6371                                 NS_LOCAL_FILE_CONTRACTID));
6372 
6373     QM_TRY(MOZ_TO_RESULT(storageDir->InitWithPath(GetStoragePath())));
6374 
6375     // The storage directory must exist before calling GetTemporaryStorageLimit.
6376     QM_TRY_INSPECT(const bool& created, EnsureDirectory(*storageDir));
6377 
6378     Unused << created;
6379 
6380     QM_TRY_UNWRAP(mTemporaryStorageLimit,
6381                   GetTemporaryStorageLimit(*storageDir));
6382 
6383     QM_TRY(MOZ_TO_RESULT(LoadQuota()));
6384 
6385     mTemporaryStorageInitialized = true;
6386 
6387     CleanupTemporaryStorage();
6388 
6389     if (mCacheUsable) {
6390       QM_TRY(InvalidateCache(*mStorageConnection));
6391     }
6392 
6393     return NS_OK;
6394   };
6395 
6396   return ExecuteInitialization(
6397       Initialization::TemporaryStorage,
6398       "dom::quota::FirstInitializationAttempt::TemporaryStorage"_ns, innerFunc);
6399 }
6400 
ShutdownStorage()6401 void QuotaManager::ShutdownStorage() {
6402   AssertIsOnIOThread();
6403 
6404   if (mStorageConnection) {
6405     mInitializationInfo.ResetOriginInitializationInfos();
6406     mInitializedOrigins.Clear();
6407 
6408     if (mTemporaryStorageInitialized) {
6409       if (mCacheUsable) {
6410         UnloadQuota();
6411       } else {
6412         RemoveQuota();
6413       }
6414 
6415       mTemporaryStorageInitialized = false;
6416     }
6417 
6418     ReleaseIOThreadObjects();
6419 
6420     mStorageConnection = nullptr;
6421     mCacheUsable = false;
6422   }
6423 
6424   mInitializationInfo.ResetFirstInitializationAttempts();
6425 }
6426 
EnsureOriginDirectory(nsIFile & aDirectory)6427 Result<bool, nsresult> QuotaManager::EnsureOriginDirectory(
6428     nsIFile& aDirectory) {
6429   AssertIsOnIOThread();
6430 
6431   QM_TRY_INSPECT(const bool& exists,
6432                  MOZ_TO_RESULT_INVOKE_MEMBER(aDirectory, Exists));
6433 
6434   if (!exists) {
6435     QM_TRY_INSPECT(
6436         const auto& leafName,
6437         MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, aDirectory, GetLeafName)
6438             .map([](const auto& leafName) {
6439               return NS_ConvertUTF16toUTF8(leafName);
6440             }));
6441 
6442     QM_TRY(OkIf(IsSanitizedOriginValid(leafName)), Err(NS_ERROR_FAILURE),
6443            [](const auto&) {
6444              QM_WARNING(
6445                  "Preventing creation of a new origin directory which is not "
6446                  "supported by our origin parser or is obsolete!");
6447            });
6448   }
6449 
6450   QM_TRY_RETURN(EnsureDirectory(aDirectory));
6451 }
6452 
AboutToClearOrigins(const Nullable<PersistenceType> & aPersistenceType,const OriginScope & aOriginScope,const Nullable<Client::Type> & aClientType)6453 nsresult QuotaManager::AboutToClearOrigins(
6454     const Nullable<PersistenceType>& aPersistenceType,
6455     const OriginScope& aOriginScope,
6456     const Nullable<Client::Type>& aClientType) {
6457   AssertIsOnIOThread();
6458 
6459   if (aClientType.IsNull()) {
6460     for (Client::Type type : AllClientTypes()) {
6461       QM_TRY(MOZ_TO_RESULT((*mClients)[type]->AboutToClearOrigins(
6462           aPersistenceType, aOriginScope)));
6463     }
6464   } else {
6465     QM_TRY(MOZ_TO_RESULT((*mClients)[aClientType.Value()]->AboutToClearOrigins(
6466         aPersistenceType, aOriginScope)));
6467   }
6468 
6469   return NS_OK;
6470 }
6471 
OriginClearCompleted(PersistenceType aPersistenceType,const nsACString & aOrigin,const Nullable<Client::Type> & aClientType)6472 void QuotaManager::OriginClearCompleted(
6473     PersistenceType aPersistenceType, const nsACString& aOrigin,
6474     const Nullable<Client::Type>& aClientType) {
6475   AssertIsOnIOThread();
6476 
6477   if (aClientType.IsNull()) {
6478     if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
6479       mInitializedOrigins.RemoveElement(aOrigin);
6480     }
6481 
6482     for (Client::Type type : AllClientTypes()) {
6483       (*mClients)[type]->OnOriginClearCompleted(aPersistenceType, aOrigin);
6484     }
6485   } else {
6486     (*mClients)[aClientType.Value()]->OnOriginClearCompleted(aPersistenceType,
6487                                                              aOrigin);
6488   }
6489 }
6490 
GetClient(Client::Type aClientType)6491 Client* QuotaManager::GetClient(Client::Type aClientType) {
6492   MOZ_ASSERT(aClientType >= Client::IDB);
6493   MOZ_ASSERT(aClientType < Client::TypeMax());
6494 
6495   return (*mClients)[aClientType];
6496 }
6497 
6498 const AutoTArray<Client::Type, Client::TYPE_MAX>&
AllClientTypes()6499 QuotaManager::AllClientTypes() {
6500   if (CachedNextGenLocalStorageEnabled()) {
6501     return *mAllClientTypes;
6502   }
6503   return *mAllClientTypesExceptLS;
6504 }
6505 
GetGroupLimit() const6506 uint64_t QuotaManager::GetGroupLimit() const {
6507   // To avoid one group evicting all the rest, limit the amount any one group
6508   // can use to 20% resp. a fifth. To prevent individual sites from using
6509   // exorbitant amounts of storage where there is a lot of free space, cap the
6510   // group limit to 10GB.
6511   const auto x = std::min<uint64_t>(mTemporaryStorageLimit / 5, 10 GB);
6512 
6513   // In low-storage situations, make an exception (while not exceeding the total
6514   // storage limit).
6515   return std::min<uint64_t>(mTemporaryStorageLimit,
6516                             std::max<uint64_t>(x, 10 MB));
6517 }
6518 
GetUsageAndLimitForEstimate(const OriginMetadata & aOriginMetadata)6519 std::pair<uint64_t, uint64_t> QuotaManager::GetUsageAndLimitForEstimate(
6520     const OriginMetadata& aOriginMetadata) {
6521   AssertIsOnIOThread();
6522 
6523   uint64_t totalGroupUsage = 0;
6524 
6525   {
6526     MutexAutoLock lock(mQuotaMutex);
6527 
6528     GroupInfoPair* pair;
6529     if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6530       for (const PersistenceType type : kBestEffortPersistenceTypes) {
6531         RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
6532         if (groupInfo) {
6533           if (type == PERSISTENCE_TYPE_DEFAULT) {
6534             RefPtr<OriginInfo> originInfo =
6535                 groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6536 
6537             if (originInfo && originInfo->LockedPersisted()) {
6538               return std::pair(mTemporaryStorageUsage, mTemporaryStorageLimit);
6539             }
6540           }
6541 
6542           AssertNoOverflow(totalGroupUsage, groupInfo->mUsage);
6543           totalGroupUsage += groupInfo->mUsage;
6544         }
6545       }
6546     }
6547   }
6548 
6549   return std::pair(totalGroupUsage, GetGroupLimit());
6550 }
6551 
GetOriginUsage(const PrincipalMetadata & aPrincipalMetadata)6552 uint64_t QuotaManager::GetOriginUsage(
6553     const PrincipalMetadata& aPrincipalMetadata) {
6554   AssertIsOnIOThread();
6555 
6556   uint64_t usage = 0;
6557 
6558   {
6559     MutexAutoLock lock(mQuotaMutex);
6560 
6561     GroupInfoPair* pair;
6562     if (mGroupInfoPairs.Get(aPrincipalMetadata.mGroup, &pair)) {
6563       for (const PersistenceType type : kBestEffortPersistenceTypes) {
6564         RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(type);
6565         if (groupInfo) {
6566           RefPtr<OriginInfo> originInfo =
6567               groupInfo->LockedGetOriginInfo(aPrincipalMetadata.mOrigin);
6568           if (originInfo) {
6569             AssertNoOverflow(usage, originInfo->LockedUsage());
6570             usage += originInfo->LockedUsage();
6571           }
6572         }
6573       }
6574     }
6575   }
6576 
6577   return usage;
6578 }
6579 
GetFullOriginMetadata(const OriginMetadata & aOriginMetadata)6580 Maybe<FullOriginMetadata> QuotaManager::GetFullOriginMetadata(
6581     const OriginMetadata& aOriginMetadata) {
6582   AssertIsOnIOThread();
6583   MOZ_DIAGNOSTIC_ASSERT(mStorageConnection);
6584   MOZ_DIAGNOSTIC_ASSERT(mTemporaryStorageInitialized);
6585 
6586   MutexAutoLock lock(mQuotaMutex);
6587 
6588   RefPtr<OriginInfo> originInfo =
6589       LockedGetOriginInfo(aOriginMetadata.mPersistenceType, aOriginMetadata);
6590   if (originInfo) {
6591     return Some(originInfo->LockedFlattenToFullOriginMetadata());
6592   }
6593 
6594   return Nothing();
6595 }
6596 
NotifyStoragePressure(uint64_t aUsage)6597 void QuotaManager::NotifyStoragePressure(uint64_t aUsage) {
6598   mQuotaMutex.AssertNotCurrentThreadOwns();
6599 
6600   RefPtr<StoragePressureRunnable> storagePressureRunnable =
6601       new StoragePressureRunnable(aUsage);
6602 
6603   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(storagePressureRunnable));
6604 }
6605 
6606 // static
GetStorageId(PersistenceType aPersistenceType,const nsACString & aOrigin,Client::Type aClientType,nsACString & aDatabaseId)6607 void QuotaManager::GetStorageId(PersistenceType aPersistenceType,
6608                                 const nsACString& aOrigin,
6609                                 Client::Type aClientType,
6610                                 nsACString& aDatabaseId) {
6611   nsAutoCString str;
6612   str.AppendInt(aPersistenceType);
6613   str.Append('*');
6614   str.Append(aOrigin);
6615   str.Append('*');
6616   str.AppendInt(aClientType);
6617 
6618   aDatabaseId = str;
6619 }
6620 
6621 // static
IsPrincipalInfoValid(const PrincipalInfo & aPrincipalInfo)6622 bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
6623   switch (aPrincipalInfo.type()) {
6624     // A system principal is acceptable.
6625     case PrincipalInfo::TSystemPrincipalInfo: {
6626       return true;
6627     }
6628 
6629     // Validate content principals to ensure that the spec, originNoSuffix and
6630     // baseDomain are sane.
6631     case PrincipalInfo::TContentPrincipalInfo: {
6632       const ContentPrincipalInfo& info =
6633           aPrincipalInfo.get_ContentPrincipalInfo();
6634 
6635       // Verify the principal spec parses.
6636       RefPtr<MozURL> specURL;
6637       nsresult rv = MozURL::Init(getter_AddRefs(specURL), info.spec());
6638       if (NS_WARN_IF(NS_FAILED(rv))) {
6639         QM_WARNING("A URL %s is not recognized by MozURL", info.spec().get());
6640         return false;
6641       }
6642 
6643       // Verify the principal originNoSuffix matches spec.
6644       nsCString originNoSuffix;
6645       specURL->Origin(originNoSuffix);
6646 
6647       if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
6648         QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
6649                    originNoSuffix.get(), info.originNoSuffix().get());
6650         return false;
6651       }
6652 
6653       if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
6654         return false;
6655       }
6656 
6657       if (NS_WARN_IF(info.originNoSuffix().FindChar('^', 0) != -1)) {
6658         QM_WARNING("originNoSuffix (%s) contains the '^' character!",
6659                    info.originNoSuffix().get());
6660         return false;
6661       }
6662 
6663       // Verify the principal baseDomain exists.
6664       if (NS_WARN_IF(info.baseDomain().IsVoid())) {
6665         return false;
6666       }
6667 
6668       // Verify the principal baseDomain matches spec.
6669       nsCString baseDomain;
6670       rv = specURL->BaseDomain(baseDomain);
6671       if (NS_WARN_IF(NS_FAILED(rv))) {
6672         return false;
6673       }
6674 
6675       if (NS_WARN_IF(baseDomain != info.baseDomain())) {
6676         QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
6677                    baseDomain.get(), info.baseDomain().get());
6678         return false;
6679       }
6680 
6681       return true;
6682     }
6683 
6684     default: {
6685       break;
6686     }
6687   }
6688 
6689   // Null and expanded principals are not acceptable.
6690   return false;
6691 }
6692 
6693 // static
GetInfoFromValidatedPrincipalInfo(const PrincipalInfo & aPrincipalInfo)6694 PrincipalMetadata QuotaManager::GetInfoFromValidatedPrincipalInfo(
6695     const PrincipalInfo& aPrincipalInfo) {
6696   MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
6697 
6698   switch (aPrincipalInfo.type()) {
6699     case PrincipalInfo::TSystemPrincipalInfo: {
6700       return GetInfoForChrome();
6701     }
6702 
6703     case PrincipalInfo::TContentPrincipalInfo: {
6704       const ContentPrincipalInfo& info =
6705           aPrincipalInfo.get_ContentPrincipalInfo();
6706 
6707       PrincipalMetadata principalMetadata;
6708 
6709       info.attrs().CreateSuffix(principalMetadata.mSuffix);
6710 
6711       principalMetadata.mGroup = info.baseDomain() + principalMetadata.mSuffix;
6712 
6713       principalMetadata.mOrigin =
6714           info.originNoSuffix() + principalMetadata.mSuffix;
6715 
6716       return principalMetadata;
6717     }
6718 
6719     default: {
6720       MOZ_CRASH("Should never get here!");
6721     }
6722   }
6723 }
6724 
6725 // static
GetOriginFromValidatedPrincipalInfo(const PrincipalInfo & aPrincipalInfo)6726 nsAutoCString QuotaManager::GetOriginFromValidatedPrincipalInfo(
6727     const PrincipalInfo& aPrincipalInfo) {
6728   MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
6729 
6730   switch (aPrincipalInfo.type()) {
6731     case PrincipalInfo::TSystemPrincipalInfo: {
6732       return nsAutoCString{GetOriginForChrome()};
6733     }
6734 
6735     case PrincipalInfo::TContentPrincipalInfo: {
6736       const ContentPrincipalInfo& info =
6737           aPrincipalInfo.get_ContentPrincipalInfo();
6738 
6739       nsAutoCString suffix;
6740 
6741       info.attrs().CreateSuffix(suffix);
6742 
6743       return info.originNoSuffix() + suffix;
6744     }
6745 
6746     default: {
6747       MOZ_CRASH("Should never get here!");
6748     }
6749   }
6750 }
6751 
6752 // static
GetInfoFromPrincipal(nsIPrincipal * aPrincipal)6753 Result<PrincipalMetadata, nsresult> QuotaManager::GetInfoFromPrincipal(
6754     nsIPrincipal* aPrincipal) {
6755   MOZ_ASSERT(NS_IsMainThread());
6756   MOZ_ASSERT(aPrincipal);
6757 
6758   if (aPrincipal->IsSystemPrincipal()) {
6759     return GetInfoForChrome();
6760   }
6761 
6762   if (aPrincipal->GetIsNullPrincipal()) {
6763     NS_WARNING("IndexedDB not supported from this principal!");
6764     return Err(NS_ERROR_FAILURE);
6765   }
6766 
6767   PrincipalMetadata principalMetadata;
6768 
6769   QM_TRY(MOZ_TO_RESULT(aPrincipal->GetOrigin(principalMetadata.mOrigin)));
6770 
6771   if (principalMetadata.mOrigin.EqualsLiteral(kChromeOrigin)) {
6772     NS_WARNING("Non-chrome principal can't use chrome origin!");
6773     return Err(NS_ERROR_FAILURE);
6774   }
6775 
6776   aPrincipal->OriginAttributesRef().CreateSuffix(principalMetadata.mSuffix);
6777 
6778   nsAutoCString baseDomain;
6779   QM_TRY(MOZ_TO_RESULT(aPrincipal->GetBaseDomain(baseDomain)));
6780 
6781   MOZ_ASSERT(!baseDomain.IsEmpty());
6782 
6783   principalMetadata.mGroup = baseDomain + principalMetadata.mSuffix;
6784 
6785   return principalMetadata;
6786 }
6787 
6788 // static
GetOriginFromPrincipal(nsIPrincipal * aPrincipal)6789 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromPrincipal(
6790     nsIPrincipal* aPrincipal) {
6791   MOZ_ASSERT(NS_IsMainThread());
6792   MOZ_ASSERT(aPrincipal);
6793 
6794   if (aPrincipal->IsSystemPrincipal()) {
6795     return nsAutoCString{GetOriginForChrome()};
6796   }
6797 
6798   if (aPrincipal->GetIsNullPrincipal()) {
6799     NS_WARNING("IndexedDB not supported from this principal!");
6800     return Err(NS_ERROR_FAILURE);
6801   }
6802 
6803   QM_TRY_UNWRAP(const auto origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
6804                                        nsAutoCString, aPrincipal, GetOrigin));
6805 
6806   if (origin.EqualsLiteral(kChromeOrigin)) {
6807     NS_WARNING("Non-chrome principal can't use chrome origin!");
6808     return Err(NS_ERROR_FAILURE);
6809   }
6810 
6811   return origin;
6812 }
6813 
6814 // static
GetOriginFromWindow(nsPIDOMWindowOuter * aWindow)6815 Result<nsAutoCString, nsresult> QuotaManager::GetOriginFromWindow(
6816     nsPIDOMWindowOuter* aWindow) {
6817   MOZ_ASSERT(NS_IsMainThread());
6818   MOZ_ASSERT(aWindow);
6819 
6820   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
6821   QM_TRY(OkIf(sop), Err(NS_ERROR_FAILURE));
6822 
6823   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
6824   QM_TRY(OkIf(principal), Err(NS_ERROR_FAILURE));
6825 
6826   QM_TRY_RETURN(GetOriginFromPrincipal(principal));
6827 }
6828 
6829 // static
GetInfoForChrome()6830 PrincipalMetadata QuotaManager::GetInfoForChrome() {
6831   return {{}, GetOriginForChrome(), GetOriginForChrome()};
6832 }
6833 
6834 // static
GetOriginForChrome()6835 nsLiteralCString QuotaManager::GetOriginForChrome() {
6836   return nsLiteralCString{kChromeOrigin};
6837 }
6838 
6839 // static
IsOriginInternal(const nsACString & aOrigin)6840 bool QuotaManager::IsOriginInternal(const nsACString& aOrigin) {
6841   MOZ_ASSERT(!aOrigin.IsEmpty());
6842 
6843   // The first prompt is not required for these origins.
6844   if (aOrigin.EqualsLiteral(kChromeOrigin) ||
6845       StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) ||
6846       StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) ||
6847       StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) {
6848     return true;
6849   }
6850 
6851   return false;
6852 }
6853 
6854 // static
AreOriginsEqualOnDisk(const nsACString & aOrigin1,const nsACString & aOrigin2)6855 bool QuotaManager::AreOriginsEqualOnDisk(const nsACString& aOrigin1,
6856                                          const nsACString& aOrigin2) {
6857   return MakeSanitizedOriginCString(aOrigin1) ==
6858          MakeSanitizedOriginCString(aOrigin2);
6859 }
6860 
6861 // static
ParseOrigin(const nsACString & aOrigin)6862 Result<PrincipalInfo, nsresult> QuotaManager::ParseOrigin(
6863     const nsACString& aOrigin) {
6864   // An origin string either corresponds to a SystemPrincipalInfo or a
6865   // ContentPrincipalInfo, see
6866   // QuotaManager::GetOriginFromValidatedPrincipalInfo.
6867 
6868   if (aOrigin.Equals(kChromeOrigin)) {
6869     return PrincipalInfo{SystemPrincipalInfo{}};
6870   }
6871 
6872   ContentPrincipalInfo contentPrincipalInfo;
6873 
6874   nsCString originalSuffix;
6875   const OriginParser::ResultType result = OriginParser::ParseOrigin(
6876       MakeSanitizedOriginCString(aOrigin), contentPrincipalInfo.spec(),
6877       &contentPrincipalInfo.attrs(), originalSuffix);
6878   QM_TRY(OkIf(result == OriginParser::ValidOrigin), Err(NS_ERROR_FAILURE));
6879 
6880   return PrincipalInfo{std::move(contentPrincipalInfo)};
6881 }
6882 
6883 // static
InvalidateQuotaCache()6884 void QuotaManager::InvalidateQuotaCache() { gInvalidateQuotaCache = true; }
6885 
LockedCollectOriginsForEviction(uint64_t aMinSizeToBeFreed,nsTArray<RefPtr<OriginDirectoryLock>> & aLocks)6886 uint64_t QuotaManager::LockedCollectOriginsForEviction(
6887     uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
6888   mQuotaMutex.AssertCurrentThreadOwns();
6889 
6890   RefPtr<CollectOriginsHelper> helper =
6891       new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
6892 
6893   // Unlock while calling out to XPCOM (code behind the dispatch method needs
6894   // to acquire its own lock which can potentially lead to a deadlock and it
6895   // also calls an observer that can do various stuff like IO, so it's better
6896   // to not hold our mutex while that happens).
6897   {
6898     MutexAutoUnlock autoUnlock(mQuotaMutex);
6899 
6900     MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL));
6901   }
6902 
6903   return helper->BlockAndReturnOriginsForEviction(aLocks);
6904 }
6905 
LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata)6906 void QuotaManager::LockedRemoveQuotaForOrigin(
6907     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6908   mQuotaMutex.AssertCurrentThreadOwns();
6909   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6910 
6911   GroupInfoPair* pair;
6912   if (!mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6913     return;
6914   }
6915 
6916   MOZ_ASSERT(pair);
6917 
6918   if (RefPtr<GroupInfo> groupInfo =
6919           pair->LockedGetGroupInfo(aPersistenceType)) {
6920     groupInfo->LockedRemoveOriginInfo(aOriginMetadata.mOrigin);
6921 
6922     if (!groupInfo->LockedHasOriginInfos()) {
6923       pair->LockedClearGroupInfo(aPersistenceType);
6924 
6925       if (!pair->LockedHasGroupInfos()) {
6926         mGroupInfoPairs.Remove(aOriginMetadata.mGroup);
6927       }
6928     }
6929   }
6930 }
6931 
LockedGetOrCreateGroupInfo(PersistenceType aPersistenceType,const nsACString & aSuffix,const nsACString & aGroup)6932 already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
6933     PersistenceType aPersistenceType, const nsACString& aSuffix,
6934     const nsACString& aGroup) {
6935   mQuotaMutex.AssertCurrentThreadOwns();
6936   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6937 
6938   GroupInfoPair* const pair =
6939       mGroupInfoPairs.GetOrInsertNew(aGroup, aSuffix, aGroup);
6940 
6941   RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6942   if (!groupInfo) {
6943     groupInfo = new GroupInfo(pair, aPersistenceType);
6944     pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
6945   }
6946 
6947   return groupInfo.forget();
6948 }
6949 
LockedGetOriginInfo(PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata)6950 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
6951     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata) {
6952   mQuotaMutex.AssertCurrentThreadOwns();
6953   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
6954 
6955   GroupInfoPair* pair;
6956   if (mGroupInfoPairs.Get(aOriginMetadata.mGroup, &pair)) {
6957     RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
6958     if (groupInfo) {
6959       return groupInfo->LockedGetOriginInfo(aOriginMetadata.mOrigin);
6960     }
6961   }
6962 
6963   return nullptr;
6964 }
6965 
6966 template <typename Iterator>
MaybeInsertNonPersistedOriginInfos(Iterator aDest,const RefPtr<GroupInfo> & aTemporaryGroupInfo,const RefPtr<GroupInfo> & aDefaultGroupInfo)6967 void QuotaManager::MaybeInsertNonPersistedOriginInfos(
6968     Iterator aDest, const RefPtr<GroupInfo>& aTemporaryGroupInfo,
6969     const RefPtr<GroupInfo>& aDefaultGroupInfo) {
6970   const auto copy = [&aDest](const GroupInfo& groupInfo) {
6971     std::copy_if(
6972         groupInfo.mOriginInfos.cbegin(), groupInfo.mOriginInfos.cend(), aDest,
6973         [](const auto& originInfo) { return !originInfo->LockedPersisted(); });
6974   };
6975 
6976   if (aTemporaryGroupInfo) {
6977     MOZ_ASSERT(PERSISTENCE_TYPE_TEMPORARY ==
6978                aTemporaryGroupInfo->GetPersistenceType());
6979 
6980     copy(*aTemporaryGroupInfo);
6981   }
6982   if (aDefaultGroupInfo) {
6983     MOZ_ASSERT(PERSISTENCE_TYPE_DEFAULT ==
6984                aDefaultGroupInfo->GetPersistenceType());
6985 
6986     copy(*aDefaultGroupInfo);
6987   }
6988 }
6989 
6990 template <typename Collect, typename Pred>
6991 QuotaManager::OriginInfosFlatTraversable
CollectLRUOriginInfosUntil(Collect && aCollect,Pred && aPred)6992 QuotaManager::CollectLRUOriginInfosUntil(Collect&& aCollect, Pred&& aPred) {
6993   OriginInfosFlatTraversable originInfos;
6994 
6995   std::forward<Collect>(aCollect)(MakeBackInserter(originInfos));
6996 
6997   originInfos.Sort(OriginInfoAccessTimeComparator());
6998 
6999   const auto foundIt = std::find_if(originInfos.cbegin(), originInfos.cend(),
7000                                     std::forward<Pred>(aPred));
7001 
7002   originInfos.TruncateLength(foundIt - originInfos.cbegin());
7003 
7004   return originInfos;
7005 }
7006 
7007 QuotaManager::OriginInfosNestedTraversable
GetOriginInfosExceedingGroupLimit() const7008 QuotaManager::GetOriginInfosExceedingGroupLimit() const {
7009   MutexAutoLock lock(mQuotaMutex);
7010 
7011   OriginInfosNestedTraversable originInfos;
7012 
7013   for (const auto& entry : mGroupInfoPairs) {
7014     const auto& pair = entry.GetData();
7015 
7016     MOZ_ASSERT(!entry.GetKey().IsEmpty());
7017     MOZ_ASSERT(pair);
7018 
7019     uint64_t groupUsage = 0;
7020 
7021     const RefPtr<GroupInfo> temporaryGroupInfo =
7022         pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
7023     if (temporaryGroupInfo) {
7024       groupUsage += temporaryGroupInfo->mUsage;
7025     }
7026 
7027     const RefPtr<GroupInfo> defaultGroupInfo =
7028         pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
7029     if (defaultGroupInfo) {
7030       groupUsage += defaultGroupInfo->mUsage;
7031     }
7032 
7033     if (groupUsage > 0) {
7034       QuotaManager* quotaManager = QuotaManager::Get();
7035       MOZ_ASSERT(quotaManager, "Shouldn't be null!");
7036 
7037       if (groupUsage > quotaManager->GetGroupLimit()) {
7038         originInfos.AppendElement(CollectLRUOriginInfosUntil(
7039             [&temporaryGroupInfo, &defaultGroupInfo](auto inserter) {
7040               MaybeInsertNonPersistedOriginInfos(
7041                   std::move(inserter), temporaryGroupInfo, defaultGroupInfo);
7042             },
7043             [&groupUsage, quotaManager](const auto& originInfo) {
7044               groupUsage -= originInfo->LockedUsage();
7045 
7046               return groupUsage <= quotaManager->GetGroupLimit();
7047             }));
7048       }
7049     }
7050   }
7051 
7052   return originInfos;
7053 }
7054 
7055 QuotaManager::OriginInfosNestedTraversable
GetOriginInfosExceedingGlobalLimit() const7056 QuotaManager::GetOriginInfosExceedingGlobalLimit() const {
7057   MutexAutoLock lock(mQuotaMutex);
7058 
7059   QuotaManager::OriginInfosNestedTraversable res;
7060   res.AppendElement(CollectLRUOriginInfosUntil(
7061       // XXX The lambda only needs to capture this, but due to Bug 1421435 it
7062       // can't.
7063       [&](auto inserter) {
7064         for (const auto& entry : mGroupInfoPairs) {
7065           const auto& pair = entry.GetData();
7066 
7067           MOZ_ASSERT(!entry.GetKey().IsEmpty());
7068           MOZ_ASSERT(pair);
7069 
7070           MaybeInsertNonPersistedOriginInfos(
7071               inserter, pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY),
7072               pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT));
7073         }
7074       },
7075       [temporaryStorageUsage = mTemporaryStorageUsage,
7076        temporaryStorageLimit = mTemporaryStorageLimit,
7077        doomedUsage = uint64_t{0}](const auto& originInfo) mutable {
7078         if (temporaryStorageUsage - doomedUsage <= temporaryStorageLimit) {
7079           return true;
7080         }
7081 
7082         doomedUsage += originInfo->LockedUsage();
7083         return false;
7084       }));
7085 
7086   return res;
7087 }
7088 
ClearOrigins(const OriginInfosNestedTraversable & aDoomedOriginInfos)7089 void QuotaManager::ClearOrigins(
7090     const OriginInfosNestedTraversable& aDoomedOriginInfos) {
7091   AssertIsOnIOThread();
7092 
7093   // XXX Does this need to be done a) in order and/or b) sequentially?
7094   for (const auto& doomedOriginInfo :
7095        Flatten<OriginInfosFlatTraversable::elem_type>(aDoomedOriginInfos)) {
7096 #ifdef DEBUG
7097     {
7098       MutexAutoLock lock(mQuotaMutex);
7099       MOZ_ASSERT(!doomedOriginInfo->LockedPersisted());
7100     }
7101 #endif
7102 
7103     DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
7104                          doomedOriginInfo->mOrigin);
7105   }
7106 
7107   struct OriginParams {
7108     nsCString mOrigin;
7109     PersistenceType mPersistenceType;
7110   };
7111 
7112   nsTArray<OriginParams> clearedOrigins;
7113 
7114   {
7115     MutexAutoLock lock(mQuotaMutex);
7116 
7117     for (const auto& doomedOriginInfo :
7118          Flatten<OriginInfosFlatTraversable::elem_type>(aDoomedOriginInfos)) {
7119       // LockedRemoveQuotaForOrigin might remove the group info;
7120       // OriginInfo::mGroupInfo is only a raw pointer, so we need to store the
7121       // information for calling OriginClearCompleted below in a separate array.
7122       clearedOrigins.AppendElement(
7123           OriginParams{doomedOriginInfo->mOrigin,
7124                        doomedOriginInfo->mGroupInfo->mPersistenceType});
7125 
7126       LockedRemoveQuotaForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType,
7127                                  doomedOriginInfo->FlattenToOriginMetadata());
7128     }
7129   }
7130 
7131   for (const auto& clearedOrigin : clearedOrigins) {
7132     OriginClearCompleted(clearedOrigin.mPersistenceType, clearedOrigin.mOrigin,
7133                          Nullable<Client::Type>());
7134   }
7135 }
7136 
CleanupTemporaryStorage()7137 void QuotaManager::CleanupTemporaryStorage() {
7138   AssertIsOnIOThread();
7139 
7140   // Evicting origins that exceed their group limit also affects the global
7141   // temporary storage usage, so these steps have to be taken sequentially.
7142   // Combining them doesn't seem worth the added complexity.
7143   ClearOrigins(GetOriginInfosExceedingGroupLimit());
7144   ClearOrigins(GetOriginInfosExceedingGlobalLimit());
7145 
7146   if (mTemporaryStorageUsage > mTemporaryStorageLimit) {
7147     // If disk space is still low after origin clear, notify storage pressure.
7148     NotifyStoragePressure(mTemporaryStorageUsage);
7149   }
7150 }
7151 
DeleteFilesForOrigin(PersistenceType aPersistenceType,const nsACString & aOrigin)7152 void QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
7153                                         const nsACString& aOrigin) {
7154   QM_TRY_INSPECT(const auto& directory,
7155                  GetDirectoryForOrigin(aPersistenceType, aOrigin), QM_VOID);
7156 
7157   nsresult rv = directory->Remove(true);
7158   if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
7159       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
7160     // This should never fail if we've closed all storage connections
7161     // correctly...
7162     NS_ERROR("Failed to remove directory!");
7163   }
7164 }
7165 
FinalizeOriginEviction(nsTArray<RefPtr<OriginDirectoryLock>> && aLocks)7166 void QuotaManager::FinalizeOriginEviction(
7167     nsTArray<RefPtr<OriginDirectoryLock>>&& aLocks) {
7168   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
7169 
7170   RefPtr<FinalizeOriginEvictionOp> op =
7171       new FinalizeOriginEvictionOp(mOwningThread, std::move(aLocks));
7172 
7173   if (IsOnIOThread()) {
7174     op->RunOnIOThreadImmediately();
7175   } else {
7176     op->Dispatch();
7177   }
7178 }
7179 
ArchiveOrigins(const nsTArray<FullOriginMetadata> & aFullOriginMetadatas)7180 Result<Ok, nsresult> QuotaManager::ArchiveOrigins(
7181     const nsTArray<FullOriginMetadata>& aFullOriginMetadatas) {
7182   AssertIsOnIOThread();
7183   MOZ_ASSERT(!aFullOriginMetadatas.IsEmpty());
7184 
7185   QM_TRY_INSPECT(const auto& storageArchivesDir,
7186                  QM_NewLocalFile(*mStorageArchivesPath));
7187 
7188   // Create another subdir, so once we decide to remove all temporary archives,
7189   // we can remove only the subdir and the parent directory can still be used
7190   // for something else or similar in future. Otherwise, we would have to
7191   // figure out a new name for it.
7192   QM_TRY(MOZ_TO_RESULT(storageArchivesDir->Append(u"0"_ns)));
7193 
7194   PRExplodedTime now;
7195   PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
7196 
7197   const auto dateStr =
7198       nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32, now.tm_year,
7199                       now.tm_month + 1, now.tm_mday);
7200 
7201   QM_TRY_INSPECT(
7202       const auto& storageArchiveDir,
7203       CloneFileAndAppend(*storageArchivesDir, NS_ConvertASCIItoUTF16(dateStr)));
7204 
7205   QM_TRY(MOZ_TO_RESULT(
7206       storageArchiveDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700)));
7207 
7208   QM_TRY_INSPECT(const auto& defaultStorageArchiveDir,
7209                  CloneFileAndAppend(*storageArchiveDir,
7210                                     nsLiteralString(DEFAULT_DIRECTORY_NAME)));
7211 
7212   QM_TRY_INSPECT(const auto& temporaryStorageArchiveDir,
7213                  CloneFileAndAppend(*storageArchiveDir,
7214                                     nsLiteralString(TEMPORARY_DIRECTORY_NAME)));
7215 
7216   for (const auto& fullOriginMetadata : aFullOriginMetadatas) {
7217     MOZ_ASSERT(
7218         IsBestEffortPersistenceType(fullOriginMetadata.mPersistenceType));
7219 
7220     QM_TRY_INSPECT(const auto& directory,
7221                    GetDirectoryForOrigin(fullOriginMetadata.mPersistenceType,
7222                                          fullOriginMetadata.mOrigin));
7223 
7224     // The origin could have been removed, for example due to corruption.
7225     QM_TRY_INSPECT(const auto& moved,
7226                    QM_OR_ELSE_WARN_IF(
7227                        // Expression.
7228                        MOZ_TO_RESULT(directory->MoveTo(
7229                                          fullOriginMetadata.mPersistenceType ==
7230                                                  PERSISTENCE_TYPE_DEFAULT
7231                                              ? defaultStorageArchiveDir
7232                                              : temporaryStorageArchiveDir,
7233                                          u""_ns))
7234                            .map([](Ok) { return true; }),
7235                        // Predicate.
7236                        ([](const nsresult rv) {
7237                          return rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ||
7238                                 rv == NS_ERROR_FILE_NOT_FOUND;
7239                        }),
7240                        // Fallback.
7241                        ErrToOk<false>));
7242 
7243     if (moved) {
7244       RemoveQuotaForOrigin(fullOriginMetadata.mPersistenceType,
7245                            fullOriginMetadata);
7246     }
7247   }
7248 
7249   return Ok{};
7250 }
7251 
GetDirectoryLockTable(PersistenceType aPersistenceType)7252 auto QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
7253     -> DirectoryLockTable& {
7254   switch (aPersistenceType) {
7255     case PERSISTENCE_TYPE_TEMPORARY:
7256       return mTemporaryDirectoryLockTable;
7257     case PERSISTENCE_TYPE_DEFAULT:
7258       return mDefaultDirectoryLockTable;
7259 
7260     case PERSISTENCE_TYPE_PERSISTENT:
7261     case PERSISTENCE_TYPE_INVALID:
7262     default:
7263       MOZ_CRASH("Bad persistence type value!");
7264   }
7265 }
7266 
IsSanitizedOriginValid(const nsACString & aSanitizedOrigin)7267 bool QuotaManager::IsSanitizedOriginValid(const nsACString& aSanitizedOrigin) {
7268   AssertIsOnIOThread();
7269 
7270   // Do not parse this sanitized origin string, if we already parsed it.
7271   return mValidOrigins.LookupOrInsertWith(
7272       aSanitizedOrigin, [&aSanitizedOrigin] {
7273         nsCString spec;
7274         OriginAttributes attrs;
7275         nsCString originalSuffix;
7276         const auto result = OriginParser::ParseOrigin(aSanitizedOrigin, spec,
7277                                                       &attrs, originalSuffix);
7278 
7279         return result == OriginParser::ValidOrigin;
7280       });
7281 }
7282 
GenerateDirectoryLockId()7283 int64_t QuotaManager::GenerateDirectoryLockId() {
7284   const int64_t directorylockId = mNextDirectoryLockId;
7285 
7286   if (CheckedInt64 result = CheckedInt64(mNextDirectoryLockId) + 1;
7287       result.isValid()) {
7288     mNextDirectoryLockId = result.value();
7289   } else {
7290     NS_WARNING("Quota manager has run out of ids for directory locks!");
7291 
7292     // There's very little chance for this to happen given the max size of
7293     // 64 bit integer but if it happens we can just reset mNextDirectoryLockId
7294     // to zero since such old directory locks shouldn't exist anymore.
7295     mNextDirectoryLockId = 0;
7296   }
7297 
7298   // TODO: Maybe add an assertion here to check that there is no existing
7299   //       directory lock with given id.
7300 
7301   return directorylockId;
7302 }
7303 
7304 template <typename Func>
ExecuteInitialization(const Initialization aInitialization,Func && aFunc)7305 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
7306                                          Func&& aFunc)
7307     -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7308                                       Initialization, StringGenerator>&> {
7309   return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
7310                                       std::forward<Func>(aFunc));
7311 }
7312 
7313 template <typename Func>
ExecuteInitialization(const Initialization aInitialization,const nsACString & aContext,Func && aFunc)7314 auto QuotaManager::ExecuteInitialization(const Initialization aInitialization,
7315                                          const nsACString& aContext,
7316                                          Func&& aFunc)
7317     -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7318                                       Initialization, StringGenerator>&> {
7319   return quota::ExecuteInitialization(mInitializationInfo, aInitialization,
7320                                       aContext, std::forward<Func>(aFunc));
7321 }
7322 
7323 template <typename Func>
ExecuteOriginInitialization(const nsACString & aOrigin,const OriginInitialization aInitialization,const nsACString & aContext,Func && aFunc)7324 auto QuotaManager::ExecuteOriginInitialization(
7325     const nsACString& aOrigin, const OriginInitialization aInitialization,
7326     const nsACString& aContext, Func&& aFunc)
7327     -> std::invoke_result_t<Func, const FirstInitializationAttempt<
7328                                       Initialization, StringGenerator>&> {
7329   return quota::ExecuteInitialization(
7330       mInitializationInfo.MutableOriginInitializationInfoRef(
7331           aOrigin, CreateIfNonExistent{}),
7332       aInitialization, aContext, std::forward<Func>(aFunc));
7333 }
7334 
7335 /*******************************************************************************
7336  * Local class implementations
7337  ******************************************************************************/
7338 
Serialize(nsACString & aText) const7339 void ClientUsageArray::Serialize(nsACString& aText) const {
7340   QuotaManager* quotaManager = QuotaManager::Get();
7341   MOZ_ASSERT(quotaManager);
7342 
7343   bool first = true;
7344 
7345   for (Client::Type type : quotaManager->AllClientTypes()) {
7346     const Maybe<uint64_t>& clientUsage = ElementAt(type);
7347     if (clientUsage.isSome()) {
7348       if (first) {
7349         first = false;
7350       } else {
7351         aText.Append(" ");
7352       }
7353 
7354       aText.Append(Client::TypeToPrefix(type));
7355       aText.AppendInt(clientUsage.value());
7356     }
7357   }
7358 }
7359 
Deserialize(const nsACString & aText)7360 nsresult ClientUsageArray::Deserialize(const nsACString& aText) {
7361   for (const auto& token :
7362        nsCCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>(aText, ' ')
7363            .ToRange()) {
7364     QM_TRY(OkIf(token.Length() >= 2), NS_ERROR_FAILURE);
7365 
7366     Client::Type clientType;
7367     QM_TRY(OkIf(Client::TypeFromPrefix(token.First(), clientType, fallible)),
7368            NS_ERROR_FAILURE);
7369 
7370     nsresult rv;
7371     const uint64_t usage = Substring(token, 1).ToInteger(&rv);
7372     QM_TRY(MOZ_TO_RESULT(rv));
7373 
7374     ElementAt(clientType) = Some(usage);
7375   }
7376 
7377   return NS_OK;
7378 }
7379 
OriginInfo(GroupInfo * aGroupInfo,const nsACString & aOrigin,const ClientUsageArray & aClientUsages,uint64_t aUsage,int64_t aAccessTime,bool aPersisted,bool aDirectoryExists)7380 OriginInfo::OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
7381                        const ClientUsageArray& aClientUsages, uint64_t aUsage,
7382                        int64_t aAccessTime, bool aPersisted,
7383                        bool aDirectoryExists)
7384     : mClientUsages(aClientUsages.Clone()),
7385       mGroupInfo(aGroupInfo),
7386       mOrigin(aOrigin),
7387       mUsage(aUsage),
7388       mAccessTime(aAccessTime),
7389       mAccessed(false),
7390       mPersisted(aPersisted),
7391       mDirectoryExists(aDirectoryExists) {
7392   MOZ_ASSERT(aGroupInfo);
7393   MOZ_ASSERT(aClientUsages.Length() == Client::TypeMax());
7394   MOZ_ASSERT_IF(aPersisted,
7395                 aGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
7396 
7397   // This constructor is called from the "QuotaManager IO" thread and so
7398   // we can't check if the principal has a WebExtensionPolicy instance
7399   // associated to it, and even besides that if the extension is currently
7400   // disabled (and so no WebExtensionPolicy instance would actually exist)
7401   // its stored data shouldn't be cleared until the extension is uninstalled
7402   // and so here we resort to check the origin scheme instead.
7403   mIsExtension = StringBeginsWith(mOrigin, "moz-extension://"_ns);
7404 
7405 #ifdef DEBUG
7406   QuotaManager* quotaManager = QuotaManager::Get();
7407   MOZ_ASSERT(quotaManager);
7408 
7409   uint64_t usage = 0;
7410   for (Client::Type type : quotaManager->AllClientTypes()) {
7411     AssertNoOverflow(usage, aClientUsages[type].valueOr(0));
7412     usage += aClientUsages[type].valueOr(0);
7413   }
7414   MOZ_ASSERT(aUsage == usage);
7415 #endif
7416 
7417   MOZ_COUNT_CTOR(OriginInfo);
7418 }
7419 
FlattenToOriginMetadata() const7420 OriginMetadata OriginInfo::FlattenToOriginMetadata() const {
7421   return {mGroupInfo->mGroupInfoPair->Suffix(),
7422           mGroupInfo->mGroupInfoPair->Group(), mOrigin,
7423           mGroupInfo->mPersistenceType};
7424 }
7425 
LockedFlattenToFullOriginMetadata() const7426 FullOriginMetadata OriginInfo::LockedFlattenToFullOriginMetadata() const {
7427   AssertCurrentThreadOwnsQuotaMutex();
7428 
7429   return {FlattenToOriginMetadata(), mPersisted, mAccessTime};
7430 }
7431 
LockedBindToStatement(mozIStorageStatement * aStatement) const7432 nsresult OriginInfo::LockedBindToStatement(
7433     mozIStorageStatement* aStatement) const {
7434   AssertCurrentThreadOwnsQuotaMutex();
7435   MOZ_ASSERT(mGroupInfo);
7436 
7437   QM_TRY(MOZ_TO_RESULT(aStatement->BindInt32ByName(
7438       "repository_id"_ns, mGroupInfo->mPersistenceType)));
7439 
7440   QM_TRY(MOZ_TO_RESULT(aStatement->BindUTF8StringByName(
7441       "suffix"_ns, mGroupInfo->mGroupInfoPair->Suffix())));
7442   QM_TRY(MOZ_TO_RESULT(aStatement->BindUTF8StringByName(
7443       "group_"_ns, mGroupInfo->mGroupInfoPair->Group())));
7444   QM_TRY(MOZ_TO_RESULT(aStatement->BindUTF8StringByName("origin"_ns, mOrigin)));
7445 
7446   nsCString clientUsagesText;
7447   mClientUsages.Serialize(clientUsagesText);
7448 
7449   QM_TRY(MOZ_TO_RESULT(
7450       aStatement->BindUTF8StringByName("client_usages"_ns, clientUsagesText)));
7451   QM_TRY(MOZ_TO_RESULT(aStatement->BindInt64ByName("usage"_ns, mUsage)));
7452   QM_TRY(MOZ_TO_RESULT(
7453       aStatement->BindInt64ByName("last_access_time"_ns, mAccessTime)));
7454   QM_TRY(MOZ_TO_RESULT(aStatement->BindInt32ByName("accessed"_ns, mAccessed)));
7455   QM_TRY(
7456       MOZ_TO_RESULT(aStatement->BindInt32ByName("persisted"_ns, mPersisted)));
7457 
7458   return NS_OK;
7459 }
7460 
LockedDecreaseUsage(Client::Type aClientType,int64_t aSize)7461 void OriginInfo::LockedDecreaseUsage(Client::Type aClientType, int64_t aSize) {
7462   AssertCurrentThreadOwnsQuotaMutex();
7463 
7464   MOZ_ASSERT(mClientUsages[aClientType].isSome());
7465   AssertNoUnderflow(mClientUsages[aClientType].value(), aSize);
7466   mClientUsages[aClientType] = Some(mClientUsages[aClientType].value() - aSize);
7467 
7468   AssertNoUnderflow(mUsage, aSize);
7469   mUsage -= aSize;
7470 
7471   if (!LockedPersisted()) {
7472     AssertNoUnderflow(mGroupInfo->mUsage, aSize);
7473     mGroupInfo->mUsage -= aSize;
7474   }
7475 
7476   QuotaManager* quotaManager = QuotaManager::Get();
7477   MOZ_ASSERT(quotaManager);
7478 
7479   AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize);
7480   quotaManager->mTemporaryStorageUsage -= aSize;
7481 }
7482 
LockedResetUsageForClient(Client::Type aClientType)7483 void OriginInfo::LockedResetUsageForClient(Client::Type aClientType) {
7484   AssertCurrentThreadOwnsQuotaMutex();
7485 
7486   uint64_t size = mClientUsages[aClientType].valueOr(0);
7487 
7488   mClientUsages[aClientType].reset();
7489 
7490   AssertNoUnderflow(mUsage, size);
7491   mUsage -= size;
7492 
7493   if (!LockedPersisted()) {
7494     AssertNoUnderflow(mGroupInfo->mUsage, size);
7495     mGroupInfo->mUsage -= size;
7496   }
7497 
7498   QuotaManager* quotaManager = QuotaManager::Get();
7499   MOZ_ASSERT(quotaManager);
7500 
7501   AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, size);
7502   quotaManager->mTemporaryStorageUsage -= size;
7503 }
7504 
LockedGetUsageForClient(Client::Type aClientType)7505 UsageInfo OriginInfo::LockedGetUsageForClient(Client::Type aClientType) {
7506   AssertCurrentThreadOwnsQuotaMutex();
7507 
7508   // The current implementation of this method only supports DOMCACHE and LS,
7509   // which only use DatabaseUsage. If this assertion is lifted, the logic below
7510   // must be adapted.
7511   MOZ_ASSERT(aClientType == Client::Type::DOMCACHE ||
7512              aClientType == Client::Type::LS);
7513 
7514   return UsageInfo{DatabaseUsageType{mClientUsages[aClientType]}};
7515 }
7516 
LockedPersist()7517 void OriginInfo::LockedPersist() {
7518   AssertCurrentThreadOwnsQuotaMutex();
7519   MOZ_ASSERT(mGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
7520   MOZ_ASSERT(!mPersisted);
7521 
7522   mPersisted = true;
7523 
7524   // Remove Usage from GroupInfo
7525   AssertNoUnderflow(mGroupInfo->mUsage, mUsage);
7526   mGroupInfo->mUsage -= mUsage;
7527 }
7528 
LockedGetOriginInfo(const nsACString & aOrigin)7529 already_AddRefed<OriginInfo> GroupInfo::LockedGetOriginInfo(
7530     const nsACString& aOrigin) {
7531   AssertCurrentThreadOwnsQuotaMutex();
7532 
7533   for (const auto& originInfo : mOriginInfos) {
7534     if (originInfo->mOrigin == aOrigin) {
7535       RefPtr<OriginInfo> result = originInfo;
7536       return result.forget();
7537     }
7538   }
7539 
7540   return nullptr;
7541 }
7542 
LockedAddOriginInfo(NotNull<RefPtr<OriginInfo>> && aOriginInfo)7543 void GroupInfo::LockedAddOriginInfo(NotNull<RefPtr<OriginInfo>>&& aOriginInfo) {
7544   AssertCurrentThreadOwnsQuotaMutex();
7545 
7546   NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo),
7547                "Replacing an existing entry!");
7548   mOriginInfos.AppendElement(std::move(aOriginInfo));
7549 
7550   uint64_t usage = aOriginInfo->LockedUsage();
7551 
7552   if (!aOriginInfo->LockedPersisted()) {
7553     AssertNoOverflow(mUsage, usage);
7554     mUsage += usage;
7555   }
7556 
7557   QuotaManager* quotaManager = QuotaManager::Get();
7558   MOZ_ASSERT(quotaManager);
7559 
7560   AssertNoOverflow(quotaManager->mTemporaryStorageUsage, usage);
7561   quotaManager->mTemporaryStorageUsage += usage;
7562 }
7563 
LockedAdjustUsageForRemovedOriginInfo(const OriginInfo & aOriginInfo)7564 void GroupInfo::LockedAdjustUsageForRemovedOriginInfo(
7565     const OriginInfo& aOriginInfo) {
7566   const uint64_t usage = aOriginInfo.LockedUsage();
7567 
7568   if (!aOriginInfo.LockedPersisted()) {
7569     AssertNoUnderflow(mUsage, usage);
7570     mUsage -= usage;
7571   }
7572 
7573   QuotaManager* const quotaManager = QuotaManager::Get();
7574   MOZ_ASSERT(quotaManager);
7575 
7576   AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, usage);
7577   quotaManager->mTemporaryStorageUsage -= usage;
7578 }
7579 
LockedRemoveOriginInfo(const nsACString & aOrigin)7580 void GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) {
7581   AssertCurrentThreadOwnsQuotaMutex();
7582 
7583   const auto foundIt = std::find_if(mOriginInfos.cbegin(), mOriginInfos.cend(),
7584                                     [&aOrigin](const auto& originInfo) {
7585                                       return originInfo->mOrigin == aOrigin;
7586                                     });
7587 
7588   // XXX Or can we MOZ_ASSERT(foundIt != mOriginInfos.cend()) ?
7589   if (foundIt != mOriginInfos.cend()) {
7590     LockedAdjustUsageForRemovedOriginInfo(**foundIt);
7591 
7592     mOriginInfos.RemoveElementAt(foundIt);
7593   }
7594 }
7595 
LockedRemoveOriginInfos()7596 void GroupInfo::LockedRemoveOriginInfos() {
7597   AssertCurrentThreadOwnsQuotaMutex();
7598 
7599   for (const auto& originInfo : std::exchange(mOriginInfos, {})) {
7600     LockedAdjustUsageForRemovedOriginInfo(*originInfo);
7601   }
7602 }
7603 
GetGroupInfoForPersistenceType(PersistenceType aPersistenceType)7604 RefPtr<GroupInfo>& GroupInfoPair::GetGroupInfoForPersistenceType(
7605     PersistenceType aPersistenceType) {
7606   switch (aPersistenceType) {
7607     case PERSISTENCE_TYPE_TEMPORARY:
7608       return mTemporaryStorageGroupInfo;
7609     case PERSISTENCE_TYPE_DEFAULT:
7610       return mDefaultStorageGroupInfo;
7611 
7612     case PERSISTENCE_TYPE_PERSISTENT:
7613     case PERSISTENCE_TYPE_INVALID:
7614     default:
7615       MOZ_CRASH("Bad persistence type value!");
7616   }
7617 }
7618 
CollectOriginsHelper(mozilla::Mutex & aMutex,uint64_t aMinSizeToBeFreed)7619 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
7620                                            uint64_t aMinSizeToBeFreed)
7621     : Runnable("dom::quota::CollectOriginsHelper"),
7622       mMinSizeToBeFreed(aMinSizeToBeFreed),
7623       mMutex(aMutex),
7624       mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
7625       mSizeToBeFreed(0),
7626       mWaiting(true) {
7627   MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7628   mMutex.AssertCurrentThreadOwns();
7629 }
7630 
BlockAndReturnOriginsForEviction(nsTArray<RefPtr<OriginDirectoryLock>> & aLocks)7631 int64_t CollectOriginsHelper::BlockAndReturnOriginsForEviction(
7632     nsTArray<RefPtr<OriginDirectoryLock>>& aLocks) {
7633   MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
7634   mMutex.AssertCurrentThreadOwns();
7635 
7636   while (mWaiting) {
7637     mCondVar.Wait();
7638   }
7639 
7640   mLocks.SwapElements(aLocks);
7641   return mSizeToBeFreed;
7642 }
7643 
7644 NS_IMETHODIMP
Run()7645 CollectOriginsHelper::Run() {
7646   AssertIsOnBackgroundThread();
7647 
7648   QuotaManager* quotaManager = QuotaManager::Get();
7649   NS_ASSERTION(quotaManager, "Shouldn't be null!");
7650 
7651   // We use extra stack vars here to avoid race detector warnings (the same
7652   // memory accessed with and without the lock held).
7653   nsTArray<RefPtr<OriginDirectoryLock>> locks;
7654   uint64_t sizeToBeFreed =
7655       quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
7656 
7657   MutexAutoLock lock(mMutex);
7658 
7659   NS_ASSERTION(mWaiting, "Huh?!");
7660 
7661   mLocks.SwapElements(locks);
7662   mSizeToBeFreed = sizeToBeFreed;
7663   mWaiting = false;
7664   mCondVar.Notify();
7665 
7666   return NS_OK;
7667 }
7668 
7669 /*******************************************************************************
7670  * OriginOperationBase
7671  ******************************************************************************/
7672 
7673 NS_IMETHODIMP
Run()7674 OriginOperationBase::Run() {
7675   nsresult rv;
7676 
7677   switch (mState) {
7678     case State_Initial: {
7679       rv = Init();
7680       break;
7681     }
7682 
7683     case State_DirectoryOpenPending: {
7684       rv = DirectoryOpen();
7685       break;
7686     }
7687 
7688     case State_DirectoryWorkOpen: {
7689       rv = DirectoryWork();
7690       break;
7691     }
7692 
7693     case State_UnblockingOpen: {
7694       UnblockOpen();
7695       return NS_OK;
7696     }
7697 
7698     default:
7699       MOZ_CRASH("Bad state!");
7700   }
7701 
7702   if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
7703     Finish(rv);
7704   }
7705 
7706   return NS_OK;
7707 }
7708 
DirectoryOpen()7709 nsresult OriginOperationBase::DirectoryOpen() {
7710   AssertIsOnOwningThread();
7711   MOZ_ASSERT(mState == State_DirectoryOpenPending);
7712 
7713   QuotaManager* const quotaManager = QuotaManager::Get();
7714   QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
7715 
7716   // Must set this before dispatching otherwise we will race with the IO thread.
7717   AdvanceState();
7718 
7719   QM_TRY(MOZ_TO_RESULT(
7720              quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
7721          NS_ERROR_FAILURE);
7722 
7723   return NS_OK;
7724 }
7725 
Finish(nsresult aResult)7726 void OriginOperationBase::Finish(nsresult aResult) {
7727   if (NS_SUCCEEDED(mResultCode)) {
7728     mResultCode = aResult;
7729   }
7730 
7731   // Must set mState before dispatching otherwise we will race with the main
7732   // thread.
7733   mState = State_UnblockingOpen;
7734 
7735   MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7736 }
7737 
Init()7738 nsresult OriginOperationBase::Init() {
7739   AssertIsOnOwningThread();
7740   MOZ_ASSERT(mState == State_Initial);
7741 
7742   if (QuotaManager::IsShuttingDown()) {
7743     return NS_ERROR_ABORT;
7744   }
7745 
7746   if (mNeedsQuotaManagerInit) {
7747     QM_TRY(QuotaManager::EnsureCreated());
7748   }
7749 
7750   Open();
7751 
7752   return NS_OK;
7753 }
7754 
DirectoryWork()7755 nsresult OriginOperationBase::DirectoryWork() {
7756   AssertIsOnIOThread();
7757   MOZ_ASSERT(mState == State_DirectoryWorkOpen);
7758 
7759   QuotaManager* const quotaManager = QuotaManager::Get();
7760   QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
7761 
7762   if (mNeedsStorageInit) {
7763     QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
7764   }
7765 
7766   QM_TRY(MOZ_TO_RESULT(DoDirectoryWork(*quotaManager)));
7767 
7768   // Must set mState before dispatching otherwise we will race with the owning
7769   // thread.
7770   AdvanceState();
7771 
7772   MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7773 
7774   return NS_OK;
7775 }
7776 
Dispatch()7777 void FinalizeOriginEvictionOp::Dispatch() {
7778   MOZ_ASSERT(!NS_IsMainThread());
7779   MOZ_ASSERT(GetState() == State_Initial);
7780 
7781   SetState(State_DirectoryOpenPending);
7782 
7783   MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
7784 }
7785 
RunOnIOThreadImmediately()7786 void FinalizeOriginEvictionOp::RunOnIOThreadImmediately() {
7787   AssertIsOnIOThread();
7788   MOZ_ASSERT(GetState() == State_Initial);
7789 
7790   SetState(State_DirectoryWorkOpen);
7791 
7792   MOZ_ALWAYS_SUCCEEDS(this->Run());
7793 }
7794 
Open()7795 void FinalizeOriginEvictionOp::Open() { MOZ_CRASH("Shouldn't get here!"); }
7796 
DoDirectoryWork(QuotaManager & aQuotaManager)7797 nsresult FinalizeOriginEvictionOp::DoDirectoryWork(
7798     QuotaManager& aQuotaManager) {
7799   AssertIsOnIOThread();
7800 
7801   AUTO_PROFILER_LABEL("FinalizeOriginEvictionOp::DoDirectoryWork", OTHER);
7802 
7803   for (const auto& lock : mLocks) {
7804     aQuotaManager.OriginClearCompleted(
7805         lock->GetPersistenceType(), lock->Origin(), Nullable<Client::Type>());
7806   }
7807 
7808   return NS_OK;
7809 }
7810 
UnblockOpen()7811 void FinalizeOriginEvictionOp::UnblockOpen() {
7812   AssertIsOnOwningThread();
7813   MOZ_ASSERT(GetState() == State_UnblockingOpen);
7814 
7815 #ifdef DEBUG
7816   NoteActorDestroyed();
7817 #endif
7818 
7819   mLocks.Clear();
7820 
7821   AdvanceState();
7822 }
7823 
NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase,Runnable)7824 NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable)
7825 
7826 RefPtr<DirectoryLock> NormalOriginOperationBase::CreateDirectoryLock() {
7827   AssertIsOnOwningThread();
7828   MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
7829   MOZ_ASSERT(QuotaManager::Get());
7830 
7831   return QuotaManager::Get()->CreateDirectoryLockInternal(
7832       mPersistenceType, mOriginScope, mClientType, mExclusive);
7833 }
7834 
Open()7835 void NormalOriginOperationBase::Open() {
7836   AssertIsOnOwningThread();
7837   MOZ_ASSERT(GetState() == State_Initial);
7838   MOZ_ASSERT(QuotaManager::Get());
7839 
7840   AdvanceState();
7841 
7842   RefPtr<DirectoryLock> directoryLock = CreateDirectoryLock();
7843   if (directoryLock) {
7844     directoryLock->Acquire(this);
7845   } else {
7846     QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
7847            [this](const nsresult rv) { Finish(rv); });
7848   }
7849 }
7850 
UnblockOpen()7851 void NormalOriginOperationBase::UnblockOpen() {
7852   AssertIsOnOwningThread();
7853   MOZ_ASSERT(GetState() == State_UnblockingOpen);
7854 
7855   SendResults();
7856 
7857   if (mDirectoryLock) {
7858     mDirectoryLock = nullptr;
7859   }
7860 
7861   UnregisterNormalOriginOp(*this);
7862 
7863   AdvanceState();
7864 }
7865 
DirectoryLockAcquired(DirectoryLock * aLock)7866 void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) {
7867   AssertIsOnOwningThread();
7868   MOZ_ASSERT(aLock);
7869   MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
7870   MOZ_ASSERT(!mDirectoryLock);
7871 
7872   mDirectoryLock = aLock;
7873 
7874   QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
7875          [this](const nsresult rv) { Finish(rv); });
7876 }
7877 
DirectoryLockFailed()7878 void NormalOriginOperationBase::DirectoryLockFailed() {
7879   AssertIsOnOwningThread();
7880   MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
7881   MOZ_ASSERT(!mDirectoryLock);
7882 
7883   Finish(NS_ERROR_FAILURE);
7884 }
7885 
DoDirectoryWork(QuotaManager & aQuotaManager)7886 nsresult SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
7887   AssertIsOnIOThread();
7888   MOZ_ASSERT(!mPersistenceType.IsNull());
7889   MOZ_ASSERT(mOriginScope.IsOrigin());
7890 
7891   AUTO_PROFILER_LABEL("SaveOriginAccessTimeOp::DoDirectoryWork", OTHER);
7892 
7893   QM_TRY_INSPECT(const auto& file,
7894                  aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
7895                                                      mOriginScope.GetOrigin()));
7896 
7897   // The origin directory might not exist
7898   // anymore, because it was deleted by a clear operation.
7899   QM_TRY_INSPECT(const bool& exists, MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
7900 
7901   if (exists) {
7902     QM_TRY(MOZ_TO_RESULT(file->Append(nsLiteralString(METADATA_V2_FILE_NAME))));
7903 
7904     QM_TRY_INSPECT(const auto& stream,
7905                    GetBinaryOutputStream(*file, FileFlag::Update));
7906     MOZ_ASSERT(stream);
7907 
7908     QM_TRY(MOZ_TO_RESULT(stream->Write64(mTimestamp)));
7909   }
7910 
7911   return NS_OK;
7912 }
7913 
SendResults()7914 void SaveOriginAccessTimeOp::SendResults() {
7915 #ifdef DEBUG
7916   NoteActorDestroyed();
7917 #endif
7918 }
7919 
7920 NS_IMETHODIMP
Run()7921 StoragePressureRunnable::Run() {
7922   MOZ_ASSERT(NS_IsMainThread());
7923 
7924   nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
7925   if (NS_WARN_IF(!obsSvc)) {
7926     return NS_ERROR_FAILURE;
7927   }
7928 
7929   nsCOMPtr<nsISupportsPRUint64> wrapper =
7930       do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
7931   if (NS_WARN_IF(!wrapper)) {
7932     return NS_ERROR_FAILURE;
7933   }
7934 
7935   wrapper->SetData(mUsage);
7936 
7937   obsSvc->NotifyObservers(wrapper, "QuotaManager::StoragePressure", u"");
7938 
7939   return NS_OK;
7940 }
7941 
Start()7942 TimeStamp RecordQuotaInfoLoadTimeHelper::Start() {
7943   AssertIsOnIOThread();
7944 
7945   // XXX: If a OS sleep/wake occur after mStartTime is initialized but before
7946   // gLastOSWake is set, then this time duration would still be recorded with
7947   // key "Normal". We are assumming this is rather rare to happen.
7948   mStartTime.init(TimeStamp::Now());
7949   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7950 
7951   return *mStartTime;
7952 }
7953 
End()7954 TimeStamp RecordQuotaInfoLoadTimeHelper::End() {
7955   AssertIsOnIOThread();
7956 
7957   mEndTime.init(TimeStamp::Now());
7958   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
7959 
7960   return *mEndTime;
7961 }
7962 
7963 NS_IMETHODIMP
Run()7964 RecordQuotaInfoLoadTimeHelper::Run() {
7965   MOZ_ASSERT(NS_IsMainThread());
7966 
7967   if (mInitializedTime.isSome()) {
7968     // Keys for QM_QUOTA_INFO_LOAD_TIME_V0:
7969     // Normal: Normal conditions.
7970     // WasSuspended: There was a OS sleep so that it was suspended.
7971     // TimeStampErr1: The recorded start time is unexpectedly greater than the
7972     //                end time.
7973     // TimeStampErr2: The initialized time for the recording class is unexpectly
7974     //                greater than the last OS wake time.
7975     const auto key = [this, wasSuspended = gLastOSWake > *mInitializedTime]() {
7976       if (wasSuspended) {
7977         return "WasSuspended"_ns;
7978       }
7979 
7980       // XXX File a bug if we have data for this key.
7981       // We found negative values in our query in STMO for
7982       // ScalarID::QM_REPOSITORIES_INITIALIZATION_TIME. This shouldn't happen
7983       // because the documentation for TimeStamp::Now() says it returns a
7984       // monotonically increasing number.
7985       if (*mStartTime > *mEndTime) {
7986         return "TimeStampErr1"_ns;
7987       }
7988 
7989       if (*mInitializedTime > gLastOSWake) {
7990         return "TimeStampErr2"_ns;
7991       }
7992 
7993       return "Normal"_ns;
7994     }();
7995 
7996     Telemetry::AccumulateTimeDelta(Telemetry::QM_QUOTA_INFO_LOAD_TIME_V0, key,
7997                                    *mStartTime, *mEndTime);
7998 
7999     return NS_OK;
8000   }
8001 
8002   gLastOSWake = TimeStamp::Now();
8003   mInitializedTime.init(gLastOSWake);
8004 
8005   return NS_OK;
8006 }
8007 
8008 /*******************************************************************************
8009  * Quota
8010  ******************************************************************************/
8011 
Quota()8012 Quota::Quota()
8013 #ifdef DEBUG
8014     : mActorDestroyed(false)
8015 #endif
8016 {
8017 }
8018 
~Quota()8019 Quota::~Quota() { MOZ_ASSERT(mActorDestroyed); }
8020 
VerifyRequestParams(const UsageRequestParams & aParams) const8021 bool Quota::VerifyRequestParams(const UsageRequestParams& aParams) const {
8022   AssertIsOnBackgroundThread();
8023   MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
8024 
8025   switch (aParams.type()) {
8026     case UsageRequestParams::TAllUsageParams:
8027       break;
8028 
8029     case UsageRequestParams::TOriginUsageParams: {
8030       const OriginUsageParams& params = aParams.get_OriginUsageParams();
8031 
8032       if (NS_WARN_IF(
8033               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8034         MOZ_CRASH_UNLESS_FUZZING();
8035         return false;
8036       }
8037 
8038       break;
8039     }
8040 
8041     default:
8042       MOZ_CRASH("Should never get here!");
8043   }
8044 
8045   return true;
8046 }
8047 
VerifyRequestParams(const RequestParams & aParams) const8048 bool Quota::VerifyRequestParams(const RequestParams& aParams) const {
8049   AssertIsOnBackgroundThread();
8050   MOZ_ASSERT(aParams.type() != RequestParams::T__None);
8051 
8052   switch (aParams.type()) {
8053     case RequestParams::TStorageNameParams:
8054     case RequestParams::TStorageInitializedParams:
8055     case RequestParams::TTemporaryStorageInitializedParams:
8056     case RequestParams::TInitParams:
8057     case RequestParams::TInitTemporaryStorageParams:
8058       break;
8059 
8060     case RequestParams::TInitializePersistentOriginParams: {
8061       const InitializePersistentOriginParams& params =
8062           aParams.get_InitializePersistentOriginParams();
8063 
8064       if (NS_WARN_IF(
8065               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8066         MOZ_CRASH_UNLESS_FUZZING();
8067         return false;
8068       }
8069 
8070       break;
8071     }
8072 
8073     case RequestParams::TInitializeTemporaryOriginParams: {
8074       const InitializeTemporaryOriginParams& params =
8075           aParams.get_InitializeTemporaryOriginParams();
8076 
8077       if (NS_WARN_IF(!IsBestEffortPersistenceType(params.persistenceType()))) {
8078         MOZ_CRASH_UNLESS_FUZZING();
8079         return false;
8080       }
8081 
8082       if (NS_WARN_IF(
8083               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8084         MOZ_CRASH_UNLESS_FUZZING();
8085         return false;
8086       }
8087 
8088       break;
8089     }
8090 
8091     case RequestParams::TGetFullOriginMetadataParams: {
8092       const GetFullOriginMetadataParams& params =
8093           aParams.get_GetFullOriginMetadataParams();
8094       if (NS_WARN_IF(!IsBestEffortPersistenceType(params.persistenceType()))) {
8095         MOZ_CRASH_UNLESS_FUZZING();
8096         return false;
8097       }
8098 
8099       if (NS_WARN_IF(
8100               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8101         MOZ_CRASH_UNLESS_FUZZING();
8102         return false;
8103       }
8104 
8105       break;
8106     }
8107 
8108     case RequestParams::TClearOriginParams: {
8109       const ClearResetOriginParams& params =
8110           aParams.get_ClearOriginParams().commonParams();
8111 
8112       if (NS_WARN_IF(
8113               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8114         MOZ_CRASH_UNLESS_FUZZING();
8115         return false;
8116       }
8117 
8118       if (params.persistenceTypeIsExplicit()) {
8119         if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
8120           MOZ_CRASH_UNLESS_FUZZING();
8121           return false;
8122         }
8123       }
8124 
8125       if (params.clientTypeIsExplicit()) {
8126         if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
8127           MOZ_CRASH_UNLESS_FUZZING();
8128           return false;
8129         }
8130       }
8131 
8132       break;
8133     }
8134 
8135     case RequestParams::TResetOriginParams: {
8136       const ClearResetOriginParams& params =
8137           aParams.get_ResetOriginParams().commonParams();
8138 
8139       if (NS_WARN_IF(
8140               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8141         MOZ_CRASH_UNLESS_FUZZING();
8142         return false;
8143       }
8144 
8145       if (params.persistenceTypeIsExplicit()) {
8146         if (NS_WARN_IF(!IsValidPersistenceType(params.persistenceType()))) {
8147           MOZ_CRASH_UNLESS_FUZZING();
8148           return false;
8149         }
8150       }
8151 
8152       if (params.clientTypeIsExplicit()) {
8153         if (NS_WARN_IF(!Client::IsValidType(params.clientType()))) {
8154           MOZ_CRASH_UNLESS_FUZZING();
8155           return false;
8156         }
8157       }
8158 
8159       break;
8160     }
8161 
8162     case RequestParams::TClearDataParams: {
8163       if (BackgroundParent::IsOtherProcessActor(Manager())) {
8164         MOZ_CRASH_UNLESS_FUZZING();
8165         return false;
8166       }
8167 
8168       break;
8169     }
8170 
8171     case RequestParams::TClearAllParams:
8172     case RequestParams::TResetAllParams:
8173     case RequestParams::TListOriginsParams:
8174       break;
8175 
8176     case RequestParams::TPersistedParams: {
8177       const PersistedParams& params = aParams.get_PersistedParams();
8178 
8179       if (NS_WARN_IF(
8180               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8181         MOZ_CRASH_UNLESS_FUZZING();
8182         return false;
8183       }
8184 
8185       break;
8186     }
8187 
8188     case RequestParams::TPersistParams: {
8189       const PersistParams& params = aParams.get_PersistParams();
8190 
8191       if (NS_WARN_IF(
8192               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8193         MOZ_CRASH_UNLESS_FUZZING();
8194         return false;
8195       }
8196 
8197       break;
8198     }
8199 
8200     case RequestParams::TEstimateParams: {
8201       const EstimateParams& params = aParams.get_EstimateParams();
8202 
8203       if (NS_WARN_IF(
8204               !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
8205         MOZ_CRASH_UNLESS_FUZZING();
8206         return false;
8207       }
8208 
8209       break;
8210     }
8211 
8212     default:
8213       MOZ_CRASH("Should never get here!");
8214   }
8215 
8216   return true;
8217 }
8218 
ActorDestroy(ActorDestroyReason aWhy)8219 void Quota::ActorDestroy(ActorDestroyReason aWhy) {
8220   AssertIsOnBackgroundThread();
8221 #ifdef DEBUG
8222   MOZ_ASSERT(!mActorDestroyed);
8223   mActorDestroyed = true;
8224 #endif
8225 }
8226 
AllocPQuotaUsageRequestParent(const UsageRequestParams & aParams)8227 PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(
8228     const UsageRequestParams& aParams) {
8229   AssertIsOnBackgroundThread();
8230   MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
8231 
8232   if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
8233     return nullptr;
8234   }
8235 
8236 #ifdef DEBUG
8237   // Always verify parameters in DEBUG builds!
8238   bool trustParams = false;
8239 #else
8240   bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
8241 #endif
8242 
8243   if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
8244     MOZ_CRASH_UNLESS_FUZZING();
8245     return nullptr;
8246   }
8247 
8248   auto actor = [&]() -> RefPtr<QuotaUsageRequestBase> {
8249     switch (aParams.type()) {
8250       case UsageRequestParams::TAllUsageParams:
8251         return MakeRefPtr<GetUsageOp>(aParams);
8252 
8253       case UsageRequestParams::TOriginUsageParams:
8254         return MakeRefPtr<GetOriginUsageOp>(aParams);
8255 
8256       default:
8257         MOZ_CRASH("Should never get here!");
8258     }
8259   }();
8260 
8261   MOZ_ASSERT(actor);
8262 
8263   RegisterNormalOriginOp(*actor);
8264 
8265   // Transfer ownership to IPDL.
8266   return actor.forget().take();
8267 }
8268 
RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent * aActor,const UsageRequestParams & aParams)8269 mozilla::ipc::IPCResult Quota::RecvPQuotaUsageRequestConstructor(
8270     PQuotaUsageRequestParent* aActor, const UsageRequestParams& aParams) {
8271   AssertIsOnBackgroundThread();
8272   MOZ_ASSERT(aActor);
8273   MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
8274   MOZ_ASSERT(!QuotaManager::IsShuttingDown());
8275 
8276   auto* op = static_cast<QuotaUsageRequestBase*>(aActor);
8277 
8278   op->Init(*this);
8279 
8280   op->RunImmediately();
8281   return IPC_OK();
8282 }
8283 
DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent * aActor)8284 bool Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) {
8285   AssertIsOnBackgroundThread();
8286   MOZ_ASSERT(aActor);
8287 
8288   // Transfer ownership back from IPDL.
8289   RefPtr<QuotaUsageRequestBase> actor =
8290       dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor));
8291   return true;
8292 }
8293 
AllocPQuotaRequestParent(const RequestParams & aParams)8294 PQuotaRequestParent* Quota::AllocPQuotaRequestParent(
8295     const RequestParams& aParams) {
8296   AssertIsOnBackgroundThread();
8297   MOZ_ASSERT(aParams.type() != RequestParams::T__None);
8298 
8299   if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
8300     return nullptr;
8301   }
8302 
8303 #ifdef DEBUG
8304   // Always verify parameters in DEBUG builds!
8305   bool trustParams = false;
8306 #else
8307   bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
8308 #endif
8309 
8310   if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
8311     MOZ_CRASH_UNLESS_FUZZING();
8312     return nullptr;
8313   }
8314 
8315   auto actor = [&]() -> RefPtr<QuotaRequestBase> {
8316     switch (aParams.type()) {
8317       case RequestParams::TStorageNameParams:
8318         return MakeRefPtr<StorageNameOp>();
8319 
8320       case RequestParams::TStorageInitializedParams:
8321         return MakeRefPtr<StorageInitializedOp>();
8322 
8323       case RequestParams::TTemporaryStorageInitializedParams:
8324         return MakeRefPtr<TemporaryStorageInitializedOp>();
8325 
8326       case RequestParams::TInitParams:
8327         return MakeRefPtr<InitOp>();
8328 
8329       case RequestParams::TInitTemporaryStorageParams:
8330         return MakeRefPtr<InitTemporaryStorageOp>();
8331 
8332       case RequestParams::TInitializePersistentOriginParams:
8333         return MakeRefPtr<InitializePersistentOriginOp>(aParams);
8334 
8335       case RequestParams::TInitializeTemporaryOriginParams:
8336         return MakeRefPtr<InitializeTemporaryOriginOp>(aParams);
8337 
8338       case RequestParams::TGetFullOriginMetadataParams:
8339         return MakeRefPtr<GetFullOriginMetadataOp>(
8340             aParams.get_GetFullOriginMetadataParams());
8341 
8342       case RequestParams::TClearOriginParams:
8343         return MakeRefPtr<ClearOriginOp>(aParams);
8344 
8345       case RequestParams::TResetOriginParams:
8346         return MakeRefPtr<ResetOriginOp>(aParams);
8347 
8348       case RequestParams::TClearDataParams:
8349         return MakeRefPtr<ClearDataOp>(aParams);
8350 
8351       case RequestParams::TClearAllParams:
8352         return MakeRefPtr<ResetOrClearOp>(/* aClear */ true);
8353 
8354       case RequestParams::TResetAllParams:
8355         return MakeRefPtr<ResetOrClearOp>(/* aClear */ false);
8356 
8357       case RequestParams::TPersistedParams:
8358         return MakeRefPtr<PersistedOp>(aParams);
8359 
8360       case RequestParams::TPersistParams:
8361         return MakeRefPtr<PersistOp>(aParams);
8362 
8363       case RequestParams::TEstimateParams:
8364         return MakeRefPtr<EstimateOp>(aParams.get_EstimateParams());
8365 
8366       case RequestParams::TListOriginsParams:
8367         return MakeRefPtr<ListOriginsOp>();
8368 
8369       default:
8370         MOZ_CRASH("Should never get here!");
8371     }
8372   }();
8373 
8374   MOZ_ASSERT(actor);
8375 
8376   RegisterNormalOriginOp(*actor);
8377 
8378   // Transfer ownership to IPDL.
8379   return actor.forget().take();
8380 }
8381 
RecvPQuotaRequestConstructor(PQuotaRequestParent * aActor,const RequestParams & aParams)8382 mozilla::ipc::IPCResult Quota::RecvPQuotaRequestConstructor(
8383     PQuotaRequestParent* aActor, const RequestParams& aParams) {
8384   AssertIsOnBackgroundThread();
8385   MOZ_ASSERT(aActor);
8386   MOZ_ASSERT(aParams.type() != RequestParams::T__None);
8387   MOZ_ASSERT(!QuotaManager::IsShuttingDown());
8388 
8389   auto* op = static_cast<QuotaRequestBase*>(aActor);
8390 
8391   op->Init(*this);
8392 
8393   op->RunImmediately();
8394   return IPC_OK();
8395 }
8396 
DeallocPQuotaRequestParent(PQuotaRequestParent * aActor)8397 bool Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) {
8398   AssertIsOnBackgroundThread();
8399   MOZ_ASSERT(aActor);
8400 
8401   // Transfer ownership back from IPDL.
8402   RefPtr<QuotaRequestBase> actor =
8403       dont_AddRef(static_cast<QuotaRequestBase*>(aActor));
8404   return true;
8405 }
8406 
RecvStartIdleMaintenance()8407 mozilla::ipc::IPCResult Quota::RecvStartIdleMaintenance() {
8408   AssertIsOnBackgroundThread();
8409 
8410   PBackgroundParent* actor = Manager();
8411   MOZ_ASSERT(actor);
8412 
8413   if (BackgroundParent::IsOtherProcessActor(actor)) {
8414     MOZ_CRASH_UNLESS_FUZZING();
8415     return IPC_FAIL_NO_REASON(this);
8416   }
8417 
8418   if (QuotaManager::IsShuttingDown()) {
8419     return IPC_OK();
8420   }
8421 
8422   QM_TRY(QuotaManager::EnsureCreated(), IPC_OK());
8423 
8424   QuotaManager* quotaManager = QuotaManager::Get();
8425   MOZ_ASSERT(quotaManager);
8426 
8427   quotaManager->StartIdleMaintenance();
8428 
8429   return IPC_OK();
8430 }
8431 
RecvStopIdleMaintenance()8432 mozilla::ipc::IPCResult Quota::RecvStopIdleMaintenance() {
8433   AssertIsOnBackgroundThread();
8434 
8435   PBackgroundParent* actor = Manager();
8436   MOZ_ASSERT(actor);
8437 
8438   if (BackgroundParent::IsOtherProcessActor(actor)) {
8439     MOZ_CRASH_UNLESS_FUZZING();
8440     return IPC_FAIL_NO_REASON(this);
8441   }
8442 
8443   if (QuotaManager::IsShuttingDown()) {
8444     return IPC_OK();
8445   }
8446 
8447   QuotaManager* quotaManager = QuotaManager::Get();
8448   if (!quotaManager) {
8449     return IPC_OK();
8450   }
8451 
8452   quotaManager->StopIdleMaintenance();
8453 
8454   return IPC_OK();
8455 }
8456 
RecvAbortOperationsForProcess(const ContentParentId & aContentParentId)8457 mozilla::ipc::IPCResult Quota::RecvAbortOperationsForProcess(
8458     const ContentParentId& aContentParentId) {
8459   AssertIsOnBackgroundThread();
8460 
8461   PBackgroundParent* actor = Manager();
8462   MOZ_ASSERT(actor);
8463 
8464   if (BackgroundParent::IsOtherProcessActor(actor)) {
8465     MOZ_CRASH_UNLESS_FUZZING();
8466     return IPC_FAIL_NO_REASON(this);
8467   }
8468 
8469   if (QuotaManager::IsShuttingDown()) {
8470     return IPC_OK();
8471   }
8472 
8473   QuotaManager* quotaManager = QuotaManager::Get();
8474   if (!quotaManager) {
8475     return IPC_OK();
8476   }
8477 
8478   quotaManager->AbortOperationsForProcess(aContentParentId);
8479 
8480   return IPC_OK();
8481 }
8482 
Init(Quota & aQuota)8483 void QuotaUsageRequestBase::Init(Quota& aQuota) {
8484   AssertIsOnOwningThread();
8485 
8486   mNeedsQuotaManagerInit = true;
8487   mNeedsStorageInit = true;
8488 }
8489 
GetUsageForOrigin(QuotaManager & aQuotaManager,PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata)8490 Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOrigin(
8491     QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
8492     const OriginMetadata& aOriginMetadata) {
8493   AssertIsOnIOThread();
8494 
8495   QM_TRY_INSPECT(const auto& directory,
8496                  aQuotaManager.GetDirectoryForOrigin(aPersistenceType,
8497                                                      aOriginMetadata.mOrigin));
8498 
8499   QM_TRY_INSPECT(const bool& exists,
8500                  MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
8501 
8502   if (!exists || mCanceled) {
8503     return UsageInfo();
8504   }
8505 
8506   // If the directory exists then enumerate all the files inside, adding up
8507   // the sizes to get the final usage statistic.
8508   bool initialized;
8509 
8510   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
8511     initialized = aQuotaManager.IsOriginInitialized(aOriginMetadata.mOrigin);
8512   } else {
8513     initialized = aQuotaManager.IsTemporaryStorageInitialized();
8514   }
8515 
8516   return GetUsageForOriginEntries(aQuotaManager, aPersistenceType,
8517                                   aOriginMetadata, *directory, initialized);
8518 }
8519 
GetUsageForOriginEntries(QuotaManager & aQuotaManager,PersistenceType aPersistenceType,const OriginMetadata & aOriginMetadata,nsIFile & aDirectory,const bool aInitialized)8520 Result<UsageInfo, nsresult> QuotaUsageRequestBase::GetUsageForOriginEntries(
8521     QuotaManager& aQuotaManager, PersistenceType aPersistenceType,
8522     const OriginMetadata& aOriginMetadata, nsIFile& aDirectory,
8523     const bool aInitialized) {
8524   AssertIsOnIOThread();
8525 
8526   QM_TRY_RETURN((ReduceEachFileAtomicCancelable(
8527       aDirectory, mCanceled, UsageInfo{},
8528       [&](UsageInfo oldUsageInfo, const nsCOMPtr<nsIFile>& file)
8529           -> mozilla::Result<UsageInfo, nsresult> {
8530         QM_TRY_INSPECT(
8531             const auto& leafName,
8532             MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
8533 
8534         QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
8535 
8536         switch (dirEntryKind) {
8537           case nsIFileKind::ExistsAsDirectory: {
8538             Client::Type clientType;
8539             const bool ok =
8540                 Client::TypeFromText(leafName, clientType, fallible);
8541             if (!ok) {
8542               // Unknown directories during getting usage for an origin (even
8543               // for an uninitialized origin) are now allowed. Just warn if we
8544               // find them.
8545               UNKNOWN_FILE_WARNING(leafName);
8546               break;
8547             }
8548 
8549             Client* const client = aQuotaManager.GetClient(clientType);
8550             MOZ_ASSERT(client);
8551 
8552             QM_TRY_INSPECT(
8553                 const auto& usageInfo,
8554                 aInitialized ? client->GetUsageForOrigin(
8555                                    aPersistenceType, aOriginMetadata, mCanceled)
8556                              : client->InitOrigin(aPersistenceType,
8557                                                   aOriginMetadata, mCanceled));
8558             return oldUsageInfo + usageInfo;
8559           }
8560 
8561           case nsIFileKind::ExistsAsFile:
8562             // We are maintaining existing behavior for unknown files here (just
8563             // continuing).
8564             // This can possibly be used by developers to add temporary backups
8565             // into origin directories without losing get usage functionality.
8566             if (IsTempMetadata(leafName)) {
8567               if (!aInitialized) {
8568                 QM_TRY(MOZ_TO_RESULT(file->Remove(/* recursive */ false)));
8569               }
8570 
8571               break;
8572             }
8573 
8574             if (IsOriginMetadata(leafName) || IsOSMetadata(leafName) ||
8575                 IsDotFile(leafName)) {
8576               break;
8577             }
8578 
8579             // Unknown files during getting usage for an origin (even for an
8580             // uninitialized origin) are now allowed. Just warn if we find them.
8581             UNKNOWN_FILE_WARNING(leafName);
8582             break;
8583 
8584           case nsIFileKind::DoesNotExist:
8585             // Ignore files that got removed externally while iterating.
8586             break;
8587         }
8588 
8589         return oldUsageInfo;
8590       })));
8591 }
8592 
SendResults()8593 void QuotaUsageRequestBase::SendResults() {
8594   AssertIsOnOwningThread();
8595 
8596   if (IsActorDestroyed()) {
8597     if (NS_SUCCEEDED(mResultCode)) {
8598       mResultCode = NS_ERROR_FAILURE;
8599     }
8600   } else {
8601     if (mCanceled) {
8602       mResultCode = NS_ERROR_FAILURE;
8603     }
8604 
8605     UsageRequestResponse response;
8606 
8607     if (NS_SUCCEEDED(mResultCode)) {
8608       GetResponse(response);
8609     } else {
8610       response = mResultCode;
8611     }
8612 
8613     Unused << PQuotaUsageRequestParent::Send__delete__(this, response);
8614   }
8615 }
8616 
ActorDestroy(ActorDestroyReason aWhy)8617 void QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
8618   AssertIsOnOwningThread();
8619 
8620   NoteActorDestroyed();
8621 }
8622 
RecvCancel()8623 mozilla::ipc::IPCResult QuotaUsageRequestBase::RecvCancel() {
8624   AssertIsOnOwningThread();
8625 
8626   if (mCanceled.exchange(true)) {
8627     NS_WARNING("Canceled more than once?!");
8628     return IPC_FAIL_NO_REASON(this);
8629   }
8630 
8631   return IPC_OK();
8632 }
8633 
TraverseRepository(QuotaManager & aQuotaManager,PersistenceType aPersistenceType)8634 nsresult TraverseRepositoryHelper::TraverseRepository(
8635     QuotaManager& aQuotaManager, PersistenceType aPersistenceType) {
8636   AssertIsOnIOThread();
8637 
8638   QM_TRY_INSPECT(
8639       const auto& directory,
8640       QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)));
8641 
8642   QM_TRY_INSPECT(const bool& exists,
8643                  MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
8644 
8645   if (!exists) {
8646     return NS_OK;
8647   }
8648 
8649   QM_TRY(CollectEachFileAtomicCancelable(
8650       *directory, GetIsCanceledFlag(),
8651       [this, aPersistenceType, &aQuotaManager,
8652        persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT](
8653           const nsCOMPtr<nsIFile>& originDir) -> Result<Ok, nsresult> {
8654         QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*originDir));
8655 
8656         switch (dirEntryKind) {
8657           case nsIFileKind::ExistsAsDirectory:
8658             QM_TRY(MOZ_TO_RESULT(ProcessOrigin(aQuotaManager, *originDir,
8659                                                persistent, aPersistenceType)));
8660             break;
8661 
8662           case nsIFileKind::ExistsAsFile: {
8663             QM_TRY_INSPECT(const auto& leafName,
8664                            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
8665                                nsAutoString, originDir, GetLeafName));
8666 
8667             // Unknown files during getting usages are allowed. Just warn if we
8668             // find them.
8669             if (!IsOSMetadata(leafName)) {
8670               UNKNOWN_FILE_WARNING(leafName);
8671             }
8672 
8673             break;
8674           }
8675 
8676           case nsIFileKind::DoesNotExist:
8677             // Ignore files that got removed externally while iterating.
8678             break;
8679         }
8680 
8681         return Ok{};
8682       }));
8683 
8684   return NS_OK;
8685 }
8686 
GetUsageOp(const UsageRequestParams & aParams)8687 GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
8688     : mGetAll(aParams.get_AllUsageParams().getAll()) {
8689   AssertIsOnOwningThread();
8690   MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
8691 }
8692 
ProcessOriginInternal(QuotaManager * aQuotaManager,const PersistenceType aPersistenceType,const nsACString & aOrigin,const int64_t aTimestamp,const bool aPersisted,const uint64_t aUsage)8693 void GetUsageOp::ProcessOriginInternal(QuotaManager* aQuotaManager,
8694                                        const PersistenceType aPersistenceType,
8695                                        const nsACString& aOrigin,
8696                                        const int64_t aTimestamp,
8697                                        const bool aPersisted,
8698                                        const uint64_t aUsage) {
8699   if (!mGetAll && aQuotaManager->IsOriginInternal(aOrigin)) {
8700     return;
8701   }
8702 
8703   // We can't store pointers to OriginUsage objects in the hashtable
8704   // since AppendElement() reallocates its internal array buffer as number
8705   // of elements grows.
8706   const auto& originUsage =
8707       mOriginUsagesIndex.WithEntryHandle(aOrigin, [&](auto&& entry) {
8708         if (entry) {
8709           return WrapNotNullUnchecked(&mOriginUsages[entry.Data()]);
8710         }
8711 
8712         entry.Insert(mOriginUsages.Length());
8713 
8714         return mOriginUsages.EmplaceBack(nsCString{aOrigin}, false, 0, 0);
8715       });
8716 
8717   if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
8718     originUsage->persisted() = aPersisted;
8719   }
8720 
8721   originUsage->usage() = originUsage->usage() + aUsage;
8722 
8723   originUsage->lastAccessed() =
8724       std::max<int64_t>(originUsage->lastAccessed(), aTimestamp);
8725 }
8726 
GetIsCanceledFlag()8727 const Atomic<bool>& GetUsageOp::GetIsCanceledFlag() {
8728   AssertIsOnIOThread();
8729 
8730   return mCanceled;
8731 }
8732 
8733 // XXX Remove aPersistent
8734 // XXX Remove aPersistenceType once GetUsageForOrigin uses the persistence
8735 // type from OriginMetadata
ProcessOrigin(QuotaManager & aQuotaManager,nsIFile & aOriginDir,const bool aPersistent,const PersistenceType aPersistenceType)8736 nsresult GetUsageOp::ProcessOrigin(QuotaManager& aQuotaManager,
8737                                    nsIFile& aOriginDir, const bool aPersistent,
8738                                    const PersistenceType aPersistenceType) {
8739   AssertIsOnIOThread();
8740 
8741   QM_TRY_INSPECT(const auto& metadata,
8742                  aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir));
8743 
8744   QM_TRY_INSPECT(const auto& usageInfo,
8745                  GetUsageForOrigin(aQuotaManager, aPersistenceType, metadata));
8746 
8747   ProcessOriginInternal(&aQuotaManager, aPersistenceType, metadata.mOrigin,
8748                         metadata.mLastAccessTime, metadata.mPersisted,
8749                         usageInfo.TotalUsage().valueOr(0));
8750 
8751   return NS_OK;
8752 }
8753 
DoDirectoryWork(QuotaManager & aQuotaManager)8754 nsresult GetUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8755   AssertIsOnIOThread();
8756   aQuotaManager.AssertStorageIsInitialized();
8757 
8758   AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
8759 
8760   nsresult rv;
8761 
8762   for (const PersistenceType type : kAllPersistenceTypes) {
8763     rv = TraverseRepository(aQuotaManager, type);
8764     if (NS_WARN_IF(NS_FAILED(rv))) {
8765       return rv;
8766     }
8767   }
8768 
8769   // TraverseRepository above only consulted the filesystem. We also need to
8770   // consider origins which may have pending quota usage, such as buffered
8771   // LocalStorage writes for an origin which didn't previously have any
8772   // LocalStorage data.
8773 
8774   aQuotaManager.CollectPendingOriginsForListing(
8775       [this, &aQuotaManager](const auto& originInfo) {
8776         ProcessOriginInternal(
8777             &aQuotaManager, originInfo->GetGroupInfo()->GetPersistenceType(),
8778             originInfo->Origin(), originInfo->LockedAccessTime(),
8779             originInfo->LockedPersisted(), originInfo->LockedUsage());
8780       });
8781 
8782   return NS_OK;
8783 }
8784 
GetResponse(UsageRequestResponse & aResponse)8785 void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
8786   AssertIsOnOwningThread();
8787 
8788   aResponse = AllUsageResponse();
8789 
8790   aResponse.get_AllUsageResponse().originUsages() = std::move(mOriginUsages);
8791 }
8792 
GetOriginUsageOp(const UsageRequestParams & aParams)8793 GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams)
8794     : mUsage(0), mFileUsage(0) {
8795   AssertIsOnOwningThread();
8796   MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams);
8797 
8798   const OriginUsageParams& params = aParams.get_OriginUsageParams();
8799 
8800   PrincipalMetadata principalMetadata =
8801       QuotaManager::GetInfoFromValidatedPrincipalInfo(params.principalInfo());
8802 
8803   mSuffix = std::move(principalMetadata.mSuffix);
8804   mGroup = std::move(principalMetadata.mGroup);
8805   mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
8806 
8807   mFromMemory = params.fromMemory();
8808 
8809   // Overwrite OriginOperationBase default values.
8810   mNeedsQuotaManagerInit = true;
8811   mNeedsStorageInit = true;
8812 }
8813 
CreateDirectoryLock()8814 RefPtr<DirectoryLock> GetOriginUsageOp::CreateDirectoryLock() {
8815   if (mFromMemory) {
8816     return nullptr;
8817   }
8818 
8819   return QuotaUsageRequestBase::CreateDirectoryLock();
8820 }
8821 
DoDirectoryWork(QuotaManager & aQuotaManager)8822 nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8823   AssertIsOnIOThread();
8824   aQuotaManager.AssertStorageIsInitialized();
8825   MOZ_ASSERT(mUsage == 0);
8826   MOZ_ASSERT(mFileUsage == 0);
8827 
8828   AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
8829 
8830   if (mFromMemory) {
8831     const PrincipalMetadata principalMetadata = {
8832         mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}};
8833 
8834     // Ensure temporary storage is initialized. If temporary storage hasn't been
8835     // initialized yet, the method will initialize it by traversing the
8836     // repositories for temporary and default storage (including our origin).
8837     QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
8838 
8839     // Get cached usage (the method doesn't have to stat any files). File usage
8840     // is not tracked in memory separately, so just add to the total usage.
8841     mUsage = aQuotaManager.GetOriginUsage(principalMetadata);
8842 
8843     return NS_OK;
8844   }
8845 
8846   UsageInfo usageInfo;
8847 
8848   // Add all the persistent/temporary/default storage files we care about.
8849   for (const PersistenceType type : kAllPersistenceTypes) {
8850     const OriginMetadata originMetadata = {
8851         mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()}, type};
8852 
8853     auto usageInfoOrErr =
8854         GetUsageForOrigin(aQuotaManager, type, originMetadata);
8855     if (NS_WARN_IF(usageInfoOrErr.isErr())) {
8856       return usageInfoOrErr.unwrapErr();
8857     }
8858 
8859     usageInfo += usageInfoOrErr.unwrap();
8860   }
8861 
8862   mUsage = usageInfo.TotalUsage().valueOr(0);
8863   mFileUsage = usageInfo.FileUsage().valueOr(0);
8864 
8865   return NS_OK;
8866 }
8867 
GetResponse(UsageRequestResponse & aResponse)8868 void GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) {
8869   AssertIsOnOwningThread();
8870 
8871   OriginUsageResponse usageResponse;
8872 
8873   usageResponse.usage() = mUsage;
8874   usageResponse.fileUsage() = mFileUsage;
8875 
8876   aResponse = usageResponse;
8877 }
8878 
Init(Quota & aQuota)8879 void QuotaRequestBase::Init(Quota& aQuota) {
8880   AssertIsOnOwningThread();
8881 
8882   mNeedsQuotaManagerInit = true;
8883   mNeedsStorageInit = true;
8884 }
8885 
SendResults()8886 void QuotaRequestBase::SendResults() {
8887   AssertIsOnOwningThread();
8888 
8889   if (IsActorDestroyed()) {
8890     if (NS_SUCCEEDED(mResultCode)) {
8891       mResultCode = NS_ERROR_FAILURE;
8892     }
8893   } else {
8894     RequestResponse response;
8895 
8896     if (NS_SUCCEEDED(mResultCode)) {
8897       GetResponse(response);
8898     } else {
8899       response = mResultCode;
8900     }
8901 
8902     Unused << PQuotaRequestParent::Send__delete__(this, response);
8903   }
8904 }
8905 
ActorDestroy(ActorDestroyReason aWhy)8906 void QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) {
8907   AssertIsOnOwningThread();
8908 
8909   NoteActorDestroyed();
8910 }
8911 
StorageNameOp()8912 StorageNameOp::StorageNameOp() : QuotaRequestBase(/* aExclusive */ false) {
8913   AssertIsOnOwningThread();
8914 
8915   // Overwrite OriginOperationBase default values.
8916   mNeedsQuotaManagerInit = true;
8917   mNeedsStorageInit = false;
8918 }
8919 
Init(Quota & aQuota)8920 void StorageNameOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8921 
CreateDirectoryLock()8922 RefPtr<DirectoryLock> StorageNameOp::CreateDirectoryLock() { return nullptr; }
8923 
DoDirectoryWork(QuotaManager & aQuotaManager)8924 nsresult StorageNameOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8925   AssertIsOnIOThread();
8926 
8927   AUTO_PROFILER_LABEL("StorageNameOp::DoDirectoryWork", OTHER);
8928 
8929   mName = aQuotaManager.GetStorageName();
8930 
8931   return NS_OK;
8932 }
8933 
GetResponse(RequestResponse & aResponse)8934 void StorageNameOp::GetResponse(RequestResponse& aResponse) {
8935   AssertIsOnOwningThread();
8936 
8937   StorageNameResponse storageNameResponse;
8938 
8939   storageNameResponse.name() = mName;
8940 
8941   aResponse = storageNameResponse;
8942 }
8943 
InitializedRequestBase()8944 InitializedRequestBase::InitializedRequestBase()
8945     : QuotaRequestBase(/* aExclusive */ false), mInitialized(false) {
8946   AssertIsOnOwningThread();
8947 
8948   // Overwrite OriginOperationBase default values.
8949   mNeedsQuotaManagerInit = true;
8950   mNeedsStorageInit = false;
8951 }
8952 
Init(Quota & aQuota)8953 void InitializedRequestBase::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
8954 
CreateDirectoryLock()8955 RefPtr<DirectoryLock> InitializedRequestBase::CreateDirectoryLock() {
8956   return nullptr;
8957 }
8958 
DoDirectoryWork(QuotaManager & aQuotaManager)8959 nsresult StorageInitializedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
8960   AssertIsOnIOThread();
8961 
8962   AUTO_PROFILER_LABEL("StorageInitializedOp::DoDirectoryWork", OTHER);
8963 
8964   mInitialized = aQuotaManager.IsStorageInitialized();
8965 
8966   return NS_OK;
8967 }
8968 
GetResponse(RequestResponse & aResponse)8969 void StorageInitializedOp::GetResponse(RequestResponse& aResponse) {
8970   AssertIsOnOwningThread();
8971 
8972   StorageInitializedResponse storageInitializedResponse;
8973 
8974   storageInitializedResponse.initialized() = mInitialized;
8975 
8976   aResponse = storageInitializedResponse;
8977 }
8978 
DoDirectoryWork(QuotaManager & aQuotaManager)8979 nsresult TemporaryStorageInitializedOp::DoDirectoryWork(
8980     QuotaManager& aQuotaManager) {
8981   AssertIsOnIOThread();
8982 
8983   AUTO_PROFILER_LABEL("TemporaryStorageInitializedOp::DoDirectoryWork", OTHER);
8984 
8985   mInitialized = aQuotaManager.IsTemporaryStorageInitialized();
8986 
8987   return NS_OK;
8988 }
8989 
GetResponse(RequestResponse & aResponse)8990 void TemporaryStorageInitializedOp::GetResponse(RequestResponse& aResponse) {
8991   AssertIsOnOwningThread();
8992 
8993   TemporaryStorageInitializedResponse temporaryStorageInitializedResponse;
8994 
8995   temporaryStorageInitializedResponse.initialized() = mInitialized;
8996 
8997   aResponse = temporaryStorageInitializedResponse;
8998 }
8999 
InitOp()9000 InitOp::InitOp() : QuotaRequestBase(/* aExclusive */ false) {
9001   AssertIsOnOwningThread();
9002 
9003   // Overwrite OriginOperationBase default values.
9004   mNeedsQuotaManagerInit = true;
9005   mNeedsStorageInit = false;
9006 }
9007 
Init(Quota & aQuota)9008 void InitOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
9009 
DoDirectoryWork(QuotaManager & aQuotaManager)9010 nsresult InitOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9011   AssertIsOnIOThread();
9012 
9013   AUTO_PROFILER_LABEL("InitOp::DoDirectoryWork", OTHER);
9014 
9015   QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureStorageIsInitialized()));
9016 
9017   return NS_OK;
9018 }
9019 
GetResponse(RequestResponse & aResponse)9020 void InitOp::GetResponse(RequestResponse& aResponse) {
9021   AssertIsOnOwningThread();
9022 
9023   aResponse = InitResponse();
9024 }
9025 
InitTemporaryStorageOp()9026 InitTemporaryStorageOp::InitTemporaryStorageOp()
9027     : QuotaRequestBase(/* aExclusive */ false) {
9028   AssertIsOnOwningThread();
9029 
9030   // Overwrite OriginOperationBase default values.
9031   mNeedsQuotaManagerInit = true;
9032   mNeedsStorageInit = false;
9033 }
9034 
Init(Quota & aQuota)9035 void InitTemporaryStorageOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
9036 
DoDirectoryWork(QuotaManager & aQuotaManager)9037 nsresult InitTemporaryStorageOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9038   AssertIsOnIOThread();
9039 
9040   AUTO_PROFILER_LABEL("InitTemporaryStorageOp::DoDirectoryWork", OTHER);
9041 
9042   QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE;);
9043 
9044   QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
9045 
9046   return NS_OK;
9047 }
9048 
GetResponse(RequestResponse & aResponse)9049 void InitTemporaryStorageOp::GetResponse(RequestResponse& aResponse) {
9050   AssertIsOnOwningThread();
9051 
9052   aResponse = InitTemporaryStorageResponse();
9053 }
9054 
InitializeOriginRequestBase(const PersistenceType aPersistenceType,const PrincipalInfo & aPrincipalInfo)9055 InitializeOriginRequestBase::InitializeOriginRequestBase(
9056     const PersistenceType aPersistenceType, const PrincipalInfo& aPrincipalInfo)
9057     : QuotaRequestBase(/* aExclusive */ false), mCreated(false) {
9058   AssertIsOnOwningThread();
9059 
9060   auto principalMetadata =
9061       QuotaManager::GetInfoFromValidatedPrincipalInfo(aPrincipalInfo);
9062 
9063   // Overwrite OriginOperationBase default values.
9064   mNeedsQuotaManagerInit = true;
9065   mNeedsStorageInit = false;
9066 
9067   // Overwrite NormalOriginOperationBase default values.
9068   mPersistenceType.SetValue(aPersistenceType);
9069   mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
9070 
9071   // Overwrite InitializeOriginRequestBase default values.
9072   mSuffix = std::move(principalMetadata.mSuffix);
9073   mGroup = std::move(principalMetadata.mGroup);
9074 }
9075 
Init(Quota & aQuota)9076 void InitializeOriginRequestBase::Init(Quota& aQuota) {
9077   AssertIsOnOwningThread();
9078 }
9079 
InitializePersistentOriginOp(const RequestParams & aParams)9080 InitializePersistentOriginOp::InitializePersistentOriginOp(
9081     const RequestParams& aParams)
9082     : InitializeOriginRequestBase(
9083           PERSISTENCE_TYPE_PERSISTENT,
9084           aParams.get_InitializePersistentOriginParams().principalInfo()) {
9085   AssertIsOnOwningThread();
9086   MOZ_ASSERT(aParams.type() ==
9087              RequestParams::TInitializePersistentOriginParams);
9088 }
9089 
DoDirectoryWork(QuotaManager & aQuotaManager)9090 nsresult InitializePersistentOriginOp::DoDirectoryWork(
9091     QuotaManager& aQuotaManager) {
9092   AssertIsOnIOThread();
9093   MOZ_ASSERT(!mPersistenceType.IsNull());
9094 
9095   AUTO_PROFILER_LABEL("InitializePersistentOriginOp::DoDirectoryWork", OTHER);
9096 
9097   QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
9098 
9099   QM_TRY_UNWRAP(mCreated,
9100                 (aQuotaManager
9101                      .EnsurePersistentOriginIsInitialized(OriginMetadata{
9102                          mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
9103                          PERSISTENCE_TYPE_PERSISTENT})
9104                      .map([](const auto& res) { return res.second; })));
9105 
9106   return NS_OK;
9107 }
9108 
GetResponse(RequestResponse & aResponse)9109 void InitializePersistentOriginOp::GetResponse(RequestResponse& aResponse) {
9110   AssertIsOnOwningThread();
9111 
9112   aResponse = InitializePersistentOriginResponse(mCreated);
9113 }
9114 
InitializeTemporaryOriginOp(const RequestParams & aParams)9115 InitializeTemporaryOriginOp::InitializeTemporaryOriginOp(
9116     const RequestParams& aParams)
9117     : InitializeOriginRequestBase(
9118           aParams.get_InitializeTemporaryOriginParams().persistenceType(),
9119           aParams.get_InitializeTemporaryOriginParams().principalInfo()) {
9120   AssertIsOnOwningThread();
9121   MOZ_ASSERT(aParams.type() == RequestParams::TInitializeTemporaryOriginParams);
9122 }
9123 
DoDirectoryWork(QuotaManager & aQuotaManager)9124 nsresult InitializeTemporaryOriginOp::DoDirectoryWork(
9125     QuotaManager& aQuotaManager) {
9126   AssertIsOnIOThread();
9127   MOZ_ASSERT(!mPersistenceType.IsNull());
9128 
9129   AUTO_PROFILER_LABEL("InitializeTemporaryOriginOp::DoDirectoryWork", OTHER);
9130 
9131   QM_TRY(OkIf(aQuotaManager.IsStorageInitialized()), NS_ERROR_FAILURE);
9132 
9133   QM_TRY(OkIf(aQuotaManager.IsTemporaryStorageInitialized()), NS_ERROR_FAILURE);
9134 
9135   QM_TRY_UNWRAP(mCreated,
9136                 (aQuotaManager
9137                      .EnsureTemporaryOriginIsInitialized(
9138                          mPersistenceType.Value(),
9139                          OriginMetadata{mSuffix, mGroup,
9140                                         nsCString{mOriginScope.GetOrigin()},
9141                                         mPersistenceType.Value()})
9142                      .map([](const auto& res) { return res.second; })));
9143 
9144   return NS_OK;
9145 }
9146 
GetResponse(RequestResponse & aResponse)9147 void InitializeTemporaryOriginOp::GetResponse(RequestResponse& aResponse) {
9148   AssertIsOnOwningThread();
9149 
9150   aResponse = InitializeTemporaryOriginResponse(mCreated);
9151 }
9152 
GetFullOriginMetadataOp(const GetFullOriginMetadataParams & aParams)9153 GetFullOriginMetadataOp::GetFullOriginMetadataOp(
9154     const GetFullOriginMetadataParams& aParams)
9155     : QuotaRequestBase(/* aExclusive */ false),
9156       mOriginMetadata(QuotaManager::GetInfoFromValidatedPrincipalInfo(
9157                           aParams.principalInfo()),
9158                       aParams.persistenceType()) {
9159   AssertIsOnOwningThread();
9160 }
9161 
CreateDirectoryLock()9162 RefPtr<DirectoryLock> GetFullOriginMetadataOp::CreateDirectoryLock() {
9163   return nullptr;
9164 }
9165 
DoDirectoryWork(QuotaManager & aQuotaManager)9166 nsresult GetFullOriginMetadataOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9167   AssertIsOnIOThread();
9168 
9169   AUTO_PROFILER_LABEL("GetFullOriginMetadataOp::DoDirectoryWork", OTHER);
9170 
9171   // Ensure temporary storage is initialized. If temporary storage hasn't
9172   // been initialized yet, the method will initialize it by traversing the
9173   // repositories for temporary and default storage (including our origin).
9174   QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
9175 
9176   // Get metadata cached in memory (the method doesn't have to stat any
9177   // files).
9178   mMaybeFullOriginMetadata =
9179       aQuotaManager.GetFullOriginMetadata(mOriginMetadata);
9180 
9181   return NS_OK;
9182 }
9183 
GetResponse(RequestResponse & aResponse)9184 void GetFullOriginMetadataOp::GetResponse(RequestResponse& aResponse) {
9185   AssertIsOnOwningThread();
9186 
9187   aResponse = GetFullOriginMetadataResponse();
9188   aResponse.get_GetFullOriginMetadataResponse().maybeFullOriginMetadata() =
9189       std::move(mMaybeFullOriginMetadata);
9190 }
9191 
ResetOrClearOp(bool aClear)9192 ResetOrClearOp::ResetOrClearOp(bool aClear)
9193     : QuotaRequestBase(/* aExclusive */ true), mClear(aClear) {
9194   AssertIsOnOwningThread();
9195 
9196   // Overwrite OriginOperationBase default values.
9197   mNeedsQuotaManagerInit = true;
9198   mNeedsStorageInit = false;
9199 }
9200 
Init(Quota & aQuota)9201 void ResetOrClearOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
9202 
DeleteFiles(QuotaManager & aQuotaManager)9203 void ResetOrClearOp::DeleteFiles(QuotaManager& aQuotaManager) {
9204   AssertIsOnIOThread();
9205 
9206   nsresult rv = aQuotaManager.AboutToClearOrigins(Nullable<PersistenceType>(),
9207                                                   OriginScope::FromNull(),
9208                                                   Nullable<Client::Type>());
9209   if (NS_WARN_IF(NS_FAILED(rv))) {
9210     return;
9211   }
9212 
9213   auto directoryOrErr = QM_NewLocalFile(aQuotaManager.GetStoragePath());
9214   if (NS_WARN_IF(directoryOrErr.isErr())) {
9215     return;
9216   }
9217 
9218   nsCOMPtr<nsIFile> directory = directoryOrErr.unwrap();
9219 
9220   rv = directory->Remove(true);
9221   if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
9222       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
9223     // This should never fail if we've closed all storage connections
9224     // correctly...
9225     MOZ_ASSERT(false, "Failed to remove storage directory!");
9226   }
9227 }
9228 
DeleteStorageFile(QuotaManager & aQuotaManager)9229 void ResetOrClearOp::DeleteStorageFile(QuotaManager& aQuotaManager) {
9230   AssertIsOnIOThread();
9231 
9232   QM_TRY_INSPECT(const auto& storageFile,
9233                  QM_NewLocalFile(aQuotaManager.GetBasePath()), QM_VOID);
9234 
9235   QM_TRY(MOZ_TO_RESULT(storageFile->Append(aQuotaManager.GetStorageName() +
9236                                            kSQLiteSuffix)),
9237          QM_VOID);
9238 
9239   const nsresult rv = storageFile->Remove(true);
9240   if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
9241       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
9242     // This should never fail if we've closed the storage connection
9243     // correctly...
9244     MOZ_ASSERT(false, "Failed to remove storage file!");
9245   }
9246 }
9247 
DoDirectoryWork(QuotaManager & aQuotaManager)9248 nsresult ResetOrClearOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9249   AssertIsOnIOThread();
9250 
9251   AUTO_PROFILER_LABEL("ResetOrClearOp::DoDirectoryWork", OTHER);
9252 
9253   if (mClear) {
9254     DeleteFiles(aQuotaManager);
9255 
9256     aQuotaManager.RemoveQuota();
9257   }
9258 
9259   aQuotaManager.ShutdownStorage();
9260 
9261   if (mClear) {
9262     DeleteStorageFile(aQuotaManager);
9263   }
9264 
9265   return NS_OK;
9266 }
9267 
GetResponse(RequestResponse & aResponse)9268 void ResetOrClearOp::GetResponse(RequestResponse& aResponse) {
9269   AssertIsOnOwningThread();
9270   if (mClear) {
9271     aResponse = ClearAllResponse();
9272   } else {
9273     aResponse = ResetAllResponse();
9274   }
9275 }
9276 
DeleteFiles(QuotaManager & aQuotaManager,PersistenceType aPersistenceType)9277 void ClearRequestBase::DeleteFiles(QuotaManager& aQuotaManager,
9278                                    PersistenceType aPersistenceType) {
9279   AssertIsOnIOThread();
9280 
9281   QM_TRY(MOZ_TO_RESULT(aQuotaManager.AboutToClearOrigins(
9282              Nullable<PersistenceType>(aPersistenceType), mOriginScope,
9283              mClientType)),
9284          QM_VOID);
9285 
9286   QM_TRY_INSPECT(
9287       const auto& directory,
9288       QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)), QM_VOID);
9289 
9290   nsTArray<nsCOMPtr<nsIFile>> directoriesForRemovalRetry;
9291 
9292   aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
9293       "ClearRequestBase: Starting deleting files"_ns);
9294 
9295   QM_TRY(
9296       CollectEachFile(
9297           *directory,
9298           [originScope =
9299                [this] {
9300                  OriginScope originScope = mOriginScope.Clone();
9301                  if (originScope.IsOrigin()) {
9302                    originScope.SetOrigin(
9303                        MakeSanitizedOriginCString(originScope.GetOrigin()));
9304                  } else if (originScope.IsPrefix()) {
9305                    originScope.SetOriginNoSuffix(MakeSanitizedOriginCString(
9306                        originScope.GetOriginNoSuffix()));
9307                  }
9308                  return originScope;
9309                }(),
9310            aPersistenceType, &aQuotaManager, &directoriesForRemovalRetry,
9311            this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
9312             QM_TRY_INSPECT(const auto& leafName,
9313                            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
9314                                                              GetLeafName));
9315 
9316             QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
9317 
9318             switch (dirEntryKind) {
9319               case nsIFileKind::ExistsAsDirectory: {
9320                 // Skip the origin directory if it doesn't match the pattern.
9321                 if (!originScope.Matches(OriginScope::FromOrigin(
9322                         NS_ConvertUTF16toUTF8(leafName)))) {
9323                   break;
9324                 }
9325 
9326                 QM_TRY_INSPECT(
9327                     const auto& metadata,
9328                     aQuotaManager.LoadFullOriginMetadataWithRestore(file));
9329 
9330                 MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
9331 
9332                 if (!mClientType.IsNull()) {
9333                   nsAutoString clientDirectoryName;
9334                   QM_TRY(
9335                       OkIf(Client::TypeToText(mClientType.Value(),
9336                                               clientDirectoryName, fallible)),
9337                       Err(NS_ERROR_FAILURE));
9338 
9339                   QM_TRY(MOZ_TO_RESULT(file->Append(clientDirectoryName)));
9340 
9341                   QM_TRY_INSPECT(const bool& exists,
9342                                  MOZ_TO_RESULT_INVOKE_MEMBER(file, Exists));
9343 
9344                   if (!exists) {
9345                     break;
9346                   }
9347                 }
9348 
9349                 // We can't guarantee that this will always succeed on
9350                 // Windows...
9351                 QM_WARNONLY_TRY(
9352                     QM_TO_RESULT(file->Remove(true)), [&](const auto&) {
9353                       directoriesForRemovalRetry.AppendElement(std::move(file));
9354                     });
9355 
9356                 const bool initialized =
9357                     aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
9358                         ? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
9359                         : aQuotaManager.IsTemporaryStorageInitialized();
9360 
9361                 // If it hasn't been initialized, we don't need to update the
9362                 // quota and notify the removing client.
9363                 if (!initialized) {
9364                   break;
9365                 }
9366 
9367                 if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
9368                   if (mClientType.IsNull()) {
9369                     aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
9370                                                        metadata);
9371                   } else {
9372                     aQuotaManager.ResetUsageForClient(
9373                         ClientMetadata{metadata, mClientType.Value()});
9374                   }
9375                 }
9376 
9377                 aQuotaManager.OriginClearCompleted(
9378                     aPersistenceType, metadata.mOrigin, mClientType);
9379 
9380                 break;
9381               }
9382 
9383               case nsIFileKind::ExistsAsFile:
9384                 // Unknown files during clearing are allowed. Just warn if we
9385                 // find them.
9386                 if (!IsOSMetadata(leafName)) {
9387                   UNKNOWN_FILE_WARNING(leafName);
9388                 }
9389 
9390                 break;
9391 
9392               case nsIFileKind::DoesNotExist:
9393                 // Ignore files that got removed externally while iterating.
9394                 break;
9395             }
9396 
9397             return Ok{};
9398           }),
9399       QM_VOID);
9400 
9401   // Retry removing any directories that failed to be removed earlier now.
9402   //
9403   // XXX This will still block this operation. We might instead dispatch a
9404   // runnable to our own thread for each retry round with a timer. We must
9405   // ensure that the directory lock is upheld until we complete or give up
9406   // though.
9407   for (uint32_t index = 0; index < 10; index++) {
9408     aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
9409       return nsPrintfCString(
9410           "ClearRequestBase: Starting repeated directory removal #%d", index);
9411     });
9412 
9413     for (auto&& file : std::exchange(directoriesForRemovalRetry,
9414                                      nsTArray<nsCOMPtr<nsIFile>>{})) {
9415       QM_WARNONLY_TRY(
9416           QM_TO_RESULT(file->Remove(true)),
9417           ([&directoriesForRemovalRetry, &file](const auto&) {
9418             directoriesForRemovalRetry.AppendElement(std::move(file));
9419           }));
9420     }
9421 
9422     aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
9423       return nsPrintfCString(
9424           "ClearRequestBase: Completed repeated directory removal #%d", index);
9425     });
9426 
9427     if (directoriesForRemovalRetry.IsEmpty()) {
9428       break;
9429     }
9430 
9431     aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
9432       return nsPrintfCString("ClearRequestBase: Before sleep #%d", index);
9433     });
9434 
9435     PR_Sleep(PR_MillisecondsToInterval(200));
9436 
9437     aQuotaManager.MaybeRecordQuotaManagerShutdownStepWith([index]() {
9438       return nsPrintfCString("ClearRequestBase: After sleep #%d", index);
9439     });
9440   }
9441 
9442   QM_WARNONLY_TRY(OkIf(directoriesForRemovalRetry.IsEmpty()));
9443 
9444   aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
9445       "ClearRequestBase: Completed deleting files"_ns);
9446 }
9447 
DoDirectoryWork(QuotaManager & aQuotaManager)9448 nsresult ClearRequestBase::DoDirectoryWork(QuotaManager& aQuotaManager) {
9449   AssertIsOnIOThread();
9450   aQuotaManager.AssertStorageIsInitialized();
9451 
9452   AUTO_PROFILER_LABEL("ClearRequestBase::DoDirectoryWork", OTHER);
9453 
9454   if (mPersistenceType.IsNull()) {
9455     for (const PersistenceType type : kAllPersistenceTypes) {
9456       DeleteFiles(aQuotaManager, type);
9457     }
9458   } else {
9459     DeleteFiles(aQuotaManager, mPersistenceType.Value());
9460   }
9461 
9462   return NS_OK;
9463 }
9464 
ClearOriginOp(const RequestParams & aParams)9465 ClearOriginOp::ClearOriginOp(const RequestParams& aParams)
9466     : ClearRequestBase(/* aExclusive */ true),
9467       mParams(aParams.get_ClearOriginParams().commonParams()),
9468       mMatchAll(aParams.get_ClearOriginParams().matchAll()) {
9469   MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams);
9470 }
9471 
Init(Quota & aQuota)9472 void ClearOriginOp::Init(Quota& aQuota) {
9473   AssertIsOnOwningThread();
9474 
9475   QuotaRequestBase::Init(aQuota);
9476 
9477   if (mParams.persistenceTypeIsExplicit()) {
9478     mPersistenceType.SetValue(mParams.persistenceType());
9479   }
9480 
9481   // Figure out which origin we're dealing with.
9482   const auto origin = QuotaManager::GetOriginFromValidatedPrincipalInfo(
9483       mParams.principalInfo());
9484 
9485   if (mMatchAll) {
9486     mOriginScope.SetFromPrefix(origin);
9487   } else {
9488     mOriginScope.SetFromOrigin(origin);
9489   }
9490 
9491   if (mParams.clientTypeIsExplicit()) {
9492     mClientType.SetValue(mParams.clientType());
9493   }
9494 }
9495 
GetResponse(RequestResponse & aResponse)9496 void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
9497   AssertIsOnOwningThread();
9498 
9499   aResponse = ClearOriginResponse();
9500 }
9501 
ClearDataOp(const RequestParams & aParams)9502 ClearDataOp::ClearDataOp(const RequestParams& aParams)
9503     : ClearRequestBase(/* aExclusive */ true), mParams(aParams) {
9504   MOZ_ASSERT(aParams.type() == RequestParams::TClearDataParams);
9505 }
9506 
Init(Quota & aQuota)9507 void ClearDataOp::Init(Quota& aQuota) {
9508   AssertIsOnOwningThread();
9509 
9510   QuotaRequestBase::Init(aQuota);
9511 
9512   mOriginScope.SetFromPattern(mParams.pattern());
9513 }
9514 
GetResponse(RequestResponse & aResponse)9515 void ClearDataOp::GetResponse(RequestResponse& aResponse) {
9516   AssertIsOnOwningThread();
9517 
9518   aResponse = ClearDataResponse();
9519 }
9520 
ResetOriginOp(const RequestParams & aParams)9521 ResetOriginOp::ResetOriginOp(const RequestParams& aParams)
9522     : QuotaRequestBase(/* aExclusive */ true) {
9523   AssertIsOnOwningThread();
9524   MOZ_ASSERT(aParams.type() == RequestParams::TResetOriginParams);
9525 
9526   const ClearResetOriginParams& params =
9527       aParams.get_ResetOriginParams().commonParams();
9528 
9529   const auto origin =
9530       QuotaManager::GetOriginFromValidatedPrincipalInfo(params.principalInfo());
9531 
9532   // Overwrite OriginOperationBase default values.
9533   mNeedsQuotaManagerInit = true;
9534   mNeedsStorageInit = false;
9535 
9536   // Overwrite NormalOriginOperationBase default values.
9537   if (params.persistenceTypeIsExplicit()) {
9538     mPersistenceType.SetValue(params.persistenceType());
9539   }
9540 
9541   mOriginScope.SetFromOrigin(origin);
9542 
9543   if (params.clientTypeIsExplicit()) {
9544     mClientType.SetValue(params.clientType());
9545   }
9546 }
9547 
Init(Quota & aQuota)9548 void ResetOriginOp::Init(Quota& aQuota) { AssertIsOnOwningThread(); }
9549 
DoDirectoryWork(QuotaManager & aQuotaManager)9550 nsresult ResetOriginOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9551   AssertIsOnIOThread();
9552 
9553   AUTO_PROFILER_LABEL("ResetOriginOp::DoDirectoryWork", OTHER);
9554 
9555   // All the work is handled by NormalOriginOperationBase parent class. In this
9556   // particular case, we just needed to acquire an exclusive directory lock and
9557   // that's it.
9558 
9559   return NS_OK;
9560 }
9561 
GetResponse(RequestResponse & aResponse)9562 void ResetOriginOp::GetResponse(RequestResponse& aResponse) {
9563   AssertIsOnOwningThread();
9564 
9565   aResponse = ResetOriginResponse();
9566 }
9567 
PersistRequestBase(const PrincipalInfo & aPrincipalInfo)9568 PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
9569     : QuotaRequestBase(/* aExclusive */ false), mPrincipalInfo(aPrincipalInfo) {
9570   AssertIsOnOwningThread();
9571 }
9572 
Init(Quota & aQuota)9573 void PersistRequestBase::Init(Quota& aQuota) {
9574   AssertIsOnOwningThread();
9575 
9576   QuotaRequestBase::Init(aQuota);
9577 
9578   mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
9579 
9580   // Figure out which origin we're dealing with.
9581   PrincipalMetadata principalMetadata =
9582       QuotaManager::GetInfoFromValidatedPrincipalInfo(mPrincipalInfo);
9583 
9584   mSuffix = std::move(principalMetadata.mSuffix);
9585   mGroup = std::move(principalMetadata.mGroup);
9586   mOriginScope.SetFromOrigin(principalMetadata.mOrigin);
9587 }
9588 
PersistedOp(const RequestParams & aParams)9589 PersistedOp::PersistedOp(const RequestParams& aParams)
9590     : PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
9591       mPersisted(false) {
9592   MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
9593 }
9594 
DoDirectoryWork(QuotaManager & aQuotaManager)9595 nsresult PersistedOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9596   AssertIsOnIOThread();
9597   aQuotaManager.AssertStorageIsInitialized();
9598   MOZ_ASSERT(!mPersistenceType.IsNull());
9599   MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
9600   MOZ_ASSERT(mOriginScope.IsOrigin());
9601 
9602   AUTO_PROFILER_LABEL("PersistedOp::DoDirectoryWork", OTHER);
9603 
9604   Nullable<bool> persisted = aQuotaManager.OriginPersisted(
9605       OriginMetadata{mSuffix, mGroup, nsCString{mOriginScope.GetOrigin()},
9606                      mPersistenceType.Value()});
9607 
9608   if (!persisted.IsNull()) {
9609     mPersisted = persisted.Value();
9610     return NS_OK;
9611   }
9612 
9613   // If we get here, it means the origin hasn't been initialized yet.
9614   // Try to get the persisted flag from directory metadata on disk.
9615 
9616   QM_TRY_INSPECT(const auto& directory,
9617                  aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
9618                                                      mOriginScope.GetOrigin()));
9619 
9620   QM_TRY_INSPECT(const bool& exists,
9621                  MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists));
9622 
9623   if (exists) {
9624     // Get the metadata. We only use the persisted flag.
9625     QM_TRY_INSPECT(const auto& metadata,
9626                    aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
9627 
9628     mPersisted = metadata.mPersisted;
9629   } else {
9630     // The directory has not been created yet.
9631     mPersisted = false;
9632   }
9633 
9634   return NS_OK;
9635 }
9636 
GetResponse(RequestResponse & aResponse)9637 void PersistedOp::GetResponse(RequestResponse& aResponse) {
9638   AssertIsOnOwningThread();
9639 
9640   PersistedResponse persistedResponse;
9641   persistedResponse.persisted() = mPersisted;
9642 
9643   aResponse = persistedResponse;
9644 }
9645 
PersistOp(const RequestParams & aParams)9646 PersistOp::PersistOp(const RequestParams& aParams)
9647     : PersistRequestBase(aParams.get_PersistParams().principalInfo()) {
9648   MOZ_ASSERT(aParams.type() == RequestParams::TPersistParams);
9649 }
9650 
DoDirectoryWork(QuotaManager & aQuotaManager)9651 nsresult PersistOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9652   AssertIsOnIOThread();
9653   aQuotaManager.AssertStorageIsInitialized();
9654   MOZ_ASSERT(!mPersistenceType.IsNull());
9655   MOZ_ASSERT(mPersistenceType.Value() == PERSISTENCE_TYPE_DEFAULT);
9656   MOZ_ASSERT(mOriginScope.IsOrigin());
9657 
9658   const OriginMetadata originMetadata = {mSuffix, mGroup,
9659                                          nsCString{mOriginScope.GetOrigin()},
9660                                          mPersistenceType.Value()};
9661 
9662   AUTO_PROFILER_LABEL("PersistOp::DoDirectoryWork", OTHER);
9663 
9664   // Update directory metadata on disk first. Then, create/update the originInfo
9665   // if needed.
9666   QM_TRY_INSPECT(const auto& directory,
9667                  aQuotaManager.GetDirectoryForOrigin(mPersistenceType.Value(),
9668                                                      originMetadata.mOrigin));
9669 
9670   QM_TRY_INSPECT(const bool& created,
9671                  aQuotaManager.EnsureOriginDirectory(*directory));
9672 
9673   if (created) {
9674     int64_t timestamp;
9675 
9676     // Origin directory has been successfully created.
9677     // Create OriginInfo too if temporary storage was already initialized.
9678     if (aQuotaManager.IsTemporaryStorageInitialized()) {
9679       timestamp = aQuotaManager.NoteOriginDirectoryCreated(
9680           originMetadata, /* aPersisted */ true);
9681     } else {
9682       timestamp = PR_Now();
9683     }
9684 
9685     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(*directory, timestamp,
9686                                                   /* aPersisted */ true,
9687                                                   originMetadata)));
9688   } else {
9689     // Get the metadata (restore the metadata file if necessary). We only use
9690     // the persisted flag.
9691     QM_TRY_INSPECT(const auto& metadata,
9692                    aQuotaManager.LoadFullOriginMetadataWithRestore(directory));
9693 
9694     if (!metadata.mPersisted) {
9695       QM_TRY_INSPECT(const auto& file,
9696                      CloneFileAndAppend(
9697                          *directory, nsLiteralString(METADATA_V2_FILE_NAME)));
9698 
9699       QM_TRY_INSPECT(const auto& stream,
9700                      GetBinaryOutputStream(*file, FileFlag::Update));
9701 
9702       MOZ_ASSERT(stream);
9703 
9704       // Update origin access time while we are here.
9705       QM_TRY(MOZ_TO_RESULT(stream->Write64(PR_Now())));
9706 
9707       // Set the persisted flag to true.
9708       QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(true)));
9709     }
9710 
9711     // Directory metadata has been successfully updated.
9712     // Update OriginInfo too if temporary storage was already initialized.
9713     if (aQuotaManager.IsTemporaryStorageInitialized()) {
9714       aQuotaManager.PersistOrigin(originMetadata);
9715     }
9716   }
9717 
9718   return NS_OK;
9719 }
9720 
GetResponse(RequestResponse & aResponse)9721 void PersistOp::GetResponse(RequestResponse& aResponse) {
9722   AssertIsOnOwningThread();
9723 
9724   aResponse = PersistResponse();
9725 }
9726 
EstimateOp(const EstimateParams & aParams)9727 EstimateOp::EstimateOp(const EstimateParams& aParams)
9728     : QuotaRequestBase(/* aExclusive */ false),
9729       mOriginMetadata(QuotaManager::GetInfoFromValidatedPrincipalInfo(
9730                           aParams.principalInfo()),
9731                       PERSISTENCE_TYPE_DEFAULT) {
9732   AssertIsOnOwningThread();
9733 
9734   // Overwrite OriginOperationBase default values.
9735   mNeedsQuotaManagerInit = true;
9736   mNeedsStorageInit = true;
9737 }
9738 
CreateDirectoryLock()9739 RefPtr<DirectoryLock> EstimateOp::CreateDirectoryLock() { return nullptr; }
9740 
DoDirectoryWork(QuotaManager & aQuotaManager)9741 nsresult EstimateOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9742   AssertIsOnIOThread();
9743   aQuotaManager.AssertStorageIsInitialized();
9744 
9745   AUTO_PROFILER_LABEL("EstimateOp::DoDirectoryWork", OTHER);
9746 
9747   // Ensure temporary storage is initialized. If temporary storage hasn't been
9748   // initialized yet, the method will initialize it by traversing the
9749   // repositories for temporary and default storage (including origins belonging
9750   // to our group).
9751   QM_TRY(MOZ_TO_RESULT(aQuotaManager.EnsureTemporaryStorageIsInitialized()));
9752 
9753   // Get cached usage (the method doesn't have to stat any files).
9754   mUsageAndLimit = aQuotaManager.GetUsageAndLimitForEstimate(mOriginMetadata);
9755 
9756   return NS_OK;
9757 }
9758 
GetResponse(RequestResponse & aResponse)9759 void EstimateOp::GetResponse(RequestResponse& aResponse) {
9760   AssertIsOnOwningThread();
9761 
9762   EstimateResponse estimateResponse;
9763 
9764   estimateResponse.usage() = mUsageAndLimit.first;
9765   estimateResponse.limit() = mUsageAndLimit.second;
9766 
9767   aResponse = estimateResponse;
9768 }
9769 
ListOriginsOp()9770 ListOriginsOp::ListOriginsOp()
9771     : QuotaRequestBase(/* aExclusive */ false), TraverseRepositoryHelper() {
9772   AssertIsOnOwningThread();
9773 }
9774 
Init(Quota & aQuota)9775 void ListOriginsOp::Init(Quota& aQuota) {
9776   AssertIsOnOwningThread();
9777 
9778   mNeedsQuotaManagerInit = true;
9779   mNeedsStorageInit = true;
9780 }
9781 
DoDirectoryWork(QuotaManager & aQuotaManager)9782 nsresult ListOriginsOp::DoDirectoryWork(QuotaManager& aQuotaManager) {
9783   AssertIsOnIOThread();
9784   aQuotaManager.AssertStorageIsInitialized();
9785 
9786   AUTO_PROFILER_LABEL("ListOriginsOp::DoDirectoryWork", OTHER);
9787 
9788   for (const PersistenceType type : kAllPersistenceTypes) {
9789     QM_TRY(MOZ_TO_RESULT(TraverseRepository(aQuotaManager, type)));
9790   }
9791 
9792   // TraverseRepository above only consulted the file-system to get a list of
9793   // known origins, but we also need to include origins that have pending quota
9794   // usage.
9795 
9796   aQuotaManager.CollectPendingOriginsForListing([this](const auto& originInfo) {
9797     mOrigins.AppendElement(originInfo->Origin());
9798   });
9799 
9800   return NS_OK;
9801 }
9802 
GetIsCanceledFlag()9803 const Atomic<bool>& ListOriginsOp::GetIsCanceledFlag() {
9804   AssertIsOnIOThread();
9805 
9806   return mCanceled;
9807 }
9808 
ProcessOrigin(QuotaManager & aQuotaManager,nsIFile & aOriginDir,const bool aPersistent,const PersistenceType aPersistenceType)9809 nsresult ListOriginsOp::ProcessOrigin(QuotaManager& aQuotaManager,
9810                                       nsIFile& aOriginDir,
9811                                       const bool aPersistent,
9812                                       const PersistenceType aPersistenceType) {
9813   AssertIsOnIOThread();
9814 
9815   // XXX We only use metadata.mOriginMetadata.mOrigin...
9816   QM_TRY_UNWRAP(auto metadata,
9817                 aQuotaManager.LoadFullOriginMetadataWithRestore(&aOriginDir));
9818 
9819   if (aQuotaManager.IsOriginInternal(metadata.mOrigin)) {
9820     return NS_OK;
9821   }
9822 
9823   mOrigins.AppendElement(std::move(metadata.mOrigin));
9824 
9825   return NS_OK;
9826 }
9827 
GetResponse(RequestResponse & aResponse)9828 void ListOriginsOp::GetResponse(RequestResponse& aResponse) {
9829   AssertIsOnOwningThread();
9830 
9831   aResponse = ListOriginsResponse();
9832   if (mOrigins.IsEmpty()) {
9833     return;
9834   }
9835 
9836   nsTArray<nsCString>& origins = aResponse.get_ListOriginsResponse().origins();
9837   mOrigins.SwapElements(origins);
9838 }
9839 
9840 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
9841 
9842 // static
CreateAndDispatch(nsTArray<PrincipalInfo> && aPrincipalInfos)9843 already_AddRefed<PrincipalVerifier> PrincipalVerifier::CreateAndDispatch(
9844     nsTArray<PrincipalInfo>&& aPrincipalInfos) {
9845   AssertIsOnIOThread();
9846 
9847   RefPtr<PrincipalVerifier> verifier =
9848       new PrincipalVerifier(std::move(aPrincipalInfos));
9849 
9850   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(verifier));
9851 
9852   return verifier.forget();
9853 }
9854 
CheckPrincipalInfoValidity(const PrincipalInfo & aPrincipalInfo)9855 Result<Ok, nsCString> PrincipalVerifier::CheckPrincipalInfoValidity(
9856     const PrincipalInfo& aPrincipalInfo) {
9857   MOZ_ASSERT(NS_IsMainThread());
9858 
9859   switch (aPrincipalInfo.type()) {
9860     // A system principal is acceptable.
9861     case PrincipalInfo::TSystemPrincipalInfo: {
9862       return Ok{};
9863     }
9864 
9865     case PrincipalInfo::TContentPrincipalInfo: {
9866       const ContentPrincipalInfo& info =
9867           aPrincipalInfo.get_ContentPrincipalInfo();
9868 
9869       nsCOMPtr<nsIURI> uri;
9870       nsresult rv = NS_NewURI(getter_AddRefs(uri), info.spec());
9871       if (NS_WARN_IF(NS_FAILED(rv))) {
9872         return Err("NS_NewURI failed"_ns);
9873       }
9874 
9875       nsCOMPtr<nsIPrincipal> principal =
9876           BasePrincipal::CreateContentPrincipal(uri, info.attrs());
9877       if (NS_WARN_IF(!principal)) {
9878         return Err("CreateContentPrincipal failed"_ns);
9879       }
9880 
9881       nsCString originNoSuffix;
9882       rv = principal->GetOriginNoSuffix(originNoSuffix);
9883       if (NS_WARN_IF(NS_FAILED(rv))) {
9884         return Err("GetOriginNoSuffix failed"_ns);
9885       }
9886 
9887       if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
9888         static const char messageTemplate[] =
9889             "originNoSuffix (%s) doesn't match passed one (%s)!";
9890 
9891         QM_WARNING(messageTemplate, originNoSuffix.get(),
9892                    info.originNoSuffix().get());
9893 
9894         return Err(nsPrintfCString(
9895             messageTemplate, AnonymizedOriginString(originNoSuffix).get(),
9896             AnonymizedOriginString(info.originNoSuffix()).get()));
9897       }
9898 
9899       nsCString baseDomain;
9900       rv = principal->GetBaseDomain(baseDomain);
9901       if (NS_WARN_IF(NS_FAILED(rv))) {
9902         return Err("GetBaseDomain failed"_ns);
9903       }
9904 
9905       if (NS_WARN_IF(baseDomain != info.baseDomain())) {
9906         static const char messageTemplate[] =
9907             "baseDomain (%s) doesn't match passed one (%s)!";
9908 
9909         QM_WARNING(messageTemplate, baseDomain.get(), info.baseDomain().get());
9910 
9911         return Err(nsPrintfCString(messageTemplate,
9912                                    AnonymizedCString(baseDomain).get(),
9913                                    AnonymizedCString(info.baseDomain()).get()));
9914       }
9915 
9916       return Ok{};
9917     }
9918 
9919     default: {
9920       break;
9921     }
9922   }
9923 
9924   return Err("Null and expanded principals are not acceptable"_ns);
9925 }
9926 
9927 NS_IMETHODIMP
Run()9928 PrincipalVerifier::Run() {
9929   MOZ_ASSERT(NS_IsMainThread());
9930 
9931   nsAutoCString allDetails;
9932   for (auto& principalInfo : mPrincipalInfos) {
9933     const auto res = CheckPrincipalInfoValidity(principalInfo);
9934     if (res.isErr()) {
9935       if (!allDetails.IsEmpty()) {
9936         allDetails.AppendLiteral(", ");
9937       }
9938 
9939       allDetails.Append(res.inspectErr());
9940     }
9941   }
9942 
9943   if (!allDetails.IsEmpty()) {
9944     allDetails.Insert("Invalid principal infos found: ", 0);
9945 
9946     // In case of invalid principal infos, this will produce a crash reason such
9947     // as:
9948     //   Invalid principal infos found: originNoSuffix (https://aaa.aaaaaaa.aaa)
9949     //   doesn't match passed one (about:aaaa)!
9950     //
9951     // In case of errors while validating a principal, it will contain a
9952     // different message describing that error, which does not contain any
9953     // details of the actual principal info at the moment.
9954     //
9955     // This string will be leaked.
9956     MOZ_CRASH_UNSAFE(strdup(allDetails.BeginReading()));
9957   }
9958 
9959   return NS_OK;
9960 }
9961 
9962 #endif
9963 
GetDirectoryMetadata(nsIFile * aDirectory,int64_t & aTimestamp,nsACString & aGroup,nsACString & aOrigin,Nullable<bool> & aIsApp)9964 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
9965                                                     int64_t& aTimestamp,
9966                                                     nsACString& aGroup,
9967                                                     nsACString& aOrigin,
9968                                                     Nullable<bool>& aIsApp) {
9969   AssertIsOnIOThread();
9970   MOZ_ASSERT(aDirectory);
9971 
9972   QM_TRY_INSPECT(
9973       const auto& binaryStream,
9974       GetBinaryInputStream(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
9975 
9976   QM_TRY_INSPECT(const uint64_t& timestamp,
9977                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
9978 
9979   QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
9980                                         nsCString, binaryStream, ReadCString));
9981 
9982   QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
9983                                          nsCString, binaryStream, ReadCString));
9984 
9985   Nullable<bool> isApp;
9986   bool value;
9987   if (NS_SUCCEEDED(binaryStream->ReadBoolean(&value))) {
9988     isApp.SetValue(value);
9989   }
9990 
9991   aTimestamp = timestamp;
9992   aGroup = group;
9993   aOrigin = origin;
9994   aIsApp = std::move(isApp);
9995   return NS_OK;
9996 }
9997 
GetDirectoryMetadata2(nsIFile * aDirectory,int64_t & aTimestamp,nsACString & aSuffix,nsACString & aGroup,nsACString & aOrigin,bool & aIsApp)9998 nsresult StorageOperationBase::GetDirectoryMetadata2(
9999     nsIFile* aDirectory, int64_t& aTimestamp, nsACString& aSuffix,
10000     nsACString& aGroup, nsACString& aOrigin, bool& aIsApp) {
10001   AssertIsOnIOThread();
10002   MOZ_ASSERT(aDirectory);
10003 
10004   QM_TRY_INSPECT(const auto& binaryStream,
10005                  GetBinaryInputStream(*aDirectory,
10006                                       nsLiteralString(METADATA_V2_FILE_NAME)));
10007 
10008   QM_TRY_INSPECT(const uint64_t& timestamp,
10009                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read64));
10010 
10011   QM_TRY_INSPECT(const bool& persisted,
10012                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
10013   Unused << persisted;
10014 
10015   QM_TRY_INSPECT(const bool& reservedData1,
10016                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
10017   Unused << reservedData1;
10018 
10019   QM_TRY_INSPECT(const bool& reservedData2,
10020                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, Read32));
10021   Unused << reservedData2;
10022 
10023   QM_TRY_INSPECT(const auto& suffix, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
10024                                          nsCString, binaryStream, ReadCString));
10025 
10026   QM_TRY_INSPECT(const auto& group, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
10027                                         nsCString, binaryStream, ReadCString));
10028 
10029   QM_TRY_INSPECT(const auto& origin, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
10030                                          nsCString, binaryStream, ReadCString));
10031 
10032   QM_TRY_INSPECT(const bool& isApp,
10033                  MOZ_TO_RESULT_INVOKE_MEMBER(binaryStream, ReadBoolean));
10034 
10035   aTimestamp = timestamp;
10036   aSuffix = suffix;
10037   aGroup = group;
10038   aOrigin = origin;
10039   aIsApp = isApp;
10040   return NS_OK;
10041 }
10042 
GetOriginLastModifiedTime(const OriginProps & aOriginProps)10043 int64_t StorageOperationBase::GetOriginLastModifiedTime(
10044     const OriginProps& aOriginProps) {
10045   return GetLastModifiedTime(*aOriginProps.mPersistenceType,
10046                              *aOriginProps.mDirectory);
10047 }
10048 
RemoveObsoleteOrigin(const OriginProps & aOriginProps)10049 nsresult StorageOperationBase::RemoveObsoleteOrigin(
10050     const OriginProps& aOriginProps) {
10051   AssertIsOnIOThread();
10052 
10053   QM_WARNING(
10054       "Deleting obsolete %s directory that is no longer a legal "
10055       "origin!",
10056       NS_ConvertUTF16toUTF8(aOriginProps.mLeafName).get());
10057 
10058   QM_TRY(MOZ_TO_RESULT(aOriginProps.mDirectory->Remove(/* recursive */ true)));
10059 
10060   return NS_OK;
10061 }
10062 
MaybeRenameOrigin(const OriginProps & aOriginProps)10063 Result<bool, nsresult> StorageOperationBase::MaybeRenameOrigin(
10064     const OriginProps& aOriginProps) {
10065   AssertIsOnIOThread();
10066 
10067   const nsAString& oldLeafName = aOriginProps.mLeafName;
10068 
10069   const auto newLeafName =
10070       MakeSanitizedOriginString(aOriginProps.mOriginMetadata.mOrigin);
10071 
10072   if (oldLeafName == newLeafName) {
10073     return false;
10074   }
10075 
10076   QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(*aOriginProps.mDirectory,
10077                                                aOriginProps.mTimestamp,
10078                                                aOriginProps.mOriginMetadata)));
10079 
10080   QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
10081       *aOriginProps.mDirectory, aOriginProps.mTimestamp,
10082       /* aPersisted */ false, aOriginProps.mOriginMetadata)));
10083 
10084   QM_TRY_INSPECT(const auto& newFile,
10085                  MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
10086                      nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, GetParent));
10087 
10088   QM_TRY(MOZ_TO_RESULT(newFile->Append(newLeafName)));
10089 
10090   QM_TRY_INSPECT(const bool& exists,
10091                  MOZ_TO_RESULT_INVOKE_MEMBER(newFile, Exists));
10092 
10093   if (exists) {
10094     QM_WARNING(
10095         "Can't rename %s directory to %s, the target already exists, removing "
10096         "instead of renaming!",
10097         NS_ConvertUTF16toUTF8(oldLeafName).get(),
10098         NS_ConvertUTF16toUTF8(newLeafName).get());
10099   }
10100 
10101   QM_TRY(CallWithDelayedRetriesIfAccessDenied(
10102       [&exists, &aOriginProps, &newLeafName] {
10103         if (exists) {
10104           QM_TRY_RETURN(MOZ_TO_RESULT(
10105               aOriginProps.mDirectory->Remove(/* recursive */ true)));
10106         }
10107         QM_TRY_RETURN(MOZ_TO_RESULT(
10108             aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
10109       },
10110       StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
10111       StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
10112 
10113   return true;
10114 }
10115 
ProcessOriginDirectories()10116 nsresult StorageOperationBase::ProcessOriginDirectories() {
10117   AssertIsOnIOThread();
10118   MOZ_ASSERT(!mOriginProps.IsEmpty());
10119 
10120 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
10121   nsTArray<PrincipalInfo> principalInfos;
10122 #endif
10123 
10124   for (auto& originProps : mOriginProps) {
10125     switch (originProps.mType) {
10126       case OriginProps::eChrome: {
10127         originProps.mOriginMetadata = {QuotaManager::GetInfoForChrome(),
10128                                        *originProps.mPersistenceType};
10129         break;
10130       }
10131 
10132       case OriginProps::eContent: {
10133         RefPtr<MozURL> specURL;
10134         nsresult rv = MozURL::Init(getter_AddRefs(specURL), originProps.mSpec);
10135         if (NS_WARN_IF(NS_FAILED(rv))) {
10136           // If a URL cannot be understood by MozURL during restoring or
10137           // upgrading, either marking the directory as broken or removing that
10138           // corresponding directory should be considered. While the cost of
10139           // marking the directory as broken during a upgrade is too high,
10140           // removing the directory is a better choice rather than blocking the
10141           // initialization or the upgrade.
10142           QM_WARNING(
10143               "A URL (%s) for the origin directory is not recognized by "
10144               "MozURL. The directory will be deleted for now to pass the "
10145               "initialization or the upgrade.",
10146               originProps.mSpec.get());
10147 
10148           originProps.mType = OriginProps::eObsolete;
10149           break;
10150         }
10151 
10152         nsCString originNoSuffix;
10153         specURL->Origin(originNoSuffix);
10154 
10155         QM_TRY_INSPECT(
10156             const auto& baseDomain,
10157             MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, specURL, BaseDomain));
10158 
10159         ContentPrincipalInfo contentPrincipalInfo;
10160         contentPrincipalInfo.attrs() = originProps.mAttrs;
10161         contentPrincipalInfo.originNoSuffix() = originNoSuffix;
10162         contentPrincipalInfo.spec() = originProps.mSpec;
10163         contentPrincipalInfo.baseDomain() = baseDomain;
10164 
10165         PrincipalInfo principalInfo(contentPrincipalInfo);
10166 
10167         originProps.mOriginMetadata = {
10168             QuotaManager::GetInfoFromValidatedPrincipalInfo(principalInfo),
10169             *originProps.mPersistenceType};
10170 
10171 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
10172         principalInfos.AppendElement(principalInfo);
10173 #endif
10174 
10175         break;
10176       }
10177 
10178       case OriginProps::eObsolete: {
10179         // There's no way to get info for obsolete origins.
10180         break;
10181       }
10182 
10183       default:
10184         MOZ_CRASH("Bad type!");
10185     }
10186   }
10187 
10188 #ifdef QM_PRINCIPALINFO_VERIFICATION_ENABLED
10189   if (!principalInfos.IsEmpty()) {
10190     RefPtr<PrincipalVerifier> principalVerifier =
10191         PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
10192   }
10193 #endif
10194 
10195   // Don't try to upgrade obsolete origins, remove them right after we detect
10196   // them.
10197   for (const auto& originProps : mOriginProps) {
10198     if (originProps.mType == OriginProps::eObsolete) {
10199       MOZ_ASSERT(originProps.mOriginMetadata.mSuffix.IsEmpty());
10200       MOZ_ASSERT(originProps.mOriginMetadata.mGroup.IsEmpty());
10201       MOZ_ASSERT(originProps.mOriginMetadata.mOrigin.IsEmpty());
10202 
10203       QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(originProps)));
10204     } else {
10205       MOZ_ASSERT(!originProps.mOriginMetadata.mGroup.IsEmpty());
10206       MOZ_ASSERT(!originProps.mOriginMetadata.mOrigin.IsEmpty());
10207 
10208       QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectory(originProps)));
10209     }
10210   }
10211 
10212   return NS_OK;
10213 }
10214 
10215 // XXX Do the fallible initialization in a separate non-static member function
10216 // of StorageOperationBase and eventually get rid of this method and use a
10217 // normal constructor instead.
10218 template <typename PersistenceTypeFunc>
Init(PersistenceTypeFunc && aPersistenceTypeFunc)10219 nsresult StorageOperationBase::OriginProps::Init(
10220     PersistenceTypeFunc&& aPersistenceTypeFunc) {
10221   AssertIsOnIOThread();
10222 
10223   QM_TRY_INSPECT(const auto& leafName,
10224                  MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, *mDirectory,
10225                                                    GetLeafName));
10226 
10227   nsCString spec;
10228   OriginAttributes attrs;
10229   nsCString originalSuffix;
10230   OriginParser::ResultType result = OriginParser::ParseOrigin(
10231       NS_ConvertUTF16toUTF8(leafName), spec, &attrs, originalSuffix);
10232   if (NS_WARN_IF(result == OriginParser::InvalidOrigin)) {
10233     mType = OriginProps::eInvalid;
10234     return NS_OK;
10235   }
10236 
10237   const auto persistenceType = [&]() -> PersistenceType {
10238     // XXX We shouldn't continue with initialization if OriginParser returned
10239     // anything else but ValidOrigin. Otherwise, we have to deal with empty
10240     // spec when the origin is obsolete, like here. The caller should handle
10241     // the errors. Until it's fixed, we have to treat obsolete origins as
10242     // origins with unknown/invalid persistence type.
10243     if (result != OriginParser::ValidOrigin) {
10244       return PERSISTENCE_TYPE_INVALID;
10245     }
10246     return std::forward<PersistenceTypeFunc>(aPersistenceTypeFunc)(spec);
10247   }();
10248 
10249   mLeafName = leafName;
10250   mSpec = spec;
10251   mAttrs = attrs;
10252   mOriginalSuffix = originalSuffix;
10253   mPersistenceType.init(persistenceType);
10254   if (result == OriginParser::ObsoleteOrigin) {
10255     mType = eObsolete;
10256   } else if (mSpec.EqualsLiteral(kChromeOrigin)) {
10257     mType = eChrome;
10258   } else {
10259     mType = eContent;
10260   }
10261 
10262   return NS_OK;
10263 }
10264 
10265 // static
ParseOrigin(const nsACString & aOrigin,nsCString & aSpec,OriginAttributes * aAttrs,nsCString & aOriginalSuffix)10266 auto OriginParser::ParseOrigin(const nsACString& aOrigin, nsCString& aSpec,
10267                                OriginAttributes* aAttrs,
10268                                nsCString& aOriginalSuffix) -> ResultType {
10269   MOZ_ASSERT(!aOrigin.IsEmpty());
10270   MOZ_ASSERT(aAttrs);
10271 
10272   nsCString origin(aOrigin);
10273   int32_t pos = origin.RFindChar('^');
10274 
10275   if (pos == kNotFound) {
10276     aOriginalSuffix.Truncate();
10277   } else {
10278     aOriginalSuffix = Substring(origin, pos);
10279   }
10280 
10281   OriginAttributes originAttributes;
10282 
10283   nsCString originNoSuffix;
10284   bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
10285   if (!ok) {
10286     return InvalidOrigin;
10287   }
10288 
10289   OriginParser parser(originNoSuffix);
10290 
10291   *aAttrs = originAttributes;
10292   return parser.Parse(aSpec);
10293 }
10294 
Parse(nsACString & aSpec)10295 auto OriginParser::Parse(nsACString& aSpec) -> ResultType {
10296   while (mTokenizer.hasMoreTokens()) {
10297     const nsDependentCSubstring& token = mTokenizer.nextToken();
10298 
10299     HandleToken(token);
10300 
10301     if (mError) {
10302       break;
10303     }
10304 
10305     if (!mHandledTokens.IsEmpty()) {
10306       mHandledTokens.AppendLiteral(", ");
10307     }
10308     mHandledTokens.Append('\'');
10309     mHandledTokens.Append(token);
10310     mHandledTokens.Append('\'');
10311   }
10312 
10313   if (!mError && mTokenizer.separatorAfterCurrentToken()) {
10314     HandleTrailingSeparator();
10315   }
10316 
10317   if (mError) {
10318     QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(),
10319                mHandledTokens.get());
10320 
10321     return (mSchemeType == eChrome || mSchemeType == eAbout) ? ObsoleteOrigin
10322                                                              : InvalidOrigin;
10323   }
10324 
10325   MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
10326 
10327   // For IPv6 URL, it should at least have three groups.
10328   MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
10329 
10330   nsAutoCString spec(mScheme);
10331 
10332   if (mSchemeType == eFile) {
10333     spec.AppendLiteral("://");
10334 
10335     if (mUniversalFileOrigin) {
10336       MOZ_ASSERT(mPathnameComponents.Length() == 1);
10337 
10338       spec.Append(mPathnameComponents[0]);
10339     } else {
10340       for (uint32_t count = mPathnameComponents.Length(), index = 0;
10341            index < count; index++) {
10342         spec.Append('/');
10343         spec.Append(mPathnameComponents[index]);
10344       }
10345     }
10346 
10347     aSpec = spec;
10348 
10349     return ValidOrigin;
10350   }
10351 
10352   if (mSchemeType == eAbout) {
10353     if (mMaybeObsolete) {
10354       // The "moz-safe-about+++home" was acciedntally created by a buggy nightly
10355       // and can be safely removed.
10356       return mHost.EqualsLiteral("home") ? ObsoleteOrigin : InvalidOrigin;
10357     }
10358     spec.Append(':');
10359   } else if (mSchemeType != eChrome) {
10360     spec.AppendLiteral("://");
10361   }
10362 
10363   spec.Append(mHost);
10364 
10365   if (!mPort.IsNull()) {
10366     spec.Append(':');
10367     spec.AppendInt(mPort.Value());
10368   }
10369 
10370   aSpec = spec;
10371 
10372   return mScheme.EqualsLiteral("app") ? ObsoleteOrigin : ValidOrigin;
10373 }
10374 
HandleScheme(const nsDependentCSubstring & aToken)10375 void OriginParser::HandleScheme(const nsDependentCSubstring& aToken) {
10376   MOZ_ASSERT(!aToken.IsEmpty());
10377   MOZ_ASSERT(mState == eExpectingAppIdOrScheme || mState == eExpectingScheme);
10378 
10379   bool isAbout = false;
10380   bool isMozSafeAbout = false;
10381   bool isFile = false;
10382   bool isChrome = false;
10383   if (aToken.EqualsLiteral("http") || aToken.EqualsLiteral("https") ||
10384       (isAbout = aToken.EqualsLiteral("about") ||
10385                  (isMozSafeAbout = aToken.EqualsLiteral("moz-safe-about"))) ||
10386       aToken.EqualsLiteral("indexeddb") ||
10387       (isFile = aToken.EqualsLiteral("file")) || aToken.EqualsLiteral("app") ||
10388       aToken.EqualsLiteral("resource") ||
10389       aToken.EqualsLiteral("moz-extension") ||
10390       (isChrome = aToken.EqualsLiteral(kChromeOrigin))) {
10391     mScheme = aToken;
10392 
10393     if (isAbout) {
10394       mSchemeType = eAbout;
10395       mState = isMozSafeAbout ? eExpectingEmptyToken1OrHost : eExpectingHost;
10396     } else if (isChrome) {
10397       mSchemeType = eChrome;
10398       if (mTokenizer.hasMoreTokens()) {
10399         mError = true;
10400       }
10401       mState = eComplete;
10402     } else {
10403       if (isFile) {
10404         mSchemeType = eFile;
10405       }
10406       mState = eExpectingEmptyToken1;
10407     }
10408 
10409     return;
10410   }
10411 
10412   QM_WARNING("'%s' is not a valid scheme!", nsCString(aToken).get());
10413 
10414   mError = true;
10415 }
10416 
HandlePathnameComponent(const nsDependentCSubstring & aToken)10417 void OriginParser::HandlePathnameComponent(
10418     const nsDependentCSubstring& aToken) {
10419   MOZ_ASSERT(!aToken.IsEmpty());
10420   MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent ||
10421              mState == eExpectingEmptyTokenOrPathnameComponent);
10422   MOZ_ASSERT(mSchemeType == eFile);
10423 
10424   mPathnameComponents.AppendElement(aToken);
10425 
10426   mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent
10427                                       : eComplete;
10428 }
10429 
HandleToken(const nsDependentCSubstring & aToken)10430 void OriginParser::HandleToken(const nsDependentCSubstring& aToken) {
10431   switch (mState) {
10432     case eExpectingAppIdOrScheme: {
10433       if (aToken.IsEmpty()) {
10434         QM_WARNING("Expected an app id or scheme (not an empty string)!");
10435 
10436         mError = true;
10437         return;
10438       }
10439 
10440       if (IsAsciiDigit(aToken.First())) {
10441         // nsDependentCSubstring doesn't provice ToInteger()
10442         nsCString token(aToken);
10443 
10444         nsresult rv;
10445         Unused << token.ToInteger(&rv);
10446         if (NS_SUCCEEDED(rv)) {
10447           mState = eExpectingInMozBrowser;
10448           return;
10449         }
10450       }
10451 
10452       HandleScheme(aToken);
10453 
10454       return;
10455     }
10456 
10457     case eExpectingInMozBrowser: {
10458       if (aToken.Length() != 1) {
10459         QM_WARNING("'%zu' is not a valid length for the inMozBrowser flag!",
10460                    aToken.Length());
10461 
10462         mError = true;
10463         return;
10464       }
10465 
10466       if (aToken.First() == 't') {
10467         mInIsolatedMozBrowser = true;
10468       } else if (aToken.First() == 'f') {
10469         mInIsolatedMozBrowser = false;
10470       } else {
10471         QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!",
10472                    nsCString(aToken).get());
10473 
10474         mError = true;
10475         return;
10476       }
10477 
10478       mState = eExpectingScheme;
10479 
10480       return;
10481     }
10482 
10483     case eExpectingScheme: {
10484       if (aToken.IsEmpty()) {
10485         QM_WARNING("Expected a scheme (not an empty string)!");
10486 
10487         mError = true;
10488         return;
10489       }
10490 
10491       HandleScheme(aToken);
10492 
10493       return;
10494     }
10495 
10496     case eExpectingEmptyToken1: {
10497       if (!aToken.IsEmpty()) {
10498         QM_WARNING("Expected the first empty token!");
10499 
10500         mError = true;
10501         return;
10502       }
10503 
10504       mState = eExpectingEmptyToken2;
10505 
10506       return;
10507     }
10508 
10509     case eExpectingEmptyToken2: {
10510       if (!aToken.IsEmpty()) {
10511         QM_WARNING("Expected the second empty token!");
10512 
10513         mError = true;
10514         return;
10515       }
10516 
10517       if (mSchemeType == eFile) {
10518         mState = eExpectingEmptyTokenOrUniversalFileOrigin;
10519       } else {
10520         if (mSchemeType == eAbout) {
10521           mMaybeObsolete = true;
10522         }
10523         mState = eExpectingHost;
10524       }
10525 
10526       return;
10527     }
10528 
10529     case eExpectingEmptyTokenOrUniversalFileOrigin: {
10530       MOZ_ASSERT(mSchemeType == eFile);
10531 
10532       if (aToken.IsEmpty()) {
10533         mState = mTokenizer.hasMoreTokens()
10534                      ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent
10535                      : eComplete;
10536 
10537         return;
10538       }
10539 
10540       if (aToken.EqualsLiteral("UNIVERSAL_FILE_URI_ORIGIN")) {
10541         mUniversalFileOrigin = true;
10542 
10543         mPathnameComponents.AppendElement(aToken);
10544 
10545         mState = eComplete;
10546 
10547         return;
10548       }
10549 
10550       QM_WARNING(
10551           "Expected the third empty token or "
10552           "UNIVERSAL_FILE_URI_ORIGIN!");
10553 
10554       mError = true;
10555       return;
10556     }
10557 
10558     case eExpectingHost: {
10559       if (aToken.IsEmpty()) {
10560         QM_WARNING("Expected a host (not an empty string)!");
10561 
10562         mError = true;
10563         return;
10564       }
10565 
10566       mHost = aToken;
10567 
10568       if (aToken.First() == '[') {
10569         MOZ_ASSERT(mIPGroup == 0);
10570 
10571         ++mIPGroup;
10572         mState = eExpectingIPV6Token;
10573 
10574         MOZ_ASSERT(mTokenizer.hasMoreTokens());
10575         return;
10576       }
10577 
10578       if (mTokenizer.hasMoreTokens()) {
10579         if (mSchemeType == eAbout) {
10580           QM_WARNING("Expected an empty string after host!");
10581 
10582           mError = true;
10583           return;
10584         }
10585 
10586         mState = eExpectingPort;
10587 
10588         return;
10589       }
10590 
10591       mState = eComplete;
10592 
10593       return;
10594     }
10595 
10596     case eExpectingPort: {
10597       MOZ_ASSERT(mSchemeType == eNone);
10598 
10599       if (aToken.IsEmpty()) {
10600         QM_WARNING("Expected a port (not an empty string)!");
10601 
10602         mError = true;
10603         return;
10604       }
10605 
10606       // nsDependentCSubstring doesn't provice ToInteger()
10607       nsCString token(aToken);
10608 
10609       nsresult rv;
10610       uint32_t port = token.ToInteger(&rv);
10611       if (NS_SUCCEEDED(rv)) {
10612         mPort.SetValue() = port;
10613       } else {
10614         QM_WARNING("'%s' is not a valid port number!", token.get());
10615 
10616         mError = true;
10617         return;
10618       }
10619 
10620       mState = eComplete;
10621 
10622       return;
10623     }
10624 
10625     case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: {
10626       MOZ_ASSERT(mSchemeType == eFile);
10627 
10628       if (aToken.IsEmpty()) {
10629         mPathnameComponents.AppendElement(""_ns);
10630 
10631         mState = mTokenizer.hasMoreTokens()
10632                      ? eExpectingEmptyTokenOrPathnameComponent
10633                      : eComplete;
10634 
10635         return;
10636       }
10637 
10638       if (aToken.Length() == 1 && IsAsciiAlpha(aToken.First())) {
10639         mMaybeDriveLetter = true;
10640 
10641         mPathnameComponents.AppendElement(aToken);
10642 
10643         mState = mTokenizer.hasMoreTokens()
10644                      ? eExpectingEmptyTokenOrPathnameComponent
10645                      : eComplete;
10646 
10647         return;
10648       }
10649 
10650       HandlePathnameComponent(aToken);
10651 
10652       return;
10653     }
10654 
10655     case eExpectingEmptyTokenOrPathnameComponent: {
10656       MOZ_ASSERT(mSchemeType == eFile);
10657 
10658       if (aToken.IsEmpty()) {
10659         if (mMaybeDriveLetter) {
10660           MOZ_ASSERT(mPathnameComponents.Length() == 1);
10661 
10662           nsCString& pathnameComponent = mPathnameComponents[0];
10663           pathnameComponent.Append(':');
10664 
10665           mMaybeDriveLetter = false;
10666         } else {
10667           mPathnameComponents.AppendElement(""_ns);
10668         }
10669 
10670         mState = mTokenizer.hasMoreTokens()
10671                      ? eExpectingEmptyTokenOrPathnameComponent
10672                      : eComplete;
10673 
10674         return;
10675       }
10676 
10677       HandlePathnameComponent(aToken);
10678 
10679       return;
10680     }
10681 
10682     case eExpectingEmptyToken1OrHost: {
10683       MOZ_ASSERT(mSchemeType == eAbout &&
10684                  mScheme.EqualsLiteral("moz-safe-about"));
10685 
10686       if (aToken.IsEmpty()) {
10687         mState = eExpectingEmptyToken2;
10688       } else {
10689         mHost = aToken;
10690         mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
10691       }
10692 
10693       return;
10694     }
10695 
10696     case eExpectingIPV6Token: {
10697       // A safe check for preventing infinity recursion.
10698       if (++mIPGroup > 8) {
10699         mError = true;
10700         return;
10701       }
10702 
10703       mHost.AppendLiteral(":");
10704       mHost.Append(aToken);
10705       if (!aToken.IsEmpty() && aToken.Last() == ']') {
10706         mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete;
10707       }
10708 
10709       return;
10710     }
10711 
10712     default:
10713       MOZ_CRASH("Should never get here!");
10714   }
10715 }
10716 
HandleTrailingSeparator()10717 void OriginParser::HandleTrailingSeparator() {
10718   MOZ_ASSERT(mState == eComplete);
10719   MOZ_ASSERT(mSchemeType == eFile);
10720 
10721   mPathnameComponents.AppendElement(""_ns);
10722 
10723   mState = eHandledTrailingSeparator;
10724 }
10725 
ProcessRepository()10726 nsresult RepositoryOperationBase::ProcessRepository() {
10727   AssertIsOnIOThread();
10728 
10729 #ifdef DEBUG
10730   {
10731     QM_TRY_INSPECT(const bool& exists,
10732                    MOZ_TO_RESULT_INVOKE_MEMBER(mDirectory, Exists),
10733                    QM_ASSERT_UNREACHABLE);
10734     MOZ_ASSERT(exists);
10735   }
10736 #endif
10737 
10738   QM_TRY(CollectEachFileEntry(
10739       *mDirectory,
10740       [](const auto& originFile) -> Result<mozilla::Ok, nsresult> {
10741         QM_TRY_INSPECT(const auto& leafName,
10742                        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
10743                            nsAutoString, originFile, GetLeafName));
10744 
10745         // Unknown files during upgrade are allowed. Just warn if we find
10746         // them.
10747         if (!IsOSMetadata(leafName)) {
10748           UNKNOWN_FILE_WARNING(leafName);
10749         }
10750 
10751         return mozilla::Ok{};
10752       },
10753       [&self = *this](const auto& originDir) -> Result<mozilla::Ok, nsresult> {
10754         OriginProps originProps(WrapMovingNotNullUnchecked(originDir));
10755         QM_TRY(MOZ_TO_RESULT(originProps.Init([&self](const auto& aSpec) {
10756           return self.PersistenceTypeFromSpec(aSpec);
10757         })));
10758         // Bypass invalid origins while upgrading
10759         QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), mozilla::Ok{});
10760 
10761         if (originProps.mType != OriginProps::eObsolete) {
10762           QM_TRY_INSPECT(const bool& removed,
10763                          MOZ_TO_RESULT_INVOKE_MEMBER(
10764                              self, PrepareOriginDirectory, originProps));
10765           if (removed) {
10766             return mozilla::Ok{};
10767           }
10768         }
10769 
10770         self.mOriginProps.AppendElement(std::move(originProps));
10771 
10772         return mozilla::Ok{};
10773       }));
10774 
10775   if (mOriginProps.IsEmpty()) {
10776     return NS_OK;
10777   }
10778 
10779   QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
10780 
10781   return NS_OK;
10782 }
10783 
10784 template <typename UpgradeMethod>
MaybeUpgradeClients(const OriginProps & aOriginProps,UpgradeMethod aMethod)10785 nsresult RepositoryOperationBase::MaybeUpgradeClients(
10786     const OriginProps& aOriginProps, UpgradeMethod aMethod) {
10787   AssertIsOnIOThread();
10788   MOZ_ASSERT(aMethod);
10789 
10790   QuotaManager* quotaManager = QuotaManager::Get();
10791   MOZ_ASSERT(quotaManager);
10792 
10793   QM_TRY(CollectEachFileEntry(
10794       *aOriginProps.mDirectory,
10795       [](const auto& file) -> Result<mozilla::Ok, nsresult> {
10796         QM_TRY_INSPECT(
10797             const auto& leafName,
10798             MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file, GetLeafName));
10799 
10800         if (!IsOriginMetadata(leafName) && !IsTempMetadata(leafName)) {
10801           UNKNOWN_FILE_WARNING(leafName);
10802         }
10803 
10804         return mozilla::Ok{};
10805       },
10806       [quotaManager, &aMethod,
10807        &self = *this](const auto& dir) -> Result<mozilla::Ok, nsresult> {
10808         QM_TRY_INSPECT(
10809             const auto& leafName,
10810             MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, dir, GetLeafName));
10811 
10812         QM_TRY_INSPECT(const bool& removed,
10813                        MOZ_TO_RESULT_INVOKE_MEMBER(self, PrepareClientDirectory,
10814                                                    dir, leafName));
10815         if (removed) {
10816           return mozilla::Ok{};
10817         }
10818 
10819         Client::Type clientType;
10820         bool ok = Client::TypeFromText(leafName, clientType, fallible);
10821         if (!ok) {
10822           UNKNOWN_FILE_WARNING(leafName);
10823           return mozilla::Ok{};
10824         }
10825 
10826         Client* client = quotaManager->GetClient(clientType);
10827         MOZ_ASSERT(client);
10828 
10829         QM_TRY(MOZ_TO_RESULT((client->*aMethod)(dir)));
10830 
10831         return mozilla::Ok{};
10832       }));
10833 
10834   return NS_OK;
10835 }
10836 
PrepareClientDirectory(nsIFile * aFile,const nsAString & aLeafName,bool & aRemoved)10837 nsresult RepositoryOperationBase::PrepareClientDirectory(
10838     nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
10839   AssertIsOnIOThread();
10840 
10841   aRemoved = false;
10842   return NS_OK;
10843 }
10844 
Init()10845 nsresult CreateOrUpgradeDirectoryMetadataHelper::Init() {
10846   AssertIsOnIOThread();
10847   MOZ_ASSERT(mDirectory);
10848 
10849   const auto maybeLegacyPersistenceType =
10850       LegacyPersistenceTypeFromFile(*mDirectory, fallible);
10851   QM_TRY(OkIf(maybeLegacyPersistenceType.isSome()), Err(NS_ERROR_FAILURE));
10852 
10853   mLegacyPersistenceType.init(maybeLegacyPersistenceType.value());
10854 
10855   return NS_OK;
10856 }
10857 
10858 Maybe<CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceType>
LegacyPersistenceTypeFromFile(nsIFile & aFile,const fallible_t &)10859 CreateOrUpgradeDirectoryMetadataHelper::LegacyPersistenceTypeFromFile(
10860     nsIFile& aFile, const fallible_t&) {
10861   nsAutoString leafName;
10862   MOZ_ALWAYS_SUCCEEDS(aFile.GetLeafName(leafName));
10863 
10864   if (leafName.Equals(u"persistent"_ns)) {
10865     return Some(LegacyPersistenceType::Persistent);
10866   }
10867 
10868   if (leafName.Equals(u"temporary"_ns)) {
10869     return Some(LegacyPersistenceType::Temporary);
10870   }
10871 
10872   return Nothing();
10873 }
10874 
10875 PersistenceType
PersistenceTypeFromLegacyPersistentSpec(const nsCString & aSpec)10876 CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromLegacyPersistentSpec(
10877     const nsCString& aSpec) {
10878   if (QuotaManager::IsOriginInternal(aSpec)) {
10879     return PERSISTENCE_TYPE_PERSISTENT;
10880   }
10881 
10882   return PERSISTENCE_TYPE_DEFAULT;
10883 }
10884 
PersistenceTypeFromSpec(const nsCString & aSpec)10885 PersistenceType CreateOrUpgradeDirectoryMetadataHelper::PersistenceTypeFromSpec(
10886     const nsCString& aSpec) {
10887   switch (*mLegacyPersistenceType) {
10888     case LegacyPersistenceType::Persistent:
10889       return PersistenceTypeFromLegacyPersistentSpec(aSpec);
10890     case LegacyPersistenceType::Temporary:
10891       return PERSISTENCE_TYPE_TEMPORARY;
10892   }
10893   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad legacy persistence type value!");
10894 }
10895 
MaybeUpgradeOriginDirectory(nsIFile * aDirectory)10896 nsresult CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory(
10897     nsIFile* aDirectory) {
10898   AssertIsOnIOThread();
10899   MOZ_ASSERT(aDirectory);
10900 
10901   QM_TRY_INSPECT(
10902       const auto& metadataFile,
10903       CloneFileAndAppend(*aDirectory, nsLiteralString(METADATA_FILE_NAME)));
10904 
10905   QM_TRY_INSPECT(const bool& exists,
10906                  MOZ_TO_RESULT_INVOKE_MEMBER(metadataFile, Exists));
10907 
10908   if (!exists) {
10909     // Directory structure upgrade needed.
10910     // Move all files to IDB specific directory.
10911 
10912     nsString idbDirectoryName;
10913     QM_TRY(OkIf(Client::TypeToText(Client::IDB, idbDirectoryName, fallible)),
10914            NS_ERROR_FAILURE);
10915 
10916     QM_TRY_INSPECT(const auto& idbDirectory,
10917                    CloneFileAndAppend(*aDirectory, idbDirectoryName));
10918 
10919     // Usually we only use QM_OR_ELSE_LOG_VERBOSE/QM_OR_ELSE_LOG_VERBOSE_IF
10920     // with Create and NS_ERROR_FILE_ALREADY_EXISTS check, but typically the
10921     // idb directory shouldn't exist during the upgrade and the upgrade runs
10922     // only once in most of the cases, so the use of QM_OR_ELSE_WARN_IF is ok
10923     // here.
10924     QM_TRY(QM_OR_ELSE_WARN_IF(
10925         // Expression.
10926         MOZ_TO_RESULT(idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
10927         // Predicate.
10928         IsSpecificError<NS_ERROR_FILE_ALREADY_EXISTS>,
10929         // Fallback.
10930         ([&idbDirectory](const nsresult rv) -> Result<Ok, nsresult> {
10931           QM_TRY_INSPECT(
10932               const bool& isDirectory,
10933               MOZ_TO_RESULT_INVOKE_MEMBER(idbDirectory, IsDirectory));
10934 
10935           QM_TRY(OkIf(isDirectory), Err(NS_ERROR_UNEXPECTED));
10936 
10937           return Ok{};
10938         })));
10939 
10940     QM_TRY(CollectEachFile(
10941         *aDirectory,
10942         [&idbDirectory, &idbDirectoryName](
10943             const nsCOMPtr<nsIFile>& file) -> Result<Ok, nsresult> {
10944           QM_TRY_INSPECT(const auto& leafName,
10945                          MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoString, file,
10946                                                            GetLeafName));
10947 
10948           if (!leafName.Equals(idbDirectoryName)) {
10949             QM_TRY(MOZ_TO_RESULT(file->MoveTo(idbDirectory, u""_ns)));
10950           }
10951 
10952           return Ok{};
10953         }));
10954 
10955     QM_TRY(
10956         MOZ_TO_RESULT(metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644)));
10957   }
10958 
10959   return NS_OK;
10960 }
10961 
PrepareOriginDirectory(OriginProps & aOriginProps,bool * aRemoved)10962 nsresult CreateOrUpgradeDirectoryMetadataHelper::PrepareOriginDirectory(
10963     OriginProps& aOriginProps, bool* aRemoved) {
10964   AssertIsOnIOThread();
10965   MOZ_ASSERT(aRemoved);
10966 
10967   if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
10968     QM_TRY(MOZ_TO_RESULT(
10969         MaybeUpgradeOriginDirectory(aOriginProps.mDirectory.get())));
10970 
10971     aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10972   } else {
10973     int64_t timestamp;
10974     nsCString group;
10975     nsCString origin;
10976     Nullable<bool> isApp;
10977 
10978     QM_WARNONLY_TRY_UNWRAP(
10979         const auto maybeDirectoryMetadata,
10980         MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
10981                                            timestamp, group, origin, isApp)));
10982     if (!maybeDirectoryMetadata) {
10983       aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
10984       aOriginProps.mNeedsRestore = true;
10985     } else if (!isApp.IsNull()) {
10986       aOriginProps.mIgnore = true;
10987     }
10988   }
10989 
10990   *aRemoved = false;
10991   return NS_OK;
10992 }
10993 
ProcessOriginDirectory(const OriginProps & aOriginProps)10994 nsresult CreateOrUpgradeDirectoryMetadataHelper::ProcessOriginDirectory(
10995     const OriginProps& aOriginProps) {
10996   AssertIsOnIOThread();
10997 
10998   if (*mLegacyPersistenceType == LegacyPersistenceType::Persistent) {
10999     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11000         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11001         aOriginProps.mOriginMetadata)));
11002 
11003     // Move internal origins to new persistent storage.
11004     if (PersistenceTypeFromLegacyPersistentSpec(aOriginProps.mSpec) ==
11005         PERSISTENCE_TYPE_PERSISTENT) {
11006       if (!mPermanentStorageDir) {
11007         QuotaManager* quotaManager = QuotaManager::Get();
11008         MOZ_ASSERT(quotaManager);
11009 
11010         const nsString& permanentStoragePath =
11011             quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT);
11012 
11013         QM_TRY_UNWRAP(mPermanentStorageDir,
11014                       QM_NewLocalFile(permanentStoragePath));
11015       }
11016 
11017       const nsAString& leafName = aOriginProps.mLeafName;
11018 
11019       QM_TRY_INSPECT(const auto& newDirectory,
11020                      CloneFileAndAppend(*mPermanentStorageDir, leafName));
11021 
11022       QM_TRY_INSPECT(const bool& exists,
11023                      MOZ_TO_RESULT_INVOKE_MEMBER(newDirectory, Exists));
11024 
11025       if (exists) {
11026         QM_WARNING("Found %s in storage/persistent and storage/permanent !",
11027                    NS_ConvertUTF16toUTF8(leafName).get());
11028 
11029         QM_TRY(MOZ_TO_RESULT(
11030             aOriginProps.mDirectory->Remove(/* recursive */ true)));
11031       } else {
11032         QM_TRY(MOZ_TO_RESULT(
11033             aOriginProps.mDirectory->MoveTo(mPermanentStorageDir, u""_ns)));
11034       }
11035     }
11036   } else if (aOriginProps.mNeedsRestore) {
11037     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11038         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11039         aOriginProps.mOriginMetadata)));
11040   } else if (!aOriginProps.mIgnore) {
11041     QM_TRY_INSPECT(const auto& file,
11042                    CloneFileAndAppend(*aOriginProps.mDirectory,
11043                                       nsLiteralString(METADATA_FILE_NAME)));
11044 
11045     QM_TRY_INSPECT(const auto& stream,
11046                    GetBinaryOutputStream(*file, FileFlag::Append));
11047 
11048     MOZ_ASSERT(stream);
11049 
11050     // Currently unused (used to be isApp).
11051     QM_TRY(MOZ_TO_RESULT(stream->WriteBoolean(false)));
11052   }
11053 
11054   return NS_OK;
11055 }
11056 
Init()11057 nsresult UpgradeStorageHelperBase::Init() {
11058   AssertIsOnIOThread();
11059   MOZ_ASSERT(mDirectory);
11060 
11061   const auto maybePersistenceType =
11062       PersistenceTypeFromFile(*mDirectory, fallible);
11063   QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
11064 
11065   mPersistenceType.init(maybePersistenceType.value());
11066 
11067   return NS_OK;
11068 }
11069 
PersistenceTypeFromSpec(const nsCString & aSpec)11070 PersistenceType UpgradeStorageHelperBase::PersistenceTypeFromSpec(
11071     const nsCString& aSpec) {
11072   // There's no moving of origin directories between repositories like in the
11073   // CreateOrUpgradeDirectoryMetadataHelper
11074   return *mPersistenceType;
11075 }
11076 
PrepareOriginDirectory(OriginProps & aOriginProps,bool * aRemoved)11077 nsresult UpgradeStorageFrom0_0To1_0Helper::PrepareOriginDirectory(
11078     OriginProps& aOriginProps, bool* aRemoved) {
11079   AssertIsOnIOThread();
11080   MOZ_ASSERT(aRemoved);
11081 
11082   int64_t timestamp;
11083   nsCString group;
11084   nsCString origin;
11085   Nullable<bool> isApp;
11086 
11087   QM_WARNONLY_TRY_UNWRAP(
11088       const auto maybeDirectoryMetadata,
11089       MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
11090                                          timestamp, group, origin, isApp)));
11091   if (!maybeDirectoryMetadata || isApp.IsNull()) {
11092     aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
11093     aOriginProps.mNeedsRestore = true;
11094   } else {
11095     aOriginProps.mTimestamp = timestamp;
11096   }
11097 
11098   *aRemoved = false;
11099   return NS_OK;
11100 }
11101 
ProcessOriginDirectory(const OriginProps & aOriginProps)11102 nsresult UpgradeStorageFrom0_0To1_0Helper::ProcessOriginDirectory(
11103     const OriginProps& aOriginProps) {
11104   AssertIsOnIOThread();
11105 
11106   // This handles changes in origin string generation from nsIPrincipal,
11107   // especially the change from: appId+inMozBrowser+originNoSuffix
11108   // to: origin (with origin suffix).
11109   QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
11110   if (renamed) {
11111     return NS_OK;
11112   }
11113 
11114   if (aOriginProps.mNeedsRestore) {
11115     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11116         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11117         aOriginProps.mOriginMetadata)));
11118   }
11119 
11120   QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
11121       *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11122       /* aPersisted */ false, aOriginProps.mOriginMetadata)));
11123 
11124   return NS_OK;
11125 }
11126 
MaybeRemoveMorgueDirectory(const OriginProps & aOriginProps)11127 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveMorgueDirectory(
11128     const OriginProps& aOriginProps) {
11129   AssertIsOnIOThread();
11130 
11131   // The Cache API was creating top level morgue directories by accident for
11132   // a short time in nightly.  This unfortunately prevents all storage from
11133   // working.  So recover these profiles permanently by removing these corrupt
11134   // directories as part of this upgrade.
11135 
11136   QM_TRY_INSPECT(const auto& morgueDir,
11137                  MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
11138                      nsCOMPtr<nsIFile>, *aOriginProps.mDirectory, Clone));
11139 
11140   QM_TRY(MOZ_TO_RESULT(morgueDir->Append(u"morgue"_ns)));
11141 
11142   QM_TRY_INSPECT(const bool& exists,
11143                  MOZ_TO_RESULT_INVOKE_MEMBER(morgueDir, Exists));
11144 
11145   if (exists) {
11146     QM_WARNING("Deleting accidental morgue directory!");
11147 
11148     QM_TRY(MOZ_TO_RESULT(morgueDir->Remove(/* recursive */ true)));
11149   }
11150 
11151   return NS_OK;
11152 }
11153 
MaybeRemoveAppsData(const OriginProps & aOriginProps)11154 Result<bool, nsresult> UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
11155     const OriginProps& aOriginProps) {
11156   AssertIsOnIOThread();
11157 
11158   // TODO: This method was empty for some time due to accidental changes done
11159   //       in bug 1320404. This led to renaming of origin directories like:
11160   //         https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1
11161   //       to:
11162   //         https+++developer.cdn.mozilla.net^inBrowser=1
11163   //       instead of just removing them.
11164 
11165   const nsCString& originalSuffix = aOriginProps.mOriginalSuffix;
11166   if (!originalSuffix.IsEmpty()) {
11167     MOZ_ASSERT(originalSuffix[0] == '^');
11168 
11169     if (!URLParams::Parse(
11170             Substring(originalSuffix, 1, originalSuffix.Length() - 1),
11171             [](const nsAString& aName, const nsAString& aValue) {
11172               if (aName.EqualsLiteral("appId")) {
11173                 return false;
11174               }
11175 
11176               return true;
11177             })) {
11178       QM_TRY(MOZ_TO_RESULT(RemoveObsoleteOrigin(aOriginProps)));
11179 
11180       return true;
11181     }
11182   }
11183 
11184   return false;
11185 }
11186 
PrepareOriginDirectory(OriginProps & aOriginProps,bool * aRemoved)11187 nsresult UpgradeStorageFrom1_0To2_0Helper::PrepareOriginDirectory(
11188     OriginProps& aOriginProps, bool* aRemoved) {
11189   AssertIsOnIOThread();
11190   MOZ_ASSERT(aRemoved);
11191 
11192   QM_TRY(MOZ_TO_RESULT(MaybeRemoveMorgueDirectory(aOriginProps)));
11193 
11194   QM_TRY(MOZ_TO_RESULT(
11195       MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom1_0To2_0)));
11196 
11197   QM_TRY_INSPECT(const bool& removed, MaybeRemoveAppsData(aOriginProps));
11198   if (removed) {
11199     *aRemoved = true;
11200     return NS_OK;
11201   }
11202 
11203   int64_t timestamp;
11204   nsCString group;
11205   nsCString origin;
11206   Nullable<bool> isApp;
11207   QM_WARNONLY_TRY_UNWRAP(
11208       const auto maybeDirectoryMetadata,
11209       MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
11210                                          timestamp, group, origin, isApp)));
11211   if (!maybeDirectoryMetadata || isApp.IsNull()) {
11212     aOriginProps.mNeedsRestore = true;
11213   }
11214 
11215   nsCString suffix;
11216   QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
11217                          MOZ_TO_RESULT(GetDirectoryMetadata2(
11218                              aOriginProps.mDirectory.get(), timestamp, suffix,
11219                              group, origin, isApp.SetValue())));
11220   if (!maybeDirectoryMetadata2) {
11221     aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
11222     aOriginProps.mNeedsRestore2 = true;
11223   } else {
11224     aOriginProps.mTimestamp = timestamp;
11225   }
11226 
11227   *aRemoved = false;
11228   return NS_OK;
11229 }
11230 
ProcessOriginDirectory(const OriginProps & aOriginProps)11231 nsresult UpgradeStorageFrom1_0To2_0Helper::ProcessOriginDirectory(
11232     const OriginProps& aOriginProps) {
11233   AssertIsOnIOThread();
11234 
11235   // This handles changes in origin string generation from nsIPrincipal,
11236   // especially the stripping of obsolete origin attributes like addonId.
11237   QM_TRY_INSPECT(const bool& renamed, MaybeRenameOrigin(aOriginProps));
11238   if (renamed) {
11239     return NS_OK;
11240   }
11241 
11242   if (aOriginProps.mNeedsRestore) {
11243     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11244         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11245         aOriginProps.mOriginMetadata)));
11246   }
11247 
11248   if (aOriginProps.mNeedsRestore2) {
11249     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
11250         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11251         /* aPersisted */ false, aOriginProps.mOriginMetadata)));
11252   }
11253 
11254   return NS_OK;
11255 }
11256 
PrepareOriginDirectory(OriginProps & aOriginProps,bool * aRemoved)11257 nsresult UpgradeStorageFrom2_0To2_1Helper::PrepareOriginDirectory(
11258     OriginProps& aOriginProps, bool* aRemoved) {
11259   AssertIsOnIOThread();
11260   MOZ_ASSERT(aRemoved);
11261 
11262   QM_TRY(MOZ_TO_RESULT(
11263       MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_0To2_1)));
11264 
11265   int64_t timestamp;
11266   nsCString group;
11267   nsCString origin;
11268   Nullable<bool> isApp;
11269   QM_WARNONLY_TRY_UNWRAP(
11270       const auto maybeDirectoryMetadata,
11271       MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
11272                                          timestamp, group, origin, isApp)));
11273   if (!maybeDirectoryMetadata || isApp.IsNull()) {
11274     aOriginProps.mNeedsRestore = true;
11275   }
11276 
11277   nsCString suffix;
11278   QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
11279                          MOZ_TO_RESULT(GetDirectoryMetadata2(
11280                              aOriginProps.mDirectory.get(), timestamp, suffix,
11281                              group, origin, isApp.SetValue())));
11282   if (!maybeDirectoryMetadata2) {
11283     aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
11284     aOriginProps.mNeedsRestore2 = true;
11285   } else {
11286     aOriginProps.mTimestamp = timestamp;
11287   }
11288 
11289   *aRemoved = false;
11290   return NS_OK;
11291 }
11292 
ProcessOriginDirectory(const OriginProps & aOriginProps)11293 nsresult UpgradeStorageFrom2_0To2_1Helper::ProcessOriginDirectory(
11294     const OriginProps& aOriginProps) {
11295   AssertIsOnIOThread();
11296 
11297   if (aOriginProps.mNeedsRestore) {
11298     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11299         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11300         aOriginProps.mOriginMetadata)));
11301   }
11302 
11303   if (aOriginProps.mNeedsRestore2) {
11304     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
11305         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11306         /* aPersisted */ false, aOriginProps.mOriginMetadata)));
11307   }
11308 
11309   return NS_OK;
11310 }
11311 
PrepareOriginDirectory(OriginProps & aOriginProps,bool * aRemoved)11312 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareOriginDirectory(
11313     OriginProps& aOriginProps, bool* aRemoved) {
11314   AssertIsOnIOThread();
11315   MOZ_ASSERT(aRemoved);
11316 
11317   QM_TRY(MOZ_TO_RESULT(
11318       MaybeUpgradeClients(aOriginProps, &Client::UpgradeStorageFrom2_1To2_2)));
11319 
11320   int64_t timestamp;
11321   nsCString group;
11322   nsCString origin;
11323   Nullable<bool> isApp;
11324   QM_WARNONLY_TRY_UNWRAP(
11325       const auto maybeDirectoryMetadata,
11326       MOZ_TO_RESULT(GetDirectoryMetadata(aOriginProps.mDirectory.get(),
11327                                          timestamp, group, origin, isApp)));
11328   if (!maybeDirectoryMetadata || isApp.IsNull()) {
11329     aOriginProps.mNeedsRestore = true;
11330   }
11331 
11332   nsCString suffix;
11333   QM_WARNONLY_TRY_UNWRAP(const auto maybeDirectoryMetadata2,
11334                          MOZ_TO_RESULT(GetDirectoryMetadata2(
11335                              aOriginProps.mDirectory.get(), timestamp, suffix,
11336                              group, origin, isApp.SetValue())));
11337   if (!maybeDirectoryMetadata2) {
11338     aOriginProps.mTimestamp = GetOriginLastModifiedTime(aOriginProps);
11339     aOriginProps.mNeedsRestore2 = true;
11340   } else {
11341     aOriginProps.mTimestamp = timestamp;
11342   }
11343 
11344   *aRemoved = false;
11345   return NS_OK;
11346 }
11347 
ProcessOriginDirectory(const OriginProps & aOriginProps)11348 nsresult UpgradeStorageFrom2_1To2_2Helper::ProcessOriginDirectory(
11349     const OriginProps& aOriginProps) {
11350   AssertIsOnIOThread();
11351 
11352   if (aOriginProps.mNeedsRestore) {
11353     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata(
11354         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11355         aOriginProps.mOriginMetadata)));
11356   }
11357 
11358   if (aOriginProps.mNeedsRestore2) {
11359     QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
11360         *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11361         /* aPersisted */ false, aOriginProps.mOriginMetadata)));
11362   }
11363 
11364   return NS_OK;
11365 }
11366 
PrepareClientDirectory(nsIFile * aFile,const nsAString & aLeafName,bool & aRemoved)11367 nsresult UpgradeStorageFrom2_1To2_2Helper::PrepareClientDirectory(
11368     nsIFile* aFile, const nsAString& aLeafName, bool& aRemoved) {
11369   AssertIsOnIOThread();
11370 
11371   if (Client::IsDeprecatedClient(aLeafName)) {
11372     QM_WARNING("Deleting deprecated %s client!",
11373                NS_ConvertUTF16toUTF8(aLeafName).get());
11374 
11375     QM_TRY(MOZ_TO_RESULT(aFile->Remove(true)));
11376 
11377     aRemoved = true;
11378   } else {
11379     aRemoved = false;
11380   }
11381 
11382   return NS_OK;
11383 }
11384 
Init()11385 nsresult RestoreDirectoryMetadata2Helper::Init() {
11386   AssertIsOnIOThread();
11387   MOZ_ASSERT(mDirectory);
11388 
11389   nsCOMPtr<nsIFile> parentDir;
11390   QM_TRY(MOZ_TO_RESULT(mDirectory->GetParent(getter_AddRefs(parentDir))));
11391 
11392   const auto maybePersistenceType =
11393       PersistenceTypeFromFile(*parentDir, fallible);
11394   QM_TRY(OkIf(maybePersistenceType.isSome()), Err(NS_ERROR_FAILURE));
11395 
11396   mPersistenceType.init(maybePersistenceType.value());
11397 
11398   return NS_OK;
11399 }
11400 
RestoreMetadata2File()11401 nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() {
11402   OriginProps originProps(WrapMovingNotNull(mDirectory));
11403   QM_TRY(MOZ_TO_RESULT(originProps.Init(
11404       [&self = *this](const auto& aSpec) { return *self.mPersistenceType; })));
11405 
11406   QM_TRY(OkIf(originProps.mType != OriginProps::eInvalid), NS_ERROR_FAILURE);
11407 
11408   originProps.mTimestamp = GetOriginLastModifiedTime(originProps);
11409 
11410   mOriginProps.AppendElement(std::move(originProps));
11411 
11412   QM_TRY(MOZ_TO_RESULT(ProcessOriginDirectories()));
11413 
11414   return NS_OK;
11415 }
11416 
ProcessOriginDirectory(const OriginProps & aOriginProps)11417 nsresult RestoreDirectoryMetadata2Helper::ProcessOriginDirectory(
11418     const OriginProps& aOriginProps) {
11419   AssertIsOnIOThread();
11420 
11421   // We don't have any approach to restore aPersisted, so reset it to false.
11422   QM_TRY(MOZ_TO_RESULT(CreateDirectoryMetadata2(
11423       *aOriginProps.mDirectory, aOriginProps.mTimestamp,
11424       /* aPersisted */ false, aOriginProps.mOriginMetadata)));
11425 
11426   return NS_OK;
11427 }
11428 
11429 }  // namespace mozilla::dom::quota
11430