tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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