DBSchema.cpp (126728B)
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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/cache/DBSchema.h" 8 9 #include "ipc/IPCMessageUtils.h" 10 #include "mozIStorageConnection.h" 11 #include "mozIStorageFunction.h" 12 #include "mozIStorageStatement.h" 13 #include "mozStorageHelper.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/ResultExtensions.h" 16 #include "mozilla/StaticPrefs_extensions.h" 17 #include "mozilla/dom/HeadersBinding.h" 18 #include "mozilla/dom/InternalHeaders.h" 19 #include "mozilla/dom/InternalResponse.h" 20 #include "mozilla/dom/RequestBinding.h" 21 #include "mozilla/dom/ResponseBinding.h" 22 #include "mozilla/dom/cache/CacheCommon.h" 23 #include "mozilla/dom/cache/CacheTypes.h" 24 #include "mozilla/dom/cache/FileUtils.h" 25 #include "mozilla/dom/cache/SavedTypes.h" 26 #include "mozilla/dom/cache/TypeUtils.h" 27 #include "mozilla/dom/cache/Types.h" 28 #include "mozilla/dom/quota/ResultExtensions.h" 29 #include "mozilla/psm/TransportSecurityInfo.h" 30 #include "mozilla/storage/Variant.h" 31 #include "nsCOMPtr.h" 32 #include "nsCharSeparatedTokenizer.h" 33 #include "nsComponentManagerUtils.h" 34 #include "nsHttp.h" 35 #include "nsIContentPolicy.h" 36 #include "nsICryptoHash.h" 37 #include "nsIURI.h" 38 #include "nsNetCID.h" 39 #include "nsPrintfCString.h" 40 #include "nsTArray.h" 41 42 namespace mozilla::dom::cache::db { 43 const int32_t kFirstShippedSchemaVersion = 15; 44 namespace { 45 // ## Firefox 57 Cache API v25/v26/v27 Schema Hack Info 46 // ### Overview 47 // In Firefox 57 we introduced Cache API schema version 26 and Quota Manager 48 // schema v3 to support tracking padding for opaque responses. Unfortunately, 49 // Firefox 57 is a big release that may potentially result in users downgrading 50 // to Firefox 56 due to 57 retiring add-ons. These schema changes have the 51 // unfortunate side-effect of causing QuotaManager and all its clients to break 52 // if the user downgrades to 56. In order to avoid making a bad situation 53 // worse, we're now retrofitting 57 so that Firefox 56 won't freak out. 54 // 55 // ### Implementation 56 // We're introducing a new schema version 27 that uses an on-disk schema version 57 // of v25. We differentiate v25 from v27 by the presence of the column added 58 // by v26. This translates to: 59 // - v25: on-disk schema=25, no "response_padding_size" column in table 60 // "entries". 61 // - v26: on-disk schema=26, yes "response_padding_size" column in table 62 // "entries". 63 // - v27: on-disk schema=25, yes "response_padding_size" column in table 64 // "entries". 65 // 66 // ### Fallout 67 // Firefox 57 is happy because it sees schema 27 and everything is as it 68 // expects. 69 // 70 // Firefox 56 non-DEBUG build is fine/happy, but DEBUG builds will not be. 71 // - Our QuotaClient will invoke `NS_WARNING("Unknown Cache file found!");` 72 // at QuotaManager init time. This is harmless but annoying and potentially 73 // misleading. 74 // - The DEBUG-only Validate() call will error out whenever an attempt is made 75 // to open a DOM Cache database because it will notice the schema is broken 76 // and there is no attempt at recovery. 77 // 78 const int32_t kHackyDowngradeSchemaVersion = 25; 79 const int32_t kHackyPaddingSizePresentVersion = 27; 80 // 81 // Update this whenever the DB schema is changed. 82 const int32_t kLatestSchemaVersion = 29; 83 // --------- 84 // The following constants define the SQL schema. These are defined in the 85 // same order the SQL should be executed in CreateOrMigrateSchema(). They are 86 // broken out as constants for convenient use in validation and migration. 87 // --------- 88 // The caches table is the single source of truth about what Cache 89 // objects exist for the origin. The contents of the Cache are stored 90 // in the entries table that references back to caches. 91 // 92 // The caches table is also referenced from storage. Rows in storage 93 // represent named Cache objects. There are cases, however, where 94 // a Cache can still exist, but not be in a named Storage. For example, 95 // when content is still using the Cache after CacheStorage::Delete() 96 // has been run. 97 // 98 // For now, the caches table mainly exists for data integrity with 99 // foreign keys, but could be expanded to contain additional cache object 100 // information. 101 // 102 // AUTOINCREMENT is necessary to prevent CacheId values from being reused. 103 const char kTableCaches[] = 104 "CREATE TABLE caches (" 105 "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT " 106 ")"; 107 108 // Security blobs are quite large and duplicated for every Response from 109 // the same https origin. This table is used to de-duplicate this data. 110 const char kTableSecurityInfo[] = 111 "CREATE TABLE security_info (" 112 "id INTEGER NOT NULL PRIMARY KEY, " 113 "hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column 114 "data BLOB NOT NULL, " // full security info data, usually a few KB 115 "refcount INTEGER NOT NULL" 116 ")"; 117 118 // Index the smaller hash value instead of the large security data blob. 119 const char kIndexSecurityInfoHash[] = 120 "CREATE INDEX security_info_hash_index ON security_info (hash)"; 121 122 const char kTableEntries[] = 123 "CREATE TABLE entries (" 124 "id INTEGER NOT NULL PRIMARY KEY, " 125 "request_method TEXT NOT NULL, " 126 "request_url_no_query TEXT NOT NULL, " 127 "request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash 128 "request_url_query TEXT NOT NULL, " 129 "request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash 130 "request_referrer TEXT NOT NULL, " 131 "request_headers_guard INTEGER NOT NULL, " 132 "request_mode INTEGER NOT NULL, " 133 "request_credentials INTEGER NOT NULL, " 134 "request_contentpolicytype INTEGER NOT NULL, " 135 "request_cache INTEGER NOT NULL, " 136 "request_body_id TEXT NULL, " 137 "response_type INTEGER NOT NULL, " 138 "response_status INTEGER NOT NULL, " 139 "response_status_text TEXT NOT NULL, " 140 "response_headers_guard INTEGER NOT NULL, " 141 "response_body_id TEXT NULL, " 142 "response_security_info_id INTEGER NULL REFERENCES security_info(id), " 143 "response_principal_info TEXT NOT NULL, " 144 "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " 145 "request_redirect INTEGER NOT NULL, " 146 "request_referrer_policy INTEGER NOT NULL, " 147 "request_integrity TEXT NOT NULL, " 148 "request_url_fragment TEXT NOT NULL, " 149 "response_padding_size INTEGER NULL, " 150 "request_body_disk_size INTEGER NULL, " 151 "response_body_disk_size INTEGER NULL " 152 // New columns must be added at the end of table to migrate and 153 // validate properly. 154 ")"; 155 // Create an index to support the QueryCache() matching algorithm. This 156 // needs to quickly find entries in a given Cache that match the request 157 // URL. The url query is separated in order to support the ignoreSearch 158 // option. Finally, we index hashes of the URL values instead of the 159 // actual strings to avoid excessive disk bloat. The index will duplicate 160 // the contents of the columsn in the index. The hash index will prune 161 // the vast majority of values from the query result so that normal 162 // scanning only has to be done on a few values to find an exact URL match. 163 const char kIndexEntriesRequest[] = 164 "CREATE INDEX entries_request_match_index " 165 "ON entries (cache_id, request_url_no_query_hash, " 166 "request_url_query_hash)"; 167 168 const char kTableRequestHeaders[] = 169 "CREATE TABLE request_headers (" 170 "name TEXT NOT NULL, " 171 "value TEXT NOT NULL, " 172 "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" 173 ")"; 174 175 const char kTableResponseHeaders[] = 176 "CREATE TABLE response_headers (" 177 "name TEXT NOT NULL, " 178 "value TEXT NOT NULL, " 179 "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" 180 ")"; 181 182 // We need an index on response_headers, but not on request_headers, 183 // because we quickly need to determine if a VARY header is present. 184 const char kIndexResponseHeadersName[] = 185 "CREATE INDEX response_headers_name_index " 186 "ON response_headers (name)"; 187 188 const char kTableResponseUrlList[] = 189 "CREATE TABLE response_url_list (" 190 "url TEXT NOT NULL, " 191 "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" 192 ")"; 193 194 // NOTE: key allows NULL below since that is how "" is represented 195 // in a BLOB column. We use BLOB to avoid encoding issues 196 // with storing DOMStrings. 197 const char kTableStorage[] = 198 "CREATE TABLE storage (" 199 "namespace INTEGER NOT NULL, " 200 "key BLOB NULL, " 201 "cache_id INTEGER NOT NULL REFERENCES caches(id), " 202 "PRIMARY KEY(namespace, key) " 203 ")"; 204 205 const char kTableUsageInfo[] = 206 "CREATE TABLE usage_info (" 207 "id INTEGER NOT NULL PRIMARY KEY, " 208 "total_disk_usage INTEGER NOT NULL " 209 ")"; 210 211 const char kTriggerEntriesInsert[] = 212 "CREATE TRIGGER entries_insert_trigger " 213 "AFTER INSERT ON entries " 214 "FOR EACH ROW " 215 "BEGIN " 216 "UPDATE usage_info SET total_disk_usage = total_disk_usage + " 217 "ifnull(NEW.request_body_disk_size, 0) + " 218 "ifnull(NEW.response_body_disk_size, 0) " 219 "WHERE usage_info.id = 1; " 220 "END"; 221 222 const char kTriggerEntriesUpdate[] = 223 "CREATE TRIGGER entries_update_trigger " 224 "AFTER UPDATE ON entries " 225 "FOR EACH ROW " 226 "BEGIN " 227 "UPDATE usage_info SET total_disk_usage = total_disk_usage - " 228 "ifnull(OLD.request_body_disk_size, 0) + " 229 "ifnull(NEW.request_body_disk_size, 0) - " 230 "ifnull(OLD.response_body_disk_size, 0) + " 231 "ifnull(NEW.response_body_disk_size, 0) " 232 "WHERE usage_info.id = 1; " 233 "END"; 234 235 const char kTriggerEntriesDelete[] = 236 "CREATE TRIGGER entries_delete_trigger " 237 "AFTER DELETE ON entries " 238 "FOR EACH ROW " 239 "BEGIN " 240 "UPDATE usage_info SET total_disk_usage = total_disk_usage - " 241 "ifnull(OLD.request_body_disk_size, 0) - " 242 "ifnull(OLD.response_body_disk_size, 0) " 243 "WHERE usage_info.id = 1; " 244 "END"; 245 246 // --------- 247 // End schema definition 248 // --------- 249 250 const uint32_t kMaxEntriesPerStatement = 255; 251 252 const uint32_t kPageSize = 4 * 1024; 253 254 // Grow the database in chunks to reduce fragmentation 255 const uint32_t kGrowthSize = 32 * 1024; 256 const uint32_t kGrowthPages = kGrowthSize / kPageSize; 257 static_assert(kGrowthSize % kPageSize == 0, 258 "Growth size must be multiple of page size"); 259 260 // Only release free pages when we have more than this limit 261 const int32_t kMaxFreePages = kGrowthPages; 262 263 // Limit WAL journal to a reasonable size 264 const uint32_t kWalAutoCheckpointSize = 512 * 1024; 265 const uint32_t kWalAutoCheckpointPages = kWalAutoCheckpointSize / kPageSize; 266 static_assert(kWalAutoCheckpointSize % kPageSize == 0, 267 "WAL checkpoint size must be multiple of page size"); 268 269 } // namespace 270 271 // If any of the static_asserts below fail, it means that you have changed 272 // the corresponding WebIDL enum in a way that may be incompatible with the 273 // existing data stored in the DOM Cache. You would need to update the Cache 274 // database schema accordingly and adjust the failing static_assert. 275 static_assert(int(HeadersGuardEnum::None) == 0 && 276 int(HeadersGuardEnum::Request) == 1 && 277 int(HeadersGuardEnum::Request_no_cors) == 2 && 278 int(HeadersGuardEnum::Response) == 3 && 279 int(HeadersGuardEnum::Immutable) == 4 && 280 ContiguousEnumSize<HeadersGuardEnum>::value == 5, 281 "HeadersGuardEnum values are as expected"); 282 static_assert(int(ReferrerPolicy::_empty) == 0 && 283 int(ReferrerPolicy::No_referrer) == 1 && 284 int(ReferrerPolicy::No_referrer_when_downgrade) == 2 && 285 int(ReferrerPolicy::Origin) == 3 && 286 int(ReferrerPolicy::Origin_when_cross_origin) == 4 && 287 int(ReferrerPolicy::Unsafe_url) == 5 && 288 int(ReferrerPolicy::Same_origin) == 6 && 289 int(ReferrerPolicy::Strict_origin) == 7 && 290 int(ReferrerPolicy::Strict_origin_when_cross_origin) == 8 && 291 ContiguousEnumSize<ReferrerPolicy>::value == 9, 292 "ReferrerPolicy values are as expected"); 293 static_assert(int(RequestMode::Same_origin) == 0 && 294 int(RequestMode::No_cors) == 1 && 295 int(RequestMode::Cors) == 2 && 296 int(RequestMode::Navigate) == 3 && 297 ContiguousEnumSize<RequestMode>::value == 4, 298 "RequestMode values are as expected"); 299 static_assert(int(RequestCredentials::Omit) == 0 && 300 int(RequestCredentials::Same_origin) == 1 && 301 int(RequestCredentials::Include) == 2 && 302 ContiguousEnumSize<RequestCredentials>::value == 3, 303 "RequestCredentials values are as expected"); 304 static_assert(int(RequestCache::Default) == 0 && 305 int(RequestCache::No_store) == 1 && 306 int(RequestCache::Reload) == 2 && 307 int(RequestCache::No_cache) == 3 && 308 int(RequestCache::Force_cache) == 4 && 309 int(RequestCache::Only_if_cached) == 5 && 310 ContiguousEnumSize<RequestCache>::value == 6, 311 "RequestCache values are as expected"); 312 static_assert(int(RequestRedirect::Follow) == 0 && 313 int(RequestRedirect::Error) == 1 && 314 int(RequestRedirect::Manual) == 2 && 315 ContiguousEnumSize<RequestRedirect>::value == 3, 316 "RequestRedirect values are as expected"); 317 static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 && 318 int(ResponseType::Default) == 2 && 319 int(ResponseType::Error) == 3 && 320 int(ResponseType::Opaque) == 4 && 321 int(ResponseType::Opaqueredirect) == 5 && 322 ContiguousEnumSize<ResponseType>::value == 6, 323 "ResponseType values are as expected"); 324 325 // If the static_asserts below fails, it means that you have changed the 326 // Namespace enum in a way that may be incompatible with the existing data 327 // stored in the DOM Cache. You would need to update the Cache database schema 328 // accordingly and adjust the failing static_assert. 329 static_assert(DEFAULT_NAMESPACE == 0 && CHROME_ONLY_NAMESPACE == 1 && 330 NUMBER_OF_NAMESPACES == 2, 331 "Namespace values are as expected"); 332 333 // If the static_asserts below fails, it means that you have changed the 334 // nsContentPolicy enum in a way that may be incompatible with the existing data 335 // stored in the DOM Cache. You would need to update the Cache database schema 336 // accordingly and adjust the failing static_assert. 337 static_assert( 338 nsIContentPolicy::TYPE_INVALID == 0 && nsIContentPolicy::TYPE_OTHER == 1 && 339 nsIContentPolicy::TYPE_SCRIPT == 2 && 340 nsIContentPolicy::TYPE_IMAGE == 3 && 341 nsIContentPolicy::TYPE_STYLESHEET == 4 && 342 nsIContentPolicy::TYPE_OBJECT == 5 && 343 nsIContentPolicy::TYPE_DOCUMENT == 6 && 344 nsIContentPolicy::TYPE_SUBDOCUMENT == 7 && 345 nsIContentPolicy::TYPE_PING == 10 && 346 nsIContentPolicy::TYPE_XMLHTTPREQUEST == 11 && 347 nsIContentPolicy::TYPE_DTD == 13 && nsIContentPolicy::TYPE_FONT == 14 && 348 nsIContentPolicy::TYPE_MEDIA == 15 && 349 nsIContentPolicy::TYPE_WEBSOCKET == 16 && 350 nsIContentPolicy::TYPE_CSP_REPORT == 17 && 351 nsIContentPolicy::TYPE_XSLT == 18 && 352 nsIContentPolicy::TYPE_BEACON == 19 && 353 nsIContentPolicy::TYPE_FETCH == 20 && 354 nsIContentPolicy::TYPE_IMAGESET == 21 && 355 nsIContentPolicy::TYPE_WEB_MANIFEST == 22 && 356 nsIContentPolicy::TYPE_INTERNAL_SCRIPT == 23 && 357 nsIContentPolicy::TYPE_INTERNAL_WORKER == 24 && 358 nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER == 25 && 359 nsIContentPolicy::TYPE_INTERNAL_EMBED == 26 && 360 nsIContentPolicy::TYPE_INTERNAL_OBJECT == 27 && 361 nsIContentPolicy::TYPE_INTERNAL_FRAME == 28 && 362 nsIContentPolicy::TYPE_INTERNAL_IFRAME == 29 && 363 nsIContentPolicy::TYPE_INTERNAL_AUDIO == 30 && 364 nsIContentPolicy::TYPE_INTERNAL_VIDEO == 31 && 365 nsIContentPolicy::TYPE_INTERNAL_TRACK == 32 && 366 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC == 33 && 367 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34 && 368 nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER == 35 && 369 nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD == 36 && 370 nsIContentPolicy::TYPE_INTERNAL_IMAGE == 37 && 371 nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD == 38 && 372 nsIContentPolicy::TYPE_INTERNAL_STYLESHEET == 39 && 373 nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD == 40 && 374 nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON == 41 && 375 nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS == 42 && 376 nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD == 43 && 377 nsIContentPolicy::TYPE_SPECULATIVE == 44 && 378 nsIContentPolicy::TYPE_INTERNAL_MODULE == 45 && 379 nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD == 46 && 380 nsIContentPolicy::TYPE_INTERNAL_DTD == 47 && 381 nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD == 48 && 382 nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET == 49 && 383 nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET == 50 && 384 nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD == 51 && 385 nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT == 52 && 386 nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT == 53 && 387 nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD == 54 && 388 nsIContentPolicy::TYPE_UA_FONT == 55 && 389 nsIContentPolicy::TYPE_WEB_IDENTITY == 57 && 390 nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE == 58 && 391 nsIContentPolicy::TYPE_WEB_TRANSPORT == 59 && 392 nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC == 60 && 393 nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE == 61 && 394 nsIContentPolicy::TYPE_JSON == 62 && 395 nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD == 63 && 396 nsIContentPolicy::TYPE_END == 64, 397 "nsContentPolicyType values are as expected"); 398 399 namespace { 400 401 using EntryId = int32_t; 402 403 struct IdCount { 404 explicit IdCount(int32_t aId) : mId(aId), mCount(1) {} 405 int32_t mId; 406 int32_t mCount; 407 }; 408 409 using EntryIds = AutoTArray<EntryId, 256>; 410 411 static Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn, 412 CacheId aCacheId); 413 static Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn, 414 CacheId aCacheId, 415 const CacheRequest& aRequest, 416 const CacheQueryParams& aParams, 417 uint32_t aMaxResults = UINT32_MAX); 418 static Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn, 419 const CacheRequest& aRequest, 420 EntryId entryId); 421 // Returns a success tuple containing the deleted body ids, deleted security ids 422 // and deleted padding size. 423 static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, 424 nsresult> 425 DeleteEntries(mozIStorageConnection& aConn, 426 const nsTArray<EntryId>& aEntryIdList); 427 428 static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, 429 nsresult> 430 DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId); 431 432 static Result<int32_t, nsresult> InsertSecurityInfo( 433 mozIStorageConnection& aConn, nsICryptoHash& aCrypto, 434 nsITransportSecurityInfo* aSecurityInfo); 435 static nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId, 436 int32_t aCount); 437 static nsresult DeleteSecurityInfoList( 438 mozIStorageConnection& aConn, 439 const nsTArray<IdCount>& aDeletedStorageIdList); 440 static nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId, 441 const CacheRequest& aRequest, 442 const nsID* aRequestBodyId, 443 const CacheResponse& aResponse, 444 const nsID* aResponseBodyId); 445 static Result<SavedResponse, nsresult> ReadResponse( 446 mozIStorageConnection& aConn, EntryId aEntryId); 447 static Result<SavedRequest, nsresult> ReadRequest(mozIStorageConnection& aConn, 448 EntryId aEntryId); 449 450 static void AppendListParamsToQuery(nsACString& aQuery, size_t aLen); 451 static nsresult BindListParamsToQuery(mozIStorageStatement& aState, 452 const Span<const EntryId>& aEntryIdList); 453 static nsresult BindId(mozIStorageStatement& aState, const nsACString& aName, 454 const nsID* aId); 455 static Result<nsID, nsresult> ExtractId(mozIStorageStatement& aState, 456 uint32_t aPos); 457 static Result<NotNull<nsCOMPtr<mozIStorageStatement>>, nsresult> 458 CreateAndBindKeyStatement(mozIStorageConnection& aConn, 459 const char* aQueryFormat, const nsAString& aKey); 460 static Result<nsAutoCString, nsresult> HashCString(nsICryptoHash& aCrypto, 461 const nsACString& aIn); 462 Result<int32_t, nsresult> GetEffectiveSchemaVersion( 463 mozIStorageConnection& aConn); 464 nsresult Validate(mozIStorageConnection& aConn); 465 nsresult Migrate(nsIFile& aDBDir, mozIStorageConnection& aConn); 466 } // namespace 467 468 class MOZ_RAII AutoDisableForeignKeyChecking { 469 public: 470 explicit AutoDisableForeignKeyChecking(mozIStorageConnection* aConn) 471 : mConn(aConn), mForeignKeyCheckingDisabled(false) { 472 QM_TRY_INSPECT(const auto& state, 473 quota::CreateAndExecuteSingleStepStatement( 474 *mConn, "PRAGMA foreign_keys;"_ns), 475 QM_VOID); 476 477 QM_TRY_INSPECT(const int32_t& mode, 478 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0), QM_VOID); 479 480 if (mode) { 481 QM_WARNONLY_TRY(MOZ_TO_RESULT(mConn->ExecuteSimpleSQL( 482 "PRAGMA foreign_keys = OFF;"_ns)) 483 .andThen([this](const auto) -> Result<Ok, nsresult> { 484 mForeignKeyCheckingDisabled = true; 485 return Ok{}; 486 })); 487 } 488 } 489 490 ~AutoDisableForeignKeyChecking() { 491 if (mForeignKeyCheckingDisabled) { 492 QM_WARNONLY_TRY(QM_TO_RESULT( 493 mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); 494 } 495 } 496 497 private: 498 nsCOMPtr<mozIStorageConnection> mConn; 499 bool mForeignKeyCheckingDisabled; 500 }; 501 502 nsresult CreateOrMigrateSchema(nsIFile& aDBDir, mozIStorageConnection& aConn) { 503 MOZ_ASSERT(!NS_IsMainThread()); 504 505 QM_TRY_UNWRAP(int32_t schemaVersion, GetEffectiveSchemaVersion(aConn)); 506 507 if (schemaVersion == kLatestSchemaVersion) { 508 // We already have the correct schema version. Validate it matches 509 // our expected schema and then proceed. 510 QM_TRY(MOZ_TO_RESULT(Validate(aConn))); 511 512 return NS_OK; 513 } 514 515 // Turn off checking foreign keys before starting a transaction, and restore 516 // it once we're done. 517 AutoDisableForeignKeyChecking restoreForeignKeyChecking(&aConn); 518 mozStorageTransaction trans(&aConn, false, 519 mozIStorageConnection::TRANSACTION_IMMEDIATE); 520 521 QM_TRY(MOZ_TO_RESULT(trans.Start())); 522 523 const bool migrating = schemaVersion != 0; 524 525 if (migrating) { 526 // A schema exists, but its not the current version. Attempt to 527 // migrate it to our new schema. 528 QM_TRY(MOZ_TO_RESULT(Migrate(aDBDir, aConn))); 529 } else { 530 // There is no schema installed. Create the database from scratch. 531 QM_TRY( 532 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableCaches)))); 533 QM_TRY(MOZ_TO_RESULT( 534 aConn.ExecuteSimpleSQL(nsLiteralCString(kTableSecurityInfo)))); 535 QM_TRY(MOZ_TO_RESULT( 536 aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexSecurityInfoHash)))); 537 QM_TRY( 538 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableEntries)))); 539 QM_TRY(MOZ_TO_RESULT( 540 aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexEntriesRequest)))); 541 QM_TRY(MOZ_TO_RESULT( 542 aConn.ExecuteSimpleSQL(nsLiteralCString(kTableRequestHeaders)))); 543 QM_TRY(MOZ_TO_RESULT( 544 aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseHeaders)))); 545 QM_TRY(MOZ_TO_RESULT( 546 aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexResponseHeadersName)))); 547 QM_TRY(MOZ_TO_RESULT( 548 aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseUrlList)))); 549 QM_TRY( 550 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableStorage)))); 551 QM_TRY(MOZ_TO_RESULT( 552 aConn.ExecuteSimpleSQL(nsLiteralCString(kTableUsageInfo)))); 553 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 554 nsLiteralCString("INSERT INTO usage_info VALUES(1, 0);")))); 555 QM_TRY(MOZ_TO_RESULT( 556 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesInsert)))); 557 QM_TRY(MOZ_TO_RESULT( 558 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesUpdate)))); 559 QM_TRY(MOZ_TO_RESULT( 560 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesDelete)))); 561 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(kLatestSchemaVersion))); 562 QM_TRY_UNWRAP(schemaVersion, GetEffectiveSchemaVersion(aConn)); 563 } 564 565 QM_TRY(MOZ_TO_RESULT(Validate(aConn))); 566 QM_TRY(MOZ_TO_RESULT(trans.Commit())); 567 568 if (migrating) { 569 // Migrations happen infrequently and reflect a chance in DB structure. 570 // This is a good time to rebuild the database. It also helps catch 571 // if a new migration is incorrect by fast failing on the corruption. 572 // Unfortunately, this must be performed outside of the transaction. 573 574 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("VACUUM"_ns))); 575 } 576 577 return NS_OK; 578 } 579 580 nsresult InitializeConnection(mozIStorageConnection& aConn) { 581 MOZ_ASSERT(!NS_IsMainThread()); 582 583 // This function needs to perform per-connection initialization tasks that 584 // need to happen regardless of the schema. 585 586 // Note, the default encoding of UTF-8 is preferred. mozStorage does all 587 // the work necessary to convert UTF-16 nsString values for us. We don't 588 // need ordering and the binary equality operations are correct. So, do 589 // NOT set PRAGMA encoding to UTF-16. 590 591 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString( 592 // Use a smaller page size to improve perf/footprint; default is too large 593 "PRAGMA page_size = %u; " 594 // Enable auto_vacuum; this must happen after page_size and before WAL 595 "PRAGMA auto_vacuum = INCREMENTAL; " 596 "PRAGMA foreign_keys = ON; ", 597 kPageSize)))); 598 599 // Limit fragmentation by growing the database by many pages at once. 600 QM_TRY(QM_OR_ELSE_WARN_IF( 601 // Expression. 602 MOZ_TO_RESULT(aConn.SetGrowthIncrement(kGrowthSize, ""_ns)), 603 // Predicate. 604 IsSpecificError<NS_ERROR_FILE_TOO_BIG>, 605 // Fallback. 606 ErrToDefaultOk<>)); 607 608 // Enable WAL journaling. This must be performed in a separate transaction 609 // after changing the page_size and enabling auto_vacuum. 610 // Note there is a default journal_size_limit set by mozStorage. 611 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString( 612 // WAL journal can grow to given number of *pages* 613 "PRAGMA wal_autocheckpoint = %u; " 614 // WAL must be enabled at the end to allow page size to be changed, etc. 615 "PRAGMA journal_mode = WAL; ", 616 kWalAutoCheckpointPages)))); 617 618 // Verify that we successfully set the vacuum mode to incremental. It 619 // is very easy to put the database in a state where the auto_vacuum 620 // pragma above fails silently. 621 #ifdef DEBUG 622 { 623 QM_TRY_INSPECT(const auto& state, 624 quota::CreateAndExecuteSingleStepStatement( 625 aConn, "PRAGMA auto_vacuum;"_ns)); 626 627 QM_TRY_INSPECT(const int32_t& mode, 628 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 629 630 // integer value 2 is incremental mode 631 QM_TRY(OkIf(mode == 2), NS_ERROR_UNEXPECTED); 632 } 633 #endif 634 635 return NS_OK; 636 } 637 638 Result<CacheId, nsresult> CreateCacheId(mozIStorageConnection& aConn) { 639 MOZ_ASSERT(!NS_IsMainThread()); 640 641 QM_TRY(MOZ_TO_RESULT( 642 aConn.ExecuteSimpleSQL("INSERT INTO caches DEFAULT VALUES;"_ns))); 643 644 QM_TRY_INSPECT(const auto& state, 645 quota::CreateAndExecuteSingleStepStatement< 646 quota::SingleStepResult::ReturnNullIfNoResult>( 647 aConn, "SELECT last_insert_rowid()"_ns)); 648 649 QM_TRY(OkIf(state), Err(NS_ERROR_UNEXPECTED)); 650 651 QM_TRY_INSPECT(const CacheId& id, 652 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt64, 0)); 653 654 return id; 655 } 656 657 Result<DeletionInfo, nsresult> DeleteCacheId(mozIStorageConnection& aConn, 658 CacheId aCacheId) { 659 MOZ_ASSERT(!NS_IsMainThread()); 660 661 // XXX only deletedBodyIdList needs to be non-const 662 QM_TRY_UNWRAP( 663 (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]), 664 DeleteAllCacheEntries(aConn, aCacheId)); 665 666 QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList))); 667 668 // Delete the remainder of the cache using cascade semantics. 669 QM_TRY_INSPECT(const auto& state, 670 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 671 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 672 "DELETE FROM caches WHERE id=:id;"_ns)); 673 674 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("id"_ns, aCacheId))); 675 676 QM_TRY(MOZ_TO_RESULT(state->Execute())); 677 678 return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize}; 679 } 680 681 Result<AutoTArray<CacheId, 8>, nsresult> FindOrphanedCacheIds( 682 mozIStorageConnection& aConn) { 683 QM_TRY_INSPECT(const auto& state, 684 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 685 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 686 "SELECT id FROM caches " 687 "WHERE id NOT IN (SELECT cache_id from storage);"_ns)); 688 689 QM_TRY_RETURN( 690 (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 8>>( 691 *state, [](auto& stmt) { 692 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 693 }))); 694 } 695 696 Result<int64_t, nsresult> FindOverallPaddingSize(mozIStorageConnection& aConn) { 697 QM_TRY_INSPECT(const auto& state, 698 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 699 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 700 "SELECT response_padding_size FROM entries " 701 "WHERE response_padding_size IS NOT NULL;"_ns)); 702 703 int64_t overallPaddingSize = 0; 704 705 QM_TRY(quota::CollectWhileHasResult( 706 *state, [&overallPaddingSize](auto& stmt) -> Result<Ok, nsresult> { 707 QM_TRY_INSPECT(const int64_t& padding_size, 708 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 709 710 MOZ_DIAGNOSTIC_ASSERT(padding_size >= 0); 711 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - padding_size >= overallPaddingSize); 712 overallPaddingSize += padding_size; 713 714 return Ok{}; 715 })); 716 717 return overallPaddingSize; 718 } 719 720 Result<int64_t, nsresult> GetTotalDiskUsage(mozIStorageConnection& aConn) { 721 QM_TRY_INSPECT( 722 const auto& state, 723 quota::CreateAndExecuteSingleStepStatement( 724 aConn, "SELECT total_disk_usage FROM usage_info WHERE id = 1;"_ns)); 725 726 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0)); 727 } 728 729 Result<nsTHashSet<nsID>, nsresult> GetKnownBodyIds( 730 mozIStorageConnection& aConn) { 731 MOZ_ASSERT(!NS_IsMainThread()); 732 733 int32_t numEntries = 0; 734 { 735 QM_TRY_INSPECT(const auto& cnt, 736 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 737 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 738 "SELECT COUNT(*) FROM entries;"_ns)); 739 740 QM_TRY(quota::CollectWhileHasResult( 741 *cnt, [&numEntries](auto& stmt) -> Result<Ok, nsresult> { 742 QM_TRY(MOZ_TO_RESULT(stmt.GetInt32(0, &numEntries))); 743 744 return Ok{}; 745 })); 746 } 747 748 // Each row can have 0 to 2 nsID values, prepare for the maximum. 749 nsTHashSet<nsID> idSet(numEntries * 2); 750 751 QM_TRY_INSPECT( 752 const auto& state, 753 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 754 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 755 "SELECT request_body_id, response_body_id FROM entries;"_ns)); 756 757 QM_TRY(quota::CollectWhileHasResult( 758 *state, [&idSet](auto& stmt) -> Result<Ok, nsresult> { 759 // extract 0 to 2 nsID structs per row 760 for (uint32_t i = 0; i < 2; ++i) { 761 QM_TRY_INSPECT(const bool& isNull, 762 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i)); 763 764 if (!isNull) { 765 QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i)); 766 767 idSet.Insert(id); 768 } 769 } 770 771 return Ok{}; 772 })); 773 774 return std::move(idSet); 775 } 776 777 Result<Maybe<SavedResponse>, nsresult> CacheMatch( 778 mozIStorageConnection& aConn, CacheId aCacheId, 779 const CacheRequest& aRequest, const CacheQueryParams& aParams) { 780 MOZ_ASSERT(!NS_IsMainThread()); 781 782 QM_TRY_INSPECT(const auto& matches, 783 QueryCache(aConn, aCacheId, aRequest, aParams, 1)); 784 785 if (matches.IsEmpty()) { 786 return Maybe<SavedResponse>(); 787 } 788 789 QM_TRY_UNWRAP(auto response, ReadResponse(aConn, matches[0])); 790 791 response.mCacheId = aCacheId; 792 793 return Some(std::move(response)); 794 } 795 796 Result<nsTArray<SavedResponse>, nsresult> CacheMatchAll( 797 mozIStorageConnection& aConn, CacheId aCacheId, 798 const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) { 799 MOZ_ASSERT(!NS_IsMainThread()); 800 801 QM_TRY_INSPECT( 802 const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] { 803 if (aMaybeRequest.isNothing()) { 804 QM_TRY_RETURN(QueryAll(aConn, aCacheId)); 805 } 806 807 QM_TRY_RETURN( 808 QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams)); 809 }())); 810 811 // TODO: replace this with a bulk load using SQL IN clause (bug 1110458) 812 QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr( 813 matches, 814 [&aConn, aCacheId](const auto match) -> Result<SavedResponse, nsresult> { 815 QM_TRY_UNWRAP(auto savedResponse, ReadResponse(aConn, match)); 816 817 savedResponse.mCacheId = aCacheId; 818 return savedResponse; 819 }, 820 fallible)); 821 } 822 823 Result<DeletionInfo, nsresult> CachePut(mozIStorageConnection& aConn, 824 CacheId aCacheId, 825 const CacheRequest& aRequest, 826 const nsID* aRequestBodyId, 827 const CacheResponse& aResponse, 828 const nsID* aResponseBodyId) { 829 MOZ_ASSERT(!NS_IsMainThread()); 830 831 QM_TRY_INSPECT( 832 const auto& matches, 833 QueryCache(aConn, aCacheId, aRequest, 834 CacheQueryParams(false, false, false, false, u""_ns))); 835 836 // XXX only deletedBodyIdList needs to be non-const 837 QM_TRY_UNWRAP( 838 (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]), 839 DeleteEntries(aConn, matches)); 840 841 QM_TRY(MOZ_TO_RESULT(InsertEntry(aConn, aCacheId, aRequest, aRequestBodyId, 842 aResponse, aResponseBodyId))); 843 844 // Delete the security values after doing the insert to avoid churning 845 // the security table when its not necessary. 846 QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList))); 847 848 return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize}; 849 } 850 851 Result<Maybe<DeletionInfo>, nsresult> CacheDelete( 852 mozIStorageConnection& aConn, CacheId aCacheId, 853 const CacheRequest& aRequest, const CacheQueryParams& aParams) { 854 MOZ_ASSERT(!NS_IsMainThread()); 855 856 QM_TRY_INSPECT(const auto& matches, 857 QueryCache(aConn, aCacheId, aRequest, aParams)); 858 859 if (matches.IsEmpty()) { 860 return Maybe<DeletionInfo>(); 861 } 862 863 // XXX only deletedBodyIdList needs to be non-const 864 QM_TRY_UNWRAP( 865 (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]), 866 DeleteEntries(aConn, matches)); 867 868 QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList))); 869 870 return Some(DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize}); 871 } 872 873 Result<nsTArray<SavedRequest>, nsresult> CacheKeys( 874 mozIStorageConnection& aConn, CacheId aCacheId, 875 const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) { 876 MOZ_ASSERT(!NS_IsMainThread()); 877 878 QM_TRY_INSPECT( 879 const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] { 880 if (aMaybeRequest.isNothing()) { 881 QM_TRY_RETURN(QueryAll(aConn, aCacheId)); 882 } 883 884 QM_TRY_RETURN( 885 QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams)); 886 }())); 887 888 // TODO: replace this with a bulk load using SQL IN clause (bug 1110458) 889 QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr( 890 matches, 891 [&aConn, aCacheId](const auto match) -> Result<SavedRequest, nsresult> { 892 QM_TRY_UNWRAP(auto savedRequest, ReadRequest(aConn, match)); 893 894 savedRequest.mCacheId = aCacheId; 895 return savedRequest; 896 }, 897 fallible)); 898 } 899 900 Result<Maybe<SavedResponse>, nsresult> StorageMatch( 901 mozIStorageConnection& aConn, Namespace aNamespace, 902 const CacheRequest& aRequest, const CacheQueryParams& aParams) { 903 MOZ_ASSERT(!NS_IsMainThread()); 904 905 // If we are given a cache to check, then simply find its cache ID 906 // and perform the match. 907 if (aParams.cacheNameSet()) { 908 QM_TRY_INSPECT(const auto& maybeCacheId, 909 StorageGetCacheId(aConn, aNamespace, aParams.cacheName())); 910 if (maybeCacheId.isNothing()) { 911 return Maybe<SavedResponse>(); 912 } 913 914 return CacheMatch(aConn, maybeCacheId.ref(), aRequest, aParams); 915 } 916 917 // Otherwise we need to get a list of all the cache IDs in this namespace. 918 919 QM_TRY_INSPECT(const auto& state, 920 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 921 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 922 "SELECT cache_id FROM storage WHERE " 923 "namespace=:namespace ORDER BY rowid;"_ns)); 924 925 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace))); 926 927 QM_TRY_INSPECT( 928 const auto& cacheIdList, 929 (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 32>>( 930 *state, [](auto& stmt) { 931 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0)); 932 }))); 933 934 // Now try to find a match in each cache in order 935 for (const auto cacheId : cacheIdList) { 936 QM_TRY_UNWRAP(auto matchedResponse, 937 CacheMatch(aConn, cacheId, aRequest, aParams)); 938 939 if (matchedResponse.isSome()) { 940 return matchedResponse; 941 } 942 } 943 944 return Maybe<SavedResponse>(); 945 } 946 947 Result<Maybe<CacheId>, nsresult> StorageGetCacheId(mozIStorageConnection& aConn, 948 Namespace aNamespace, 949 const nsAString& aKey) { 950 MOZ_ASSERT(!NS_IsMainThread()); 951 952 // How we constrain the key column depends on the value of our key. Use 953 // a format string for the query and let CreateAndBindKeyStatement() fill 954 // it in for us. 955 const char* const query = 956 "SELECT cache_id FROM storage " 957 "WHERE namespace=:namespace AND %s " 958 "ORDER BY rowid;"; 959 960 QM_TRY_INSPECT(const auto& state, 961 CreateAndBindKeyStatement(aConn, query, aKey)); 962 963 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace))); 964 965 QM_TRY_INSPECT(const bool& hasMoreData, 966 MOZ_TO_RESULT_INVOKE_MEMBER(*state, ExecuteStep)); 967 968 if (!hasMoreData) { 969 return Maybe<CacheId>(); 970 } 971 972 QM_TRY_RETURN( 973 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0).map(Some<CacheId>)); 974 } 975 976 nsresult StoragePutCache(mozIStorageConnection& aConn, Namespace aNamespace, 977 const nsAString& aKey, CacheId aCacheId) { 978 MOZ_ASSERT(!NS_IsMainThread()); 979 980 QM_TRY_INSPECT(const auto& state, 981 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 982 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 983 "INSERT INTO storage (namespace, key, cache_id) " 984 "VALUES (:namespace, :key, :cache_id);"_ns)); 985 986 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace))); 987 QM_TRY(MOZ_TO_RESULT(state->BindStringAsBlobByName("key"_ns, aKey))); 988 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 989 QM_TRY(MOZ_TO_RESULT(state->Execute())); 990 991 return NS_OK; 992 } 993 994 nsresult StorageForgetCache(mozIStorageConnection& aConn, Namespace aNamespace, 995 const nsAString& aKey) { 996 MOZ_ASSERT(!NS_IsMainThread()); 997 998 // How we constrain the key column depends on the value of our key. Use 999 // a format string for the query and let CreateAndBindKeyStatement() fill 1000 // it in for us. 1001 const char* const query = 1002 "DELETE FROM storage WHERE namespace=:namespace AND %s;"; 1003 1004 QM_TRY_INSPECT(const auto& state, 1005 CreateAndBindKeyStatement(aConn, query, aKey)); 1006 1007 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace))); 1008 1009 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1010 1011 return NS_OK; 1012 } 1013 1014 Result<nsTArray<nsString>, nsresult> StorageGetKeys( 1015 mozIStorageConnection& aConn, Namespace aNamespace) { 1016 MOZ_ASSERT(!NS_IsMainThread()); 1017 1018 QM_TRY_INSPECT( 1019 const auto& state, 1020 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1021 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1022 "SELECT key FROM storage WHERE namespace=:namespace ORDER BY rowid;"_ns)); 1023 1024 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace))); 1025 1026 QM_TRY_RETURN(quota::CollectElementsWhileHasResult(*state, [](auto& stmt) { 1027 QM_TRY_RETURN( 1028 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetBlobAsString, 0)); 1029 })); 1030 } 1031 1032 namespace { 1033 1034 Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn, 1035 CacheId aCacheId) { 1036 MOZ_ASSERT(!NS_IsMainThread()); 1037 1038 QM_TRY_INSPECT( 1039 const auto& state, 1040 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1041 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1042 "SELECT id FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns)); 1043 1044 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 1045 1046 QM_TRY_RETURN((quota::CollectElementsWhileHasResultTyped<EntryIds>( 1047 *state, [](auto& stmt) { 1048 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0)); 1049 }))); 1050 } 1051 1052 Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn, 1053 CacheId aCacheId, 1054 const CacheRequest& aRequest, 1055 const CacheQueryParams& aParams, 1056 uint32_t aMaxResults) { 1057 MOZ_ASSERT(!NS_IsMainThread()); 1058 MOZ_DIAGNOSTIC_ASSERT(aMaxResults > 0); 1059 1060 if (!aParams.ignoreMethod() && 1061 !aRequest.method().LowerCaseEqualsLiteral("get")) { 1062 return Result<EntryIds, nsresult>{std::in_place}; 1063 } 1064 1065 nsAutoCString query( 1066 "SELECT id, COUNT(response_headers.name) AS vary_count, response_type " 1067 "FROM entries " 1068 "LEFT OUTER JOIN response_headers ON " 1069 "entries.id=response_headers.entry_id " 1070 "AND response_headers.name='vary' COLLATE NOCASE " 1071 "WHERE entries.cache_id=:cache_id " 1072 "AND entries.request_url_no_query_hash=:url_no_query_hash "); 1073 1074 if (!aParams.ignoreSearch()) { 1075 query.AppendLiteral("AND entries.request_url_query_hash=:url_query_hash "); 1076 } 1077 1078 query.AppendLiteral("AND entries.request_url_no_query=:url_no_query "); 1079 1080 if (!aParams.ignoreSearch()) { 1081 query.AppendLiteral("AND entries.request_url_query=:url_query "); 1082 } 1083 1084 query.AppendLiteral("GROUP BY entries.id ORDER BY entries.id;"); 1085 1086 QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1087 nsCOMPtr<mozIStorageStatement>, aConn, 1088 CreateStatement, query)); 1089 1090 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 1091 1092 QM_TRY_INSPECT(const auto& crypto, 1093 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>, 1094 MOZ_SELECT_OVERLOAD(do_CreateInstance), 1095 NS_CRYPTO_HASH_CONTRACTID)); 1096 1097 QM_TRY_INSPECT(const auto& urlWithoutQueryHash, 1098 HashCString(*crypto, aRequest.urlWithoutQuery())); 1099 1100 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("url_no_query_hash"_ns, 1101 urlWithoutQueryHash))); 1102 1103 if (!aParams.ignoreSearch()) { 1104 QM_TRY_INSPECT(const auto& urlQueryHash, 1105 HashCString(*crypto, aRequest.urlQuery())); 1106 1107 QM_TRY(MOZ_TO_RESULT( 1108 state->BindUTF8StringAsBlobByName("url_query_hash"_ns, urlQueryHash))); 1109 } 1110 1111 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName( 1112 "url_no_query"_ns, aRequest.urlWithoutQuery()))); 1113 1114 if (!aParams.ignoreSearch()) { 1115 QM_TRY(MOZ_TO_RESULT( 1116 state->BindUTF8StringByName("url_query"_ns, aRequest.urlQuery()))); 1117 } 1118 1119 EntryIds entryIdList; 1120 1121 QM_TRY(CollectWhile( 1122 [&state, &entryIdList, aMaxResults]() -> Result<bool, nsresult> { 1123 if (entryIdList.Length() == aMaxResults) { 1124 return false; 1125 } 1126 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(state, ExecuteStep)); 1127 }, 1128 [&state, &entryIdList, &aParams, &aConn, 1129 &aRequest]() -> Result<Ok, nsresult> { 1130 QM_TRY_INSPECT(const EntryId& entryId, 1131 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 0)); 1132 1133 QM_TRY_INSPECT(const int32_t& varyCount, 1134 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 1)); 1135 1136 QM_TRY_INSPECT(const int32_t& responseType, 1137 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 2)); 1138 1139 auto ignoreVary = 1140 aParams.ignoreVary() || 1141 responseType == static_cast<int>(ResponseType::Opaque); 1142 1143 if (!ignoreVary && varyCount > 0) { 1144 QM_TRY_INSPECT(const bool& matchedByVary, 1145 MatchByVaryHeader(aConn, aRequest, entryId)); 1146 if (!matchedByVary) { 1147 return Ok{}; 1148 } 1149 } 1150 1151 entryIdList.AppendElement(entryId); 1152 1153 return Ok{}; 1154 })); 1155 1156 return entryIdList; 1157 } 1158 1159 Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn, 1160 const CacheRequest& aRequest, 1161 EntryId entryId) { 1162 MOZ_ASSERT(!NS_IsMainThread()); 1163 1164 QM_TRY_INSPECT( 1165 const auto& varyValues, 1166 ([&aConn, entryId]() -> Result<AutoTArray<nsCString, 8>, nsresult> { 1167 QM_TRY_INSPECT( 1168 const auto& state, 1169 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1170 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1171 "SELECT value FROM response_headers " 1172 "WHERE name='vary' COLLATE NOCASE " 1173 "AND entry_id=:entry_id;"_ns)); 1174 1175 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId))); 1176 1177 QM_TRY_RETURN(( 1178 quota::CollectElementsWhileHasResultTyped<AutoTArray<nsCString, 8>>( 1179 *state, [](auto& stmt) { 1180 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1181 nsCString, stmt, GetUTF8String, 0)); 1182 }))); 1183 }())); 1184 1185 // Should not have called this function if this was not the case 1186 MOZ_DIAGNOSTIC_ASSERT(!varyValues.IsEmpty()); 1187 1188 QM_TRY_INSPECT(const auto& state, 1189 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1190 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1191 "SELECT name, value FROM request_headers " 1192 "WHERE entry_id=:entry_id;"_ns)); 1193 1194 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId))); 1195 1196 RefPtr<InternalHeaders> cachedHeaders = 1197 new InternalHeaders(HeadersGuardEnum::None); 1198 1199 QM_TRY(quota::CollectWhileHasResult( 1200 *state, [&cachedHeaders](auto& stmt) -> Result<Ok, nsresult> { 1201 QM_TRY_INSPECT(const auto& name, 1202 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 1203 GetUTF8String, 0)); 1204 QM_TRY_INSPECT(const auto& value, 1205 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt, 1206 GetUTF8String, 1)); 1207 1208 ErrorResult errorResult; 1209 1210 cachedHeaders->Append(name, value, errorResult); 1211 if (errorResult.Failed()) { 1212 return Err(errorResult.StealNSResult()); 1213 } 1214 1215 return Ok{}; 1216 })); 1217 1218 RefPtr<InternalHeaders> queryHeaders = 1219 TypeUtils::ToInternalHeaders(aRequest.headers()); 1220 1221 // Assume the vary headers match until we find a conflict 1222 bool varyHeadersMatch = true; 1223 1224 for (const auto& varyValue : varyValues) { 1225 // Extract the header names inside the Vary header value. 1226 bool bailOut = false; 1227 for (const nsACString& header : 1228 nsCCharSeparatedTokenizer(varyValue, NS_HTTP_HEADER_SEP).ToRange()) { 1229 MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"), 1230 "We should have already caught this in " 1231 "TypeUtils::ToPCacheResponseWithoutBody()"); 1232 1233 ErrorResult errorResult; 1234 nsAutoCString queryValue; 1235 queryHeaders->Get(header, queryValue, errorResult); 1236 if (errorResult.Failed()) { 1237 errorResult.SuppressException(); 1238 MOZ_DIAGNOSTIC_ASSERT(queryValue.IsEmpty()); 1239 } 1240 1241 nsAutoCString cachedValue; 1242 cachedHeaders->Get(header, cachedValue, errorResult); 1243 if (errorResult.Failed()) { 1244 errorResult.SuppressException(); 1245 MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty()); 1246 } 1247 1248 if (queryValue != cachedValue) { 1249 varyHeadersMatch = false; 1250 bailOut = true; 1251 break; 1252 } 1253 } 1254 1255 if (bailOut) { 1256 break; 1257 } 1258 } 1259 1260 return varyHeadersMatch; 1261 } 1262 1263 static nsresult SelectAndDeleteEntriesInternal( 1264 mozIStorageConnection& aConn, const Span<const EntryId>& aEntryIdList, 1265 nsTArray<nsID>& aDeletedBodyIdListOut, 1266 nsTArray<IdCount>& aDeletedSecurityIdListOut, 1267 int64_t& aDeletedPaddingSizeOut) { 1268 nsAutoCString query( 1269 "SELECT " 1270 "request_body_id, " 1271 "response_body_id, " 1272 "response_security_info_id, " 1273 "response_padding_size " 1274 "FROM entries WHERE id IN ("); 1275 1276 AppendListParamsToQuery(query, aEntryIdList.Length()); 1277 query.AppendLiteral(")"); 1278 1279 QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1280 nsCOMPtr<mozIStorageStatement>, aConn, 1281 CreateStatement, query)); 1282 1283 QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList))); 1284 1285 int64_t overallPaddingSize = 0; 1286 1287 QM_TRY(quota::CollectWhileHasResult( 1288 *state, 1289 [&overallPaddingSize, &aDeletedBodyIdListOut, 1290 &aDeletedSecurityIdListOut](auto& stmt) -> Result<Ok, nsresult> { 1291 // extract 0 to 2 nsID structs per row 1292 for (uint32_t i = 0; i < 2; ++i) { 1293 QM_TRY_INSPECT(const bool& isNull, 1294 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i)); 1295 1296 if (!isNull) { 1297 QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i)); 1298 1299 aDeletedBodyIdListOut.AppendElement(id); 1300 } 1301 } 1302 1303 { // and then a possible third entry for the security id 1304 QM_TRY_INSPECT(const bool& isNull, 1305 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2)); 1306 1307 if (!isNull) { 1308 QM_TRY_INSPECT(const int32_t& securityId, 1309 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2)); 1310 1311 // XXXtt: Consider using map for aDeletedSecuityIdListOut. 1312 auto foundIt = 1313 std::find_if(aDeletedSecurityIdListOut.begin(), 1314 aDeletedSecurityIdListOut.end(), 1315 [securityId](const auto& deletedSecurityId) { 1316 return deletedSecurityId.mId == securityId; 1317 }); 1318 1319 if (foundIt == aDeletedSecurityIdListOut.end()) { 1320 // Add a new entry for this ID with a count of 1, if it's not in 1321 // the list 1322 aDeletedSecurityIdListOut.AppendElement(IdCount(securityId)); 1323 } else { 1324 // Otherwise, increment the count for this ID 1325 foundIt->mCount += 1; 1326 } 1327 } 1328 } 1329 1330 { 1331 // It's possible to have null padding size for non-opaque response 1332 QM_TRY_INSPECT(const bool& isNull, 1333 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3)); 1334 1335 if (!isNull) { 1336 QM_TRY_INSPECT(const int64_t& paddingSize, 1337 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3)); 1338 1339 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); 1340 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - overallPaddingSize >= 1341 paddingSize); 1342 overallPaddingSize += paddingSize; 1343 } 1344 } 1345 1346 return Ok{}; 1347 })); 1348 1349 aDeletedPaddingSizeOut += overallPaddingSize; 1350 1351 // Dependent records removed via ON DELETE CASCADE 1352 1353 query = "DELETE FROM entries WHERE id IN ("_ns; 1354 AppendListParamsToQuery(query, aEntryIdList.Length()); 1355 query.AppendLiteral(")"); 1356 1357 { 1358 QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1359 nsCOMPtr<mozIStorageStatement>, aConn, 1360 CreateStatement, query)); 1361 1362 QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList))); 1363 1364 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1365 } 1366 1367 return NS_OK; 1368 } 1369 1370 static nsresult DeleteEntriesInternal( 1371 mozIStorageConnection& aConn, const nsTArray<EntryId>& aEntryIdList, 1372 nsTArray<nsID>& aDeletedBodyIdListOut, 1373 nsTArray<IdCount>& aDeletedSecurityIdListOut, 1374 int64_t& aDeletedPaddingSizeOut, uint32_t aPos, uint32_t aLen) { 1375 MOZ_ASSERT(!NS_IsMainThread()); 1376 1377 if (aEntryIdList.IsEmpty()) { 1378 return NS_OK; 1379 } 1380 1381 MOZ_DIAGNOSTIC_ASSERT(aPos < aEntryIdList.Length()); 1382 1383 auto remaining = aLen; 1384 uint32_t currPos = 0; 1385 1386 do { 1387 // Sqlite limits the number of entries allowed for an IN clause, 1388 // so split up larger operations. 1389 auto currLen = std::min(kMaxEntriesPerStatement, remaining); 1390 1391 SelectAndDeleteEntriesInternal( 1392 aConn, Span<const EntryId>(aEntryIdList.Elements() + currPos, currLen), 1393 aDeletedBodyIdListOut, aDeletedSecurityIdListOut, 1394 aDeletedPaddingSizeOut); 1395 1396 remaining -= currLen; 1397 currPos += currLen; 1398 1399 } while (remaining > 0); 1400 1401 return NS_OK; 1402 } 1403 1404 Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult> 1405 DeleteEntries(mozIStorageConnection& aConn, 1406 const nsTArray<EntryId>& aEntryIdList) { 1407 auto result = 1408 std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0}); 1409 1410 QM_TRY(MOZ_TO_RESULT(DeleteEntriesInternal( 1411 aConn, aEntryIdList, std::get<0>(result), std::get<1>(result), 1412 std::get<2>(result), 0, aEntryIdList.Length()))); 1413 1414 return result; 1415 } 1416 1417 Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult> 1418 DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId) { 1419 auto result = 1420 std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0}); 1421 auto& deletedBodyIdList = std::get<0>(result); 1422 auto& deletedSecurityIdList = std::get<1>(result); 1423 auto& deletedPaddingSize = std::get<2>(result); 1424 1425 // XXX: We could create a query string with aggregation that would generate 1426 // a single summary result such that we don't have to go through each row 1427 // just to aggregate the result. This method could become much more 1428 // performant. 1429 // 1430 // The columns could look like: 1431 // 1432 // GROUP_CONCAT(request_body_id || ',' || response_body_id), 1433 // GROUP_CONCAT(response_security_info_id), 1434 // SUM(response_padding_size) 1435 // 1436 // strtok the result row to generate the desired output to fill-in 1437 // deletedBodyIdList, deletedSecurityIdList and deletedPaddingSize 1438 // 1439 // I am not sure about the memory requirements for such operation; 1440 // it will all depend upon the result filtered by the `cache_id`. 1441 nsAutoCString query( 1442 "SELECT " 1443 "request_body_id, " 1444 "response_body_id, " 1445 "response_security_info_id, " 1446 "response_padding_size " 1447 "FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns); 1448 1449 QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1450 nsCOMPtr<mozIStorageStatement>, aConn, 1451 CreateStatement, query)); 1452 1453 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 1454 1455 QM_TRY(quota::CollectWhileHasResult( 1456 *state, 1457 [&deletedPaddingSize, &deletedBodyIdList, 1458 &deletedSecurityIdList](auto& stmt) -> Result<Ok, nsresult> { 1459 // extract 0 to 2 nsID structs per row 1460 for (uint32_t i = 0; i < 2; ++i) { 1461 QM_TRY_INSPECT(const bool& isNull, 1462 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i)); 1463 1464 if (!isNull) { 1465 QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i)); 1466 1467 deletedBodyIdList.AppendElement(id); 1468 } 1469 } 1470 1471 { // and then a possible third entry for the security id 1472 QM_TRY_INSPECT(const bool& isNull, 1473 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2)); 1474 1475 if (!isNull) { 1476 QM_TRY_INSPECT(const int32_t& securityId, 1477 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2)); 1478 1479 // XXXtt: Consider using map for aDeletedSecuityIdListOut. 1480 auto foundIt = std::find_if( 1481 deletedSecurityIdList.begin(), deletedSecurityIdList.end(), 1482 [securityId](const auto& deletedSecurityId) { 1483 return deletedSecurityId.mId == securityId; 1484 }); 1485 1486 if (foundIt == deletedSecurityIdList.end()) { 1487 // Add a new entry for this ID with a count of 1, if it's not in 1488 // the list 1489 deletedSecurityIdList.AppendElement(IdCount(securityId)); 1490 } else { 1491 // Otherwise, increment the count for this ID 1492 foundIt->mCount += 1; 1493 } 1494 } 1495 } 1496 1497 { 1498 // It's possible to have null padding size for non-opaque response 1499 QM_TRY_INSPECT(const bool& isNull, 1500 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3)); 1501 1502 if (!isNull) { 1503 QM_TRY_INSPECT(const int64_t& paddingSize, 1504 MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3)); 1505 1506 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); 1507 1508 // Assert overflow 1509 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - deletedPaddingSize >= 1510 paddingSize); 1511 1512 deletedPaddingSize += paddingSize; 1513 } 1514 } 1515 1516 return Ok{}; 1517 })); 1518 1519 // Dependent records removed via ON DELETE CASCADE 1520 1521 query = "DELETE FROM entries WHERE cache_id=:cache_id"_ns; 1522 1523 { 1524 QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1525 nsCOMPtr<mozIStorageStatement>, aConn, 1526 CreateStatement, query)); 1527 1528 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 1529 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1530 } 1531 1532 return result; 1533 } 1534 1535 Result<int32_t, nsresult> InsertSecurityInfo( 1536 mozIStorageConnection& aConn, nsICryptoHash& aCrypto, 1537 nsITransportSecurityInfo* aSecurityInfo) { 1538 MOZ_DIAGNOSTIC_ASSERT(aSecurityInfo); 1539 if (!aSecurityInfo) { 1540 return Err(NS_ERROR_FAILURE); 1541 } 1542 nsCString data; 1543 nsresult rv = aSecurityInfo->ToString(data); 1544 if (NS_FAILED(rv)) { 1545 return Err(rv); 1546 } 1547 1548 // We want to use an index to find existing security blobs, but indexing 1549 // the full blob would be quite expensive. Instead, we index a small 1550 // hash value. Calculate this hash as the first 8 bytes of the SHA1 of 1551 // the full data. 1552 QM_TRY_INSPECT(const auto& hash, HashCString(aCrypto, data)); 1553 1554 // Next, search for an existing entry for this blob by comparing the hash 1555 // value first and then the full data. SQLite is smart enough to use 1556 // the index on the hash to search the table before doing the expensive 1557 // comparison of the large data column. (This was verified with EXPLAIN.) 1558 QM_TRY_INSPECT( 1559 const auto& selectStmt, 1560 quota::CreateAndExecuteSingleStepStatement< 1561 quota::SingleStepResult::ReturnNullIfNoResult>( 1562 aConn, 1563 // Note that hash and data are blobs, but we can use = here since the 1564 // columns are NOT NULL. 1565 "SELECT id, refcount FROM security_info WHERE hash=:hash AND " 1566 "data=:data;"_ns, 1567 [&hash, &data](auto& state) -> Result<Ok, nsresult> { 1568 QM_TRY(MOZ_TO_RESULT( 1569 state.BindUTF8StringAsBlobByName("hash"_ns, hash))); 1570 QM_TRY(MOZ_TO_RESULT( 1571 state.BindUTF8StringAsBlobByName("data"_ns, data))); 1572 1573 return Ok{}; 1574 })); 1575 1576 // This security info blob is already in the database 1577 if (selectStmt) { 1578 // get the existing security blob id to return 1579 QM_TRY_INSPECT(const int32_t& id, 1580 MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 0)); 1581 QM_TRY_INSPECT(const int32_t& refcount, 1582 MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 1)); 1583 1584 // But first, update the refcount in the database. 1585 QM_TRY_INSPECT( 1586 const auto& state, 1587 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1588 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1589 "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns)); 1590 1591 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, refcount + 1))); 1592 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, id))); 1593 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1594 1595 return id; 1596 } 1597 1598 // This is a new security info blob. Create a new row in the security table 1599 // with an initial refcount of 1. 1600 QM_TRY_INSPECT(const auto& state, 1601 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1602 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1603 "INSERT INTO security_info (hash, data, refcount) " 1604 "VALUES (:hash, :data, 1);"_ns)); 1605 1606 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("hash"_ns, hash))); 1607 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("data"_ns, data))); 1608 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1609 1610 { 1611 QM_TRY_INSPECT(const auto& state, 1612 quota::CreateAndExecuteSingleStepStatement( 1613 aConn, "SELECT last_insert_rowid()"_ns)); 1614 1615 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 1616 } 1617 } 1618 1619 nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId, 1620 int32_t aCount) { 1621 // First, we need to determine the current refcount for this security blob. 1622 QM_TRY_INSPECT( 1623 const int32_t& refcount, ([&aConn, aId]() -> Result<int32_t, nsresult> { 1624 QM_TRY_INSPECT( 1625 const auto& state, 1626 quota::CreateAndExecuteSingleStepStatement( 1627 aConn, "SELECT refcount FROM security_info WHERE id=:id;"_ns, 1628 [aId](auto& state) -> Result<Ok, nsresult> { 1629 QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aId))); 1630 return Ok{}; 1631 })); 1632 1633 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 1634 }())); 1635 1636 MOZ_ASSERT_DEBUG_OR_FUZZING(refcount >= aCount); 1637 1638 // Next, calculate the new refcount 1639 int32_t newCount = refcount - aCount; 1640 1641 // If the last reference to this security blob was removed we can 1642 // just remove the entire row. 1643 if (newCount == 0) { 1644 QM_TRY_INSPECT(const auto& state, 1645 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1646 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1647 "DELETE FROM security_info WHERE id=:id;"_ns)); 1648 1649 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId))); 1650 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1651 1652 return NS_OK; 1653 } 1654 1655 // Otherwise update the refcount in the table to reflect the reduced 1656 // number of references to the security blob. 1657 QM_TRY_INSPECT( 1658 const auto& state, 1659 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1660 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1661 "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns)); 1662 1663 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, newCount))); 1664 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId))); 1665 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1666 1667 return NS_OK; 1668 } 1669 1670 nsresult DeleteSecurityInfoList( 1671 mozIStorageConnection& aConn, 1672 const nsTArray<IdCount>& aDeletedStorageIdList) { 1673 for (const auto& deletedStorageId : aDeletedStorageIdList) { 1674 QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfo(aConn, deletedStorageId.mId, 1675 deletedStorageId.mCount))); 1676 } 1677 1678 return NS_OK; 1679 } 1680 1681 nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId, 1682 const CacheRequest& aRequest, const nsID* aRequestBodyId, 1683 const CacheResponse& aResponse, 1684 const nsID* aResponseBodyId) { 1685 MOZ_ASSERT(!NS_IsMainThread()); 1686 1687 QM_TRY_INSPECT(const auto& crypto, 1688 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>, 1689 MOZ_SELECT_OVERLOAD(do_CreateInstance), 1690 NS_CRYPTO_HASH_CONTRACTID)); 1691 1692 int32_t securityId = -1; 1693 if (aResponse.securityInfo()) { 1694 QM_TRY_UNWRAP(securityId, 1695 InsertSecurityInfo(aConn, *crypto, aResponse.securityInfo())); 1696 } 1697 1698 { 1699 QM_TRY_INSPECT(const auto& state, 1700 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1701 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1702 "INSERT INTO entries (" 1703 "request_method, " 1704 "request_url_no_query, " 1705 "request_url_no_query_hash, " 1706 "request_url_query, " 1707 "request_url_query_hash, " 1708 "request_url_fragment, " 1709 "request_referrer, " 1710 "request_referrer_policy, " 1711 "request_headers_guard, " 1712 "request_mode, " 1713 "request_credentials, " 1714 "request_contentpolicytype, " 1715 "request_cache, " 1716 "request_redirect, " 1717 "request_integrity, " 1718 "request_body_id, " 1719 "request_body_disk_size, " 1720 "response_type, " 1721 "response_status, " 1722 "response_status_text, " 1723 "response_headers_guard, " 1724 "response_body_id, " 1725 "response_body_disk_size, " 1726 "response_security_info_id, " 1727 "response_principal_info, " 1728 "response_padding_size, " 1729 "cache_id " 1730 ") VALUES (" 1731 ":request_method, " 1732 ":request_url_no_query, " 1733 ":request_url_no_query_hash, " 1734 ":request_url_query, " 1735 ":request_url_query_hash, " 1736 ":request_url_fragment, " 1737 ":request_referrer, " 1738 ":request_referrer_policy, " 1739 ":request_headers_guard, " 1740 ":request_mode, " 1741 ":request_credentials, " 1742 ":request_contentpolicytype, " 1743 ":request_cache, " 1744 ":request_redirect, " 1745 ":request_integrity, " 1746 ":request_body_id, " 1747 ":request_body_disk_size, " 1748 ":response_type, " 1749 ":response_status, " 1750 ":response_status_text, " 1751 ":response_headers_guard, " 1752 ":response_body_id, " 1753 ":response_body_disk_size, " 1754 ":response_security_info_id, " 1755 ":response_principal_info, " 1756 ":response_padding_size, " 1757 ":cache_id " 1758 ");"_ns)); 1759 1760 QM_TRY(MOZ_TO_RESULT( 1761 state->BindUTF8StringByName("request_method"_ns, aRequest.method()))); 1762 1763 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName( 1764 "request_url_no_query"_ns, aRequest.urlWithoutQuery()))); 1765 1766 QM_TRY_INSPECT(const auto& urlWithoutQueryHash, 1767 HashCString(*crypto, aRequest.urlWithoutQuery())); 1768 1769 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName( 1770 "request_url_no_query_hash"_ns, urlWithoutQueryHash))); 1771 1772 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_url_query"_ns, 1773 aRequest.urlQuery()))); 1774 1775 QM_TRY_INSPECT(const auto& urlQueryHash, 1776 HashCString(*crypto, aRequest.urlQuery())); 1777 1778 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName( 1779 "request_url_query_hash"_ns, urlQueryHash))); 1780 1781 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_url_fragment"_ns, 1782 aRequest.urlFragment()))); 1783 1784 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("request_referrer"_ns, 1785 aRequest.referrer()))); 1786 1787 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1788 "request_referrer_policy"_ns, 1789 static_cast<int32_t>(aRequest.referrerPolicy())))); 1790 1791 QM_TRY(MOZ_TO_RESULT( 1792 state->BindInt32ByName("request_headers_guard"_ns, 1793 static_cast<int32_t>(aRequest.headersGuard())))); 1794 1795 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1796 "request_mode"_ns, static_cast<int32_t>(aRequest.mode())))); 1797 1798 QM_TRY(MOZ_TO_RESULT( 1799 state->BindInt32ByName("request_credentials"_ns, 1800 static_cast<int32_t>(aRequest.credentials())))); 1801 1802 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1803 "request_contentpolicytype"_ns, 1804 static_cast<int32_t>(aRequest.contentPolicyType())))); 1805 1806 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1807 "request_cache"_ns, static_cast<int32_t>(aRequest.requestCache())))); 1808 1809 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1810 "request_redirect"_ns, 1811 static_cast<int32_t>(aRequest.requestRedirect())))); 1812 1813 QM_TRY(MOZ_TO_RESULT( 1814 state->BindStringByName("request_integrity"_ns, aRequest.integrity()))); 1815 1816 QM_TRY(MOZ_TO_RESULT(BindId(*state, "request_body_id"_ns, aRequestBodyId))); 1817 1818 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("request_body_disk_size"_ns, 1819 aRequest.bodyDiskSize()))); 1820 1821 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1822 "response_type"_ns, static_cast<int32_t>(aResponse.type())))); 1823 1824 QM_TRY(MOZ_TO_RESULT( 1825 state->BindInt32ByName("response_status"_ns, aResponse.status()))); 1826 1827 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("response_status_text"_ns, 1828 aResponse.statusText()))); 1829 1830 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName( 1831 "response_headers_guard"_ns, 1832 static_cast<int32_t>(aResponse.headersGuard())))); 1833 1834 QM_TRY( 1835 MOZ_TO_RESULT(BindId(*state, "response_body_id"_ns, aResponseBodyId))); 1836 1837 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("response_body_disk_size"_ns, 1838 aResponse.bodyDiskSize()))); 1839 1840 if (!aResponse.securityInfo()) { 1841 QM_TRY( 1842 MOZ_TO_RESULT(state->BindNullByName("response_security_info_id"_ns))); 1843 } else { 1844 QM_TRY(MOZ_TO_RESULT( 1845 state->BindInt32ByName("response_security_info_id"_ns, securityId))); 1846 } 1847 1848 nsAutoCString serializedInfo; 1849 // We only allow content serviceworkers right now. 1850 if (aResponse.principalInfo().isSome()) { 1851 const mozilla::ipc::PrincipalInfo& principalInfo = 1852 aResponse.principalInfo().ref(); 1853 MOZ_DIAGNOSTIC_ASSERT(principalInfo.type() == 1854 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); 1855 const mozilla::ipc::ContentPrincipalInfo& cInfo = 1856 principalInfo.get_ContentPrincipalInfo(); 1857 1858 serializedInfo.Append(cInfo.spec()); 1859 1860 nsAutoCString suffix; 1861 cInfo.attrs().CreateSuffix(suffix); 1862 serializedInfo.Append(suffix); 1863 } 1864 1865 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName( 1866 "response_principal_info"_ns, serializedInfo))); 1867 1868 if (aResponse.paddingSize() == InternalResponse::UNKNOWN_PADDING_SIZE) { 1869 MOZ_DIAGNOSTIC_ASSERT(aResponse.type() != ResponseType::Opaque); 1870 QM_TRY(MOZ_TO_RESULT(state->BindNullByName("response_padding_size"_ns))); 1871 } else { 1872 MOZ_DIAGNOSTIC_ASSERT(aResponse.paddingSize() >= 0); 1873 MOZ_DIAGNOSTIC_ASSERT(aResponse.type() == ResponseType::Opaque); 1874 1875 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("response_padding_size"_ns, 1876 aResponse.paddingSize()))); 1877 } 1878 1879 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId))); 1880 1881 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1882 } 1883 1884 QM_TRY_INSPECT( 1885 const int32_t& entryId, ([&aConn]() -> Result<int32_t, nsresult> { 1886 QM_TRY_INSPECT(const auto& state, 1887 quota::CreateAndExecuteSingleStepStatement( 1888 aConn, "SELECT last_insert_rowid()"_ns)); 1889 1890 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 1891 }())); 1892 1893 { 1894 QM_TRY_INSPECT(const auto& state, 1895 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1896 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1897 "INSERT INTO request_headers (" 1898 "name, " 1899 "value, " 1900 "entry_id " 1901 ") VALUES (:name, :value, :entry_id)"_ns)); 1902 1903 for (const auto& requestHeader : aRequest.headers()) { 1904 QM_TRY(MOZ_TO_RESULT( 1905 state->BindUTF8StringByName("name"_ns, requestHeader.name()))); 1906 1907 QM_TRY(MOZ_TO_RESULT( 1908 state->BindUTF8StringByName("value"_ns, requestHeader.value()))); 1909 1910 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId))); 1911 1912 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1913 } 1914 } 1915 1916 { 1917 QM_TRY_INSPECT(const auto& state, 1918 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1919 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1920 "INSERT INTO response_headers (" 1921 "name, " 1922 "value, " 1923 "entry_id " 1924 ") VALUES (:name, :value, :entry_id)"_ns)); 1925 1926 for (const auto& responseHeader : aResponse.headers()) { 1927 QM_TRY(MOZ_TO_RESULT( 1928 state->BindUTF8StringByName("name"_ns, responseHeader.name()))); 1929 QM_TRY(MOZ_TO_RESULT( 1930 state->BindUTF8StringByName("value"_ns, responseHeader.value()))); 1931 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId))); 1932 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1933 } 1934 } 1935 1936 { 1937 QM_TRY_INSPECT(const auto& state, 1938 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1939 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 1940 "INSERT INTO response_url_list (" 1941 "url, " 1942 "entry_id " 1943 ") VALUES (:url, :entry_id)"_ns)); 1944 1945 for (const auto& responseUrl : aResponse.urlList()) { 1946 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName("url"_ns, responseUrl))); 1947 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId))); 1948 QM_TRY(MOZ_TO_RESULT(state->Execute())); 1949 } 1950 } 1951 1952 return NS_OK; 1953 } 1954 1955 /** 1956 * Gets a HeadersEntry from a storage statement by retrieving the first column 1957 * as the name and the second column as the value. 1958 */ 1959 Result<HeadersEntry, nsresult> GetHeadersEntryFromStatement( 1960 mozIStorageStatement& aStmt) { 1961 HeadersEntry header; 1962 1963 QM_TRY_UNWRAP(header.name(), MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1964 nsCString, aStmt, GetUTF8String, 0)); 1965 QM_TRY_UNWRAP(header.value(), MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 1966 nsCString, aStmt, GetUTF8String, 1)); 1967 1968 return header; 1969 } 1970 1971 Result<SavedResponse, nsresult> ReadResponse(mozIStorageConnection& aConn, 1972 EntryId aEntryId) { 1973 MOZ_ASSERT(!NS_IsMainThread()); 1974 1975 SavedResponse savedResponse; 1976 1977 QM_TRY_INSPECT( 1978 const auto& state, 1979 quota::CreateAndExecuteSingleStepStatement( 1980 aConn, 1981 "SELECT " 1982 "entries.response_type, " 1983 "entries.response_status, " 1984 "entries.response_status_text, " 1985 "entries.response_headers_guard, " 1986 "entries.response_body_id, " 1987 "entries.response_principal_info, " 1988 "entries.response_padding_size, " 1989 "security_info.data, " 1990 "entries.request_credentials " 1991 "FROM entries " 1992 "LEFT OUTER JOIN security_info " 1993 "ON entries.response_security_info_id=security_info.id " 1994 "WHERE entries.id=:id;"_ns, 1995 [aEntryId](auto& state) -> Result<Ok, nsresult> { 1996 QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aEntryId))); 1997 1998 return Ok{}; 1999 })); 2000 2001 QM_TRY_INSPECT(const int32_t& type, 2002 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 2003 savedResponse.mValue.type() = static_cast<ResponseType>(type); 2004 2005 QM_TRY_INSPECT(const int32_t& status, 2006 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 1)); 2007 savedResponse.mValue.status() = static_cast<uint32_t>(status); 2008 2009 QM_TRY(MOZ_TO_RESULT( 2010 state->GetUTF8String(2, savedResponse.mValue.statusText()))); 2011 2012 QM_TRY_INSPECT(const int32_t& guard, 2013 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 3)); 2014 savedResponse.mValue.headersGuard() = static_cast<HeadersGuardEnum>(guard); 2015 2016 QM_TRY_INSPECT(const bool& nullBody, 2017 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetIsNull, 4)); 2018 savedResponse.mHasBodyId = !nullBody; 2019 2020 if (savedResponse.mHasBodyId) { 2021 QM_TRY_UNWRAP(savedResponse.mBodyId, ExtractId(*state, 4)); 2022 } 2023 2024 QM_TRY_INSPECT(const auto& serializedInfo, 2025 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, *state, 2026 GetUTF8String, 5)); 2027 2028 savedResponse.mValue.principalInfo() = Nothing(); 2029 if (!serializedInfo.IsEmpty()) { 2030 nsAutoCString specNoSuffix; 2031 OriginAttributes attrs; 2032 if (!attrs.PopulateFromOrigin(serializedInfo, specNoSuffix)) { 2033 NS_WARNING("Something went wrong parsing a serialized principal!"); 2034 return Err(NS_ERROR_FAILURE); 2035 } 2036 2037 nsCOMPtr<nsIURI> url; 2038 QM_TRY(MOZ_TO_RESULT(NS_NewURI(getter_AddRefs(url), specNoSuffix))); 2039 2040 #ifdef DEBUG 2041 nsAutoCString scheme; 2042 QM_TRY(MOZ_TO_RESULT(url->GetScheme(scheme))); 2043 2044 MOZ_ASSERT( 2045 scheme == "http" || scheme == "https" || scheme == "file" || 2046 // A cached response entry may have a moz-extension principal if: 2047 // 2048 // - This is an extension background service worker. The response for 2049 // the main script is expected tobe a moz-extension content principal 2050 // (the pref "extensions.backgroundServiceWorker.enabled" must be 2051 // enabled, if the pref is toggled to false at runtime then any 2052 // service worker registered for a moz-extension principal will be 2053 // unregistered on the next startup). 2054 // 2055 // - An extension is redirecting a script being imported info a worker 2056 // created from a regular webpage to a web-accessible extension 2057 // script. The reponse for these redirects will have a moz-extension 2058 // principal. Although extensions can attempt to redirect the main 2059 // script of service workers, this will always cause the install 2060 // process to fail. 2061 scheme == "moz-extension"); 2062 #endif 2063 2064 nsCOMPtr<nsIPrincipal> principal = 2065 BasePrincipal::CreateContentPrincipal(url, attrs); 2066 if (!principal) { 2067 return Err(NS_ERROR_NULL_POINTER); 2068 } 2069 2070 nsCString origin; 2071 QM_TRY(MOZ_TO_RESULT(principal->GetOriginNoSuffix(origin))); 2072 2073 nsCString baseDomain; 2074 QM_TRY(MOZ_TO_RESULT(principal->GetBaseDomain(baseDomain))); 2075 2076 savedResponse.mValue.principalInfo() = 2077 Some(mozilla::ipc::ContentPrincipalInfo(attrs, origin, specNoSuffix, 2078 Nothing(), baseDomain)); 2079 } 2080 2081 QM_TRY_INSPECT(const bool& nullPadding, 2082 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetIsNull, 6)); 2083 2084 if (nullPadding) { 2085 MOZ_DIAGNOSTIC_ASSERT(savedResponse.mValue.type() != ResponseType::Opaque); 2086 savedResponse.mValue.paddingSize() = InternalResponse::UNKNOWN_PADDING_SIZE; 2087 } else { 2088 MOZ_DIAGNOSTIC_ASSERT(savedResponse.mValue.type() == ResponseType::Opaque); 2089 QM_TRY_INSPECT(const int64_t& paddingSize, 2090 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 6)); 2091 2092 MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); 2093 savedResponse.mValue.paddingSize() = paddingSize; 2094 } 2095 2096 nsCString data; 2097 QM_TRY(MOZ_TO_RESULT(state->GetBlobAsUTF8String(7, data))); 2098 if (!data.IsEmpty()) { 2099 nsCOMPtr<nsITransportSecurityInfo> securityInfo; 2100 nsresult rv = mozilla::psm::TransportSecurityInfo::Read( 2101 data, getter_AddRefs(securityInfo)); 2102 if (NS_FAILED(rv)) { 2103 return Err(rv); 2104 } 2105 if (!securityInfo) { 2106 return Err(NS_ERROR_FAILURE); 2107 } 2108 savedResponse.mValue.securityInfo() = securityInfo.forget(); 2109 } 2110 2111 QM_TRY_INSPECT(const int32_t& credentials, 2112 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 8)); 2113 savedResponse.mValue.credentials() = 2114 static_cast<RequestCredentials>(credentials); 2115 2116 { 2117 QM_TRY_INSPECT(const auto& state, 2118 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2119 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2120 "SELECT " 2121 "name, " 2122 "value " 2123 "FROM response_headers " 2124 "WHERE entry_id=:entry_id;"_ns)); 2125 2126 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId))); 2127 2128 QM_TRY_UNWRAP(savedResponse.mValue.headers(), 2129 quota::CollectElementsWhileHasResult( 2130 *state, GetHeadersEntryFromStatement)); 2131 } 2132 2133 { 2134 QM_TRY_INSPECT(const auto& state, 2135 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2136 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2137 "SELECT " 2138 "url " 2139 "FROM response_url_list " 2140 "WHERE entry_id=:entry_id;"_ns)); 2141 2142 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId))); 2143 2144 QM_TRY_UNWRAP(savedResponse.mValue.urlList(), 2145 quota::CollectElementsWhileHasResult( 2146 *state, [](auto& stmt) -> Result<nsCString, nsresult> { 2147 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2148 nsCString, stmt, GetUTF8String, 0)); 2149 })); 2150 } 2151 2152 return savedResponse; 2153 } 2154 2155 Result<SavedRequest, nsresult> ReadRequest(mozIStorageConnection& aConn, 2156 EntryId aEntryId) { 2157 MOZ_ASSERT(!NS_IsMainThread()); 2158 2159 SavedRequest savedRequest; 2160 2161 QM_TRY_INSPECT( 2162 const auto& state, 2163 quota::CreateAndExecuteSingleStepStatement< 2164 quota::SingleStepResult::ReturnNullIfNoResult>( 2165 aConn, 2166 "SELECT " 2167 "request_method, " 2168 "request_url_no_query, " 2169 "request_url_query, " 2170 "request_url_fragment, " 2171 "request_referrer, " 2172 "request_referrer_policy, " 2173 "request_headers_guard, " 2174 "request_mode, " 2175 "request_credentials, " 2176 "request_contentpolicytype, " 2177 "request_cache, " 2178 "request_redirect, " 2179 "request_integrity, " 2180 "request_body_id " 2181 "FROM entries " 2182 "WHERE id=:id;"_ns, 2183 [aEntryId](auto& state) -> Result<Ok, nsresult> { 2184 QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aEntryId))); 2185 2186 return Ok{}; 2187 })); 2188 2189 QM_TRY(OkIf(state), Err(NS_ERROR_UNEXPECTED)); 2190 2191 QM_TRY(MOZ_TO_RESULT(state->GetUTF8String(0, savedRequest.mValue.method()))); 2192 QM_TRY(MOZ_TO_RESULT( 2193 state->GetUTF8String(1, savedRequest.mValue.urlWithoutQuery()))); 2194 QM_TRY( 2195 MOZ_TO_RESULT(state->GetUTF8String(2, savedRequest.mValue.urlQuery()))); 2196 QM_TRY(MOZ_TO_RESULT( 2197 state->GetUTF8String(3, savedRequest.mValue.urlFragment()))); 2198 QM_TRY( 2199 MOZ_TO_RESULT(state->GetUTF8String(4, savedRequest.mValue.referrer()))); 2200 2201 QM_TRY_INSPECT(const int32_t& referrerPolicy, 2202 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 5)); 2203 savedRequest.mValue.referrerPolicy() = 2204 static_cast<ReferrerPolicy>(referrerPolicy); 2205 2206 QM_TRY_INSPECT(const int32_t& guard, 2207 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 6)); 2208 savedRequest.mValue.headersGuard() = static_cast<HeadersGuardEnum>(guard); 2209 2210 QM_TRY_INSPECT(const int32_t& mode, 2211 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 7)); 2212 savedRequest.mValue.mode() = static_cast<RequestMode>(mode); 2213 2214 QM_TRY_INSPECT(const int32_t& credentials, 2215 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 8)); 2216 savedRequest.mValue.credentials() = 2217 static_cast<RequestCredentials>(credentials); 2218 2219 QM_TRY_INSPECT(const int32_t& requestContentPolicyType, 2220 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 9)); 2221 savedRequest.mValue.contentPolicyType() = 2222 static_cast<nsContentPolicyType>(requestContentPolicyType); 2223 2224 QM_TRY_INSPECT(const int32_t& requestCache, 2225 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 10)); 2226 savedRequest.mValue.requestCache() = static_cast<RequestCache>(requestCache); 2227 2228 QM_TRY_INSPECT(const int32_t& requestRedirect, 2229 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 11)); 2230 savedRequest.mValue.requestRedirect() = 2231 static_cast<RequestRedirect>(requestRedirect); 2232 2233 QM_TRY(MOZ_TO_RESULT(state->GetString(12, savedRequest.mValue.integrity()))); 2234 2235 QM_TRY_INSPECT(const bool& nullBody, 2236 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetIsNull, 13)); 2237 savedRequest.mHasBodyId = !nullBody; 2238 if (savedRequest.mHasBodyId) { 2239 QM_TRY_UNWRAP(savedRequest.mBodyId, ExtractId(*state, 13)); 2240 } 2241 2242 { 2243 QM_TRY_INSPECT(const auto& state, 2244 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2245 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2246 "SELECT " 2247 "name, " 2248 "value " 2249 "FROM request_headers " 2250 "WHERE entry_id=:entry_id;"_ns)); 2251 2252 QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, aEntryId))); 2253 2254 QM_TRY_UNWRAP(savedRequest.mValue.headers(), 2255 quota::CollectElementsWhileHasResult( 2256 *state, GetHeadersEntryFromStatement)); 2257 } 2258 2259 return savedRequest; 2260 } 2261 2262 void AppendListParamsToQuery(nsACString& aQuery, size_t aLen) { 2263 MOZ_ASSERT(!NS_IsMainThread()); 2264 2265 aQuery.AppendLiteral("?"); 2266 for (size_t i = 1; i < aLen; ++i) { 2267 aQuery.AppendLiteral(",?"); 2268 } 2269 } 2270 2271 nsresult BindListParamsToQuery(mozIStorageStatement& aState, 2272 const Span<const EntryId>& aEntryIdList) { 2273 MOZ_ASSERT(!NS_IsMainThread()); 2274 for (size_t i = 0, n = aEntryIdList.Length(); i < n; ++i) { 2275 QM_TRY(MOZ_TO_RESULT(aState.BindInt32ByIndex(i, aEntryIdList[i]))); 2276 } 2277 return NS_OK; 2278 } 2279 2280 nsresult BindId(mozIStorageStatement& aState, const nsACString& aName, 2281 const nsID* aId) { 2282 MOZ_ASSERT(!NS_IsMainThread()); 2283 2284 if (!aId) { 2285 QM_TRY(MOZ_TO_RESULT(aState.BindNullByName(aName))); 2286 return NS_OK; 2287 } 2288 2289 char idBuf[NSID_LENGTH]; 2290 aId->ToProvidedString(idBuf); 2291 QM_TRY(MOZ_TO_RESULT( 2292 aState.BindUTF8StringByName(aName, nsDependentCString(idBuf)))); 2293 2294 return NS_OK; 2295 } 2296 2297 Result<nsID, nsresult> ExtractId(mozIStorageStatement& aState, uint32_t aPos) { 2298 MOZ_ASSERT(!NS_IsMainThread()); 2299 2300 QM_TRY_INSPECT(const auto& idString, 2301 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, aState, 2302 GetUTF8String, aPos)); 2303 2304 nsID id; 2305 QM_TRY(OkIf(id.Parse(idString.get())), Err(NS_ERROR_UNEXPECTED)); 2306 2307 return id; 2308 } 2309 2310 Result<NotNull<nsCOMPtr<mozIStorageStatement>>, nsresult> 2311 CreateAndBindKeyStatement(mozIStorageConnection& aConn, 2312 const char* const aQueryFormat, 2313 const nsAString& aKey) { 2314 MOZ_DIAGNOSTIC_ASSERT(aQueryFormat); 2315 2316 // The key is stored as a blob to avoid encoding issues. An empty string 2317 // is mapped to NULL for blobs. Normally we would just write the query 2318 // as "key IS :key" to do the proper NULL checking, but that prevents 2319 // sqlite from using the key index. Therefore use "IS NULL" explicitly 2320 // if the key is empty, otherwise use "=:key" so that sqlite uses the 2321 // index. 2322 2323 QM_TRY_UNWRAP( 2324 auto state, 2325 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2326 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2327 nsPrintfCString(aQueryFormat, 2328 aKey.IsEmpty() ? "key IS NULL" : "key=:key"))); 2329 2330 if (!aKey.IsEmpty()) { 2331 QM_TRY(MOZ_TO_RESULT(state->BindStringAsBlobByName("key"_ns, aKey))); 2332 } 2333 2334 return WrapNotNull(std::move(state)); 2335 } 2336 2337 Result<nsAutoCString, nsresult> HashCString(nsICryptoHash& aCrypto, 2338 const nsACString& aIn) { 2339 QM_TRY(MOZ_TO_RESULT(aCrypto.Init(nsICryptoHash::SHA1))); 2340 2341 QM_TRY(MOZ_TO_RESULT(aCrypto.Update( 2342 reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length()))); 2343 2344 nsAutoCString fullHash; 2345 QM_TRY(MOZ_TO_RESULT(aCrypto.Finish(false /* based64 result */, fullHash))); 2346 2347 return Result<nsAutoCString, nsresult>{std::in_place, 2348 Substring(fullHash, 0, 8)}; 2349 } 2350 2351 } // namespace 2352 2353 nsresult IncrementalVacuum(mozIStorageConnection& aConn) { 2354 // Determine how much free space is in the database. 2355 QM_TRY_INSPECT(const auto& state, quota::CreateAndExecuteSingleStepStatement( 2356 aConn, "PRAGMA freelist_count;"_ns)); 2357 2358 QM_TRY_INSPECT(const int32_t& freePages, 2359 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 2360 2361 // We have a relatively small page size, so we want to be careful to avoid 2362 // fragmentation. We already use a growth incremental which will cause 2363 // sqlite to allocate and release multiple pages at the same time. We can 2364 // further reduce fragmentation by making our allocated chunks a bit 2365 // "sticky". This is done by creating some hysteresis where we allocate 2366 // pages/chunks as soon as we need them, but we only release pages/chunks 2367 // when we have a large amount of free space. This helps with the case 2368 // where a page is adding and remove resources causing it to dip back and 2369 // forth across a chunk boundary. 2370 // 2371 // So only proceed with releasing pages if we have more than our constant 2372 // threshold. 2373 if (freePages <= kMaxFreePages) { 2374 return NS_OK; 2375 } 2376 2377 // Release the excess pages back to the sqlite VFS. This may also release 2378 // chunks of multiple pages back to the OS. 2379 const int32_t pagesToRelease = freePages - kMaxFreePages; 2380 2381 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2382 nsPrintfCString("PRAGMA incremental_vacuum(%d);", pagesToRelease)))); 2383 2384 // Verify that our incremental vacuum actually did something 2385 #ifdef DEBUG 2386 { 2387 QM_TRY_INSPECT(const auto& state, 2388 quota::CreateAndExecuteSingleStepStatement( 2389 aConn, "PRAGMA freelist_count;"_ns)); 2390 2391 QM_TRY_INSPECT(const int32_t& freePages, 2392 MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0)); 2393 2394 MOZ_ASSERT(freePages <= kMaxFreePages); 2395 } 2396 #endif 2397 2398 return NS_OK; 2399 } 2400 2401 namespace { 2402 2403 // Wrapper around mozIStorageConnection::GetSchemaVersion() that compensates 2404 // for hacky downgrade schema version tricks. See the block comments for 2405 // kHackyDowngradeSchemaVersion and kHackyPaddingSizePresentVersion. 2406 Result<int32_t, nsresult> GetEffectiveSchemaVersion( 2407 mozIStorageConnection& aConn) { 2408 QM_TRY_INSPECT(const int32_t& schemaVersion, 2409 MOZ_TO_RESULT_INVOKE_MEMBER(aConn, GetSchemaVersion)); 2410 2411 if (schemaVersion == kHackyDowngradeSchemaVersion) { 2412 // This is the special case. Check for the existence of the 2413 // "response_padding_size" colum in table "entries". 2414 // 2415 // (pragma_table_info is a table-valued function format variant of 2416 // "PRAGMA table_info" supported since SQLite 3.16.0. Firefox 53 shipped 2417 // was the first release with this functionality, shipping 3.16.2.) 2418 // 2419 // If there are any result rows, then the column is present. 2420 QM_TRY_INSPECT(const bool& hasColumn, 2421 quota::CreateAndExecuteSingleStepStatement< 2422 quota::SingleStepResult::ReturnNullIfNoResult>( 2423 aConn, 2424 "SELECT name FROM pragma_table_info('entries') WHERE " 2425 "name = 'response_padding_size'"_ns)); 2426 2427 if (hasColumn) { 2428 return kHackyPaddingSizePresentVersion; 2429 } 2430 } 2431 2432 return schemaVersion; 2433 } 2434 2435 #ifdef DEBUG 2436 struct Expect { 2437 // Expect exact SQL 2438 Expect(const char* aName, const char* aType, const char* aSql) 2439 : mName(aName), mType(aType), mSql(aSql), mIgnoreSql(false) {} 2440 2441 // Ignore SQL 2442 Expect(const char* aName, const char* aType) 2443 : mName(aName), mType(aType), mIgnoreSql(true) {} 2444 2445 const nsCString mName; 2446 const nsCString mType; 2447 const nsCString mSql; 2448 const bool mIgnoreSql; 2449 }; 2450 #endif 2451 2452 nsresult Validate(mozIStorageConnection& aConn) { 2453 QM_TRY_INSPECT(const int32_t& schemaVersion, 2454 GetEffectiveSchemaVersion(aConn)); 2455 QM_TRY(OkIf(schemaVersion == kLatestSchemaVersion), NS_ERROR_FAILURE); 2456 2457 #ifdef DEBUG 2458 // This is the schema we expect the database at the latest version to 2459 // contain. Update this list if you add a new table or index. 2460 const Expect expects[] = { 2461 Expect("caches", "table", kTableCaches), 2462 Expect("sqlite_sequence", "table"), // auto-gen by sqlite 2463 Expect("security_info", "table", kTableSecurityInfo), 2464 Expect("security_info_hash_index", "index", kIndexSecurityInfoHash), 2465 Expect("entries", "table", kTableEntries), 2466 Expect("entries_request_match_index", "index", kIndexEntriesRequest), 2467 Expect("request_headers", "table", kTableRequestHeaders), 2468 Expect("response_headers", "table", kTableResponseHeaders), 2469 Expect("response_headers_name_index", "index", kIndexResponseHeadersName), 2470 Expect("response_url_list", "table", kTableResponseUrlList), 2471 Expect("storage", "table", kTableStorage), 2472 Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite 2473 Expect("usage_info", "table", kTableUsageInfo), 2474 Expect("entries_insert_trigger", "trigger", kTriggerEntriesInsert), 2475 Expect("entries_update_trigger", "trigger", kTriggerEntriesUpdate), 2476 Expect("entries_delete_trigger", "trigger", kTriggerEntriesDelete), 2477 }; 2478 2479 // Read the schema from the sqlite_master table and compare. 2480 QM_TRY_INSPECT(const auto& state, 2481 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2482 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2483 "SELECT name, type, sql FROM sqlite_master;"_ns)); 2484 2485 QM_TRY(quota::CollectWhileHasResult( 2486 *state, [&expects](auto& stmt) -> Result<Ok, nsresult> { 2487 QM_TRY_INSPECT(const auto& name, 2488 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt, 2489 GetUTF8String, 0)); 2490 QM_TRY_INSPECT(const auto& type, 2491 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt, 2492 GetUTF8String, 1)); 2493 QM_TRY_INSPECT(const auto& sql, 2494 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, stmt, 2495 GetUTF8String, 2)); 2496 2497 bool foundMatch = false; 2498 for (const auto& expect : expects) { 2499 if (name == expect.mName) { 2500 if (type != expect.mType) { 2501 NS_WARNING( 2502 nsPrintfCString("Unexpected type for Cache schema entry %s", 2503 name.get()) 2504 .get()); 2505 return Err(NS_ERROR_FAILURE); 2506 } 2507 2508 if (!expect.mIgnoreSql && sql != expect.mSql) { 2509 NS_WARNING( 2510 nsPrintfCString("Unexpected SQL for Cache schema entry %s", 2511 name.get()) 2512 .get()); 2513 return Err(NS_ERROR_FAILURE); 2514 } 2515 2516 foundMatch = true; 2517 break; 2518 } 2519 } 2520 2521 if (NS_WARN_IF(!foundMatch)) { 2522 NS_WARNING( 2523 nsPrintfCString("Unexpected schema entry %s in Cache database", 2524 name.get()) 2525 .get()); 2526 return Err(NS_ERROR_FAILURE); 2527 } 2528 2529 return Ok{}; 2530 })); 2531 #endif 2532 2533 return NS_OK; 2534 } 2535 2536 // ----- 2537 // Schema migration code 2538 // ----- 2539 2540 using MigrationFunc = nsresult (*)(nsIFile&, mozIStorageConnection&, bool&); 2541 struct Migration { 2542 int32_t mFromVersion; 2543 MigrationFunc mFunc; 2544 }; 2545 2546 // Declare migration functions here. Each function should upgrade 2547 // the version by a single increment. Don't skip versions. 2548 nsresult MigrateFrom15To16(nsIFile& aDBDir, mozIStorageConnection& aConn, 2549 bool& aRewriteSchema); 2550 nsresult MigrateFrom16To17(nsIFile& aDBDir, mozIStorageConnection& aConn, 2551 bool& aRewriteSchema); 2552 nsresult MigrateFrom17To18(nsIFile& aDBDir, mozIStorageConnection& aConn, 2553 bool& aRewriteSchema); 2554 nsresult MigrateFrom18To19(nsIFile& aDBDir, mozIStorageConnection& aConn, 2555 bool& aRewriteSchema); 2556 nsresult MigrateFrom19To20(nsIFile& aDBDir, mozIStorageConnection& aConn, 2557 bool& aRewriteSchema); 2558 nsresult MigrateFrom20To21(nsIFile& aDBDir, mozIStorageConnection& aConn, 2559 bool& aRewriteSchema); 2560 nsresult MigrateFrom21To22(nsIFile& aDBDir, mozIStorageConnection& aConn, 2561 bool& aRewriteSchema); 2562 nsresult MigrateFrom22To23(nsIFile& aDBDir, mozIStorageConnection& aConn, 2563 bool& aRewriteSchema); 2564 nsresult MigrateFrom23To24(nsIFile& aDBDir, mozIStorageConnection& aConn, 2565 bool& aRewriteSchema); 2566 nsresult MigrateFrom24To25(nsIFile& aDBDir, mozIStorageConnection& aConn, 2567 bool& aRewriteSchema); 2568 nsresult MigrateFrom25To26(nsIFile& aDBDir, mozIStorageConnection& aConn, 2569 bool& aRewriteSchema); 2570 nsresult MigrateFrom26To27(nsIFile& aDBDir, mozIStorageConnection& aConn, 2571 bool& aRewriteSchema); 2572 nsresult MigrateFrom27To28(nsIFile& aDBDir, mozIStorageConnection& aConn, 2573 bool& aRewriteSchema); 2574 nsresult MigrateFrom28To29(nsIFile& aDBDir, mozIStorageConnection& aConn, 2575 bool& aRewriteSchema); 2576 // Configure migration functions to run for the given starting version. 2577 constexpr Migration sMigrationList[] = { 2578 Migration{15, MigrateFrom15To16}, Migration{16, MigrateFrom16To17}, 2579 Migration{17, MigrateFrom17To18}, Migration{18, MigrateFrom18To19}, 2580 Migration{19, MigrateFrom19To20}, Migration{20, MigrateFrom20To21}, 2581 Migration{21, MigrateFrom21To22}, Migration{22, MigrateFrom22To23}, 2582 Migration{23, MigrateFrom23To24}, Migration{24, MigrateFrom24To25}, 2583 Migration{25, MigrateFrom25To26}, Migration{26, MigrateFrom26To27}, 2584 Migration{27, MigrateFrom27To28}, Migration{28, MigrateFrom28To29}, 2585 }; 2586 2587 nsresult RewriteEntriesSchema(mozIStorageConnection& aConn) { 2588 QM_TRY( 2589 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("PRAGMA writable_schema = ON"_ns))); 2590 2591 QM_TRY_INSPECT( 2592 const auto& state, 2593 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 2594 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 2595 "UPDATE sqlite_master SET sql=:sql WHERE name='entries'"_ns)); 2596 2597 QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName( 2598 "sql"_ns, nsDependentCString(kTableEntries)))); 2599 QM_TRY(MOZ_TO_RESULT(state->Execute())); 2600 2601 QM_TRY( 2602 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("PRAGMA writable_schema = OFF"_ns))); 2603 2604 return NS_OK; 2605 } 2606 2607 nsresult Migrate(nsIFile& aDBDir, mozIStorageConnection& aConn) { 2608 MOZ_ASSERT(!NS_IsMainThread()); 2609 2610 QM_TRY_UNWRAP(int32_t currentVersion, GetEffectiveSchemaVersion(aConn)); 2611 2612 bool rewriteSchema = false; 2613 2614 while (currentVersion < kLatestSchemaVersion) { 2615 // Wiping old databases is handled in DBAction because it requires 2616 // making a whole new mozIStorageConnection. Make sure we don't 2617 // accidentally get here for one of those old databases. 2618 MOZ_DIAGNOSTIC_ASSERT(currentVersion >= kFirstShippedSchemaVersion); 2619 2620 for (const auto& migration : sMigrationList) { 2621 if (migration.mFromVersion == currentVersion) { 2622 bool shouldRewrite = false; 2623 QM_TRY(MOZ_TO_RESULT(migration.mFunc(aDBDir, aConn, shouldRewrite))); 2624 if (shouldRewrite) { 2625 rewriteSchema = true; 2626 } 2627 break; 2628 } 2629 } 2630 2631 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 2632 int32_t lastVersion = currentVersion; 2633 #endif 2634 QM_TRY_UNWRAP(currentVersion, GetEffectiveSchemaVersion(aConn)); 2635 2636 MOZ_DIAGNOSTIC_ASSERT(currentVersion > lastVersion); 2637 } 2638 2639 // Don't release assert this since people do sometimes share profiles 2640 // across schema versions. Our check in Validate() will catch it. 2641 MOZ_ASSERT(currentVersion == kLatestSchemaVersion); 2642 2643 nsresult rv = NS_OK; 2644 if (rewriteSchema) { 2645 // Now overwrite the master SQL for the entries table to remove the column 2646 // default value. This is also necessary for our Validate() method to 2647 // pass on this database. 2648 rv = RewriteEntriesSchema(aConn); 2649 } 2650 2651 return rv; 2652 } 2653 2654 nsresult MigrateFrom15To16(nsIFile& aDBDir, mozIStorageConnection& aConn, 2655 bool& aRewriteSchema) { 2656 MOZ_ASSERT(!NS_IsMainThread()); 2657 2658 // Add the request_redirect column with a default value of "follow". Note, 2659 // we only use a default value here because its required by ALTER TABLE and 2660 // we need to apply the default "follow" to existing records in the table. 2661 // We don't actually want to keep the default in the schema for future 2662 // INSERTs. 2663 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2664 "ALTER TABLE entries " 2665 "ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0"_ns))); 2666 2667 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(16))); 2668 2669 aRewriteSchema = true; 2670 2671 return NS_OK; 2672 } 2673 2674 nsresult MigrateFrom16To17(nsIFile& aDBDir, mozIStorageConnection& aConn, 2675 bool& aRewriteSchema) { 2676 MOZ_ASSERT(!NS_IsMainThread()); 2677 2678 // This migration path removes the response_redirected and 2679 // response_redirected_url columns from the entries table. sqlite doesn't 2680 // support removing a column from a table using ALTER TABLE, so we need to 2681 // create a new table without those columns, fill it up with the existing 2682 // data, and then drop the original table and rename the new one to the old 2683 // one. 2684 2685 // Create a new_entries table with the new fields as of version 17. 2686 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2687 "CREATE TABLE new_entries (" 2688 "id INTEGER NOT NULL PRIMARY KEY, " 2689 "request_method TEXT NOT NULL, " 2690 "request_url_no_query TEXT NOT NULL, " 2691 "request_url_no_query_hash BLOB NOT NULL, " 2692 "request_url_query TEXT NOT NULL, " 2693 "request_url_query_hash BLOB NOT NULL, " 2694 "request_referrer TEXT NOT NULL, " 2695 "request_headers_guard INTEGER NOT NULL, " 2696 "request_mode INTEGER NOT NULL, " 2697 "request_credentials INTEGER NOT NULL, " 2698 "request_contentpolicytype INTEGER NOT NULL, " 2699 "request_cache INTEGER NOT NULL, " 2700 "request_body_id TEXT NULL, " 2701 "response_type INTEGER NOT NULL, " 2702 "response_url TEXT NOT NULL, " 2703 "response_status INTEGER NOT NULL, " 2704 "response_status_text TEXT NOT NULL, " 2705 "response_headers_guard INTEGER NOT NULL, " 2706 "response_body_id TEXT NULL, " 2707 "response_security_info_id INTEGER NULL REFERENCES security_info(id), " 2708 "response_principal_info TEXT NOT NULL, " 2709 "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " 2710 "request_redirect INTEGER NOT NULL" 2711 ")"_ns))); 2712 2713 // Copy all of the data to the newly created table. 2714 QM_TRY( 2715 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO new_entries (" 2716 "id, " 2717 "request_method, " 2718 "request_url_no_query, " 2719 "request_url_no_query_hash, " 2720 "request_url_query, " 2721 "request_url_query_hash, " 2722 "request_referrer, " 2723 "request_headers_guard, " 2724 "request_mode, " 2725 "request_credentials, " 2726 "request_contentpolicytype, " 2727 "request_cache, " 2728 "request_redirect, " 2729 "request_body_id, " 2730 "response_type, " 2731 "response_url, " 2732 "response_status, " 2733 "response_status_text, " 2734 "response_headers_guard, " 2735 "response_body_id, " 2736 "response_security_info_id, " 2737 "response_principal_info, " 2738 "cache_id " 2739 ") SELECT " 2740 "id, " 2741 "request_method, " 2742 "request_url_no_query, " 2743 "request_url_no_query_hash, " 2744 "request_url_query, " 2745 "request_url_query_hash, " 2746 "request_referrer, " 2747 "request_headers_guard, " 2748 "request_mode, " 2749 "request_credentials, " 2750 "request_contentpolicytype, " 2751 "request_cache, " 2752 "request_redirect, " 2753 "request_body_id, " 2754 "response_type, " 2755 "response_url, " 2756 "response_status, " 2757 "response_status_text, " 2758 "response_headers_guard, " 2759 "response_body_id, " 2760 "response_security_info_id, " 2761 "response_principal_info, " 2762 "cache_id " 2763 "FROM entries;"_ns))); 2764 2765 // Remove the old table. 2766 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("DROP TABLE entries;"_ns))); 2767 2768 // Rename new_entries to entries. 2769 QM_TRY(MOZ_TO_RESULT( 2770 aConn.ExecuteSimpleSQL("ALTER TABLE new_entries RENAME to entries;"_ns))); 2771 2772 // Now, recreate our indices. 2773 QM_TRY(MOZ_TO_RESULT( 2774 aConn.ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest)))); 2775 2776 // Revalidate the foreign key constraints, and ensure that there are no 2777 // violations. 2778 QM_TRY_INSPECT(const bool& hasResult, 2779 quota::CreateAndExecuteSingleStepStatement< 2780 quota::SingleStepResult::ReturnNullIfNoResult>( 2781 aConn, "PRAGMA foreign_key_check;"_ns)); 2782 2783 QM_TRY(OkIf(!hasResult), NS_ERROR_FAILURE); 2784 2785 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(17))); 2786 2787 return NS_OK; 2788 } 2789 2790 nsresult MigrateFrom17To18(nsIFile& aDBDir, mozIStorageConnection& aConn, 2791 bool& aRewriteSchema) { 2792 MOZ_ASSERT(!NS_IsMainThread()); 2793 2794 // This migration is needed in order to remove "only-if-cached" RequestCache 2795 // values from the database. This enum value was removed from the spec in 2796 // https://github.com/whatwg/fetch/issues/39 but we unfortunately happily 2797 // accepted this value in the Request constructor. 2798 // 2799 // There is no good value to upgrade this to, so we just stick to "default". 2800 2801 static_assert(int(RequestCache::Default) == 0, 2802 "This is where the 0 below comes from!"); 2803 QM_TRY(MOZ_TO_RESULT( 2804 aConn.ExecuteSimpleSQL("UPDATE entries SET request_cache = 0 " 2805 "WHERE request_cache = 5;"_ns))); 2806 2807 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(18))); 2808 2809 return NS_OK; 2810 } 2811 2812 nsresult MigrateFrom18To19(nsIFile& aDBDir, mozIStorageConnection& aConn, 2813 bool& aRewriteSchema) { 2814 MOZ_ASSERT(!NS_IsMainThread()); 2815 2816 // This migration is needed in order to update the RequestMode values for 2817 // Request objects corresponding to a navigation content policy type to 2818 // "navigate". 2819 2820 static_assert(int(nsIContentPolicy::TYPE_DOCUMENT) == 6 && 2821 int(nsIContentPolicy::TYPE_SUBDOCUMENT) == 7 && 2822 int(nsIContentPolicy::TYPE_INTERNAL_FRAME) == 28 && 2823 int(nsIContentPolicy::TYPE_INTERNAL_IFRAME) == 29 && 2824 int(RequestMode::Navigate) == 3, 2825 "This is where the numbers below come from!"); 2826 // 8 is former TYPE_REFRESH. 2827 2828 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2829 "UPDATE entries SET request_mode = 3 " 2830 "WHERE request_contentpolicytype IN (6, 7, 28, 29, 8);"_ns))); 2831 2832 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(19))); 2833 2834 return NS_OK; 2835 } 2836 2837 nsresult MigrateFrom19To20(nsIFile& aDBDir, mozIStorageConnection& aConn, 2838 bool& aRewriteSchema) { 2839 MOZ_ASSERT(!NS_IsMainThread()); 2840 2841 // Add the request_referrer_policy column with a default value of 2842 // "no-referrer-when-downgrade". Note, we only use a default value here 2843 // because its required by ALTER TABLE and we need to apply the default 2844 // "no-referrer-when-downgrade" to existing records in the table. We don't 2845 // actually want to keep the default in the schema for future INSERTs. 2846 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2847 "ALTER TABLE entries " 2848 "ADD COLUMN request_referrer_policy INTEGER NOT NULL DEFAULT 2"_ns))); 2849 2850 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(20))); 2851 2852 aRewriteSchema = true; 2853 2854 return NS_OK; 2855 } 2856 2857 nsresult MigrateFrom20To21(nsIFile& aDBDir, mozIStorageConnection& aConn, 2858 bool& aRewriteSchema) { 2859 MOZ_ASSERT(!NS_IsMainThread()); 2860 2861 // This migration creates response_url_list table to store response_url and 2862 // removes the response_url column from the entries table. 2863 // sqlite doesn't support removing a column from a table using ALTER TABLE, 2864 // so we need to create a new table without those columns, fill it up with the 2865 // existing data, and then drop the original table and rename the new one to 2866 // the old one. 2867 2868 // Create a new_entries table with the new fields as of version 21. 2869 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2870 "CREATE TABLE new_entries (" 2871 "id INTEGER NOT NULL PRIMARY KEY, " 2872 "request_method TEXT NOT NULL, " 2873 "request_url_no_query TEXT NOT NULL, " 2874 "request_url_no_query_hash BLOB NOT NULL, " 2875 "request_url_query TEXT NOT NULL, " 2876 "request_url_query_hash BLOB NOT NULL, " 2877 "request_referrer TEXT NOT NULL, " 2878 "request_headers_guard INTEGER NOT NULL, " 2879 "request_mode INTEGER NOT NULL, " 2880 "request_credentials INTEGER NOT NULL, " 2881 "request_contentpolicytype INTEGER NOT NULL, " 2882 "request_cache INTEGER NOT NULL, " 2883 "request_body_id TEXT NULL, " 2884 "response_type INTEGER NOT NULL, " 2885 "response_status INTEGER NOT NULL, " 2886 "response_status_text TEXT NOT NULL, " 2887 "response_headers_guard INTEGER NOT NULL, " 2888 "response_body_id TEXT NULL, " 2889 "response_security_info_id INTEGER NULL REFERENCES security_info(id), " 2890 "response_principal_info TEXT NOT NULL, " 2891 "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " 2892 "request_redirect INTEGER NOT NULL, " 2893 "request_referrer_policy INTEGER NOT NULL" 2894 ")"_ns))); 2895 2896 // Create a response_url_list table with the new fields as of version 21. 2897 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2898 "CREATE TABLE response_url_list (" 2899 "url TEXT NOT NULL, " 2900 "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE" 2901 ")"_ns))); 2902 2903 // Copy all of the data to the newly created entries table. 2904 QM_TRY( 2905 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO new_entries (" 2906 "id, " 2907 "request_method, " 2908 "request_url_no_query, " 2909 "request_url_no_query_hash, " 2910 "request_url_query, " 2911 "request_url_query_hash, " 2912 "request_referrer, " 2913 "request_headers_guard, " 2914 "request_mode, " 2915 "request_credentials, " 2916 "request_contentpolicytype, " 2917 "request_cache, " 2918 "request_redirect, " 2919 "request_referrer_policy, " 2920 "request_body_id, " 2921 "response_type, " 2922 "response_status, " 2923 "response_status_text, " 2924 "response_headers_guard, " 2925 "response_body_id, " 2926 "response_security_info_id, " 2927 "response_principal_info, " 2928 "cache_id " 2929 ") SELECT " 2930 "id, " 2931 "request_method, " 2932 "request_url_no_query, " 2933 "request_url_no_query_hash, " 2934 "request_url_query, " 2935 "request_url_query_hash, " 2936 "request_referrer, " 2937 "request_headers_guard, " 2938 "request_mode, " 2939 "request_credentials, " 2940 "request_contentpolicytype, " 2941 "request_cache, " 2942 "request_redirect, " 2943 "request_referrer_policy, " 2944 "request_body_id, " 2945 "response_type, " 2946 "response_status, " 2947 "response_status_text, " 2948 "response_headers_guard, " 2949 "response_body_id, " 2950 "response_security_info_id, " 2951 "response_principal_info, " 2952 "cache_id " 2953 "FROM entries;"_ns))); 2954 2955 // Copy reponse_url to the newly created response_url_list table. 2956 QM_TRY( 2957 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("INSERT INTO response_url_list (" 2958 "url, " 2959 "entry_id " 2960 ") SELECT " 2961 "response_url, " 2962 "id " 2963 "FROM entries;"_ns))); 2964 2965 // Remove the old table. 2966 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("DROP TABLE entries;"_ns))); 2967 2968 // Rename new_entries to entries. 2969 QM_TRY(MOZ_TO_RESULT( 2970 aConn.ExecuteSimpleSQL("ALTER TABLE new_entries RENAME to entries;"_ns))); 2971 2972 // Now, recreate our indices. 2973 QM_TRY(MOZ_TO_RESULT( 2974 aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexEntriesRequest)))); 2975 2976 // Revalidate the foreign key constraints, and ensure that there are no 2977 // violations. 2978 QM_TRY_INSPECT(const bool& hasResult, 2979 quota::CreateAndExecuteSingleStepStatement< 2980 quota::SingleStepResult::ReturnNullIfNoResult>( 2981 aConn, "PRAGMA foreign_key_check;"_ns)); 2982 2983 QM_TRY(OkIf(!hasResult), NS_ERROR_FAILURE); 2984 2985 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(21))); 2986 2987 aRewriteSchema = true; 2988 2989 return NS_OK; 2990 } 2991 2992 nsresult MigrateFrom21To22(nsIFile& aDBDir, mozIStorageConnection& aConn, 2993 bool& aRewriteSchema) { 2994 MOZ_ASSERT(!NS_IsMainThread()); 2995 2996 // Add the request_integrity column. 2997 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 2998 "ALTER TABLE entries " 2999 "ADD COLUMN request_integrity TEXT NOT NULL DEFAULT '';"_ns))); 3000 3001 QM_TRY(MOZ_TO_RESULT( 3002 aConn.ExecuteSimpleSQL("UPDATE entries SET request_integrity = '';"_ns))); 3003 3004 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(22))); 3005 3006 aRewriteSchema = true; 3007 3008 return NS_OK; 3009 } 3010 3011 nsresult MigrateFrom22To23(nsIFile& aDBDir, mozIStorageConnection& aConn, 3012 bool& aRewriteSchema) { 3013 MOZ_ASSERT(!NS_IsMainThread()); 3014 3015 // The only change between 22 and 23 was a different snappy compression 3016 // format, but it's backwards-compatible. 3017 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(23))); 3018 3019 return NS_OK; 3020 } 3021 3022 nsresult MigrateFrom23To24(nsIFile& aDBDir, mozIStorageConnection& aConn, 3023 bool& aRewriteSchema) { 3024 MOZ_ASSERT(!NS_IsMainThread()); 3025 3026 // Add the request_url_fragment column. 3027 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 3028 "ALTER TABLE entries " 3029 "ADD COLUMN request_url_fragment TEXT NOT NULL DEFAULT ''"_ns))); 3030 3031 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(24))); 3032 3033 aRewriteSchema = true; 3034 3035 return NS_OK; 3036 } 3037 3038 nsresult MigrateFrom24To25(nsIFile& aDBDir, mozIStorageConnection& aConn, 3039 bool& aRewriteSchema) { 3040 MOZ_ASSERT(!NS_IsMainThread()); 3041 3042 // The only change between 24 and 25 was a new nsIContentPolicy type. 3043 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(25))); 3044 3045 return NS_OK; 3046 } 3047 3048 nsresult MigrateFrom25To26(nsIFile& aDBDir, mozIStorageConnection& aConn, 3049 bool& aRewriteSchema) { 3050 MOZ_ASSERT(!NS_IsMainThread()); 3051 3052 // Add the response_padding_size column. 3053 // Note: only opaque repsonse should be non-null interger. 3054 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 3055 "ALTER TABLE entries " 3056 "ADD COLUMN response_padding_size INTEGER NULL "_ns))); 3057 3058 QM_TRY(MOZ_TO_RESULT( 3059 aConn.ExecuteSimpleSQL("UPDATE entries SET response_padding_size = 0 " 3060 "WHERE response_type = 4"_ns // opaque response 3061 ))); 3062 3063 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(26))); 3064 3065 aRewriteSchema = true; 3066 3067 return NS_OK; 3068 } 3069 3070 nsresult MigrateFrom26To27(nsIFile& aDBDir, mozIStorageConnection& aConn, 3071 bool& aRewriteSchema) { 3072 MOZ_ASSERT(!NS_IsMainThread()); 3073 3074 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(kHackyDowngradeSchemaVersion))); 3075 3076 return NS_OK; 3077 } 3078 3079 nsresult MigrateFrom27To28(nsIFile& aDBDir, mozIStorageConnection& aConn, 3080 bool& aRewriteSchema) { 3081 MOZ_ASSERT(!NS_IsMainThread()); 3082 3083 // In Bug 1264178, we added a column request_integrity into table entries. 3084 // However, at that time, the default value for the existing rows is NULL 3085 // which against the statement in kTableEntries. Thus, we need to have another 3086 // upgrade to update these values to an empty string. 3087 QM_TRY(MOZ_TO_RESULT( 3088 aConn.ExecuteSimpleSQL("UPDATE entries SET request_integrity = '' " 3089 "WHERE request_integrity is NULL;"_ns))); 3090 3091 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(28))); 3092 3093 return NS_OK; 3094 } 3095 3096 class BodyDiskSizeGetterFunction final : public mozIStorageFunction { 3097 public: 3098 explicit BodyDiskSizeGetterFunction(nsCOMPtr<nsIFile> aDBDir) 3099 : mDBDir(std::move(aDBDir)), mTotalDiskUsage(0) {} 3100 3101 NS_DECL_ISUPPORTS 3102 3103 int64_t TotalDiskUsage() const { return mTotalDiskUsage; } 3104 3105 private: 3106 ~BodyDiskSizeGetterFunction() = default; 3107 3108 NS_IMETHOD 3109 OnFunctionCall(mozIStorageValueArray* aArguments, 3110 nsIVariant** aResult) override { 3111 MOZ_ASSERT(aArguments); 3112 MOZ_ASSERT(aResult); 3113 3114 AUTO_PROFILER_LABEL("BodyDiskSizeGetterFunction::OnFunctionCall", DOM); 3115 3116 uint32_t argc; 3117 QM_TRY(MOZ_TO_RESULT(aArguments->GetNumEntries(&argc))); 3118 3119 if (argc != 1) { 3120 NS_WARNING("Don't call me with the wrong number of arguments!"); 3121 return NS_ERROR_UNEXPECTED; 3122 } 3123 3124 int32_t type; 3125 QM_TRY(MOZ_TO_RESULT(aArguments->GetTypeOfIndex(0, &type))); 3126 3127 if (type == mozIStorageStatement::VALUE_TYPE_NULL) { 3128 nsCOMPtr<nsIVariant> result = new mozilla::storage::NullVariant(); 3129 3130 result.forget(aResult); 3131 return NS_OK; 3132 } 3133 3134 if (type != mozIStorageStatement::VALUE_TYPE_TEXT) { 3135 NS_WARNING("Don't call me with the wrong type of arguments!"); 3136 return NS_ERROR_UNEXPECTED; 3137 } 3138 3139 QM_TRY_INSPECT(const auto& idString, 3140 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsAutoCString, aArguments, 3141 GetUTF8String, 0)); 3142 3143 nsID id{}; 3144 QM_TRY(OkIf(id.Parse(idString.get())), Err(NS_ERROR_UNEXPECTED)); 3145 3146 QM_TRY_INSPECT( 3147 const auto& fileSize, 3148 QM_OR_ELSE_WARN_IF( 3149 // Expression. 3150 GetBodyDiskSize(*mDBDir, id), 3151 // Predicate. 3152 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }), 3153 // Fallback. If the file does no longer exist, treat 3154 // it as 0-sized. 3155 (ErrToOk<0, int64_t>))); 3156 3157 CheckedInt64 totalDiskUsage = mTotalDiskUsage + fileSize; 3158 mTotalDiskUsage = 3159 totalDiskUsage.isValid() ? totalDiskUsage.value() : INT64_MAX; 3160 3161 nsCOMPtr<nsIVariant> result = 3162 new mozilla::storage::IntegerVariant(fileSize); 3163 3164 result.forget(aResult); 3165 return NS_OK; 3166 } 3167 3168 nsCOMPtr<nsIFile> mDBDir; 3169 int64_t mTotalDiskUsage; 3170 }; 3171 3172 NS_IMPL_ISUPPORTS(BodyDiskSizeGetterFunction, mozIStorageFunction) 3173 3174 nsresult MigrateFrom28To29(nsIFile& aDBDir, mozIStorageConnection& aConn, 3175 bool& aRewriteSchema) { 3176 MOZ_ASSERT(!NS_IsMainThread()); 3177 3178 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 3179 "ALTER TABLE entries " 3180 "ADD COLUMN request_body_disk_size INTEGER NULL;"_ns))); 3181 3182 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 3183 "ALTER TABLE entries " 3184 "ADD COLUMN response_body_disk_size INTEGER NULL;"_ns))); 3185 3186 RefPtr<BodyDiskSizeGetterFunction> bodyDiskSizeGetter = 3187 new BodyDiskSizeGetterFunction(&aDBDir); 3188 3189 constexpr auto bodyDiskSizeGetterName = "get_body_disk_size"_ns; 3190 3191 QM_TRY(MOZ_TO_RESULT( 3192 aConn.CreateFunction(bodyDiskSizeGetterName, 1, bodyDiskSizeGetter))); 3193 3194 QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL( 3195 "UPDATE entries SET " 3196 "request_body_disk_size = get_body_disk_size(request_body_id), " 3197 "response_body_disk_size = get_body_disk_size(response_body_id);"_ns))); 3198 3199 QM_TRY(MOZ_TO_RESULT(aConn.RemoveFunction(bodyDiskSizeGetterName))); 3200 3201 QM_TRY( 3202 MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableUsageInfo)))); 3203 3204 QM_TRY_INSPECT( 3205 const auto& state, 3206 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 3207 nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement, 3208 "INSERT INTO usage_info VALUES(1, :total_disk_usage);"_ns)); 3209 3210 QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName( 3211 "total_disk_usage"_ns, bodyDiskSizeGetter->TotalDiskUsage()))); 3212 3213 QM_TRY(MOZ_TO_RESULT(state->Execute())); 3214 3215 QM_TRY(MOZ_TO_RESULT( 3216 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesInsert)))); 3217 3218 QM_TRY(MOZ_TO_RESULT( 3219 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesUpdate)))); 3220 3221 QM_TRY(MOZ_TO_RESULT( 3222 aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesDelete)))); 3223 3224 QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(29))); 3225 3226 aRewriteSchema = true; 3227 3228 return NS_OK; 3229 } 3230 3231 } // anonymous namespace 3232 } // namespace mozilla::dom::cache::db