Dictionary.cpp (56675B)
1 /* vim: set ts=2 sts=2 et sw=2: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include <stdlib.h> 7 8 #include "Dictionary.h" 9 10 #include "CacheFileUtils.h" 11 #include "nsAttrValue.h" 12 #include "nsContentPolicyUtils.h" 13 #include "nsString.h" 14 #include "nsAppDirectoryServiceDefs.h" 15 #include "nsIAsyncInputStream.h" 16 #include "nsICacheStorageService.h" 17 #include "nsICacheStorage.h" 18 #include "nsICacheEntry.h" 19 #include "nsICachingChannel.h" 20 #include "nsICancelable.h" 21 #include "nsIChannel.h" 22 #include "nsContentUtils.h" 23 #include "nsIFile.h" 24 #include "nsIInputStream.h" 25 #include "nsILoadContext.h" 26 #include "nsILoadContextInfo.h" 27 #include "nsILoadGroup.h" 28 #include "nsIObserverService.h" 29 #include "nsIURI.h" 30 #include "nsIURIMutator.h" 31 #include "nsInputStreamPump.h" 32 #include "nsNetUtil.h" 33 #include "nsServiceManagerUtils.h" 34 #include "nsSimpleURI.h" 35 #include "nsStandardURL.h" 36 #include "nsStreamUtils.h" 37 #include "nsString.h" 38 #include "nsThreadUtils.h" 39 #include "mozilla/Logging.h" 40 41 #include "mozilla/Components.h" 42 #include "mozilla/dom/Document.h" 43 #include "mozilla/FlowMarkers.h" 44 #include "mozilla/OriginAttributes.h" 45 #include "mozilla/Preferences.h" 46 #include "mozilla/SchedulerGroup.h" 47 #include "mozilla/StaticPrefs_network.h" 48 #include "mozilla/glean/NetwerkMetrics.h" 49 50 #include "mozilla/net/NeckoCommon.h" 51 #include "mozilla/net/NeckoParent.h" 52 #include "mozilla/net/NeckoChild.h" 53 #include "mozilla/net/URLPatternGlue.h" 54 #include "mozilla/net/urlpattern_glue.h" 55 56 #include "LoadContextInfo.h" 57 #include "mozilla/ipc/URIUtils.h" 58 #include "SerializedLoadContext.h" 59 60 #include "mozilla/dom/ContentParent.h" 61 #include "mozilla/dom/InternalRequest.h" 62 #include "mozilla/ClearOnShutdown.h" 63 64 #include "ReferrerInfo.h" 65 66 using namespace mozilla; 67 68 namespace mozilla { 69 namespace net { 70 71 // Access to all these classes is from MainThread unless otherwise specified 72 73 LazyLogModule gDictionaryLog("CompressionDictionaries"); 74 75 #define DICTIONARY_LOG(args) \ 76 MOZ_LOG(gDictionaryLog, mozilla::LogLevel::Debug, args) 77 78 /** 79 * Reference to the DictionaryCache singleton. May be null. 80 */ 81 StaticRefPtr<DictionaryCache> gDictionaryCache; 82 StaticRefPtr<nsICacheStorage> DictionaryCache::sCacheStorage; 83 84 // about:cache gets upset about entries that don't fit URL specs, so we need 85 // to add the trailing '/' to GetPrePath() 86 static nsresult GetDictPath(nsIURI* aURI, nsACString& aPrePath) { 87 if (NS_FAILED(aURI->GetPrePath(aPrePath))) { 88 return NS_ERROR_FAILURE; 89 } 90 aPrePath += '/'; 91 return NS_OK; 92 } 93 94 DictionaryCacheEntry::DictionaryCacheEntry(const char* aKey) { 95 mURI = aKey; 96 DICTIONARY_LOG(("Created DictionaryCacheEntry %p, uri=%s", this, aKey)); 97 } 98 99 DictionaryCacheEntry::~DictionaryCacheEntry() { 100 MOZ_ASSERT(mUsers == 0); 101 DICTIONARY_LOG( 102 ("Destroyed DictionaryCacheEntry %p, uri=%s, pattern=%s, id=%s", this, 103 mURI.get(), mPattern.get(), mId.get())); 104 } 105 106 DictionaryCacheEntry::DictionaryCacheEntry(const nsACString& aURI, 107 const nsACString& aPattern, 108 nsTArray<nsCString>& aMatchDest, 109 const nsACString& aId, 110 uint32_t aExpiration, 111 const Maybe<nsCString>& aHash) 112 : mURI(aURI), mExpiration(aExpiration), mPattern(aPattern), mId(aId) { 113 ConvertMatchDestToEnumArray(aMatchDest, mMatchDest); 114 DICTIONARY_LOG( 115 ("Created DictionaryCacheEntry %p, uri=%s, pattern=%s, id=%s, " 116 "expiration=%u", 117 this, PromiseFlatCString(aURI).get(), PromiseFlatCString(aPattern).get(), 118 PromiseFlatCString(aId).get(), aExpiration)); 119 if (aHash) { 120 mHash = aHash.value(); 121 } 122 } 123 124 NS_IMPL_ISUPPORTS(DictionaryCacheEntry, nsICacheEntryOpenCallback, 125 nsIStreamListener) 126 127 // Convert string MatchDest array to enum array 128 // static 129 void DictionaryCacheEntry::ConvertMatchDestToEnumArray( 130 const nsTArray<nsCString>& aMatchDest, 131 nsTArray<dom::RequestDestination>& aMatchEnums) { 132 AutoTArray<dom::RequestDestination, 3> temp; 133 for (auto& string : aMatchDest) { 134 dom::RequestDestination dest = 135 dom::StringToEnum<dom::RequestDestination>(string).valueOr( 136 dom::RequestDestination::_empty); 137 if (dest != dom::RequestDestination::_empty) { 138 temp.AppendElement(dest); 139 } 140 } 141 aMatchEnums.SwapElements(temp); 142 } 143 144 // Returns true if the pattern for the dictionary matches the path given. 145 // Note: we need to verify that this entry has not expired due to 2.2.1 of 146 // https://datatracker.ietf.org/doc/draft-ietf-httpbis-compression-dictionary/ 147 bool DictionaryCacheEntry::Match(const nsACString& aFilePath, 148 ExtContentPolicyType aType, uint32_t aNow, 149 uint32_t& aLongest) { 150 if (mHash.IsEmpty()) { 151 // We don't have the file yet 152 return false; 153 } 154 if (mNotCached) { 155 // Not actually in the cache 156 // May not actually be necessary, but good safety valve. 157 return false; 158 } 159 // Not worth checking if we wouldn't use it 160 DICTIONARY_LOG(("Match: %p %s to %s, %s (now=%u, expiration=%u)", this, 161 PromiseFlatCString(aFilePath).get(), mPattern.get(), 162 NS_CP_ContentTypeName(aType), aNow, mExpiration)); 163 if ((mExpiration == 0 || aNow < mExpiration) && 164 mPattern.Length() > aLongest) { 165 // Need to match using match-dest, if it exists 166 if (mMatchDest.IsEmpty() || 167 mMatchDest.IndexOf( 168 dom::InternalRequest::MapContentPolicyTypeToRequestDestination( 169 aType)) != mMatchDest.NoIndex) { 170 UrlpPattern pattern; 171 UrlpOptions options{}; 172 const nsCString base(mURI); 173 if (!urlp_parse_pattern_from_string(&mPattern, &base, options, 174 &pattern)) { 175 DICTIONARY_LOG( 176 ("Failed to parse dictionary pattern %s", mPattern.get())); 177 return false; 178 } 179 180 UrlpInput input = net::CreateUrlpInput(aFilePath); 181 bool result = net::UrlpPatternTest(pattern, input, Some(base)); 182 DICTIONARY_LOG(("URLPattern result was %d", result)); 183 if (result) { 184 aLongest = mPattern.Length(); 185 DICTIONARY_LOG(("Match: %s (longest %u)", mURI.get(), aLongest)); 186 } 187 return result; 188 } else { 189 DICTIONARY_LOG((" Failed on matchDest")); 190 } 191 } else { 192 DICTIONARY_LOG( 193 (" Failed due to expiration: %u vs %u", aNow, mExpiration)); 194 } 195 return false; 196 } 197 198 void DictionaryCacheEntry::InUse() { 199 mUsers++; 200 DICTIONARY_LOG(("Dictionary users for %s -- %u Users", mURI.get(), mUsers)); 201 } 202 203 void DictionaryCacheEntry::UseCompleted() { 204 MOZ_ASSERT(mUsers > 0); 205 mUsers--; 206 // Purge mDictionaryData 207 if (mUsers == 0) { // XXX perhaps we should hold it for a bit longer? 208 DICTIONARY_LOG(("Clearing Dictionary data for %s", mURI.get())); 209 mDictionaryData.clear(); 210 mDictionaryDataComplete = false; 211 } else { 212 DICTIONARY_LOG(("Not clearing Dictionary data for %s -- %u Users", 213 mURI.get(), mUsers)); 214 } 215 } 216 217 // returns aShouldSuspend=true if we should suspend to wait for the prefetch 218 nsresult DictionaryCacheEntry::Prefetch( 219 nsILoadContextInfo* aLoadContextInfo, bool& aShouldSuspend, 220 const std::function<void(nsresult)>& aFunc) { 221 DICTIONARY_LOG(("Prefetch for %s", mURI.get())); 222 // Start reading the cache entry into memory and call completion 223 // function when done 224 if (mWaitingPrefetch.IsEmpty()) { 225 // Note that if the cache entry has been cleared, and we still have active 226 // users of it, we'll hold onto that data since we have outstanding requests 227 // for it. Probably we shouldn't allow new requests to use this data (and 228 // the WPTs assume we shouldn't). 229 if (mDictionaryDataComplete) { 230 DICTIONARY_LOG( 231 ("Prefetch for %s - already have data in memory (%u users)", 232 mURI.get(), mUsers)); 233 aShouldSuspend = false; 234 return NS_OK; 235 } 236 237 // We haven't requested it yet from the Cache and don't have it in memory 238 // already. 239 // We can't use sCacheStorage because we need the correct LoadContextInfo 240 nsCOMPtr<nsICacheStorageService> cacheStorageService( 241 components::CacheStorage::Service()); 242 if (!cacheStorageService) { 243 aShouldSuspend = false; 244 return NS_ERROR_FAILURE; 245 } 246 nsCOMPtr<nsICacheStorage> cacheStorage; 247 nsresult rv = cacheStorageService->DiskCacheStorage( 248 aLoadContextInfo, getter_AddRefs(cacheStorage)); 249 if (NS_FAILED(rv)) { 250 aShouldSuspend = false; 251 return NS_ERROR_FAILURE; 252 } 253 // If the file isn't available in the cache, AsyncOpenURIString() 254 // will synchronously make a callback to OnCacheEntryAvailable() with 255 // nullptr. We can key off that to fail Prefetch(), and also to 256 // remove ourselves from the origin. 257 if (NS_FAILED(cacheStorage->AsyncOpenURIString( 258 mURI, ""_ns, 259 nsICacheStorage::OPEN_READONLY | 260 nsICacheStorage::OPEN_COMPLETE_ONLY | 261 nsICacheStorage::CHECK_MULTITHREADED, 262 this)) || 263 mNotCached) { 264 DICTIONARY_LOG(("AsyncOpenURIString failed for %s", mURI.get())); 265 // For some reason the cache no longer has this entry; fail Prefetch 266 // and also remove this from our origin 267 aShouldSuspend = false; 268 // Remove from origin 269 if (mOrigin) { 270 mOrigin->RemoveEntry(this); 271 mOrigin = nullptr; 272 } 273 return NS_ERROR_FAILURE; 274 } 275 mWaitingPrefetch.AppendElement(aFunc); 276 DICTIONARY_LOG(("Started Prefetch for %s, anonymous=%d", mURI.get(), 277 aLoadContextInfo->IsAnonymous())); 278 aShouldSuspend = true; 279 return NS_OK; 280 } 281 DICTIONARY_LOG(("Prefetch for %s - already waiting", mURI.get())); 282 aShouldSuspend = true; 283 return NS_OK; 284 } 285 286 void DictionaryCacheEntry::AccumulateHash(const char* aBuf, int32_t aCount) { 287 MOZ_ASSERT(NS_IsMainThread()); 288 if (!mHash.IsEmpty()) { 289 if (!mDictionaryData.empty()) { 290 // We have data from the cache.... but if we change the hash there will 291 // be problems 292 // XXX dragons here 293 return; 294 } 295 // accumulating a new hash when we have an existing? 296 // XXX probably kill the hash when we get an overwrite; tricky, need to 297 // handle loading the old one into ram to decompress the new one. Also, 298 // what if the old one is being used for multiple requests, one of which 299 // is an overwrite? This is an edge case not discussed in the spec - we 300 // could separate out a structure for in-flight requests where the data 301 // would be used from, so the Entry could be overwritten as needed 302 return; // XXX 303 } 304 if (!mCrypto) { 305 DICTIONARY_LOG(("Calculating new hash for %s", mURI.get())); 306 // If mCrypto is null, and mDictionaryData is set, we've already got the 307 // data for this dictionary. 308 nsresult rv; 309 mCrypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); 310 if (NS_WARN_IF(NS_FAILED(rv))) { 311 return; 312 } 313 rv = mCrypto->Init(nsICryptoHash::SHA256); 314 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Cache InitCrypto failed"); 315 } 316 mCrypto->Update(reinterpret_cast<const uint8_t*>(aBuf), aCount); 317 DICTIONARY_LOG(("Accumulate Hash %p: %d bytes, total %zu", this, aCount, 318 mDictionaryData.length())); 319 } 320 321 void DictionaryCacheEntry::FinishHash() { 322 MOZ_ASSERT(NS_IsMainThread()); 323 if (mCrypto) { 324 mCrypto->Finish(true, mHash); 325 mCrypto = nullptr; 326 DICTIONARY_LOG(("Hash for %p (%s) is %s", this, mURI.get(), mHash.get())); 327 if (mOrigin) { 328 DICTIONARY_LOG(("Write on hash")); 329 // This will also move us from mPendingEntries to mEntries 330 if (NS_FAILED(mOrigin->Write(this))) { 331 mOrigin->RemoveEntry(this); 332 return; 333 } 334 if (!mBlocked) { 335 mOrigin->FinishAddEntry(this); 336 } 337 } 338 } 339 } 340 341 // Version of metadata entries we expect 342 static const uint32_t METADATA_VERSION = 1; 343 344 // Metadata format: 345 // |version|hash|pattern|[matchdest|]*||id|expiration|type 346 // 347 // * Entries: 348 // ** CString: URI -- the key, not in the entry 349 // ** CString: Version (1) 350 // ** CString: Hash 351 // ** CString: Pattern 352 // ** match-dest CString list, terminated by empty string 353 // *** CString: Match-dest 354 // ** CString: Id 355 // ** uint32 as a CString: expiration. If missing, 0 (none) 356 // ** CString: type -- defaults to 'raw' if missing 357 // We store strings with a delimiter, and use escapes for delimiters or escape 358 // characters in the source strings. 359 // 360 361 // Escape the string and append to aOutput 362 static void EscapeMetadataString(const nsACString& aInput, nsCString& aOutput) { 363 // First calculate how much we'll need to append. Means we'll walk the source 364 // twice, but avoids any potential multiple reallocations 365 const char* src = aInput.BeginReading(); 366 size_t len = 1; // for initial | 367 while (*src) { 368 if (*src == '|' || *src == '\\') { 369 len += 2; 370 } else { 371 len++; 372 } 373 src++; 374 } 375 aOutput.SetCapacity(aOutput.Length() + len); 376 src = aInput.BeginReading(); 377 378 aOutput.AppendLiteral("|"); 379 while (*src) { 380 if (*src == '|' || *src == '\\') { 381 aOutput.AppendLiteral("\\"); 382 } 383 aOutput.Append(*src++); 384 } 385 } 386 387 void DictionaryCacheEntry::MakeMetadataEntry(nsCString& aNewValue) { 388 aNewValue.AppendLiteral("|"), aNewValue.AppendInt(METADATA_VERSION), 389 EscapeMetadataString(mHash, aNewValue); 390 EscapeMetadataString(mPattern, aNewValue); 391 EscapeMetadataString(mId, aNewValue); 392 for (auto& dest : mMatchDest) { 393 EscapeMetadataString(dom::GetEnumString(dest), aNewValue); 394 } 395 // List of match-dest values is terminated by an empty string 396 EscapeMetadataString(""_ns, aNewValue); 397 // Expiration time, as a CString 398 nsAutoCStringN<12> expiration; 399 expiration = nsPrintfCString("%u", mExpiration); 400 EscapeMetadataString(expiration, aNewValue); 401 // We don't store type, since we only support type 'raw' We can support 402 // type in the future by considering missing type as raw without changing the 403 // format 404 } 405 406 nsresult DictionaryCacheEntry::Write(nsICacheEntry* aCacheEntry) { 407 nsAutoCStringN<2048> metadata; 408 MakeMetadataEntry(metadata); 409 DICTIONARY_LOG( 410 ("DictionaryCacheEntry::Write %s %s", mURI.get(), metadata.get())); 411 nsresult rv = aCacheEntry->SetMetaDataElement(mURI.get(), metadata.get()); 412 if (NS_FAILED(rv)) { 413 return rv; 414 } 415 return aCacheEntry->MetaDataReady(); 416 } 417 418 nsresult DictionaryCacheEntry::RemoveEntry(nsICacheEntry* aCacheEntry) { 419 DICTIONARY_LOG(("RemoveEntry from metadata for %s", mURI.get())); 420 nsresult rv = aCacheEntry->SetMetaDataElement(mURI.BeginReading(), nullptr); 421 if (NS_FAILED(rv)) { 422 return rv; 423 } 424 return aCacheEntry->MetaDataReady(); 425 } 426 427 // Parse - | for field seperator; \ for escape of | or \ . 428 static const char* GetEncodedString(const char* aSrc, nsACString& aOutput) { 429 // scan the input string and build the output, handling escapes 430 aOutput.Truncate(); 431 MOZ_ASSERT(*aSrc == '|' || *aSrc == 0); 432 if (!aSrc || *aSrc != '|') { 433 return aSrc; 434 } 435 aSrc++; 436 while (*aSrc) { 437 if (*aSrc == '|') { 438 break; 439 } 440 if (*aSrc == '\\') { 441 aSrc++; 442 } 443 aOutput.Append(*aSrc++); 444 } 445 return aSrc; 446 } 447 448 // Parse metadata from DictionaryOrigin 449 bool DictionaryCacheEntry::ParseMetadata(const char* aSrc) { 450 // Using mHash as a temp for version 451 aSrc = GetEncodedString(aSrc, mHash); 452 const char* tmp = mHash.get(); 453 uint32_t version = atoi(tmp); 454 if (version != METADATA_VERSION) { 455 return false; 456 } 457 aSrc = GetEncodedString(aSrc, mHash); 458 aSrc = GetEncodedString(aSrc, mPattern); 459 aSrc = GetEncodedString(aSrc, mId); 460 nsAutoCString temp; 461 // get match-dest values (list ended with empty string) 462 do { 463 aSrc = GetEncodedString(aSrc, temp); 464 if (!temp.IsEmpty()) { 465 dom::RequestDestination dest = 466 dom::StringToEnum<dom::RequestDestination>(temp).valueOr( 467 dom::RequestDestination::_empty); 468 if (dest != dom::RequestDestination::_empty) { 469 mMatchDest.AppendElement(dest); 470 } 471 } 472 } while (!temp.IsEmpty()); 473 if (*aSrc == '|') { 474 char* newSrc; 475 mExpiration = strtoul(++aSrc, &newSrc, 10); 476 aSrc = newSrc; 477 } // else leave default of 0 478 // XXX type - we assume and only support 'raw', may be missing 479 aSrc = GetEncodedString(aSrc, temp); 480 481 DICTIONARY_LOG( 482 ("Parse entry %s: |%s| %s match-dest[0]=%s id=%s", mURI.get(), 483 mHash.get(), mPattern.get(), 484 mMatchDest.Length() > 0 ? dom::GetEnumString(mMatchDest[0]).get() : "", 485 mId.get())); 486 return true; 487 } 488 489 void DictionaryCacheEntry::AppendMatchDest(nsACString& aDest) const { 490 for (auto& dest : mMatchDest) { 491 aDest.Append(dom::GetEnumString(dest)); 492 aDest.Append(" "); 493 } 494 } 495 496 //----------------------------------------------------------------------------- 497 // nsIStreamListener implementation 498 //----------------------------------------------------------------------------- 499 500 NS_IMETHODIMP 501 DictionaryCacheEntry::OnStartRequest(nsIRequest* request) { 502 DICTIONARY_LOG(("DictionaryCacheEntry %s OnStartRequest", mURI.get())); 503 return NS_OK; 504 } 505 506 NS_IMETHODIMP 507 DictionaryCacheEntry::OnDataAvailable(nsIRequest* request, 508 nsIInputStream* aInputStream, 509 uint64_t aOffset, uint32_t aCount) { 510 uint32_t n; 511 DICTIONARY_LOG( 512 ("DictionaryCacheEntry %s OnDataAvailable %u", mURI.get(), aCount)); 513 return aInputStream->ReadSegments(&DictionaryCacheEntry::ReadCacheData, this, 514 aCount, &n); 515 } 516 517 /* static */ 518 nsresult DictionaryCacheEntry::ReadCacheData( 519 nsIInputStream* aInStream, void* aClosure, const char* aFromSegment, 520 uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) { 521 DictionaryCacheEntry* self = static_cast<DictionaryCacheEntry*>(aClosure); 522 523 (void)self->mDictionaryData.append(aFromSegment, aCount); 524 DICTIONARY_LOG(("Accumulate %p (%s): %d bytes, total %zu", self, 525 self->mURI.get(), aCount, self->mDictionaryData.length())); 526 *aWriteCount = aCount; 527 return NS_OK; 528 } 529 530 NS_IMETHODIMP 531 DictionaryCacheEntry::OnStopRequest(nsIRequest* request, nsresult result) { 532 DICTIONARY_LOG(("DictionaryCacheEntry %s OnStopRequest", mURI.get())); 533 if (NS_SUCCEEDED(result)) { 534 mDictionaryDataComplete = true; 535 536 // Validate that the loaded dictionary data matches the stored hash 537 if (!mHash.IsEmpty()) { 538 nsCOMPtr<nsICryptoHash> hasher = 539 do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID); 540 if (hasher) { 541 hasher->Init(nsICryptoHash::SHA256); 542 hasher->Update(mDictionaryData.begin(), 543 static_cast<uint32_t>(mDictionaryData.length())); 544 nsAutoCString computedHash; 545 MOZ_ALWAYS_SUCCEEDS(hasher->Finish(true, computedHash)); 546 547 if (!computedHash.Equals(mHash)) { 548 DICTIONARY_LOG(("Hash mismatch for %s: expected %s, computed %s", 549 mURI.get(), mHash.get(), computedHash.get())); 550 result = NS_ERROR_CORRUPTED_CONTENT; 551 mDictionaryDataComplete = false; 552 mDictionaryData.clear(); 553 // Remove this corrupted dictionary entry 554 DictionaryCache::RemoveDictionaryFor(mURI); 555 } 556 } 557 } 558 559 DICTIONARY_LOG(("Unsuspending %zu channels, Dictionary len %zu", 560 mWaitingPrefetch.Length(), mDictionaryData.length())); 561 // if we suspended, un-suspend the channel(s) 562 for (auto& lambda : mWaitingPrefetch) { 563 (lambda)(result); 564 } 565 mWaitingPrefetch.Clear(); 566 } else { 567 // Pass error to waiting callbacks 568 for (auto& lambda : mWaitingPrefetch) { 569 (lambda)(result); 570 } 571 mWaitingPrefetch.Clear(); 572 } 573 574 // If we're being replaced by a new entry, swap now 575 RefPtr<DictionaryCacheEntry> self; 576 if (mReplacement) { 577 DICTIONARY_LOG(("Replacing entry %p with %p for %s", this, 578 mReplacement.get(), mURI.get())); 579 // Make sure we don't destroy ourselves 580 self = this; 581 mReplacement->mShouldSuspend = false; 582 mOrigin->RemoveEntry(this); 583 // When mReplacement gets all it's data, it will be added to mEntries 584 mReplacement->UnblockAddEntry(mOrigin); 585 mOrigin = nullptr; 586 } 587 588 mStopReceived = true; 589 return NS_OK; 590 } 591 592 void DictionaryCacheEntry::UnblockAddEntry(DictionaryOrigin* aOrigin) { 593 MOZ_ASSERT(NS_IsMainThread()); 594 if (!mHash.IsEmpty()) { 595 // Already done, we can move to mEntries now 596 aOrigin->FinishAddEntry(this); 597 } 598 mBlocked = false; 599 } 600 601 void DictionaryCacheEntry::WriteOnHash() { 602 bool hasHash = false; 603 { 604 MOZ_ASSERT(NS_IsMainThread()); 605 if (!mHash.IsEmpty()) { 606 hasHash = true; 607 } 608 } 609 if (hasHash && mOrigin) { 610 DICTIONARY_LOG(("Write already hashed")); 611 mOrigin->Write(this); 612 } 613 } 614 615 //----------------------------------------------------------------------------- 616 // nsICacheEntryOpenCallback implementation 617 //----------------------------------------------------------------------------- 618 // Note: we don't care if the entry is stale since we're not loading it; we're 619 // just saying with have this specific set of bits with this hash available 620 // to use as a dictionary. 621 622 // This may be called on a random thread due to 623 // nsICacheStorage::CHECK_MULTITHREADED 624 NS_IMETHODIMP 625 DictionaryCacheEntry::OnCacheEntryCheck(nsICacheEntry* aEntry, 626 uint32_t* result) { 627 DICTIONARY_LOG(("OnCacheEntryCheck %s", mURI.get())); 628 *result = nsICacheEntryOpenCallback::ENTRY_WANTED; 629 return NS_OK; 630 } 631 632 NS_IMETHODIMP 633 DictionaryCacheEntry::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew, 634 nsresult status) { 635 DICTIONARY_LOG(("OnCacheEntryAvailable %s, result %u, entry %p", mURI.get(), 636 (uint32_t)status, entry)); 637 if (entry) { 638 nsCOMPtr<nsIInputStream> stream; 639 entry->OpenInputStream(0, getter_AddRefs(stream)); 640 if (!stream) { 641 return NS_OK; 642 } 643 644 RefPtr<nsInputStreamPump> pump; 645 nsresult rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream); 646 if (NS_FAILED(rv)) { 647 return NS_OK; // just ignore 648 } 649 650 rv = pump->AsyncRead(this); 651 if (NS_FAILED(rv)) { 652 return NS_OK; // just ignore 653 } 654 DICTIONARY_LOG(("Waiting for data")); 655 } else { 656 // XXX Error out any channels waiting on this cache entry. Also, 657 // remove the dictionary entry from the origin. 658 mNotCached = true; // For Prefetch() 659 DICTIONARY_LOG(("Prefetched cache entry not available!")); 660 } 661 662 return NS_OK; 663 } 664 665 //---------------------------------------------------------------------------------- 666 667 // Read the metadata for an Origin and parse it, creating DictionaryCacheEntrys 668 // as needed. If aType is TYPE_OTHER, there is no Match() to do 669 void DictionaryOriginReader::Start( 670 bool aCreate, DictionaryOrigin* aOrigin, nsACString& aKey, nsIURI* aURI, 671 ExtContentPolicyType aType, DictionaryCache* aCache, 672 const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) { 673 mOrigin = aOrigin; 674 mURI = aURI; 675 mType = aType; 676 mCallback = aCallback; 677 mCache = aCache; 678 679 AUTO_PROFILER_FLOW_MARKER("DictionaryOriginReader::Start", NETWORK, 680 Flow::FromPointer(this)); 681 682 // The cache entry is for originattribute extension of 683 // META_DICTIONARY_PREFIX, plus key of prepath 684 685 // This also keeps this alive until we get the callback. We must do this 686 // BEFORE we call AsyncOpenURIString, or we may get a callback to 687 // OnCacheEntryAvailable before we can do this 688 mOrigin->mWaitingCacheRead.AppendElement(this); 689 if (mOrigin->mWaitingCacheRead.Length() == 1) { // was empty 690 DICTIONARY_LOG(("DictionaryOriginReader::Start(%s): %p", 691 PromiseFlatCString(aKey).get(), this)); 692 DictionaryCache::sCacheStorage->AsyncOpenURIString( 693 aKey, META_DICTIONARY_PREFIX, 694 aCreate 695 ? nsICacheStorage::OPEN_NORMALLY | 696 nsICacheStorage::CHECK_MULTITHREADED 697 : nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY | 698 nsICacheStorage::CHECK_MULTITHREADED, 699 this); 700 // This one will get the direct callback to do Match() 701 } 702 // Else we already have a read for this cache entry pending, just wait 703 // for that 704 } 705 706 void DictionaryOriginReader::FinishMatch() { 707 RefPtr<DictionaryCacheEntry> result; 708 // Don't Match if this was a call from AddEntry() 709 if (mType != ExtContentPolicy::TYPE_OTHER) { 710 nsCString path; 711 mURI->GetPathQueryRef(path); 712 result = mOrigin->Match(path, mType); 713 } 714 DICTIONARY_LOG(("Done with reading origin for %p", mOrigin.get())); 715 (mCallback)(true, result); 716 } 717 718 NS_IMPL_ISUPPORTS(DictionaryOriginReader, nsICacheEntryOpenCallback, 719 nsIStreamListener) 720 721 //----------------------------------------------------------------------------- 722 // nsICacheEntryOpenCallback implementation 723 //----------------------------------------------------------------------------- 724 725 // This may be called on a random thread due to 726 // nsICacheStorage::CHECK_MULTITHREADED 727 NS_IMETHODIMP DictionaryOriginReader::OnCacheEntryCheck(nsICacheEntry* entry, 728 uint32_t* result) { 729 *result = nsICacheEntryOpenCallback::ENTRY_WANTED; 730 DICTIONARY_LOG( 731 ("DictionaryOriginReader::OnCacheEntryCheck this=%p for entry %p", this, 732 entry)); 733 return NS_OK; 734 } 735 736 NS_IMETHODIMP DictionaryOriginReader::OnCacheEntryAvailable( 737 nsICacheEntry* aCacheEntry, bool isNew, nsresult result) { 738 MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!"); 739 DICTIONARY_LOG( 740 ("DictionaryOriginReader::OnCacheEntryAvailable this=%p for entry %p", 741 this, aCacheEntry)); 742 743 if (!aCacheEntry) { 744 // Didn't have any dictionaries for this origin, and must have been readonly 745 for (auto& reader : mOrigin->mWaitingCacheRead) { 746 (reader->mCallback)(true, nullptr); 747 } 748 mOrigin->mWaitingCacheRead.Clear(); 749 AUTO_PROFILER_TERMINATING_FLOW_MARKER( 750 "DictionaryOriginReader::OnCacheEntryAvailable", NETWORK, 751 Flow::FromPointer(this)); 752 return NS_OK; 753 } 754 755 mOrigin->SetCacheEntry(aCacheEntry); 756 AUTO_PROFILER_FLOW_MARKER("DictionaryOriginReader::VisitMetaData", NETWORK, 757 Flow::FromPointer(this)); 758 bool empty = false; 759 aCacheEntry->GetIsEmpty(&empty); 760 if (!empty) { 761 // There's no data in the cache entry, just metadata 762 nsCOMPtr<nsICacheEntryMetaDataVisitor> metadata(mOrigin); 763 aCacheEntry->VisitMetaData(metadata); 764 } // else new cache entry 765 766 // This list is the only thing keeping us alive 767 RefPtr<DictionaryOriginReader> safety(this); 768 for (auto& reader : mOrigin->mWaitingCacheRead) { 769 reader->FinishMatch(); 770 } 771 mOrigin->mWaitingCacheRead.Clear(); 772 AUTO_PROFILER_TERMINATING_FLOW_MARKER( 773 "DictionaryOriginReader::OnCacheEntryAvailable", NETWORK, 774 Flow::FromPointer(this)); 775 return NS_OK; 776 } 777 778 //----------------------------------------------------------------------------- 779 // nsIStreamListener implementation 780 //----------------------------------------------------------------------------- 781 782 NS_IMETHODIMP 783 DictionaryOriginReader::OnStartRequest(nsIRequest* request) { 784 DICTIONARY_LOG(("DictionaryOriginReader %p OnStartRequest", this)); 785 return NS_OK; 786 } 787 788 NS_IMETHODIMP 789 DictionaryOriginReader::OnDataAvailable(nsIRequest* request, 790 nsIInputStream* aInputStream, 791 uint64_t aOffset, uint32_t aCount) { 792 DICTIONARY_LOG( 793 ("DictionaryOriginReader %p OnDataAvailable %u", this, aCount)); 794 return NS_OK; 795 } 796 797 NS_IMETHODIMP 798 DictionaryOriginReader::OnStopRequest(nsIRequest* request, nsresult result) { 799 DICTIONARY_LOG(("DictionaryOriginReader %p OnStopRequest", this)); 800 return NS_OK; 801 } 802 803 // static 804 already_AddRefed<DictionaryCache> DictionaryCache::GetInstance() { 805 // XXX lock? In practice probably not needed, in theory yes 806 if (!gDictionaryCache) { 807 gDictionaryCache = new DictionaryCache(); 808 MOZ_ASSERT(NS_SUCCEEDED(gDictionaryCache->Init())); 809 } 810 return do_AddRef(gDictionaryCache); 811 } 812 813 nsresult DictionaryCache::Init() { 814 if (XRE_IsParentProcess()) { 815 nsCOMPtr<nsICacheStorageService> cacheStorageService( 816 components::CacheStorage::Service()); 817 if (!cacheStorageService) { 818 return NS_ERROR_FAILURE; 819 } 820 nsCOMPtr<nsICacheStorage> temp; 821 nsresult rv = cacheStorageService->DiskCacheStorage( 822 nullptr, getter_AddRefs(temp)); // Don't need a load context 823 if (NS_FAILED(rv)) { 824 return rv; 825 } 826 sCacheStorage = temp; 827 } 828 DICTIONARY_LOG(("Inited DictionaryCache %p", sCacheStorage.get())); 829 return NS_OK; 830 } 831 832 // static 833 void DictionaryCache::Shutdown() { 834 gDictionaryCache = nullptr; 835 sCacheStorage = nullptr; 836 } 837 838 nsresult DictionaryCache::AddEntry(nsIURI* aURI, const nsACString& aKey, 839 const nsACString& aPattern, 840 nsTArray<nsCString>& aMatchDest, 841 const nsACString& aId, 842 const Maybe<nsCString>& aHash, 843 bool aNewEntry, uint32_t aExpiration, 844 DictionaryCacheEntry** aDictEntry) { 845 // Note that normally we're getting an entry in and until all the data 846 // has been received, we can't use it. The Hash being null is a flag 847 // that it's not yet valid. 848 DICTIONARY_LOG(("AddEntry for %s, pattern %s, id %s, expiration %u", 849 PromiseFlatCString(aKey).get(), 850 PromiseFlatCString(aPattern).get(), 851 PromiseFlatCString(aId).get(), aExpiration)); 852 // Note that we don't know if there's an entry for this key in the origin 853 RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry( 854 aKey, aPattern, aMatchDest, aId, aExpiration, aHash); 855 dict = AddEntry(aURI, aNewEntry, dict); 856 if (dict) { 857 *aDictEntry = do_AddRef(dict).take(); 858 return NS_OK; 859 } 860 DICTIONARY_LOG( 861 ("Failed adding entry for %s", PromiseFlatCString(aKey).get())); 862 *aDictEntry = nullptr; 863 return NS_ERROR_FAILURE; 864 } 865 866 already_AddRefed<DictionaryCacheEntry> DictionaryCache::AddEntry( 867 nsIURI* aURI, bool aNewEntry, DictionaryCacheEntry* aDictEntry) { 868 // Note that normally we're getting an entry in and until all the data 869 // has been received, we can't use it. The Hash being null is a flag 870 // that it's not yet valid. 871 nsCString prepath; 872 if (NS_FAILED(GetDictPath(aURI, prepath))) { 873 return nullptr; 874 } 875 DICTIONARY_LOG( 876 ("AddEntry: %s, %d, %p", prepath.get(), aNewEntry, aDictEntry)); 877 // create for the origin if it doesn't exist 878 RefPtr<DictionaryCacheEntry> newEntry; 879 (void)mDictionaryCache.WithEntryHandle(prepath, [&](auto&& entry) { 880 auto& origin = entry.OrInsertWith([&] { 881 RefPtr<DictionaryOrigin> origin = new DictionaryOrigin(prepath, nullptr); 882 // Create a cache entry for this if it doesn't exist. Note 883 // that the entry we're adding will need to be saved later once 884 // we have the cache entry 885 886 // This creates a cycle until the dictionary is removed from the cache 887 aDictEntry->SetOrigin(origin); 888 DICTIONARY_LOG(("Creating cache entry for origin %s", prepath.get())); 889 890 // Open (and parse metadata) or create 891 RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); 892 // the type is irrelevant; we won't be calling Match() 893 reader->Start( 894 true, origin, prepath, aURI, ExtContentPolicy::TYPE_OTHER, this, 895 [entry = RefPtr(aDictEntry)]( 896 bool, DictionaryCacheEntry* aDict) { // XXX avoid so many lambdas 897 // which cause allocations 898 // Write the dirty entry we couldn't write before once 899 // we get the hash 900 entry->WriteOnHash(); 901 return NS_OK; 902 }); 903 // Since this is read asynchronously, we need to either add the entry 904 // async once the read is done and it's populated, or we have to handle 905 // collisions on the read 906 return origin; 907 }); 908 909 newEntry = origin->AddEntry(aDictEntry, aNewEntry); 910 DICTIONARY_LOG(("AddEntry: added %s", prepath.get())); 911 return NS_OK; 912 }); 913 return newEntry.forget(); 914 } 915 916 nsresult DictionaryCache::RemoveEntry(nsIURI* aURI, const nsACString& aKey) { 917 nsCString prepath; 918 if (NS_FAILED(GetDictPath(aURI, prepath))) { 919 return NS_ERROR_FAILURE; 920 } 921 DICTIONARY_LOG(("DictionaryCache::RemoveEntry for %s : %s", prepath.get(), 922 PromiseFlatCString(aKey).get())); 923 if (auto origin = mDictionaryCache.Lookup(prepath)) { 924 return origin.Data()->RemoveEntry(aKey); 925 } 926 return NS_ERROR_FAILURE; 927 } 928 929 void DictionaryCache::Clear() { 930 // There may be active Prefetch()es running, note, and active fetches 931 // using dictionaries. They will stay alive until the channels using 932 // them go away. However, no new requests will see them. 933 934 // This can be used to make the cache return to the state it has at 935 // startup, especially useful for tests. 936 mDictionaryCache.Clear(); 937 } 938 939 void DictionaryCache::CorruptHashForTesting(const nsACString& aURI) { 940 DICTIONARY_LOG(("DictionaryCache::CorruptHashForTesting for %s", 941 PromiseFlatCString(aURI).get())); 942 nsCOMPtr<nsIURI> uri; 943 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURI))) { 944 return; 945 } 946 nsAutoCString prepath; 947 if (NS_FAILED(GetDictPath(uri, prepath))) { 948 return; 949 } 950 if (auto origin = mDictionaryCache.Lookup(prepath)) { 951 for (auto& entry : origin.Data()->mEntries) { 952 if (entry->GetURI().Equals(aURI)) { 953 DICTIONARY_LOG(("Corrupting hash for %s", 954 PromiseFlatCString(entry->GetURI()).get())); 955 entry->SetHash("CORRUPTED_FOR_TESTING"_ns); 956 return; 957 } 958 } 959 for (auto& entry : origin.Data()->mPendingEntries) { 960 if (entry->GetURI().Equals(aURI)) { 961 DICTIONARY_LOG(("Corrupting hash for %s (pending)", 962 PromiseFlatCString(entry->GetURI()).get())); 963 entry->SetHash("CORRUPTED_FOR_TESTING"_ns); 964 return; 965 } 966 } 967 } 968 } 969 970 void DictionaryCache::ClearDictionaryDataForTesting(const nsACString& aURI) { 971 DICTIONARY_LOG(("DictionaryCache::ClearDictionaryDataForTesting for %s", 972 PromiseFlatCString(aURI).get())); 973 nsCOMPtr<nsIURI> uri; 974 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aURI))) { 975 return; 976 } 977 nsAutoCString prepath; 978 if (NS_FAILED(GetDictPath(uri, prepath))) { 979 return; 980 } 981 if (auto origin = mDictionaryCache.Lookup(prepath)) { 982 for (auto& entry : origin.Data()->mEntries) { 983 if (entry->GetURI().Equals(aURI)) { 984 DICTIONARY_LOG(("Clearing data for %s", 985 PromiseFlatCString(entry->GetURI()).get())); 986 entry->ClearDataForTesting(); 987 return; 988 } 989 } 990 } 991 } 992 993 // Remove a dictionary if it exists for the key given 994 // static 995 void DictionaryCache::RemoveDictionaryFor(const nsACString& aKey) { 996 RefPtr<DictionaryCache> cache = GetInstance(); 997 DICTIONARY_LOG( 998 ("Removing dictionary for %s", PromiseFlatCString(aKey).get())); 999 NS_DispatchToMainThread(NewRunnableMethod<const nsCString>( 1000 "DictionaryCache::RemoveDictionaryFor", cache, 1001 &DictionaryCache::RemoveDictionary, aKey)); 1002 } 1003 1004 // Remove a dictionary if it exists for the key given 1005 void DictionaryCache::RemoveDictionary(const nsACString& aKey) { 1006 DICTIONARY_LOG( 1007 ("Removing dictionary for %s", PromiseFlatCString(aKey).get())); 1008 1009 nsCOMPtr<nsIURI> uri; 1010 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aKey))) { 1011 return; 1012 } 1013 nsAutoCString prepath; 1014 if (NS_SUCCEEDED(GetDictPath(uri, prepath))) { 1015 if (auto origin = mDictionaryCache.Lookup(prepath)) { 1016 origin.Data()->RemoveEntry(aKey); 1017 } 1018 } 1019 } 1020 1021 // static 1022 void DictionaryCache::RemoveOriginFor(const nsACString& aKey) { 1023 RefPtr<DictionaryCache> cache = GetInstance(); 1024 DICTIONARY_LOG( 1025 ("Removing dictionary origin %s", PromiseFlatCString(aKey).get())); 1026 NS_DispatchToMainThread(NewRunnableMethod<const nsCString>( 1027 "DictionaryCache::RemoveOriginFor", cache, 1028 &DictionaryCache::RemoveOriginForInternal, aKey)); 1029 } 1030 1031 // Remove a dictionary if it exists for the key given, if it's empty 1032 void DictionaryCache::RemoveOriginForInternal(const nsACString& aKey) { 1033 nsCOMPtr<nsIURI> uri; 1034 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aKey))) { 1035 return; 1036 } 1037 nsAutoCString prepath; 1038 if (NS_SUCCEEDED(GetDictPath(uri, prepath))) { 1039 if (auto origin = mDictionaryCache.Lookup(prepath)) { 1040 if (MOZ_UNLIKELY(origin.Data()->IsEmpty())) { 1041 DICTIONARY_LOG( 1042 ("Removing origin for %s", PromiseFlatCString(aKey).get())); 1043 // This removes it from the global list and also dooms the entry 1044 origin.Data()->Clear(); 1045 } else { 1046 DICTIONARY_LOG( 1047 ("Origin not empty: %s", PromiseFlatCString(aKey).get())); 1048 } 1049 } 1050 } 1051 } 1052 1053 // Remove a dictionary if it exists for the key given (key should be prepath) 1054 void DictionaryCache::RemoveOrigin(const nsACString& aOrigin) { 1055 mDictionaryCache.Remove(aOrigin); 1056 } 1057 1058 // Remove a dictionary if it exists for the key given. Mainthread only. 1059 // Note: due to cookie samesite rules, we need to clean for all ports! 1060 // static 1061 void DictionaryCache::RemoveDictionariesForOrigin(nsIURI* aURI) { 1062 // There's no PrePathNoPort() 1063 nsAutoCString temp; 1064 aURI->GetScheme(temp); 1065 nsCString origin(temp); 1066 aURI->GetUserPass(temp); 1067 origin += "://"_ns + temp; 1068 aURI->GetHost(temp); 1069 origin += temp; 1070 1071 DICTIONARY_LOG(("Removing all dictionaries for origin of %s (%zu)", 1072 PromiseFlatCString(origin).get(), origin.Length())); 1073 RefPtr<DictionaryCache> cache = GetInstance(); 1074 // We can't just use Remove here; the ClearSiteData service strips the 1075 // port. We need to clear all that match the host with any port or none. 1076 1077 // Keep an array of origins to clear since tht will want to modify the 1078 // hash table we're iterating 1079 AutoTArray<RefPtr<DictionaryOrigin>, 1> toClear; 1080 for (auto& entry : cache->mDictionaryCache) { 1081 // We need to drop any port from entry (and origin). Assuming they're 1082 // the same up to the / or : in mOrigin, we want to limit the host 1083 // there. We also know that entry is https://. 1084 // Verify that: 1085 // a) they're equal to that point 1086 // b) that the next character of mOrigin is '/' or ':', which avoids 1087 // issues like matching https://foo.bar to (mOrigin) 1088 // https://foo.barsoom.com:666/ 1089 DICTIONARY_LOG( 1090 ("Possibly removing dictionary origin for %s (vs %s), %zu vs %zu", 1091 entry.GetData()->mOrigin.get(), PromiseFlatCString(origin).get(), 1092 entry.GetData()->mOrigin.Length(), origin.Length())); 1093 if (entry.GetData()->mOrigin.Length() > origin.Length() && 1094 (entry.GetData()->mOrigin[origin.Length()] == '/' || // no port 1095 entry.GetData()->mOrigin[origin.Length()] == ':')) { // port 1096 // no strncmp() for nsCStrings... 1097 nsDependentCSubstring host = 1098 Substring(entry.GetData()->mOrigin, 0, 1099 origin.Length()); // not including '/' or ':' 1100 DICTIONARY_LOG(("Compare %s vs %s", entry.GetData()->mOrigin.get(), 1101 PromiseFlatCString(host).get())); 1102 if (origin.Equals(host)) { 1103 DICTIONARY_LOG( 1104 ("RemoveDictionaries: Removing dictionary origin %p for %s", 1105 entry.GetData().get(), entry.GetData()->mOrigin.get())); 1106 toClear.AppendElement(entry.GetData()); 1107 } 1108 } 1109 } 1110 // Now clear and doom all the entries 1111 for (auto& entry : toClear) { 1112 entry->Clear(); 1113 } 1114 } 1115 1116 // Remove a dictionary if it exists for the key given. Mainthread only 1117 // static 1118 void DictionaryCache::RemoveAllDictionaries() { 1119 RefPtr<DictionaryCache> cache = GetInstance(); 1120 1121 DICTIONARY_LOG(("Removing all dictionaries")); 1122 for (auto& origin : cache->mDictionaryCache) { 1123 origin.GetData()->Clear(); 1124 } 1125 cache->mDictionaryCache.Clear(); 1126 } 1127 1128 // Return an entry via a callback (async). 1129 // If we don't have the origin in-memory, ask the cache for the origin, and 1130 // when we get it, parse the metadata to build a DictionaryOrigin. 1131 // Once we have a DictionaryOrigin (in-memory or parsed), scan it for matches. 1132 // If it's not in the cache, return nullptr via callback. 1133 void DictionaryCache::GetDictionaryFor( 1134 nsIURI* aURI, ExtContentPolicyType aType, nsHttpChannel* aChan, 1135 void (*aSuspend)(nsHttpChannel*), 1136 const std::function<nsresult(bool, DictionaryCacheEntry*)>& aCallback) { 1137 // Note: IETF 2.2.3 Multiple Matching Directories 1138 // We need to return match-dest matches first 1139 // If no match-dest, then the longest match 1140 nsCString prepath; 1141 if (NS_FAILED(GetDictPath(aURI, prepath))) { 1142 (aCallback)(false, nullptr); 1143 return; 1144 } 1145 // Match immediately if we've already created the origin and read any 1146 // metadata 1147 if (auto existing = mDictionaryCache.Lookup(prepath)) { 1148 if (existing.Data()->mWaitingCacheRead.IsEmpty()) { 1149 // Find the longest match 1150 nsCString path; 1151 RefPtr<DictionaryCacheEntry> result; 1152 1153 aURI->GetSpec(path); 1154 DICTIONARY_LOG(("GetDictionaryFor(%s %s)", prepath.get(), path.get())); 1155 1156 result = existing.Data()->Match(path, aType); 1157 (aCallback)(false, result); 1158 } else { 1159 DICTIONARY_LOG( 1160 ("GetDictionaryFor(%s): Waiting for metadata read to match", 1161 prepath.get())); 1162 // Wait for the metadata read to complete 1163 RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); 1164 // Must do this before calling start, which can run the callbacks and call 1165 // Resume 1166 DICTIONARY_LOG(("Suspending to get Dictionary headers")); 1167 aSuspend(aChan); 1168 reader->Start(false, existing.Data(), prepath, aURI, aType, this, 1169 aCallback); 1170 } 1171 return; 1172 } 1173 // We don't have an entry at all. We need to check if there's an entry 1174 // on disk for <origin>, unless we know we have all entries in the memory 1175 // cache. 1176 1177 // Handle unknown origins by checking the disk cache 1178 if (!sCacheStorage) { 1179 (aCallback)(false, nullptr); // in case we have no disk storage 1180 return; 1181 } 1182 1183 // Sync check to see if the entry exists 1184 bool exists; 1185 nsCOMPtr<nsIURI> prepathURI; 1186 1187 if (NS_SUCCEEDED(NS_MutateURI(new net::nsStandardURL::Mutator()) 1188 .SetSpec(prepath) 1189 .Finalize(prepathURI)) && 1190 NS_SUCCEEDED( 1191 sCacheStorage->Exists(prepathURI, META_DICTIONARY_PREFIX, &exists)) && 1192 exists) { 1193 // To keep track of the callback, we need a new object to get the 1194 // OnCacheEntryAvailable can resolve the callback. 1195 DICTIONARY_LOG(("Reading %s for dictionary entries", prepath.get())); 1196 RefPtr<DictionaryOrigin> origin = new DictionaryOrigin(prepath, nullptr); 1197 // Add the origin to the list; we'll immediately start a reader which 1198 // will set mWaitingCacheRead, so future GetDictionaryFor() calls 1199 // will wait for the metadata to be read before doing Match() 1200 mDictionaryCache.InsertOrUpdate(prepath, origin); 1201 1202 RefPtr<DictionaryOriginReader> reader = new DictionaryOriginReader(); 1203 // After Start(), if we drop this ref reader will kill itself on 1204 // completion; it holds a self-ref 1205 DICTIONARY_LOG(("Suspending to get Dictionary headers")); 1206 aSuspend(aChan); 1207 reader->Start(false, origin, prepath, aURI, aType, this, aCallback); 1208 return; 1209 } 1210 // No dictionaries for origin 1211 (aCallback)(false, nullptr); 1212 } 1213 1214 //----------------------------------------------------------------------------- 1215 // DictionaryOrigin 1216 //----------------------------------------------------------------------------- 1217 NS_IMPL_ISUPPORTS(DictionaryOrigin, nsICacheEntryMetaDataVisitor) 1218 1219 nsresult DictionaryOrigin::Write(DictionaryCacheEntry* aDictEntry) { 1220 DICTIONARY_LOG(("DictionaryOrigin::Write %s %p", mOrigin.get(), aDictEntry)); 1221 if (mEntry) { 1222 return aDictEntry->Write(mEntry); 1223 } 1224 // Write it once DictionaryOriginReader creates the entry 1225 mDeferredWrites = true; 1226 return NS_OK; 1227 } 1228 1229 void DictionaryOrigin::SetCacheEntry(nsICacheEntry* aEntry) { 1230 mEntry = aEntry; 1231 mEntry->SetContentType(nsICacheEntry::CONTENT_TYPE_DICTIONARY); 1232 if (mDeferredWrites) { 1233 for (auto& entry : mEntries) { 1234 if (NS_FAILED(Write(entry))) { 1235 RemoveEntry(entry); 1236 } 1237 } 1238 } 1239 mDeferredWrites = false; 1240 // Handle removes that were pending 1241 for (auto& remove : mPendingRemove) { 1242 DICTIONARY_LOG(("Pending RemoveEntry for %s", remove->mURI.get())); 1243 remove->RemoveEntry(mEntry); 1244 } 1245 mPendingRemove.Clear(); 1246 } 1247 1248 already_AddRefed<DictionaryCacheEntry> DictionaryOrigin::AddEntry( 1249 DictionaryCacheEntry* aDictEntry, bool aNewEntry) { 1250 // Remove any entry for the same item 1251 for (size_t i = 0; i < mEntries.Length(); i++) { 1252 if (mEntries[i]->GetURI().Equals(aDictEntry->GetURI())) { 1253 DictionaryCacheEntry* oldEntry = mEntries[i]; 1254 if (aNewEntry) { 1255 // We're overwriting an existing entry, perhaps with a new hash. It 1256 // might be different, of course. 1257 // Until we receive and save the new data, we should use the old data. 1258 1259 // We need to pause this channel, regardless of how it's encoded, 1260 // until the entry we're replacing has either no users, or has data 1261 // read in from the cache. Then we can un-Suspend and start 1262 // replacing the data in the cache itself. If there are no current 1263 // users, and we start replacing the data, we need to remove the 1264 // old entry so no one tries to use the old data/hash for a new 1265 // request. 1266 1267 // Note that when we start replacing data in the cache we need to 1268 // also remove it from the origin's entry in the cache, in case we 1269 // exit or crash before we finish replacing the entry and updating 1270 // the origin's entry with the new hash. 1271 1272 // Once we've replaced the entry (which will be after we have 1273 // hash), new requests will use the new data/hash. I.e. we'll 1274 // still allow new requests to use the old cache data/hash until 1275 // the swap occurs. Once the swap happens, the channels using the 1276 // old data/hash will still have an mDictDecoding reference to the 1277 // DictionaryCacheEntry for the old data/hash. 1278 1279 // XXX possible edge case: if a second request to replace the 1280 // entry appears. Is this possible, or would the second request 1281 // for the same URI get subsumed into the older one still in 1282 // process? I'm guessing it doesn't, so we may need to deal with this 1283 1284 DICTIONARY_LOG(( 1285 "Replacing dictionary %p for %s: new will be %p", mEntries[i].get(), 1286 PromiseFlatCString(oldEntry->GetURI()).get(), oldEntry)); 1287 // May be overkill to check HasHash here 1288 if (mEntries[i]->IsReading() && !aDictEntry->HasHash()) { 1289 DICTIONARY_LOG(("Old entry is reading data")); 1290 // If the old entry doesn't already have the data from the 1291 // dictionary, we'll need to Suspend this channel, and do a 1292 // replace later. Remember this new entry so when the current 1293 // entry has it's data in memory we can un-Suspend the new 1294 // channel/entry. When the new entry finishes saving, it will 1295 // use the mReplacement link to come back and insert itself 1296 // into mEntries and drop the old entry. Use an origin link 1297 // for that since the old entry could in theory get purged and 1298 // removed from the origin before we finish. 1299 mEntries[i]->SetReplacement(aDictEntry, this); 1300 // SetReplacement will also set aDictEntry->mShouldSuspend 1301 return do_AddRef(aDictEntry); 1302 } else { 1303 DICTIONARY_LOG(("Removing old entry, no users or already read data")); 1304 // We can just replace, there are no users active for the old data. 1305 // This stops new requests from trying to use the old data we're in 1306 // the process of replacing Remove the entry from the Origin and 1307 // Write(). 1308 if (mEntry) { 1309 mEntries[i]->RemoveEntry(mEntry); 1310 } 1311 mEntries.RemoveElementAt(i); 1312 } 1313 } else { 1314 // We're updating an existing entry (on a 304 Not Modified). Assume 1315 // the values may have changed (though likely they haven't). Check Spec 1316 // XXX 1317 DICTIONARY_LOG( 1318 ("Updating dictionary for %s (%p)", mOrigin.get(), oldEntry)); 1319 oldEntry->CopyFrom(aDictEntry); 1320 // write the entry back if something changed 1321 // XXX Check if something changed 1322 oldEntry->Write(mEntry); 1323 1324 // We don't need to reference the entry 1325 return nullptr; 1326 } 1327 break; 1328 } 1329 } 1330 1331 DICTIONARY_LOG(("New dictionary %sfor %s: %p", 1332 aDictEntry->HasHash() ? "" : "(pending) ", mOrigin.get(), 1333 aDictEntry)); 1334 if (aDictEntry->HasHash()) { 1335 mEntries.AppendElement(aDictEntry); 1336 } else { 1337 // Still need to receive the data. When we have the hash, move to 1338 // mEntries (and Write) using entry->mOrigin 1339 mPendingEntries.AppendElement(aDictEntry); 1340 aDictEntry->SetReplacement(nullptr, this); 1341 } 1342 1343 // DictionaryCache/caller is responsible for ensure this gets written if 1344 // needed 1345 return do_AddRef(aDictEntry); 1346 } 1347 1348 nsresult DictionaryOrigin::RemoveEntry(const nsACString& aKey) { 1349 DICTIONARY_LOG( 1350 ("DictionaryOrigin::RemoveEntry for %s", PromiseFlatCString(aKey).get())); 1351 RefPtr<DictionaryCacheEntry> hold; 1352 for (const auto& dict : mEntries) { 1353 DICTIONARY_LOG( 1354 (" Comparing to %s", PromiseFlatCString(dict->GetURI()).get())); 1355 if (dict->GetURI().Equals(aKey)) { 1356 // Ensure it doesn't disappear on us 1357 hold = dict; 1358 DICTIONARY_LOG(("Removing %p", dict.get())); 1359 mEntries.RemoveElement(dict); 1360 if (mEntry) { 1361 hold->RemoveEntry(mEntry); 1362 } else { 1363 // We don't have the cache entry yet. Defer the removal from 1364 // the entry until we do 1365 mPendingRemove.AppendElement(hold); 1366 return NS_OK; 1367 } 1368 break; 1369 } 1370 } 1371 if (!hold) { 1372 DICTIONARY_LOG(("DictionaryOrigin::RemoveEntry (pending) for %s", 1373 PromiseFlatCString(aKey).get())); 1374 for (const auto& dict : mPendingEntries) { 1375 DICTIONARY_LOG( 1376 (" Comparing to %s", PromiseFlatCString(dict->GetURI()).get())); 1377 if (dict->GetURI().Equals(aKey)) { 1378 // Ensure it doesn't disappear on us 1379 RefPtr<DictionaryCacheEntry> hold(dict); 1380 DICTIONARY_LOG(("Removing %p", dict.get())); 1381 mPendingEntries.RemoveElement(dict); 1382 break; 1383 } 1384 } 1385 } 1386 // If this origin has no entries, remove it and doom the entry 1387 if (IsEmpty()) { 1388 Clear(); 1389 } 1390 return hold ? NS_OK : NS_ERROR_FAILURE; 1391 } 1392 1393 void DictionaryOrigin::FinishAddEntry(DictionaryCacheEntry* aEntry) { 1394 // if aDictEntry is in mPendingEntries, move to mEntries 1395 if (mPendingEntries.RemoveElement(aEntry)) { 1396 // We need to give priority to elements fetched most recently if they 1397 // have an equivalent match length (and dest) 1398 mEntries.InsertElementAt(0, aEntry); 1399 } 1400 DICTIONARY_LOG(("FinishAddEntry(%s)", aEntry->mURI.get())); 1401 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { 1402 DumpEntries(); 1403 } 1404 } 1405 1406 void DictionaryOrigin::RemoveEntry(DictionaryCacheEntry* aEntry) { 1407 DICTIONARY_LOG(("RemoveEntry(%s)", aEntry->mURI.get())); 1408 if (!mEntries.RemoveElement(aEntry)) { 1409 mPendingEntries.RemoveElement(aEntry); 1410 } 1411 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gDictionaryLog, mozilla::LogLevel::Debug))) { 1412 DumpEntries(); 1413 } 1414 } 1415 1416 void DictionaryOrigin::DumpEntries() { 1417 DICTIONARY_LOG(("*** Origin %s ***", mOrigin.get())); 1418 for (const auto& dict : mEntries) { 1419 DICTIONARY_LOG( 1420 ("* %s: pattern %s, id %s, match-dest[0]: %s, hash: %s, expiration: " 1421 "%u", 1422 dict->mURI.get(), dict->mPattern.get(), dict->mId.get(), 1423 dict->mMatchDest.IsEmpty() 1424 ? "" 1425 : dom::GetEnumString(dict->mMatchDest[0]).get(), 1426 dict->mHash.get(), dict->mExpiration)); 1427 } 1428 DICTIONARY_LOG(("*** Pending ***")); 1429 for (const auto& dict : mPendingEntries) { 1430 DICTIONARY_LOG( 1431 ("* %s: pattern %s, id %s, match-dest[0]: %s, hash: %s, expiration: " 1432 "%u", 1433 dict->mURI.get(), dict->mPattern.get(), dict->mId.get(), 1434 dict->mMatchDest.IsEmpty() 1435 ? "" 1436 : dom::GetEnumString(dict->mMatchDest[0]).get(), 1437 dict->mHash.get(), dict->mExpiration)); 1438 } 1439 } 1440 1441 void DictionaryOrigin::Clear() { 1442 DICTIONARY_LOG(("*** Clearing origin %s", mOrigin.get())); 1443 mEntries.Clear(); 1444 mPendingEntries.Clear(); 1445 mPendingRemove.Clear(); 1446 // We may be under a lock; doom this asynchronously 1447 if (mEntry) { 1448 // This will attempt to delete the DictionaryOrigin, but we'll do 1449 // that more directly 1450 NS_DispatchBackgroundTask(NS_NewRunnableFunction( 1451 "DictionaryOrigin::Clear", [entry = mEntry, origin(mOrigin)]() { 1452 DICTIONARY_LOG(("*** Dooming origin %s", origin.get())); 1453 entry->AsyncDoom(nullptr); 1454 })); 1455 } 1456 gDictionaryCache->RemoveOrigin(mOrigin); 1457 } 1458 1459 // caller will throw this into a RefPtr 1460 DictionaryCacheEntry* DictionaryOrigin::Match(const nsACString& aPath, 1461 ExtContentPolicyType aType) { 1462 uint32_t longest = 0; 1463 DictionaryCacheEntry* result = nullptr; 1464 uint32_t now = mozilla::net::NowInSeconds(); 1465 1466 for (const auto& dict : mEntries) { 1467 if (dict->Match(aPath, aType, now, longest)) { 1468 result = dict; 1469 } 1470 } 1471 // XXX if we want to LRU the origins so we can push them out of memory based 1472 // on LRU, do something like this: 1473 /* 1474 if (result) { 1475 removeFrom(dictionarycase->mDictionaryCacheLRU); 1476 dictionarycase->mDictionaryCacheLRU.insertFront(this); 1477 } 1478 */ 1479 return result; 1480 } 1481 1482 //----------------------------------------------------------------------------- 1483 // DictionaryOrigin::nsICacheEntryMetaDataVisitor 1484 //----------------------------------------------------------------------------- 1485 nsresult DictionaryOrigin::OnMetaDataElement(const char* asciiKey, 1486 const char* asciiValue) { 1487 DICTIONARY_LOG(("DictionaryOrigin::OnMetaDataElement %s %s", 1488 asciiKey ? asciiKey : "", asciiValue)); 1489 1490 // We set the content ID to CONTENT_TYPE_DICTIONARY, ensure that's correct 1491 if (strcmp(asciiKey, "ctid") == 0) { 1492 MOZ_ASSERT(strcmp(asciiValue, "7") == 0); 1493 return NS_OK; 1494 } 1495 // All other keys should be URLs for dictionaries 1496 // If we already have an entry for this key (pending or in the list), 1497 // don't override it 1498 for (auto& entry : mEntries) { 1499 if (entry->GetURI().Equals(asciiKey)) { 1500 return NS_OK; 1501 } 1502 } 1503 for (auto& entry : mPendingEntries) { 1504 if (entry->GetURI().Equals(asciiKey)) { 1505 return NS_OK; 1506 } 1507 } 1508 RefPtr<DictionaryCacheEntry> entry = new DictionaryCacheEntry(asciiKey); 1509 if (entry->ParseMetadata(asciiValue)) { 1510 mEntries.AppendElement(entry); 1511 } 1512 return NS_OK; 1513 } 1514 1515 // Overall structure: 1516 // Dictionary: 1517 // DictionaryCache: 1518 // OriginHashmap: 1519 // LinkedList: DictionaryCacheEntry 1520 // Data from cache (sometimes) 1521 // 1522 // Each origin is in the cache as a dictionary-origin entry. In that 1523 // entry's metadata, we have an LRU-sorted list of dictionary entries to be able 1524 // to build a DictionaryCacheEntry. 1525 // When we offer a dictionary on a load, we'll start prefetching the data into 1526 // the DictionaryCacheEntry for the item in the cache. When the response comes 1527 // in, we'll either use it to decompress, or indicate we no longer care about 1528 // the data. If no one cares about the data, we'll purge it from memory. 1529 // Hold refs to the data in requests. When the only ref is in the 1530 // DictionaryCacheEntry, purge the data. This could also be done via the 1531 // InUse counter 1532 // 1533 // XXX be careful about thrashing the cache loading and purging, esp with RCWN. 1534 // Note that this makes RCWN somewhat superfluous for loads that have a 1535 // dictionary. 1536 // XXX Perhaps allow a little dwell time in ram if not too large? 1537 1538 // When a load comes in, we need to block decompressing it on having the data 1539 // from the cache if it's dcb or dcz. 1540 // XXX If the cache fails for some reason, we probably should consider 1541 // re-fetching the data without Dictionary-Available. 1542 1543 } // namespace net 1544 } // namespace mozilla