tor-browser

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

nsJAR.cpp (24047B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 #include <string.h>
      7 #include "nsJARInputStream.h"
      8 #include "nsJAR.h"
      9 #include "nsIFile.h"
     10 #include "nsIObserverService.h"
     11 #include "mozilla/DebugOnly.h"
     12 #include "mozilla/Logging.h"
     13 #include "mozilla/Omnijar.h"
     14 
     15 #ifdef XP_UNIX
     16 #  include <sys/stat.h>
     17 #elif defined(XP_WIN)
     18 #  include <io.h>
     19 #endif
     20 
     21 using namespace mozilla;
     22 
     23 static LazyLogModule gJarLog("nsJAR");
     24 
     25 #ifdef LOG
     26 #  undef LOG
     27 #endif
     28 #ifdef LOG_ENABLED
     29 #  undef LOG_ENABLED
     30 #endif
     31 
     32 #define LOG(args) MOZ_LOG(gJarLog, mozilla::LogLevel::Debug, args)
     33 #define LOG_ENABLED() MOZ_LOG_TEST(gJarLog, mozilla::LogLevel::Debug)
     34 
     35 //----------------------------------------------
     36 // nsJAR constructor/destructor
     37 //----------------------------------------------
     38 
     39 // The following initialization makes a guess of 10 entries per jarfile.
     40 nsJAR::nsJAR()
     41    : mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
     42      mLock("nsJAR::mLock"),
     43      mCache(nullptr) {}
     44 
     45 nsJAR::~nsJAR() { Close(); }
     46 
     47 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
     48 NS_IMPL_ADDREF(nsJAR)
     49 
     50 // Custom Release method works with nsZipReaderCache...
     51 // Release might be called from multi-thread, we have to
     52 // take this function carefully to avoid delete-after-use.
     53 MozExternalRefCountType nsJAR::Release(void) {
     54  nsrefcnt count;
     55  MOZ_ASSERT(0 != mRefCnt, "dup release");
     56 
     57  RefPtr<nsZipReaderCache> cache;
     58  if (mRefCnt == 2) {  // don't use a lock too frequently
     59    // Use a mutex here to guarantee mCache is not racing and the target
     60    // instance is still valid to increase ref-count.
     61    RecursiveMutexAutoLock lock(mLock);
     62    cache = mCache;
     63    mCache = nullptr;
     64  }
     65  if (cache) {
     66    DebugOnly<nsresult> rv = cache->ReleaseZip(this);
     67    MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file");
     68  }
     69 
     70  count = --mRefCnt;  // don't access any member variable after this line
     71  NS_LOG_RELEASE(this, count, "nsJAR");
     72  if (0 == count) {
     73    mRefCnt = 1; /* stabilize */
     74    /* enable this to find non-threadsafe destructors: */
     75    /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
     76    delete this;
     77    return 0;
     78  }
     79 
     80  return count;
     81 }
     82 
     83 //----------------------------------------------
     84 // nsIZipReader implementation
     85 //----------------------------------------------
     86 
     87 NS_IMETHODIMP
     88 nsJAR::Open(nsIFile* zipFile) {
     89  NS_ENSURE_ARG_POINTER(zipFile);
     90  RecursiveMutexAutoLock lock(mLock);
     91  LOG(("Open[%p] %s", this, zipFile->HumanReadablePath().get()));
     92  if (mZip) return NS_ERROR_FAILURE;  // Already open!
     93 
     94  mZipFile = zipFile;
     95  mOuterZipEntry.Truncate();
     96 
     97  // The omnijar is special, it is opened early on and closed late
     98  RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
     99  if (!zip) {
    100    zip = nsZipArchive::OpenArchive(zipFile);
    101  }
    102  mZip = zip;
    103  return mZip ? NS_OK : NS_ERROR_FAILURE;
    104 }
    105 
    106 NS_IMETHODIMP
    107 nsJAR::OpenInner(nsIZipReader* aZipReader, const nsACString& aZipEntry) {
    108  nsresult rv;
    109 
    110  LOG(("OpenInner[%p] %s", this, PromiseFlatCString(aZipEntry).get()));
    111  NS_ENSURE_ARG_POINTER(aZipReader);
    112 
    113  nsCOMPtr<nsIFile> zipFile;
    114  rv = aZipReader->GetFile(getter_AddRefs(zipFile));
    115  NS_ENSURE_SUCCESS(rv, rv);
    116 
    117  RefPtr<nsZipArchive> innerZip =
    118      mozilla::Omnijar::GetInnerReader(zipFile, aZipEntry);
    119  if (innerZip) {
    120    RecursiveMutexAutoLock lock(mLock);
    121    if (mZip) {
    122      return NS_ERROR_FAILURE;
    123    }
    124    mZip = innerZip;
    125    return NS_OK;
    126  }
    127 
    128  bool exist;
    129  rv = aZipReader->HasEntry(aZipEntry, &exist);
    130  NS_ENSURE_SUCCESS(rv, rv);
    131  NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
    132 
    133  RefPtr<nsZipHandle> handle;
    134  {
    135    nsJAR* outerJAR = static_cast<nsJAR*>(aZipReader);
    136    RecursiveMutexAutoLock outerLock(outerJAR->mLock);
    137    rv = nsZipHandle::Init(outerJAR->mZip.get(), aZipEntry,
    138                           getter_AddRefs(handle));
    139    NS_ENSURE_SUCCESS(rv, rv);
    140  }
    141 
    142  RecursiveMutexAutoLock lock(mLock);
    143  MOZ_ASSERT(!mZip, "Another thread tried to open this nsJAR racily!");
    144  mZipFile = zipFile.forget();
    145  mOuterZipEntry.Assign(aZipEntry);
    146  mZip = nsZipArchive::OpenArchive(handle);
    147  return mZip ? NS_OK : NS_ERROR_FAILURE;
    148 }
    149 
    150 NS_IMETHODIMP
    151 nsJAR::OpenMemory(void* aData, uint32_t aLength) {
    152  NS_ENSURE_ARG_POINTER(aData);
    153  RecursiveMutexAutoLock lock(mLock);
    154  if (mZip) return NS_ERROR_FAILURE;  // Already open!
    155 
    156  RefPtr<nsZipHandle> handle;
    157  nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength,
    158                                  getter_AddRefs(handle));
    159  if (NS_FAILED(rv)) return rv;
    160 
    161  mZip = nsZipArchive::OpenArchive(handle);
    162  return mZip ? NS_OK : NS_ERROR_FAILURE;
    163 }
    164 
    165 NS_IMETHODIMP
    166 nsJAR::GetFile(nsIFile** result) {
    167  RecursiveMutexAutoLock lock(mLock);
    168  LOG(("GetFile[%p]", this));
    169  *result = mZipFile;
    170  NS_IF_ADDREF(*result);
    171  return NS_OK;
    172 }
    173 
    174 NS_IMETHODIMP
    175 nsJAR::Close() {
    176  RecursiveMutexAutoLock lock(mLock);
    177  LOG(("Close[%p]", this));
    178  if (!mZip) {
    179    return NS_ERROR_FAILURE;  // Never opened or already closed.
    180  }
    181 
    182  mZip = nullptr;
    183  return NS_OK;
    184 }
    185 
    186 NS_IMETHODIMP
    187 nsJAR::Test(const nsACString& aEntryName) {
    188  RecursiveMutexAutoLock lock(mLock);
    189  if (!mZip) {
    190    return NS_ERROR_FAILURE;
    191  }
    192  return mZip->Test(aEntryName);
    193 }
    194 
    195 NS_IMETHODIMP
    196 nsJAR::Extract(const nsACString& aEntryName, nsIFile* outFile) {
    197  // nsZipArchive and zlib are not thread safe
    198  // we need to use a lock to prevent bug #51267
    199  RecursiveMutexAutoLock lock(mLock);
    200  if (!mZip) {
    201    return NS_ERROR_FAILURE;
    202  }
    203 
    204  LOG(("Extract[%p] %s", this, PromiseFlatCString(aEntryName).get()));
    205  nsZipItem* item = mZip->GetItem(aEntryName);
    206  NS_ENSURE_TRUE(item, NS_ERROR_FILE_NOT_FOUND);
    207 
    208  // Remove existing file or directory so we set permissions correctly.
    209  // If it's a directory that already exists and contains files, throw
    210  // an exception and return.
    211 
    212  nsresult rv = outFile->Remove(false);
    213  if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || rv == NS_ERROR_FAILURE) return rv;
    214 
    215  if (item->IsDirectory()) {
    216    rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
    217    // XXX Do this in nsZipArchive?  It would be nice to keep extraction
    218    // XXX code completely there, but that would require a way to get a
    219    // XXX PRDir from outFile.
    220  } else {
    221    PRFileDesc* fd;
    222    rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(),
    223                                   &fd);
    224    if (NS_FAILED(rv)) return rv;
    225 
    226    // ExtractFile also closes the fd handle and resolves the symlink if needed
    227    rv = mZip->ExtractFile(item, outFile, fd);
    228  }
    229  if (NS_FAILED(rv)) return rv;
    230 
    231  // nsIFile needs milliseconds, while prtime is in microseconds.
    232  // non-fatal if this fails, ignore errors
    233  outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
    234 
    235  return NS_OK;
    236 }
    237 
    238 NS_IMETHODIMP
    239 nsJAR::GetEntry(const nsACString& aEntryName, nsIZipEntry** result) {
    240  RecursiveMutexAutoLock lock(mLock);
    241  LOG(("GetEntry[%p] %s", this, PromiseFlatCString(aEntryName).get()));
    242  if (!mZip) {
    243    return NS_ERROR_FAILURE;
    244  }
    245  nsZipItem* zipItem = mZip->GetItem(aEntryName);
    246  NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_NOT_FOUND);
    247 
    248  RefPtr<nsJARItem> jarItem = new nsJARItem(zipItem);
    249 
    250  *result = jarItem.forget().take();
    251  return NS_OK;
    252 }
    253 
    254 NS_IMETHODIMP
    255 nsJAR::HasEntry(const nsACString& aEntryName, bool* result) {
    256  RecursiveMutexAutoLock lock(mLock);
    257  LOG(("HasEntry[%p] %s", this, PromiseFlatCString(aEntryName).get()));
    258  if (!mZip) {
    259    return NS_ERROR_FAILURE;
    260  }
    261  *result = mZip->GetItem(aEntryName) != nullptr;
    262  return NS_OK;
    263 }
    264 
    265 NS_IMETHODIMP
    266 nsJAR::FindEntries(const nsACString& aPattern,
    267                   nsIUTF8StringEnumerator** result) {
    268  NS_ENSURE_ARG_POINTER(result);
    269  RecursiveMutexAutoLock lock(mLock);
    270  LOG(("FindEntries[%p] %s", this, PromiseFlatCString(aPattern).get()));
    271  if (!mZip) {
    272    return NS_ERROR_FAILURE;
    273  }
    274 
    275  nsZipFind* find;
    276  nsresult rv = mZip->FindInit(
    277      aPattern.IsEmpty() ? nullptr : PromiseFlatCString(aPattern).get(), &find);
    278  NS_ENSURE_SUCCESS(rv, rv);
    279 
    280  RefPtr<nsIUTF8StringEnumerator> zipEnum = new nsJAREnumerator(find);
    281 
    282  // Callers use getter_addrefs
    283  *result = zipEnum.forget().take();
    284  return NS_OK;
    285 }
    286 
    287 NS_IMETHODIMP
    288 nsJAR::GetInputStream(const nsACString& aEntryName, nsIInputStream** result) {
    289  NS_ENSURE_ARG_POINTER(result);
    290  RecursiveMutexAutoLock lock(mLock);
    291  if (!mZip) {
    292    return NS_ERROR_FAILURE;
    293  }
    294 
    295  LOG(("GetInputStream[%p] %s", this, PromiseFlatCString(aEntryName).get()));
    296  // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
    297  nsZipItem* item = nullptr;
    298  const nsCString& entry = PromiseFlatCString(aEntryName);
    299  if (*entry.get()) {
    300    // First check if item exists in jar
    301    item = mZip->GetItem(entry);
    302    if (!item) return NS_ERROR_FILE_NOT_FOUND;
    303  }
    304  RefPtr<nsJARInputStream> jis = new nsJARInputStream();
    305 
    306  nsresult rv = NS_OK;
    307  if (!item || item->IsDirectory()) {
    308    rv = jis->InitDirectory(this, entry.get());
    309  } else {
    310    RefPtr<nsZipHandle> fd = mZip->GetFD();
    311    rv = jis->InitFile(fd, mZip->GetData(item), item);
    312  }
    313  if (NS_SUCCEEDED(rv)) {
    314    // Callers use getter_addrefs
    315    *result = jis.forget().take();
    316  }
    317  return rv;
    318 }
    319 
    320 nsresult nsJAR::GetFullJarPath(nsACString& aResult) {
    321  RecursiveMutexAutoLock lock(mLock);
    322  NS_ENSURE_ARG_POINTER(mZipFile);
    323 
    324  nsresult rv = mZipFile->GetPersistentDescriptor(aResult);
    325  if (NS_FAILED(rv)) {
    326    return rv;
    327  }
    328 
    329  if (mOuterZipEntry.IsEmpty()) {
    330    aResult.InsertLiteral("file:", 0);
    331  } else {
    332    aResult.InsertLiteral("jar:", 0);
    333    aResult.AppendLiteral("!/");
    334    aResult.Append(mOuterZipEntry);
    335  }
    336  return NS_OK;
    337 }
    338 
    339 nsresult nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) {
    340  RecursiveMutexAutoLock lock(mLock);
    341  if (!aNSPRFileDesc) {
    342    return NS_ERROR_ILLEGAL_VALUE;
    343  }
    344  *aNSPRFileDesc = nullptr;
    345 
    346  if (!mZip) {
    347    return NS_ERROR_FAILURE;
    348  }
    349 
    350  RefPtr<nsZipHandle> handle = mZip->GetFD();
    351  if (!handle) {
    352    return NS_ERROR_FAILURE;
    353  }
    354 
    355  return handle->GetNSPRFileDesc(aNSPRFileDesc);
    356 }
    357 
    358 //----------------------------------------------
    359 // nsJAR private implementation
    360 //----------------------------------------------
    361 nsresult nsJAR::LoadEntry(const nsACString& aFilename, nsCString& aBuf) {
    362  //-- Get a stream for reading the file
    363  nsresult rv;
    364  nsCOMPtr<nsIInputStream> manifestStream;
    365  rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
    366  if (NS_FAILED(rv)) return NS_ERROR_FILE_NOT_FOUND;
    367 
    368  //-- Read the manifest file into memory
    369  char* buf;
    370  uint64_t len64;
    371  rv = manifestStream->Available(&len64);
    372  if (NS_FAILED(rv)) return rv;
    373  NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED);  // bug 164695
    374  uint32_t len = (uint32_t)len64;
    375  buf = (char*)malloc(len + 1);
    376  if (!buf) return NS_ERROR_OUT_OF_MEMORY;
    377  uint32_t bytesRead;
    378  rv = manifestStream->Read(buf, len, &bytesRead);
    379  if (bytesRead != len) {
    380    rv = NS_ERROR_FILE_CORRUPTED;
    381  }
    382  if (NS_FAILED(rv)) {
    383    free(buf);
    384    return rv;
    385  }
    386  buf[len] = '\0';  // Null-terminate the buffer
    387  aBuf.Adopt(buf, len);
    388  return NS_OK;
    389 }
    390 
    391 int32_t nsJAR::ReadLine(const char** src) {
    392  if (!*src) {
    393    return 0;
    394  }
    395 
    396  //--Moves pointer to beginning of next line and returns line length
    397  //  not including CR/LF.
    398  int32_t length;
    399  const char* eol = strpbrk(*src, "\r\n");
    400 
    401  if (eol == nullptr)  // Probably reached end of file before newline
    402  {
    403    length = strlen(*src);
    404    if (length == 0)  // immediate end-of-file
    405      *src = nullptr;
    406    else  // some data left on this line
    407      *src += length;
    408  } else {
    409    length = eol - *src;
    410    if (eol[0] == '\r' && eol[1] == '\n')  // CR LF, so skip 2
    411      *src = eol + 2;
    412    else  // Either CR or LF, so skip 1
    413      *src = eol + 1;
    414  }
    415  return length;
    416 }
    417 
    418 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator, nsIStringEnumerator)
    419 
    420 //----------------------------------------------
    421 // nsJAREnumerator::HasMore
    422 //----------------------------------------------
    423 NS_IMETHODIMP
    424 nsJAREnumerator::HasMore(bool* aResult) {
    425  // try to get the next element
    426  if (!mName) {
    427    NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
    428    nsresult rv = mFind->FindNext(&mName, &mNameLen);
    429    if (rv == NS_ERROR_FILE_NOT_FOUND) {
    430      *aResult = false;  // No more matches available
    431      return NS_OK;
    432    }
    433    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);  // no error translation
    434  }
    435 
    436  *aResult = true;
    437  return NS_OK;
    438 }
    439 
    440 //----------------------------------------------
    441 // nsJAREnumerator::GetNext
    442 //----------------------------------------------
    443 NS_IMETHODIMP
    444 nsJAREnumerator::GetNext(nsACString& aResult) {
    445  // check if the current item is "stale"
    446  if (!mName) {
    447    bool bMore;
    448    nsresult rv = HasMore(&bMore);
    449    if (NS_FAILED(rv) || !bMore)
    450      return NS_ERROR_FAILURE;  // no error translation
    451  }
    452  aResult.Assign(mName, mNameLen);
    453  mName = 0;  // we just gave this one away
    454  return NS_OK;
    455 }
    456 
    457 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
    458 
    459 nsJARItem::nsJARItem(nsZipItem* aZipItem)
    460    : mSize(aZipItem->Size()),
    461      mRealsize(aZipItem->RealSize()),
    462      mCrc32(aZipItem->CRC32()),
    463      mLastModTime(aZipItem->LastModTime()),
    464      mCompression(aZipItem->Compression()),
    465      mPermissions(aZipItem->Mode()),
    466      mIsDirectory(aZipItem->IsDirectory()),
    467      mIsSynthetic(aZipItem->isSynthetic) {}
    468 
    469 //------------------------------------------
    470 // nsJARItem::GetCompression
    471 //------------------------------------------
    472 NS_IMETHODIMP
    473 nsJARItem::GetCompression(uint16_t* aCompression) {
    474  NS_ENSURE_ARG_POINTER(aCompression);
    475 
    476  *aCompression = mCompression;
    477  return NS_OK;
    478 }
    479 
    480 //------------------------------------------
    481 // nsJARItem::GetSize
    482 //------------------------------------------
    483 NS_IMETHODIMP
    484 nsJARItem::GetSize(uint32_t* aSize) {
    485  NS_ENSURE_ARG_POINTER(aSize);
    486 
    487  *aSize = mSize;
    488  return NS_OK;
    489 }
    490 
    491 //------------------------------------------
    492 // nsJARItem::GetRealSize
    493 //------------------------------------------
    494 NS_IMETHODIMP
    495 nsJARItem::GetRealSize(uint32_t* aRealsize) {
    496  NS_ENSURE_ARG_POINTER(aRealsize);
    497 
    498  *aRealsize = mRealsize;
    499  return NS_OK;
    500 }
    501 
    502 //------------------------------------------
    503 // nsJARItem::GetCrc32
    504 //------------------------------------------
    505 NS_IMETHODIMP
    506 nsJARItem::GetCRC32(uint32_t* aCrc32) {
    507  NS_ENSURE_ARG_POINTER(aCrc32);
    508 
    509  *aCrc32 = mCrc32;
    510  return NS_OK;
    511 }
    512 
    513 //------------------------------------------
    514 // nsJARItem::GetIsDirectory
    515 //------------------------------------------
    516 NS_IMETHODIMP
    517 nsJARItem::GetIsDirectory(bool* aIsDirectory) {
    518  NS_ENSURE_ARG_POINTER(aIsDirectory);
    519 
    520  *aIsDirectory = mIsDirectory;
    521  return NS_OK;
    522 }
    523 
    524 //------------------------------------------
    525 // nsJARItem::GetIsSynthetic
    526 //------------------------------------------
    527 NS_IMETHODIMP
    528 nsJARItem::GetIsSynthetic(bool* aIsSynthetic) {
    529  NS_ENSURE_ARG_POINTER(aIsSynthetic);
    530 
    531  *aIsSynthetic = mIsSynthetic;
    532  return NS_OK;
    533 }
    534 
    535 //------------------------------------------
    536 // nsJARItem::GetLastModifiedTime
    537 //------------------------------------------
    538 NS_IMETHODIMP
    539 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) {
    540  NS_ENSURE_ARG_POINTER(aLastModTime);
    541 
    542  *aLastModTime = mLastModTime;
    543  return NS_OK;
    544 }
    545 
    546 //------------------------------------------
    547 // nsJARItem::GetPermissions
    548 //------------------------------------------
    549 NS_IMETHODIMP
    550 nsJARItem::GetPermissions(uint32_t* aPermissions) {
    551  NS_ENSURE_ARG_POINTER(aPermissions);
    552 
    553  *aPermissions = mPermissions;
    554  return NS_OK;
    555 }
    556 
    557 ////////////////////////////////////////////////////////////////////////////////
    558 // nsIZipReaderCache
    559 
    560 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver,
    561                  nsISupportsWeakReference)
    562 
    563 nsZipReaderCache::nsZipReaderCache()
    564    : mLock("nsZipReaderCache.mLock"),
    565      mCacheSize(0),
    566      mZips()
    567 #ifdef ZIP_CACHE_HIT_RATE
    568      ,
    569      mZipCacheLookups(0),
    570      mZipCacheHits(0),
    571      mZipCacheFlushes(0),
    572      mZipSyncMisses(0)
    573 #endif
    574 {
    575 }
    576 
    577 NS_IMETHODIMP
    578 nsZipReaderCache::Init(uint32_t cacheSize) {
    579  MutexAutoLock lock(mLock);
    580  mCacheSize = cacheSize;
    581 
    582  // Register as a memory pressure observer
    583  nsCOMPtr<nsIObserverService> os =
    584      do_GetService("@mozilla.org/observer-service;1");
    585  if (os) {
    586    os->AddObserver(this, "memory-pressure", true);
    587    os->AddObserver(this, "chrome-flush-caches", true);
    588    os->AddObserver(this, "flush-cache-entry", true);
    589  }
    590  // ignore failure of the observer registration.
    591 
    592  return NS_OK;
    593 }
    594 
    595 nsZipReaderCache::~nsZipReaderCache() {
    596  for (const auto& zip : mZips.Values()) {
    597    zip->SetZipReaderCache(nullptr);
    598  }
    599 
    600 #ifdef ZIP_CACHE_HIT_RATE
    601  printf(
    602      "nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed "
    603      "%d\n",
    604      mCacheSize, mZipCacheHits, mZipCacheLookups,
    605      (float)mZipCacheHits / mZipCacheLookups, mZipCacheFlushes,
    606      mZipSyncMisses);
    607 #endif
    608 }
    609 
    610 NS_IMETHODIMP
    611 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) {
    612  NS_ENSURE_ARG_POINTER(zipFile);
    613  nsresult rv;
    614  MutexAutoLock lock(mLock);
    615 
    616  nsAutoCString uri;
    617  rv = zipFile->GetPersistentDescriptor(uri);
    618  if (NS_FAILED(rv)) return rv;
    619 
    620  uri.InsertLiteral("file:", 0);
    621 
    622  *aResult = mZips.Contains(uri);
    623  return NS_OK;
    624 }
    625 
    626 nsresult nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result,
    627                                  bool failOnMiss) {
    628  NS_ENSURE_ARG_POINTER(zipFile);
    629  nsresult rv;
    630  MutexAutoLock lock(mLock);
    631 
    632 #ifdef ZIP_CACHE_HIT_RATE
    633  mZipCacheLookups++;
    634 #endif
    635 
    636  nsAutoCString uri;
    637  rv = zipFile->GetPersistentDescriptor(uri);
    638  if (NS_FAILED(rv)) return rv;
    639 
    640  uri.InsertLiteral("file:", 0);
    641 
    642  RefPtr<nsJAR> zip;
    643  mZips.Get(uri, getter_AddRefs(zip));
    644  if (zip) {
    645 #ifdef ZIP_CACHE_HIT_RATE
    646    mZipCacheHits++;
    647 #endif
    648    zip->ClearReleaseTime();
    649  } else {
    650    if (failOnMiss) {
    651      return NS_ERROR_CACHE_KEY_NOT_FOUND;
    652    }
    653 
    654    zip = new nsJAR();
    655    zip->SetZipReaderCache(this);
    656    rv = zip->Open(zipFile);
    657    if (NS_FAILED(rv)) {
    658      return rv;
    659    }
    660 
    661    MOZ_ASSERT(!mZips.Contains(uri));
    662    mZips.InsertOrUpdate(uri, RefPtr{zip});
    663  }
    664  zip.forget(result);
    665  return rv;
    666 }
    667 
    668 NS_IMETHODIMP
    669 nsZipReaderCache::GetZipIfCached(nsIFile* zipFile, nsIZipReader** result) {
    670  return GetZip(zipFile, result, true);
    671 }
    672 
    673 NS_IMETHODIMP
    674 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result) {
    675  return GetZip(zipFile, result, false);
    676 }
    677 
    678 NS_IMETHODIMP
    679 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString& entry,
    680                              nsIZipReader** result) {
    681  NS_ENSURE_ARG_POINTER(zipFile);
    682 
    683  nsCOMPtr<nsIZipReader> outerZipReader;
    684  nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
    685  NS_ENSURE_SUCCESS(rv, rv);
    686 
    687  MutexAutoLock lock(mLock);
    688 
    689 #ifdef ZIP_CACHE_HIT_RATE
    690  mZipCacheLookups++;
    691 #endif
    692 
    693  nsAutoCString uri;
    694  rv = zipFile->GetPersistentDescriptor(uri);
    695  if (NS_FAILED(rv)) return rv;
    696 
    697  uri.InsertLiteral("jar:", 0);
    698  uri.AppendLiteral("!/");
    699  uri.Append(entry);
    700 
    701  RefPtr<nsJAR> zip;
    702  mZips.Get(uri, getter_AddRefs(zip));
    703  if (zip) {
    704 #ifdef ZIP_CACHE_HIT_RATE
    705    mZipCacheHits++;
    706 #endif
    707    zip->ClearReleaseTime();
    708  } else {
    709    zip = new nsJAR();
    710    zip->SetZipReaderCache(this);
    711 
    712    rv = zip->OpenInner(outerZipReader, entry);
    713    if (NS_FAILED(rv)) {
    714      return rv;
    715    }
    716 
    717    MOZ_ASSERT(!mZips.Contains(uri));
    718    mZips.InsertOrUpdate(uri, RefPtr{zip});
    719  }
    720  zip.forget(result);
    721  return rv;
    722 }
    723 
    724 NS_IMETHODIMP
    725 nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal) {
    726 #if defined(XP_WIN)
    727  MOZ_CRASH("Not implemented");
    728  return NS_ERROR_NOT_IMPLEMENTED;
    729 #else
    730  if (!zipFile) {
    731    return NS_ERROR_FAILURE;
    732  }
    733 
    734  nsresult rv;
    735  nsAutoCString uri;
    736  rv = zipFile->GetPersistentDescriptor(uri);
    737  if (NS_FAILED(rv)) {
    738    return rv;
    739  }
    740  uri.InsertLiteral("file:", 0);
    741 
    742  MutexAutoLock lock(mLock);
    743  RefPtr<nsJAR> zip;
    744  mZips.Get(uri, getter_AddRefs(zip));
    745  if (!zip) {
    746    return NS_ERROR_FAILURE;
    747  }
    748 
    749  zip->ClearReleaseTime();
    750  rv = zip->GetNSPRFileDesc(aRetVal);
    751  // Do this to avoid possible deadlock on mLock with ReleaseZip().
    752  {
    753    MutexAutoUnlock unlock(mLock);
    754    zip = nullptr;
    755  }
    756  return rv;
    757 #endif /* XP_WIN */
    758 }
    759 
    760 nsresult nsZipReaderCache::ReleaseZip(nsJAR* zip) {
    761  nsresult rv;
    762  MutexAutoLock lock(mLock);
    763 
    764  // It is possible that two thread compete for this zip. The dangerous
    765  // case is where one thread Releases the zip and discovers that the ref
    766  // count has gone to one. Before it can call this ReleaseZip method
    767  // another thread calls our GetZip method. The ref count goes to two. That
    768  // second thread then Releases the zip and the ref count goes to one. It
    769  // then tries to enter this ReleaseZip method and blocks while the first
    770  // thread is still here. The first thread continues and remove the zip from
    771  // the cache and calls its Release method sending the ref count to 0 and
    772  // deleting the zip. However, the second thread is still blocked at the
    773  // start of ReleaseZip, but the 'zip' param now hold a reference to a
    774  // deleted zip!
    775  //
    776  // So, we are going to try safeguarding here by searching our hashtable while
    777  // locked here for the zip. We return fast if it is not found.
    778 
    779  bool found = false;
    780  for (const auto& current : mZips.Values()) {
    781    if (zip == current) {
    782      found = true;
    783      break;
    784    }
    785  }
    786 
    787  if (!found) {
    788 #ifdef ZIP_CACHE_HIT_RATE
    789    mZipSyncMisses++;
    790 #endif
    791    return NS_OK;
    792  }
    793 
    794  zip->SetReleaseTime();
    795 
    796  if (mZips.Count() <= mCacheSize) return NS_OK;
    797 
    798  // Find the oldest zip.
    799  nsJAR* oldest = nullptr;
    800  for (const auto& current : mZips.Values()) {
    801    PRIntervalTime currentReleaseTime = current->GetReleaseTime();
    802    if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
    803      if (oldest == nullptr || currentReleaseTime < oldest->GetReleaseTime()) {
    804        oldest = current;
    805      }
    806    }
    807  }
    808 
    809  // Because of the craziness above it is possible that there is no zip that
    810  // needs removing.
    811  if (!oldest) return NS_OK;
    812 
    813 #ifdef ZIP_CACHE_HIT_RATE
    814  mZipCacheFlushes++;
    815 #endif
    816 
    817  // remove from hashtable
    818  nsAutoCString uri;
    819  rv = oldest->GetFullJarPath(uri);
    820  if (NS_FAILED(rv)) {
    821    return rv;
    822  }
    823 
    824  // Retrieving and removing the JAR should be done without an extra AddRef
    825  // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
    826  // an extra time.
    827  RefPtr<nsJAR> removed;
    828  mZips.Remove(uri, getter_AddRefs(removed));
    829  NS_ASSERTION(removed, "botched");
    830  NS_ASSERTION(oldest == removed, "removed wrong entry");
    831 
    832  if (removed) removed->SetZipReaderCache(nullptr);
    833 
    834  return NS_OK;
    835 }
    836 
    837 NS_IMETHODIMP
    838 nsZipReaderCache::Observe(nsISupports* aSubject, const char* aTopic,
    839                          const char16_t* aSomeData) {
    840  if (strcmp(aTopic, "memory-pressure") == 0) {
    841    MutexAutoLock lock(mLock);
    842    for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
    843      RefPtr<nsJAR>& current = iter.Data();
    844      if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
    845        current->SetZipReaderCache(nullptr);
    846        iter.Remove();
    847      }
    848    }
    849  } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
    850    MutexAutoLock lock(mLock);
    851    for (const auto& current : mZips.Values()) {
    852      current->SetZipReaderCache(nullptr);
    853    }
    854    mZips.Clear();
    855  } else if (strcmp(aTopic, "flush-cache-entry") == 0) {
    856    nsCOMPtr<nsIFile> file;
    857    if (aSubject) {
    858      file = do_QueryInterface(aSubject);
    859    } else if (aSomeData) {
    860      nsDependentString fileName(aSomeData);
    861      (void)NS_NewLocalFile(fileName, getter_AddRefs(file));
    862    }
    863 
    864    if (!file) return NS_OK;
    865 
    866    nsAutoCString uri;
    867    if (NS_FAILED(file->GetPersistentDescriptor(uri))) return NS_OK;
    868 
    869    uri.InsertLiteral("file:", 0);
    870 
    871    MutexAutoLock lock(mLock);
    872 
    873    RefPtr<nsJAR> zip;
    874    mZips.Get(uri, getter_AddRefs(zip));
    875    if (!zip) return NS_OK;
    876 
    877 #ifdef ZIP_CACHE_HIT_RATE
    878    mZipCacheFlushes++;
    879 #endif
    880 
    881    zip->SetZipReaderCache(nullptr);
    882 
    883    mZips.Remove(uri);
    884  }
    885  return NS_OK;
    886 }
    887 
    888 ////////////////////////////////////////////////////////////////////////////////