modules/libjar/nsJAR.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/modules/libjar/nsJAR.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1320 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +#include <string.h>
     1.9 +#include "nsJARInputStream.h"
    1.10 +#include "nsJAR.h"
    1.11 +#include "nsIFile.h"
    1.12 +#include "nsIConsoleService.h"
    1.13 +#include "nsICryptoHash.h"
    1.14 +#include "prprf.h"
    1.15 +#include "mozilla/Omnijar.h"
    1.16 +
    1.17 +#ifdef XP_UNIX
    1.18 +  #include <sys/stat.h>
    1.19 +#elif defined (XP_WIN)
    1.20 +  #include <io.h>
    1.21 +#endif
    1.22 +
    1.23 +using namespace mozilla;
    1.24 +
    1.25 +//----------------------------------------------
    1.26 +// nsJARManifestItem declaration
    1.27 +//----------------------------------------------
    1.28 +/*
    1.29 + * nsJARManifestItem contains meta-information pertaining
    1.30 + * to an individual JAR entry, taken from the
    1.31 + * META-INF/MANIFEST.MF and META-INF/ *.SF files.
    1.32 + * This is security-critical information, defined here so it is not
    1.33 + * accessible from anywhere else.
    1.34 + */
    1.35 +typedef enum
    1.36 +{
    1.37 +  JAR_INVALID       = 1,
    1.38 +  JAR_INTERNAL      = 2,
    1.39 +  JAR_EXTERNAL      = 3
    1.40 +} JARManifestItemType;
    1.41 +
    1.42 +class nsJARManifestItem
    1.43 +{
    1.44 +public:
    1.45 +  JARManifestItemType mType;
    1.46 +
    1.47 +  // True if the second step of verification (VerifyEntry)
    1.48 +  // has taken place:
    1.49 +  bool                entryVerified;
    1.50 +
    1.51 +  // Not signed, valid, or failure code
    1.52 +  int16_t             status;
    1.53 +
    1.54 +  // Internal storage of digests
    1.55 +  nsCString           calculatedSectionDigest;
    1.56 +  nsCString           storedEntryDigest;
    1.57 +
    1.58 +  nsJARManifestItem();
    1.59 +  virtual ~nsJARManifestItem();
    1.60 +};
    1.61 +
    1.62 +//-------------------------------------------------
    1.63 +// nsJARManifestItem constructors and destructor
    1.64 +//-------------------------------------------------
    1.65 +nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
    1.66 +                                        entryVerified(false),
    1.67 +                                        status(JAR_NOT_SIGNED)
    1.68 +{
    1.69 +}
    1.70 +
    1.71 +nsJARManifestItem::~nsJARManifestItem()
    1.72 +{
    1.73 +}
    1.74 +
    1.75 +//----------------------------------------------
    1.76 +// nsJAR constructor/destructor
    1.77 +//----------------------------------------------
    1.78 +
    1.79 +// The following initialization makes a guess of 10 entries per jarfile.
    1.80 +nsJAR::nsJAR(): mZip(new nsZipArchive()),
    1.81 +                mManifestData(10),
    1.82 +                mParsedManifest(false),
    1.83 +                mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
    1.84 +                mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
    1.85 +                mCache(nullptr),
    1.86 +                mLock("nsJAR::mLock"),
    1.87 +                mTotalItemsInManifest(0),
    1.88 +                mOpened(false)
    1.89 +{
    1.90 +}
    1.91 +
    1.92 +nsJAR::~nsJAR()
    1.93 +{
    1.94 +  Close();
    1.95 +}
    1.96 +
    1.97 +NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
    1.98 +NS_IMPL_ADDREF(nsJAR)
    1.99 +
   1.100 +// Custom Release method works with nsZipReaderCache...
   1.101 +MozExternalRefCountType nsJAR::Release(void)
   1.102 +{
   1.103 +  nsrefcnt count;
   1.104 +  NS_PRECONDITION(0 != mRefCnt, "dup release");
   1.105 +  count = --mRefCnt;
   1.106 +  NS_LOG_RELEASE(this, count, "nsJAR");
   1.107 +  if (0 == count) {
   1.108 +    mRefCnt = 1; /* stabilize */
   1.109 +    /* enable this to find non-threadsafe destructors: */
   1.110 +    /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
   1.111 +    delete this;
   1.112 +    return 0;
   1.113 +  }
   1.114 +  else if (1 == count && mCache) {
   1.115 +#ifdef DEBUG
   1.116 +    nsresult rv =
   1.117 +#endif
   1.118 +      mCache->ReleaseZip(this);
   1.119 +    NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
   1.120 +  }
   1.121 +  return count;
   1.122 +}
   1.123 +
   1.124 +//----------------------------------------------
   1.125 +// nsIZipReader implementation
   1.126 +//----------------------------------------------
   1.127 +
   1.128 +NS_IMETHODIMP
   1.129 +nsJAR::Open(nsIFile* zipFile)
   1.130 +{
   1.131 +  NS_ENSURE_ARG_POINTER(zipFile);
   1.132 +  if (mOpened) return NS_ERROR_FAILURE; // Already open!
   1.133 +
   1.134 +  mZipFile = zipFile;
   1.135 +  mOuterZipEntry.Truncate();
   1.136 +  mOpened = true;
   1.137 +
   1.138 +  // The omnijar is special, it is opened early on and closed late
   1.139 +  // this avoids reopening it
   1.140 +  nsRefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
   1.141 +  if (zip) {
   1.142 +    mZip = zip;
   1.143 +    return NS_OK;
   1.144 +  }
   1.145 +  return mZip->OpenArchive(zipFile);
   1.146 +}
   1.147 +
   1.148 +NS_IMETHODIMP
   1.149 +nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
   1.150 +{
   1.151 +  NS_ENSURE_ARG_POINTER(aZipReader);
   1.152 +  if (mOpened) return NS_ERROR_FAILURE; // Already open!
   1.153 +
   1.154 +  bool exist;
   1.155 +  nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
   1.156 +  NS_ENSURE_SUCCESS(rv, rv);
   1.157 +  NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
   1.158 +
   1.159 +  rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
   1.160 +  NS_ENSURE_SUCCESS(rv, rv);
   1.161 +
   1.162 +  mOpened = true;
   1.163 +
   1.164 +  mOuterZipEntry.Assign(aZipEntry);
   1.165 +
   1.166 +  nsRefPtr<nsZipHandle> handle;
   1.167 +  rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
   1.168 +                         getter_AddRefs(handle));
   1.169 +  if (NS_FAILED(rv))
   1.170 +    return rv;
   1.171 +
   1.172 +  return mZip->OpenArchive(handle);
   1.173 +}
   1.174 +
   1.175 +NS_IMETHODIMP
   1.176 +nsJAR::GetFile(nsIFile* *result)
   1.177 +{
   1.178 +  *result = mZipFile;
   1.179 +  NS_IF_ADDREF(*result);
   1.180 +  return NS_OK;
   1.181 +}
   1.182 +
   1.183 +NS_IMETHODIMP
   1.184 +nsJAR::Close()
   1.185 +{
   1.186 +  mOpened = false;
   1.187 +  mParsedManifest = false;
   1.188 +  mManifestData.Clear();
   1.189 +  mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
   1.190 +  mTotalItemsInManifest = 0;
   1.191 +
   1.192 +  nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
   1.193 +  nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
   1.194 +
   1.195 +  if (mZip == greOmni || mZip == appOmni) {
   1.196 +    mZip = new nsZipArchive();
   1.197 +    return NS_OK;
   1.198 +  }
   1.199 +  return mZip->CloseArchive();
   1.200 +}
   1.201 +
   1.202 +NS_IMETHODIMP
   1.203 +nsJAR::Test(const nsACString &aEntryName)
   1.204 +{
   1.205 +  return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
   1.206 +}
   1.207 +
   1.208 +NS_IMETHODIMP
   1.209 +nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
   1.210 +{
   1.211 +  // nsZipArchive and zlib are not thread safe
   1.212 +  // we need to use a lock to prevent bug #51267
   1.213 +  MutexAutoLock lock(mLock);
   1.214 +
   1.215 +  nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
   1.216 +  NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
   1.217 +
   1.218 +  // Remove existing file or directory so we set permissions correctly.
   1.219 +  // If it's a directory that already exists and contains files, throw
   1.220 +  // an exception and return.
   1.221 +
   1.222 +  nsresult rv = outFile->Remove(false);
   1.223 +  if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
   1.224 +      rv == NS_ERROR_FAILURE)
   1.225 +    return rv;
   1.226 +
   1.227 +  if (item->IsDirectory())
   1.228 +  {
   1.229 +    rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
   1.230 +    //XXX Do this in nsZipArchive?  It would be nice to keep extraction
   1.231 +    //XXX code completely there, but that would require a way to get a
   1.232 +    //XXX PRDir from outFile.
   1.233 +  }
   1.234 +  else
   1.235 +  {
   1.236 +    PRFileDesc* fd;
   1.237 +    rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
   1.238 +    if (NS_FAILED(rv)) return rv;
   1.239 +
   1.240 +    // ExtractFile also closes the fd handle and resolves the symlink if needed
   1.241 +    nsAutoCString path;
   1.242 +    rv = outFile->GetNativePath(path);
   1.243 +    if (NS_FAILED(rv)) return rv;
   1.244 +
   1.245 +    rv = mZip->ExtractFile(item, path.get(), fd);
   1.246 +  }
   1.247 +  if (NS_FAILED(rv)) return rv;
   1.248 +
   1.249 +  // nsIFile needs milliseconds, while prtime is in microseconds.
   1.250 +  // non-fatal if this fails, ignore errors
   1.251 +  outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
   1.252 +
   1.253 +  return NS_OK;
   1.254 +}
   1.255 +
   1.256 +NS_IMETHODIMP
   1.257 +nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
   1.258 +{
   1.259 +  nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
   1.260 +  NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
   1.261 +
   1.262 +  nsJARItem* jarItem = new nsJARItem(zipItem);
   1.263 +
   1.264 +  NS_ADDREF(*result = jarItem);
   1.265 +  return NS_OK;
   1.266 +}
   1.267 +
   1.268 +NS_IMETHODIMP
   1.269 +nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
   1.270 +{
   1.271 +  *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
   1.272 +  return NS_OK;
   1.273 +}
   1.274 +
   1.275 +NS_IMETHODIMP
   1.276 +nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
   1.277 +{
   1.278 +  NS_ENSURE_ARG_POINTER(result);
   1.279 +
   1.280 +  nsZipFind *find;
   1.281 +  nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
   1.282 +  NS_ENSURE_SUCCESS(rv, rv);
   1.283 +
   1.284 +  nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
   1.285 +
   1.286 +  NS_ADDREF(*result = zipEnum);
   1.287 +  return NS_OK;
   1.288 +}
   1.289 +
   1.290 +NS_IMETHODIMP
   1.291 +nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
   1.292 +{
   1.293 +  return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
   1.294 +}
   1.295 +
   1.296 +NS_IMETHODIMP
   1.297 +nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
   1.298 +                          const nsACString &aEntryName, nsIInputStream** result)
   1.299 +{
   1.300 +  NS_ENSURE_ARG_POINTER(result);
   1.301 +
   1.302 +  // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
   1.303 +  nsZipItem *item = nullptr;
   1.304 +  const char *entry = PromiseFlatCString(aEntryName).get();
   1.305 +  if (*entry) {
   1.306 +    // First check if item exists in jar
   1.307 +    item = mZip->GetItem(entry);
   1.308 +    if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
   1.309 +  }
   1.310 +  nsJARInputStream* jis = new nsJARInputStream();
   1.311 +  // addref now so we can call InitFile/InitDirectory()
   1.312 +  NS_ADDREF(*result = jis);
   1.313 +
   1.314 +  nsresult rv = NS_OK;
   1.315 +  if (!item || item->IsDirectory()) {
   1.316 +    rv = jis->InitDirectory(this, aJarDirSpec, entry);
   1.317 +  } else {
   1.318 +    rv = jis->InitFile(this, item);
   1.319 +  }
   1.320 +  if (NS_FAILED(rv)) {
   1.321 +    NS_RELEASE(*result);
   1.322 +  }
   1.323 +  return rv;
   1.324 +}
   1.325 +
   1.326 +NS_IMETHODIMP
   1.327 +nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal)
   1.328 +{
   1.329 +  //-- Parameter check
   1.330 +  if (!aPrincipal)
   1.331 +    return NS_ERROR_NULL_POINTER;
   1.332 +  *aPrincipal = nullptr;
   1.333 +
   1.334 +  // Don't check signatures in the omnijar - this is only
   1.335 +  // interesting for extensions/XPIs.
   1.336 +  nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
   1.337 +  nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
   1.338 +
   1.339 +  if (mZip == greOmni || mZip == appOmni)
   1.340 +    return NS_OK;
   1.341 +
   1.342 +  //-- Parse the manifest
   1.343 +  nsresult rv = ParseManifest();
   1.344 +  if (NS_FAILED(rv)) return rv;
   1.345 +  if (mGlobalStatus == JAR_NO_MANIFEST)
   1.346 +    return NS_OK;
   1.347 +
   1.348 +  int16_t requestedStatus;
   1.349 +  if (!aFilename.IsEmpty())
   1.350 +  {
   1.351 +    //-- Find the item
   1.352 +    nsJARManifestItem* manItem = mManifestData.Get(aFilename);
   1.353 +    if (!manItem)
   1.354 +      return NS_OK;
   1.355 +    //-- Verify the item against the manifest
   1.356 +    if (!manItem->entryVerified)
   1.357 +    {
   1.358 +      nsXPIDLCString entryData;
   1.359 +      uint32_t entryDataLen;
   1.360 +      rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
   1.361 +      if (NS_FAILED(rv)) return rv;
   1.362 +      rv = VerifyEntry(manItem, entryData, entryDataLen);
   1.363 +      if (NS_FAILED(rv)) return rv;
   1.364 +    }
   1.365 +    requestedStatus = manItem->status;
   1.366 +  }
   1.367 +  else // User wants identity of signer w/o verifying any entries
   1.368 +    requestedStatus = mGlobalStatus;
   1.369 +
   1.370 +  if (requestedStatus != JAR_VALID_MANIFEST)
   1.371 +    ReportError(aFilename, requestedStatus);
   1.372 +  else // Valid signature
   1.373 +  {
   1.374 +    *aPrincipal = mPrincipal;
   1.375 +    NS_IF_ADDREF(*aPrincipal);
   1.376 +  }
   1.377 +  return NS_OK;
   1.378 +}
   1.379 +
   1.380 +NS_IMETHODIMP
   1.381 +nsJAR::GetManifestEntriesCount(uint32_t* count)
   1.382 +{
   1.383 +  *count = mTotalItemsInManifest;
   1.384 +  return NS_OK;
   1.385 +}
   1.386 +
   1.387 +nsresult
   1.388 +nsJAR::GetJarPath(nsACString& aResult)
   1.389 +{
   1.390 +  NS_ENSURE_ARG_POINTER(mZipFile);
   1.391 +
   1.392 +  return mZipFile->GetNativePath(aResult);
   1.393 +}
   1.394 +
   1.395 +//----------------------------------------------
   1.396 +// nsJAR private implementation
   1.397 +//----------------------------------------------
   1.398 +nsresult
   1.399 +nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen)
   1.400 +{
   1.401 +  //-- Get a stream for reading the file
   1.402 +  nsresult rv;
   1.403 +  nsCOMPtr<nsIInputStream> manifestStream;
   1.404 +  rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
   1.405 +  if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
   1.406 +
   1.407 +  //-- Read the manifest file into memory
   1.408 +  char* buf;
   1.409 +  uint64_t len64;
   1.410 +  rv = manifestStream->Available(&len64);
   1.411 +  if (NS_FAILED(rv)) return rv;
   1.412 +  NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
   1.413 +  uint32_t len = (uint32_t)len64;
   1.414 +  buf = (char*)malloc(len+1);
   1.415 +  if (!buf) return NS_ERROR_OUT_OF_MEMORY;
   1.416 +  uint32_t bytesRead;
   1.417 +  rv = manifestStream->Read(buf, len, &bytesRead);
   1.418 +  if (bytesRead != len)
   1.419 +    rv = NS_ERROR_FILE_CORRUPTED;
   1.420 +  if (NS_FAILED(rv)) {
   1.421 +    free(buf);
   1.422 +    return rv;
   1.423 +  }
   1.424 +  buf[len] = '\0'; //Null-terminate the buffer
   1.425 +  *aBuf = buf;
   1.426 +  if (aBufLen)
   1.427 +    *aBufLen = len;
   1.428 +  return NS_OK;
   1.429 +}
   1.430 +
   1.431 +
   1.432 +int32_t
   1.433 +nsJAR::ReadLine(const char** src)
   1.434 +{
   1.435 +  if (!*src) {
   1.436 +    return 0;
   1.437 +  }
   1.438 +
   1.439 +  //--Moves pointer to beginning of next line and returns line length
   1.440 +  //  not including CR/LF.
   1.441 +  int32_t length;
   1.442 +  char* eol = PL_strpbrk(*src, "\r\n");
   1.443 +
   1.444 +  if (eol == nullptr) // Probably reached end of file before newline
   1.445 +  {
   1.446 +    length = strlen(*src);
   1.447 +    if (length == 0) // immediate end-of-file
   1.448 +      *src = nullptr;
   1.449 +    else             // some data left on this line
   1.450 +      *src += length;
   1.451 +  }
   1.452 +  else
   1.453 +  {
   1.454 +    length = eol - *src;
   1.455 +    if (eol[0] == '\r' && eol[1] == '\n')      // CR LF, so skip 2
   1.456 +      *src = eol+2;
   1.457 +    else                                       // Either CR or LF, so skip 1
   1.458 +      *src = eol+1;
   1.459 +  }
   1.460 +  return length;
   1.461 +}
   1.462 +
   1.463 +//-- The following #defines are used by ParseManifest()
   1.464 +//   and ParseOneFile(). The header strings are defined in the JAR specification.
   1.465 +#define JAR_MF 1
   1.466 +#define JAR_SF 2
   1.467 +#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
   1.468 +#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
   1.469 +#define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
   1.470 +#define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
   1.471 +
   1.472 +nsresult
   1.473 +nsJAR::ParseManifest()
   1.474 +{
   1.475 +  //-- Verification Step 1
   1.476 +  if (mParsedManifest)
   1.477 +    return NS_OK;
   1.478 +  //-- (1)Manifest (MF) file
   1.479 +  nsCOMPtr<nsIUTF8StringEnumerator> files;
   1.480 +  nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
   1.481 +  if (!files) rv = NS_ERROR_FAILURE;
   1.482 +  if (NS_FAILED(rv)) return rv;
   1.483 +
   1.484 +  //-- Load the file into memory
   1.485 +  bool more;
   1.486 +  rv = files->HasMore(&more);
   1.487 +  NS_ENSURE_SUCCESS(rv, rv);
   1.488 +  if (!more)
   1.489 +  {
   1.490 +    mGlobalStatus = JAR_NO_MANIFEST;
   1.491 +    mParsedManifest = true;
   1.492 +    return NS_OK;
   1.493 +  }
   1.494 +
   1.495 +  nsAutoCString manifestFilename;
   1.496 +  rv = files->GetNext(manifestFilename);
   1.497 +  NS_ENSURE_SUCCESS(rv, rv);
   1.498 +
   1.499 +  // Check if there is more than one manifest, if so then error!
   1.500 +  rv = files->HasMore(&more);
   1.501 +  if (NS_FAILED(rv)) return rv;
   1.502 +  if (more)
   1.503 +  {
   1.504 +    mParsedManifest = true;
   1.505 +    return NS_ERROR_FILE_CORRUPTED; // More than one MF file
   1.506 +  }
   1.507 +
   1.508 +  nsXPIDLCString manifestBuffer;
   1.509 +  uint32_t manifestLen;
   1.510 +  rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
   1.511 +  if (NS_FAILED(rv)) return rv;
   1.512 +
   1.513 +  //-- Parse it
   1.514 +  rv = ParseOneFile(manifestBuffer, JAR_MF);
   1.515 +  if (NS_FAILED(rv)) return rv;
   1.516 +
   1.517 +  //-- (2)Signature (SF) file
   1.518 +  // If there are multiple signatures, we select one.
   1.519 +  rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
   1.520 +  if (!files) rv = NS_ERROR_FAILURE;
   1.521 +  if (NS_FAILED(rv)) return rv;
   1.522 +  //-- Get an SF file
   1.523 +  rv = files->HasMore(&more);
   1.524 +  if (NS_FAILED(rv)) return rv;
   1.525 +  if (!more)
   1.526 +  {
   1.527 +    mGlobalStatus = JAR_NO_MANIFEST;
   1.528 +    mParsedManifest = true;
   1.529 +    return NS_OK;
   1.530 +  }
   1.531 +  rv = files->GetNext(manifestFilename);
   1.532 +  if (NS_FAILED(rv)) return rv;
   1.533 +
   1.534 +  rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
   1.535 +  if (NS_FAILED(rv)) return rv;
   1.536 +
   1.537 +  //-- Get its corresponding signature file
   1.538 +  nsAutoCString sigFilename(manifestFilename);
   1.539 +  int32_t extension = sigFilename.RFindChar('.') + 1;
   1.540 +  NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
   1.541 +  (void)sigFilename.Cut(extension, 2);
   1.542 +  nsXPIDLCString sigBuffer;
   1.543 +  uint32_t sigLen;
   1.544 +  {
   1.545 +    nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
   1.546 +    rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
   1.547 +  }
   1.548 +  if (NS_FAILED(rv))
   1.549 +  {
   1.550 +    nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
   1.551 +    rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
   1.552 +  }
   1.553 +  if (NS_FAILED(rv))
   1.554 +  {
   1.555 +    mGlobalStatus = JAR_NO_MANIFEST;
   1.556 +    mParsedManifest = true;
   1.557 +    return NS_OK;
   1.558 +  }
   1.559 +
   1.560 +  //-- Get the signature verifier service
   1.561 +  nsCOMPtr<nsISignatureVerifier> verifier =
   1.562 +           do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv);
   1.563 +  if (NS_FAILED(rv)) // No signature verifier available
   1.564 +  {
   1.565 +    mGlobalStatus = JAR_NO_MANIFEST;
   1.566 +    mParsedManifest = true;
   1.567 +    return NS_OK;
   1.568 +  }
   1.569 +
   1.570 +  //-- Verify that the signature file is a valid signature of the SF file
   1.571 +  int32_t verifyError;
   1.572 +  rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
   1.573 +                                 &verifyError, getter_AddRefs(mPrincipal));
   1.574 +  if (NS_FAILED(rv)) return rv;
   1.575 +  if (mPrincipal && verifyError == 0)
   1.576 +    mGlobalStatus = JAR_VALID_MANIFEST;
   1.577 +  else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
   1.578 +    mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
   1.579 +  else
   1.580 +    mGlobalStatus = JAR_INVALID_SIG;
   1.581 +
   1.582 +  //-- Parse the SF file. If the verification above failed, principal
   1.583 +  // is null, and ParseOneFile will mark the relevant entries as invalid.
   1.584 +  // if ParseOneFile fails, then it has no effect, and we can safely
   1.585 +  // continue to the next SF file, or return.
   1.586 +  ParseOneFile(manifestBuffer, JAR_SF);
   1.587 +  mParsedManifest = true;
   1.588 +
   1.589 +  return NS_OK;
   1.590 +}
   1.591 +
   1.592 +nsresult
   1.593 +nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
   1.594 +{
   1.595 +  //-- Check file header
   1.596 +  const char* nextLineStart = filebuf;
   1.597 +  nsAutoCString curLine;
   1.598 +  int32_t linelen;
   1.599 +  linelen = ReadLine(&nextLineStart);
   1.600 +  curLine.Assign(filebuf, linelen);
   1.601 +
   1.602 +  if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
   1.603 +       ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) )
   1.604 +     return NS_ERROR_FILE_CORRUPTED;
   1.605 +
   1.606 +  //-- Skip header section
   1.607 +  do {
   1.608 +    linelen = ReadLine(&nextLineStart);
   1.609 +  } while (linelen > 0);
   1.610 +
   1.611 +  //-- Set up parsing variables
   1.612 +  const char* curPos;
   1.613 +  const char* sectionStart = nextLineStart;
   1.614 +
   1.615 +  nsJARManifestItem* curItemMF = nullptr;
   1.616 +  bool foundName = false;
   1.617 +  if (aFileType == JAR_MF) {
   1.618 +    curItemMF = new nsJARManifestItem();
   1.619 +  }
   1.620 +
   1.621 +  nsAutoCString curItemName;
   1.622 +  nsAutoCString storedSectionDigest;
   1.623 +
   1.624 +  for(;;)
   1.625 +  {
   1.626 +    curPos = nextLineStart;
   1.627 +    linelen = ReadLine(&nextLineStart);
   1.628 +    curLine.Assign(curPos, linelen);
   1.629 +    if (linelen == 0)
   1.630 +    // end of section (blank line or end-of-file)
   1.631 +    {
   1.632 +      if (aFileType == JAR_MF)
   1.633 +      {
   1.634 +        mTotalItemsInManifest++;
   1.635 +        if (curItemMF->mType != JAR_INVALID)
   1.636 +        {
   1.637 +          //-- Did this section have a name: line?
   1.638 +          if(!foundName)
   1.639 +            curItemMF->mType = JAR_INVALID;
   1.640 +          else
   1.641 +          {
   1.642 +            //-- If it's an internal item, it must correspond
   1.643 +            //   to a valid jar entry
   1.644 +            if (curItemMF->mType == JAR_INTERNAL)
   1.645 +            {
   1.646 +              bool exists;
   1.647 +              nsresult rv = HasEntry(curItemName, &exists);
   1.648 +              if (NS_FAILED(rv) || !exists)
   1.649 +                curItemMF->mType = JAR_INVALID;
   1.650 +            }
   1.651 +            //-- Check for duplicates
   1.652 +            if (mManifestData.Contains(curItemName)) {
   1.653 +              curItemMF->mType = JAR_INVALID;
   1.654 +            }
   1.655 +          }
   1.656 +        }
   1.657 +
   1.658 +        if (curItemMF->mType == JAR_INVALID)
   1.659 +          delete curItemMF;
   1.660 +        else //-- calculate section digest
   1.661 +        {
   1.662 +          uint32_t sectionLength = curPos - sectionStart;
   1.663 +          CalculateDigest(sectionStart, sectionLength,
   1.664 +                          curItemMF->calculatedSectionDigest);
   1.665 +          //-- Save item in the hashtable
   1.666 +          mManifestData.Put(curItemName, curItemMF);
   1.667 +        }
   1.668 +        if (nextLineStart == nullptr) // end-of-file
   1.669 +          break;
   1.670 +
   1.671 +        sectionStart = nextLineStart;
   1.672 +        curItemMF = new nsJARManifestItem();
   1.673 +      } // (aFileType == JAR_MF)
   1.674 +      else
   1.675 +        //-- file type is SF, compare digest with calculated
   1.676 +        //   section digests from MF file.
   1.677 +      {
   1.678 +        if (foundName)
   1.679 +        {
   1.680 +          nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
   1.681 +          if(curItemSF)
   1.682 +          {
   1.683 +            NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
   1.684 +                         "SECURITY ERROR: nsJARManifestItem not correctly initialized");
   1.685 +            curItemSF->status = mGlobalStatus;
   1.686 +            if (curItemSF->status == JAR_VALID_MANIFEST)
   1.687 +            { // Compare digests
   1.688 +              if (storedSectionDigest.IsEmpty())
   1.689 +                curItemSF->status = JAR_NOT_SIGNED;
   1.690 +              else
   1.691 +              {
   1.692 +                if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
   1.693 +                  curItemSF->status = JAR_INVALID_MANIFEST;
   1.694 +                curItemSF->calculatedSectionDigest.Truncate();
   1.695 +                storedSectionDigest.Truncate();
   1.696 +              }
   1.697 +            } // (aPrincipal != nullptr)
   1.698 +          } // if(curItemSF)
   1.699 +        } // if(foundName)
   1.700 +
   1.701 +        if(nextLineStart == nullptr) // end-of-file
   1.702 +          break;
   1.703 +      } // aFileType == JAR_SF
   1.704 +      foundName = false;
   1.705 +      continue;
   1.706 +    } // if(linelen == 0)
   1.707 +
   1.708 +    //-- Look for continuations (beginning with a space) on subsequent lines
   1.709 +    //   and append them to the current line.
   1.710 +    while(*nextLineStart == ' ')
   1.711 +    {
   1.712 +      curPos = nextLineStart;
   1.713 +      int32_t continuationLen = ReadLine(&nextLineStart) - 1;
   1.714 +      nsAutoCString continuation(curPos+1, continuationLen);
   1.715 +      curLine += continuation;
   1.716 +      linelen += continuationLen;
   1.717 +    }
   1.718 +
   1.719 +    //-- Find colon in current line, this separates name from value
   1.720 +    int32_t colonPos = curLine.FindChar(':');
   1.721 +    if (colonPos == -1)    // No colon on line, ignore line
   1.722 +      continue;
   1.723 +    //-- Break down the line
   1.724 +    nsAutoCString lineName;
   1.725 +    curLine.Left(lineName, colonPos);
   1.726 +    nsAutoCString lineData;
   1.727 +    curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
   1.728 +
   1.729 +    //-- Lines to look for:
   1.730 +    // (1) Digest:
   1.731 +    if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
   1.732 +    //-- This is a digest line, save the data in the appropriate place
   1.733 +    {
   1.734 +      if(aFileType == JAR_MF)
   1.735 +        curItemMF->storedEntryDigest = lineData;
   1.736 +      else
   1.737 +        storedSectionDigest = lineData;
   1.738 +      continue;
   1.739 +    }
   1.740 +
   1.741 +    // (2) Name: associates this manifest section with a file in the jar.
   1.742 +    if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
   1.743 +    {
   1.744 +      curItemName = lineData;
   1.745 +      foundName = true;
   1.746 +      continue;
   1.747 +    }
   1.748 +
   1.749 +    // (3) Magic: this may be an inline Javascript.
   1.750 +    //     We can't do any other kind of magic.
   1.751 +    if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
   1.752 +    {
   1.753 +      if (lineData.LowerCaseEqualsLiteral("javascript"))
   1.754 +        curItemMF->mType = JAR_EXTERNAL;
   1.755 +      else
   1.756 +        curItemMF->mType = JAR_INVALID;
   1.757 +      continue;
   1.758 +    }
   1.759 +
   1.760 +  } // for (;;)
   1.761 +  return NS_OK;
   1.762 +} //ParseOneFile()
   1.763 +
   1.764 +nsresult
   1.765 +nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
   1.766 +                   uint32_t aLen)
   1.767 +{
   1.768 +  if (aManItem->status == JAR_VALID_MANIFEST)
   1.769 +  {
   1.770 +    if (aManItem->storedEntryDigest.IsEmpty())
   1.771 +      // No entry digests in manifest file. Entry is unsigned.
   1.772 +      aManItem->status = JAR_NOT_SIGNED;
   1.773 +    else
   1.774 +    { //-- Calculate and compare digests
   1.775 +      nsCString calculatedEntryDigest;
   1.776 +      nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
   1.777 +      if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
   1.778 +      if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
   1.779 +        aManItem->status = JAR_INVALID_ENTRY;
   1.780 +      aManItem->storedEntryDigest.Truncate();
   1.781 +    }
   1.782 +  }
   1.783 +  aManItem->entryVerified = true;
   1.784 +  return NS_OK;
   1.785 +}
   1.786 +
   1.787 +void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
   1.788 +{
   1.789 +  //-- Generate error message
   1.790 +  nsAutoString message;
   1.791 +  message.AssignLiteral("Signature Verification Error: the signature on ");
   1.792 +  if (!aFilename.IsEmpty())
   1.793 +    AppendASCIItoUTF16(aFilename, message);
   1.794 +  else
   1.795 +    message.AppendLiteral("this .jar archive");
   1.796 +  message.AppendLiteral(" is invalid because ");
   1.797 +  switch(errorCode)
   1.798 +  {
   1.799 +  case JAR_NOT_SIGNED:
   1.800 +    message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
   1.801 +    break;
   1.802 +  case JAR_INVALID_SIG:
   1.803 +    message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
   1.804 +    break;
   1.805 +  case JAR_INVALID_UNKNOWN_CA:
   1.806 +    message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
   1.807 +    break;
   1.808 +  case JAR_INVALID_MANIFEST:
   1.809 +    message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
   1.810 +    break;
   1.811 +  case JAR_INVALID_ENTRY:
   1.812 +    message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
   1.813 +    break;
   1.814 +  case JAR_NO_MANIFEST:
   1.815 +    message.AppendLiteral("the archive did not contain a manifest.");
   1.816 +    break;
   1.817 +  default:
   1.818 +    message.AppendLiteral("of an unknown problem.");
   1.819 +  }
   1.820 +
   1.821 +  // Report error in JS console
   1.822 +  nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
   1.823 +  if (console)
   1.824 +  {
   1.825 +    console->LogStringMessage(message.get());
   1.826 +  }
   1.827 +#ifdef DEBUG
   1.828 +  char* messageCstr = ToNewCString(message);
   1.829 +  if (!messageCstr) return;
   1.830 +  fprintf(stderr, "%s\n", messageCstr);
   1.831 +  nsMemory::Free(messageCstr);
   1.832 +#endif
   1.833 +}
   1.834 +
   1.835 +
   1.836 +nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
   1.837 +                                nsCString& digest)
   1.838 +{
   1.839 +  nsresult rv;
   1.840 +
   1.841 +  nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
   1.842 +  if (NS_FAILED(rv)) return rv;
   1.843 +
   1.844 +  rv = hasher->Init(nsICryptoHash::SHA1);
   1.845 +  if (NS_FAILED(rv)) return rv;
   1.846 +
   1.847 +  rv = hasher->Update((const uint8_t*) aInBuf, aLen);
   1.848 +  if (NS_FAILED(rv)) return rv;
   1.849 +
   1.850 +  return hasher->Finish(true, digest);
   1.851 +}
   1.852 +
   1.853 +NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
   1.854 +
   1.855 +//----------------------------------------------
   1.856 +// nsJAREnumerator::HasMore
   1.857 +//----------------------------------------------
   1.858 +NS_IMETHODIMP
   1.859 +nsJAREnumerator::HasMore(bool* aResult)
   1.860 +{
   1.861 +    // try to get the next element
   1.862 +    if (!mName) {
   1.863 +        NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
   1.864 +        nsresult rv = mFind->FindNext( &mName, &mNameLen );
   1.865 +        if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
   1.866 +            *aResult = false;                    // No more matches available
   1.867 +            return NS_OK;
   1.868 +        }
   1.869 +        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);    // no error translation
   1.870 +    }
   1.871 +
   1.872 +    *aResult = true;
   1.873 +    return NS_OK;
   1.874 +}
   1.875 +
   1.876 +//----------------------------------------------
   1.877 +// nsJAREnumerator::GetNext
   1.878 +//----------------------------------------------
   1.879 +NS_IMETHODIMP
   1.880 +nsJAREnumerator::GetNext(nsACString& aResult)
   1.881 +{
   1.882 +    // check if the current item is "stale"
   1.883 +    if (!mName) {
   1.884 +        bool     bMore;
   1.885 +        nsresult rv = HasMore(&bMore);
   1.886 +        if (NS_FAILED(rv) || !bMore)
   1.887 +            return NS_ERROR_FAILURE; // no error translation
   1.888 +    }
   1.889 +    aResult.Assign(mName, mNameLen);
   1.890 +    mName = 0; // we just gave this one away
   1.891 +    return NS_OK;
   1.892 +}
   1.893 +
   1.894 +
   1.895 +NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
   1.896 +
   1.897 +nsJARItem::nsJARItem(nsZipItem* aZipItem)
   1.898 +    : mSize(aZipItem->Size()),
   1.899 +      mRealsize(aZipItem->RealSize()),
   1.900 +      mCrc32(aZipItem->CRC32()),
   1.901 +      mLastModTime(aZipItem->LastModTime()),
   1.902 +      mCompression(aZipItem->Compression()),
   1.903 +      mPermissions(aZipItem->Mode()),
   1.904 +      mIsDirectory(aZipItem->IsDirectory()),
   1.905 +      mIsSynthetic(aZipItem->isSynthetic)
   1.906 +{
   1.907 +}
   1.908 +
   1.909 +//------------------------------------------
   1.910 +// nsJARItem::GetCompression
   1.911 +//------------------------------------------
   1.912 +NS_IMETHODIMP
   1.913 +nsJARItem::GetCompression(uint16_t *aCompression)
   1.914 +{
   1.915 +    NS_ENSURE_ARG_POINTER(aCompression);
   1.916 +
   1.917 +    *aCompression = mCompression;
   1.918 +    return NS_OK;
   1.919 +}
   1.920 +
   1.921 +//------------------------------------------
   1.922 +// nsJARItem::GetSize
   1.923 +//------------------------------------------
   1.924 +NS_IMETHODIMP
   1.925 +nsJARItem::GetSize(uint32_t *aSize)
   1.926 +{
   1.927 +    NS_ENSURE_ARG_POINTER(aSize);
   1.928 +
   1.929 +    *aSize = mSize;
   1.930 +    return NS_OK;
   1.931 +}
   1.932 +
   1.933 +//------------------------------------------
   1.934 +// nsJARItem::GetRealSize
   1.935 +//------------------------------------------
   1.936 +NS_IMETHODIMP
   1.937 +nsJARItem::GetRealSize(uint32_t *aRealsize)
   1.938 +{
   1.939 +    NS_ENSURE_ARG_POINTER(aRealsize);
   1.940 +
   1.941 +    *aRealsize = mRealsize;
   1.942 +    return NS_OK;
   1.943 +}
   1.944 +
   1.945 +//------------------------------------------
   1.946 +// nsJARItem::GetCrc32
   1.947 +//------------------------------------------
   1.948 +NS_IMETHODIMP
   1.949 +nsJARItem::GetCRC32(uint32_t *aCrc32)
   1.950 +{
   1.951 +    NS_ENSURE_ARG_POINTER(aCrc32);
   1.952 +
   1.953 +    *aCrc32 = mCrc32;
   1.954 +    return NS_OK;
   1.955 +}
   1.956 +
   1.957 +//------------------------------------------
   1.958 +// nsJARItem::GetIsDirectory
   1.959 +//------------------------------------------
   1.960 +NS_IMETHODIMP
   1.961 +nsJARItem::GetIsDirectory(bool *aIsDirectory)
   1.962 +{
   1.963 +    NS_ENSURE_ARG_POINTER(aIsDirectory);
   1.964 +
   1.965 +    *aIsDirectory = mIsDirectory;
   1.966 +    return NS_OK;
   1.967 +}
   1.968 +
   1.969 +//------------------------------------------
   1.970 +// nsJARItem::GetIsSynthetic
   1.971 +//------------------------------------------
   1.972 +NS_IMETHODIMP
   1.973 +nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
   1.974 +{
   1.975 +    NS_ENSURE_ARG_POINTER(aIsSynthetic);
   1.976 +
   1.977 +    *aIsSynthetic = mIsSynthetic;
   1.978 +    return NS_OK;
   1.979 +}
   1.980 +
   1.981 +//------------------------------------------
   1.982 +// nsJARItem::GetLastModifiedTime
   1.983 +//------------------------------------------
   1.984 +NS_IMETHODIMP
   1.985 +nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
   1.986 +{
   1.987 +    NS_ENSURE_ARG_POINTER(aLastModTime);
   1.988 +
   1.989 +    *aLastModTime = mLastModTime;
   1.990 +    return NS_OK;
   1.991 +}
   1.992 +
   1.993 +//------------------------------------------
   1.994 +// nsJARItem::GetPermissions
   1.995 +//------------------------------------------
   1.996 +NS_IMETHODIMP
   1.997 +nsJARItem::GetPermissions(uint32_t* aPermissions)
   1.998 +{
   1.999 +    NS_ENSURE_ARG_POINTER(aPermissions);
  1.1000 +
  1.1001 +    *aPermissions = mPermissions;
  1.1002 +    return NS_OK;
  1.1003 +}
  1.1004 +
  1.1005 +////////////////////////////////////////////////////////////////////////////////
  1.1006 +// nsIZipReaderCache
  1.1007 +
  1.1008 +NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
  1.1009 +
  1.1010 +nsZipReaderCache::nsZipReaderCache()
  1.1011 +  : mLock("nsZipReaderCache.mLock")
  1.1012 +  , mZips(16)
  1.1013 +#ifdef ZIP_CACHE_HIT_RATE
  1.1014 +    ,
  1.1015 +    mZipCacheLookups(0),
  1.1016 +    mZipCacheHits(0),
  1.1017 +    mZipCacheFlushes(0),
  1.1018 +    mZipSyncMisses(0)
  1.1019 +#endif
  1.1020 +{
  1.1021 +}
  1.1022 +
  1.1023 +NS_IMETHODIMP
  1.1024 +nsZipReaderCache::Init(uint32_t cacheSize)
  1.1025 +{
  1.1026 +  mCacheSize = cacheSize;
  1.1027 +
  1.1028 +// Register as a memory pressure observer
  1.1029 +  nsCOMPtr<nsIObserverService> os =
  1.1030 +           do_GetService("@mozilla.org/observer-service;1");
  1.1031 +  if (os)
  1.1032 +  {
  1.1033 +    os->AddObserver(this, "memory-pressure", true);
  1.1034 +    os->AddObserver(this, "chrome-flush-caches", true);
  1.1035 +    os->AddObserver(this, "flush-cache-entry", true);
  1.1036 +  }
  1.1037 +// ignore failure of the observer registration.
  1.1038 +
  1.1039 +  return NS_OK;
  1.1040 +}
  1.1041 +
  1.1042 +static PLDHashOperator
  1.1043 +DropZipReaderCache(const nsACString &aKey, nsJAR* aZip, void*)
  1.1044 +{
  1.1045 +  aZip->SetZipReaderCache(nullptr);
  1.1046 +  return PL_DHASH_NEXT;
  1.1047 +}
  1.1048 +
  1.1049 +nsZipReaderCache::~nsZipReaderCache()
  1.1050 +{
  1.1051 +  mZips.EnumerateRead(DropZipReaderCache, nullptr);
  1.1052 +
  1.1053 +#ifdef ZIP_CACHE_HIT_RATE
  1.1054 +  printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
  1.1055 +         mCacheSize, mZipCacheHits, mZipCacheLookups,
  1.1056 +         (float)mZipCacheHits / mZipCacheLookups,
  1.1057 +         mZipCacheFlushes, mZipSyncMisses);
  1.1058 +#endif
  1.1059 +}
  1.1060 +
  1.1061 +NS_IMETHODIMP
  1.1062 +nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
  1.1063 +{
  1.1064 +  NS_ENSURE_ARG_POINTER(zipFile);
  1.1065 +  nsresult rv;
  1.1066 +  nsCOMPtr<nsIZipReader> antiLockZipGrip;
  1.1067 +  MutexAutoLock lock(mLock);
  1.1068 +
  1.1069 +  nsAutoCString uri;
  1.1070 +  rv = zipFile->GetNativePath(uri);
  1.1071 +  if (NS_FAILED(rv))
  1.1072 +    return rv;
  1.1073 +
  1.1074 +  uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
  1.1075 +
  1.1076 +  *aResult = mZips.Contains(uri);
  1.1077 +  return NS_OK;
  1.1078 +}
  1.1079 +
  1.1080 +NS_IMETHODIMP
  1.1081 +nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
  1.1082 +{
  1.1083 +  NS_ENSURE_ARG_POINTER(zipFile);
  1.1084 +  nsresult rv;
  1.1085 +  nsCOMPtr<nsIZipReader> antiLockZipGrip;
  1.1086 +  MutexAutoLock lock(mLock);
  1.1087 +
  1.1088 +#ifdef ZIP_CACHE_HIT_RATE
  1.1089 +  mZipCacheLookups++;
  1.1090 +#endif
  1.1091 +
  1.1092 +  nsAutoCString uri;
  1.1093 +  rv = zipFile->GetNativePath(uri);
  1.1094 +  if (NS_FAILED(rv)) return rv;
  1.1095 +
  1.1096 +  uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
  1.1097 +
  1.1098 +  nsRefPtr<nsJAR> zip;
  1.1099 +  mZips.Get(uri, getter_AddRefs(zip));
  1.1100 +  if (zip) {
  1.1101 +#ifdef ZIP_CACHE_HIT_RATE
  1.1102 +    mZipCacheHits++;
  1.1103 +#endif
  1.1104 +    zip->ClearReleaseTime();
  1.1105 +  } else {
  1.1106 +    zip = new nsJAR();
  1.1107 +    zip->SetZipReaderCache(this);
  1.1108 +
  1.1109 +    rv = zip->Open(zipFile);
  1.1110 +    if (NS_FAILED(rv)) {
  1.1111 +      return rv;
  1.1112 +    }
  1.1113 +
  1.1114 +    MOZ_ASSERT(!mZips.Contains(uri));
  1.1115 +    mZips.Put(uri, zip);
  1.1116 +  }
  1.1117 +  zip.forget(result);
  1.1118 +  return rv;
  1.1119 +}
  1.1120 +
  1.1121 +NS_IMETHODIMP
  1.1122 +nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
  1.1123 +                              nsIZipReader* *result)
  1.1124 +{
  1.1125 +  NS_ENSURE_ARG_POINTER(zipFile);
  1.1126 +
  1.1127 +  nsCOMPtr<nsIZipReader> outerZipReader;
  1.1128 +  nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
  1.1129 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1130 +
  1.1131 +#ifdef ZIP_CACHE_HIT_RATE
  1.1132 +  mZipCacheLookups++;
  1.1133 +#endif
  1.1134 +
  1.1135 +  nsAutoCString uri;
  1.1136 +  rv = zipFile->GetNativePath(uri);
  1.1137 +  if (NS_FAILED(rv)) return rv;
  1.1138 +
  1.1139 +  uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
  1.1140 +  uri.AppendLiteral("!/");
  1.1141 +  uri.Append(entry);
  1.1142 +
  1.1143 +  nsRefPtr<nsJAR> zip;
  1.1144 +  mZips.Get(uri, getter_AddRefs(zip));
  1.1145 +  if (zip) {
  1.1146 +#ifdef ZIP_CACHE_HIT_RATE
  1.1147 +    mZipCacheHits++;
  1.1148 +#endif
  1.1149 +    zip->ClearReleaseTime();
  1.1150 +  } else {
  1.1151 +    zip = new nsJAR();
  1.1152 +    zip->SetZipReaderCache(this);
  1.1153 +
  1.1154 +    rv = zip->OpenInner(outerZipReader, entry);
  1.1155 +    if (NS_FAILED(rv)) {
  1.1156 +      return rv;
  1.1157 +    }
  1.1158 +
  1.1159 +    MOZ_ASSERT(!mZips.Contains(uri));
  1.1160 +    mZips.Put(uri, zip);
  1.1161 +  }
  1.1162 +  zip.forget(result);
  1.1163 +  return rv;
  1.1164 +}
  1.1165 +
  1.1166 +static PLDHashOperator
  1.1167 +FindOldestZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
  1.1168 +{
  1.1169 +  nsJAR** oldestPtr = static_cast<nsJAR**>(aClosure);
  1.1170 +  nsJAR* oldest = *oldestPtr;
  1.1171 +  nsJAR* current = aZip;
  1.1172 +  PRIntervalTime currentReleaseTime = current->GetReleaseTime();
  1.1173 +  if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
  1.1174 +    if (oldest == nullptr ||
  1.1175 +        currentReleaseTime < oldest->GetReleaseTime()) {
  1.1176 +      *oldestPtr = current;
  1.1177 +    }
  1.1178 +  }
  1.1179 +  return PL_DHASH_NEXT;
  1.1180 +}
  1.1181 +
  1.1182 +struct ZipFindData {nsJAR* zip; bool found;};
  1.1183 +
  1.1184 +static PLDHashOperator
  1.1185 +FindZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
  1.1186 +{
  1.1187 +  ZipFindData* find_data = static_cast<ZipFindData*>(aClosure);
  1.1188 +
  1.1189 +  if (find_data->zip == aZip) {
  1.1190 +    find_data->found = true;
  1.1191 +    return PL_DHASH_STOP;
  1.1192 +  }
  1.1193 +  return PL_DHASH_NEXT;
  1.1194 +}
  1.1195 +
  1.1196 +nsresult
  1.1197 +nsZipReaderCache::ReleaseZip(nsJAR* zip)
  1.1198 +{
  1.1199 +  nsresult rv;
  1.1200 +  MutexAutoLock lock(mLock);
  1.1201 +
  1.1202 +  // It is possible that two thread compete for this zip. The dangerous
  1.1203 +  // case is where one thread Releases the zip and discovers that the ref
  1.1204 +  // count has gone to one. Before it can call this ReleaseZip method
  1.1205 +  // another thread calls our GetZip method. The ref count goes to two. That
  1.1206 +  // second thread then Releases the zip and the ref count goes to one. It
  1.1207 +  // then tries to enter this ReleaseZip method and blocks while the first
  1.1208 +  // thread is still here. The first thread continues and remove the zip from
  1.1209 +  // the cache and calls its Release method sending the ref count to 0 and
  1.1210 +  // deleting the zip. However, the second thread is still blocked at the
  1.1211 +  // start of ReleaseZip, but the 'zip' param now hold a reference to a
  1.1212 +  // deleted zip!
  1.1213 +  //
  1.1214 +  // So, we are going to try safeguarding here by searching our hashtable while
  1.1215 +  // locked here for the zip. We return fast if it is not found.
  1.1216 +
  1.1217 +  ZipFindData find_data = {zip, false};
  1.1218 +  mZips.EnumerateRead(FindZip, &find_data);
  1.1219 +  if (!find_data.found) {
  1.1220 +#ifdef ZIP_CACHE_HIT_RATE
  1.1221 +    mZipSyncMisses++;
  1.1222 +#endif
  1.1223 +    return NS_OK;
  1.1224 +  }
  1.1225 +
  1.1226 +  zip->SetReleaseTime();
  1.1227 +
  1.1228 +  if (mZips.Count() <= mCacheSize)
  1.1229 +    return NS_OK;
  1.1230 +
  1.1231 +  nsJAR* oldest = nullptr;
  1.1232 +  mZips.EnumerateRead(FindOldestZip, &oldest);
  1.1233 +
  1.1234 +  // Because of the craziness above it is possible that there is no zip that
  1.1235 +  // needs removing.
  1.1236 +  if (!oldest)
  1.1237 +    return NS_OK;
  1.1238 +
  1.1239 +#ifdef ZIP_CACHE_HIT_RATE
  1.1240 +    mZipCacheFlushes++;
  1.1241 +#endif
  1.1242 +
  1.1243 +  // remove from hashtable
  1.1244 +  nsAutoCString uri;
  1.1245 +  rv = oldest->GetJarPath(uri);
  1.1246 +  if (NS_FAILED(rv))
  1.1247 +    return rv;
  1.1248 +
  1.1249 +  if (oldest->mOuterZipEntry.IsEmpty()) {
  1.1250 +    uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
  1.1251 +  } else {
  1.1252 +    uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
  1.1253 +    uri.AppendLiteral("!/");
  1.1254 +    uri.Append(oldest->mOuterZipEntry);
  1.1255 +  }
  1.1256 +
  1.1257 +  // Retrieving and removing the JAR must be done without an extra AddRef
  1.1258 +  // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
  1.1259 +  // an extra time and trigger a deadlock.
  1.1260 +  nsRefPtr<nsJAR> removed;
  1.1261 +  mZips.Remove(uri, getter_AddRefs(removed));
  1.1262 +  NS_ASSERTION(removed, "botched");
  1.1263 +  NS_ASSERTION(oldest == removed, "removed wrong entry");
  1.1264 +
  1.1265 +  if (removed)
  1.1266 +    removed->SetZipReaderCache(nullptr);
  1.1267 +
  1.1268 +  return NS_OK;
  1.1269 +}
  1.1270 +
  1.1271 +static PLDHashOperator
  1.1272 +FindFlushableZip(const nsACString &aKey, nsRefPtr<nsJAR>& aCurrent, void*)
  1.1273 +{
  1.1274 +  if (aCurrent->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
  1.1275 +    aCurrent->SetZipReaderCache(nullptr);
  1.1276 +    return PL_DHASH_REMOVE;
  1.1277 +  }
  1.1278 +  return PL_DHASH_NEXT;
  1.1279 +}
  1.1280 +
  1.1281 +NS_IMETHODIMP
  1.1282 +nsZipReaderCache::Observe(nsISupports *aSubject,
  1.1283 +                          const char *aTopic,
  1.1284 +                          const char16_t *aSomeData)
  1.1285 +{
  1.1286 +  if (strcmp(aTopic, "memory-pressure") == 0) {
  1.1287 +    MutexAutoLock lock(mLock);
  1.1288 +    mZips.Enumerate(FindFlushableZip, nullptr);
  1.1289 +  }
  1.1290 +  else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
  1.1291 +    mZips.EnumerateRead(DropZipReaderCache, nullptr);
  1.1292 +    mZips.Clear();
  1.1293 +  }
  1.1294 +  else if (strcmp(aTopic, "flush-cache-entry") == 0) {
  1.1295 +    nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
  1.1296 +    if (!file)
  1.1297 +      return NS_OK;
  1.1298 +
  1.1299 +    nsAutoCString uri;
  1.1300 +    if (NS_FAILED(file->GetNativePath(uri)))
  1.1301 +      return NS_OK;
  1.1302 +
  1.1303 +    uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
  1.1304 +
  1.1305 +    MutexAutoLock lock(mLock);
  1.1306 +
  1.1307 +    nsRefPtr<nsJAR> zip;
  1.1308 +    mZips.Get(uri, getter_AddRefs(zip));
  1.1309 +    if (!zip)
  1.1310 +      return NS_OK;
  1.1311 +
  1.1312 +#ifdef ZIP_CACHE_HIT_RATE
  1.1313 +    mZipCacheFlushes++;
  1.1314 +#endif
  1.1315 +
  1.1316 +    zip->SetZipReaderCache(nullptr);
  1.1317 +
  1.1318 +    mZips.Remove(uri);
  1.1319 +  }
  1.1320 +  return NS_OK;
  1.1321 +}
  1.1322 +
  1.1323 +////////////////////////////////////////////////////////////////////////////////

mercurial