modules/libjar/nsJAR.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5 #include <string.h>
michael@0 6 #include "nsJARInputStream.h"
michael@0 7 #include "nsJAR.h"
michael@0 8 #include "nsIFile.h"
michael@0 9 #include "nsIConsoleService.h"
michael@0 10 #include "nsICryptoHash.h"
michael@0 11 #include "prprf.h"
michael@0 12 #include "mozilla/Omnijar.h"
michael@0 13
michael@0 14 #ifdef XP_UNIX
michael@0 15 #include <sys/stat.h>
michael@0 16 #elif defined (XP_WIN)
michael@0 17 #include <io.h>
michael@0 18 #endif
michael@0 19
michael@0 20 using namespace mozilla;
michael@0 21
michael@0 22 //----------------------------------------------
michael@0 23 // nsJARManifestItem declaration
michael@0 24 //----------------------------------------------
michael@0 25 /*
michael@0 26 * nsJARManifestItem contains meta-information pertaining
michael@0 27 * to an individual JAR entry, taken from the
michael@0 28 * META-INF/MANIFEST.MF and META-INF/ *.SF files.
michael@0 29 * This is security-critical information, defined here so it is not
michael@0 30 * accessible from anywhere else.
michael@0 31 */
michael@0 32 typedef enum
michael@0 33 {
michael@0 34 JAR_INVALID = 1,
michael@0 35 JAR_INTERNAL = 2,
michael@0 36 JAR_EXTERNAL = 3
michael@0 37 } JARManifestItemType;
michael@0 38
michael@0 39 class nsJARManifestItem
michael@0 40 {
michael@0 41 public:
michael@0 42 JARManifestItemType mType;
michael@0 43
michael@0 44 // True if the second step of verification (VerifyEntry)
michael@0 45 // has taken place:
michael@0 46 bool entryVerified;
michael@0 47
michael@0 48 // Not signed, valid, or failure code
michael@0 49 int16_t status;
michael@0 50
michael@0 51 // Internal storage of digests
michael@0 52 nsCString calculatedSectionDigest;
michael@0 53 nsCString storedEntryDigest;
michael@0 54
michael@0 55 nsJARManifestItem();
michael@0 56 virtual ~nsJARManifestItem();
michael@0 57 };
michael@0 58
michael@0 59 //-------------------------------------------------
michael@0 60 // nsJARManifestItem constructors and destructor
michael@0 61 //-------------------------------------------------
michael@0 62 nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
michael@0 63 entryVerified(false),
michael@0 64 status(JAR_NOT_SIGNED)
michael@0 65 {
michael@0 66 }
michael@0 67
michael@0 68 nsJARManifestItem::~nsJARManifestItem()
michael@0 69 {
michael@0 70 }
michael@0 71
michael@0 72 //----------------------------------------------
michael@0 73 // nsJAR constructor/destructor
michael@0 74 //----------------------------------------------
michael@0 75
michael@0 76 // The following initialization makes a guess of 10 entries per jarfile.
michael@0 77 nsJAR::nsJAR(): mZip(new nsZipArchive()),
michael@0 78 mManifestData(10),
michael@0 79 mParsedManifest(false),
michael@0 80 mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
michael@0 81 mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
michael@0 82 mCache(nullptr),
michael@0 83 mLock("nsJAR::mLock"),
michael@0 84 mTotalItemsInManifest(0),
michael@0 85 mOpened(false)
michael@0 86 {
michael@0 87 }
michael@0 88
michael@0 89 nsJAR::~nsJAR()
michael@0 90 {
michael@0 91 Close();
michael@0 92 }
michael@0 93
michael@0 94 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
michael@0 95 NS_IMPL_ADDREF(nsJAR)
michael@0 96
michael@0 97 // Custom Release method works with nsZipReaderCache...
michael@0 98 MozExternalRefCountType nsJAR::Release(void)
michael@0 99 {
michael@0 100 nsrefcnt count;
michael@0 101 NS_PRECONDITION(0 != mRefCnt, "dup release");
michael@0 102 count = --mRefCnt;
michael@0 103 NS_LOG_RELEASE(this, count, "nsJAR");
michael@0 104 if (0 == count) {
michael@0 105 mRefCnt = 1; /* stabilize */
michael@0 106 /* enable this to find non-threadsafe destructors: */
michael@0 107 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
michael@0 108 delete this;
michael@0 109 return 0;
michael@0 110 }
michael@0 111 else if (1 == count && mCache) {
michael@0 112 #ifdef DEBUG
michael@0 113 nsresult rv =
michael@0 114 #endif
michael@0 115 mCache->ReleaseZip(this);
michael@0 116 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
michael@0 117 }
michael@0 118 return count;
michael@0 119 }
michael@0 120
michael@0 121 //----------------------------------------------
michael@0 122 // nsIZipReader implementation
michael@0 123 //----------------------------------------------
michael@0 124
michael@0 125 NS_IMETHODIMP
michael@0 126 nsJAR::Open(nsIFile* zipFile)
michael@0 127 {
michael@0 128 NS_ENSURE_ARG_POINTER(zipFile);
michael@0 129 if (mOpened) return NS_ERROR_FAILURE; // Already open!
michael@0 130
michael@0 131 mZipFile = zipFile;
michael@0 132 mOuterZipEntry.Truncate();
michael@0 133 mOpened = true;
michael@0 134
michael@0 135 // The omnijar is special, it is opened early on and closed late
michael@0 136 // this avoids reopening it
michael@0 137 nsRefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
michael@0 138 if (zip) {
michael@0 139 mZip = zip;
michael@0 140 return NS_OK;
michael@0 141 }
michael@0 142 return mZip->OpenArchive(zipFile);
michael@0 143 }
michael@0 144
michael@0 145 NS_IMETHODIMP
michael@0 146 nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
michael@0 147 {
michael@0 148 NS_ENSURE_ARG_POINTER(aZipReader);
michael@0 149 if (mOpened) return NS_ERROR_FAILURE; // Already open!
michael@0 150
michael@0 151 bool exist;
michael@0 152 nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
michael@0 153 NS_ENSURE_SUCCESS(rv, rv);
michael@0 154 NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
michael@0 155
michael@0 156 rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
michael@0 157 NS_ENSURE_SUCCESS(rv, rv);
michael@0 158
michael@0 159 mOpened = true;
michael@0 160
michael@0 161 mOuterZipEntry.Assign(aZipEntry);
michael@0 162
michael@0 163 nsRefPtr<nsZipHandle> handle;
michael@0 164 rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
michael@0 165 getter_AddRefs(handle));
michael@0 166 if (NS_FAILED(rv))
michael@0 167 return rv;
michael@0 168
michael@0 169 return mZip->OpenArchive(handle);
michael@0 170 }
michael@0 171
michael@0 172 NS_IMETHODIMP
michael@0 173 nsJAR::GetFile(nsIFile* *result)
michael@0 174 {
michael@0 175 *result = mZipFile;
michael@0 176 NS_IF_ADDREF(*result);
michael@0 177 return NS_OK;
michael@0 178 }
michael@0 179
michael@0 180 NS_IMETHODIMP
michael@0 181 nsJAR::Close()
michael@0 182 {
michael@0 183 mOpened = false;
michael@0 184 mParsedManifest = false;
michael@0 185 mManifestData.Clear();
michael@0 186 mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
michael@0 187 mTotalItemsInManifest = 0;
michael@0 188
michael@0 189 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
michael@0 190 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
michael@0 191
michael@0 192 if (mZip == greOmni || mZip == appOmni) {
michael@0 193 mZip = new nsZipArchive();
michael@0 194 return NS_OK;
michael@0 195 }
michael@0 196 return mZip->CloseArchive();
michael@0 197 }
michael@0 198
michael@0 199 NS_IMETHODIMP
michael@0 200 nsJAR::Test(const nsACString &aEntryName)
michael@0 201 {
michael@0 202 return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
michael@0 203 }
michael@0 204
michael@0 205 NS_IMETHODIMP
michael@0 206 nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
michael@0 207 {
michael@0 208 // nsZipArchive and zlib are not thread safe
michael@0 209 // we need to use a lock to prevent bug #51267
michael@0 210 MutexAutoLock lock(mLock);
michael@0 211
michael@0 212 nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
michael@0 213 NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
michael@0 214
michael@0 215 // Remove existing file or directory so we set permissions correctly.
michael@0 216 // If it's a directory that already exists and contains files, throw
michael@0 217 // an exception and return.
michael@0 218
michael@0 219 nsresult rv = outFile->Remove(false);
michael@0 220 if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
michael@0 221 rv == NS_ERROR_FAILURE)
michael@0 222 return rv;
michael@0 223
michael@0 224 if (item->IsDirectory())
michael@0 225 {
michael@0 226 rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
michael@0 227 //XXX Do this in nsZipArchive? It would be nice to keep extraction
michael@0 228 //XXX code completely there, but that would require a way to get a
michael@0 229 //XXX PRDir from outFile.
michael@0 230 }
michael@0 231 else
michael@0 232 {
michael@0 233 PRFileDesc* fd;
michael@0 234 rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
michael@0 235 if (NS_FAILED(rv)) return rv;
michael@0 236
michael@0 237 // ExtractFile also closes the fd handle and resolves the symlink if needed
michael@0 238 nsAutoCString path;
michael@0 239 rv = outFile->GetNativePath(path);
michael@0 240 if (NS_FAILED(rv)) return rv;
michael@0 241
michael@0 242 rv = mZip->ExtractFile(item, path.get(), fd);
michael@0 243 }
michael@0 244 if (NS_FAILED(rv)) return rv;
michael@0 245
michael@0 246 // nsIFile needs milliseconds, while prtime is in microseconds.
michael@0 247 // non-fatal if this fails, ignore errors
michael@0 248 outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
michael@0 249
michael@0 250 return NS_OK;
michael@0 251 }
michael@0 252
michael@0 253 NS_IMETHODIMP
michael@0 254 nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
michael@0 255 {
michael@0 256 nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
michael@0 257 NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
michael@0 258
michael@0 259 nsJARItem* jarItem = new nsJARItem(zipItem);
michael@0 260
michael@0 261 NS_ADDREF(*result = jarItem);
michael@0 262 return NS_OK;
michael@0 263 }
michael@0 264
michael@0 265 NS_IMETHODIMP
michael@0 266 nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
michael@0 267 {
michael@0 268 *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
michael@0 269 return NS_OK;
michael@0 270 }
michael@0 271
michael@0 272 NS_IMETHODIMP
michael@0 273 nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
michael@0 274 {
michael@0 275 NS_ENSURE_ARG_POINTER(result);
michael@0 276
michael@0 277 nsZipFind *find;
michael@0 278 nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
michael@0 279 NS_ENSURE_SUCCESS(rv, rv);
michael@0 280
michael@0 281 nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
michael@0 282
michael@0 283 NS_ADDREF(*result = zipEnum);
michael@0 284 return NS_OK;
michael@0 285 }
michael@0 286
michael@0 287 NS_IMETHODIMP
michael@0 288 nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
michael@0 289 {
michael@0 290 return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
michael@0 291 }
michael@0 292
michael@0 293 NS_IMETHODIMP
michael@0 294 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
michael@0 295 const nsACString &aEntryName, nsIInputStream** result)
michael@0 296 {
michael@0 297 NS_ENSURE_ARG_POINTER(result);
michael@0 298
michael@0 299 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
michael@0 300 nsZipItem *item = nullptr;
michael@0 301 const char *entry = PromiseFlatCString(aEntryName).get();
michael@0 302 if (*entry) {
michael@0 303 // First check if item exists in jar
michael@0 304 item = mZip->GetItem(entry);
michael@0 305 if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
michael@0 306 }
michael@0 307 nsJARInputStream* jis = new nsJARInputStream();
michael@0 308 // addref now so we can call InitFile/InitDirectory()
michael@0 309 NS_ADDREF(*result = jis);
michael@0 310
michael@0 311 nsresult rv = NS_OK;
michael@0 312 if (!item || item->IsDirectory()) {
michael@0 313 rv = jis->InitDirectory(this, aJarDirSpec, entry);
michael@0 314 } else {
michael@0 315 rv = jis->InitFile(this, item);
michael@0 316 }
michael@0 317 if (NS_FAILED(rv)) {
michael@0 318 NS_RELEASE(*result);
michael@0 319 }
michael@0 320 return rv;
michael@0 321 }
michael@0 322
michael@0 323 NS_IMETHODIMP
michael@0 324 nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal)
michael@0 325 {
michael@0 326 //-- Parameter check
michael@0 327 if (!aPrincipal)
michael@0 328 return NS_ERROR_NULL_POINTER;
michael@0 329 *aPrincipal = nullptr;
michael@0 330
michael@0 331 // Don't check signatures in the omnijar - this is only
michael@0 332 // interesting for extensions/XPIs.
michael@0 333 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
michael@0 334 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
michael@0 335
michael@0 336 if (mZip == greOmni || mZip == appOmni)
michael@0 337 return NS_OK;
michael@0 338
michael@0 339 //-- Parse the manifest
michael@0 340 nsresult rv = ParseManifest();
michael@0 341 if (NS_FAILED(rv)) return rv;
michael@0 342 if (mGlobalStatus == JAR_NO_MANIFEST)
michael@0 343 return NS_OK;
michael@0 344
michael@0 345 int16_t requestedStatus;
michael@0 346 if (!aFilename.IsEmpty())
michael@0 347 {
michael@0 348 //-- Find the item
michael@0 349 nsJARManifestItem* manItem = mManifestData.Get(aFilename);
michael@0 350 if (!manItem)
michael@0 351 return NS_OK;
michael@0 352 //-- Verify the item against the manifest
michael@0 353 if (!manItem->entryVerified)
michael@0 354 {
michael@0 355 nsXPIDLCString entryData;
michael@0 356 uint32_t entryDataLen;
michael@0 357 rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
michael@0 358 if (NS_FAILED(rv)) return rv;
michael@0 359 rv = VerifyEntry(manItem, entryData, entryDataLen);
michael@0 360 if (NS_FAILED(rv)) return rv;
michael@0 361 }
michael@0 362 requestedStatus = manItem->status;
michael@0 363 }
michael@0 364 else // User wants identity of signer w/o verifying any entries
michael@0 365 requestedStatus = mGlobalStatus;
michael@0 366
michael@0 367 if (requestedStatus != JAR_VALID_MANIFEST)
michael@0 368 ReportError(aFilename, requestedStatus);
michael@0 369 else // Valid signature
michael@0 370 {
michael@0 371 *aPrincipal = mPrincipal;
michael@0 372 NS_IF_ADDREF(*aPrincipal);
michael@0 373 }
michael@0 374 return NS_OK;
michael@0 375 }
michael@0 376
michael@0 377 NS_IMETHODIMP
michael@0 378 nsJAR::GetManifestEntriesCount(uint32_t* count)
michael@0 379 {
michael@0 380 *count = mTotalItemsInManifest;
michael@0 381 return NS_OK;
michael@0 382 }
michael@0 383
michael@0 384 nsresult
michael@0 385 nsJAR::GetJarPath(nsACString& aResult)
michael@0 386 {
michael@0 387 NS_ENSURE_ARG_POINTER(mZipFile);
michael@0 388
michael@0 389 return mZipFile->GetNativePath(aResult);
michael@0 390 }
michael@0 391
michael@0 392 //----------------------------------------------
michael@0 393 // nsJAR private implementation
michael@0 394 //----------------------------------------------
michael@0 395 nsresult
michael@0 396 nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen)
michael@0 397 {
michael@0 398 //-- Get a stream for reading the file
michael@0 399 nsresult rv;
michael@0 400 nsCOMPtr<nsIInputStream> manifestStream;
michael@0 401 rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
michael@0 402 if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
michael@0 403
michael@0 404 //-- Read the manifest file into memory
michael@0 405 char* buf;
michael@0 406 uint64_t len64;
michael@0 407 rv = manifestStream->Available(&len64);
michael@0 408 if (NS_FAILED(rv)) return rv;
michael@0 409 NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695
michael@0 410 uint32_t len = (uint32_t)len64;
michael@0 411 buf = (char*)malloc(len+1);
michael@0 412 if (!buf) return NS_ERROR_OUT_OF_MEMORY;
michael@0 413 uint32_t bytesRead;
michael@0 414 rv = manifestStream->Read(buf, len, &bytesRead);
michael@0 415 if (bytesRead != len)
michael@0 416 rv = NS_ERROR_FILE_CORRUPTED;
michael@0 417 if (NS_FAILED(rv)) {
michael@0 418 free(buf);
michael@0 419 return rv;
michael@0 420 }
michael@0 421 buf[len] = '\0'; //Null-terminate the buffer
michael@0 422 *aBuf = buf;
michael@0 423 if (aBufLen)
michael@0 424 *aBufLen = len;
michael@0 425 return NS_OK;
michael@0 426 }
michael@0 427
michael@0 428
michael@0 429 int32_t
michael@0 430 nsJAR::ReadLine(const char** src)
michael@0 431 {
michael@0 432 if (!*src) {
michael@0 433 return 0;
michael@0 434 }
michael@0 435
michael@0 436 //--Moves pointer to beginning of next line and returns line length
michael@0 437 // not including CR/LF.
michael@0 438 int32_t length;
michael@0 439 char* eol = PL_strpbrk(*src, "\r\n");
michael@0 440
michael@0 441 if (eol == nullptr) // Probably reached end of file before newline
michael@0 442 {
michael@0 443 length = strlen(*src);
michael@0 444 if (length == 0) // immediate end-of-file
michael@0 445 *src = nullptr;
michael@0 446 else // some data left on this line
michael@0 447 *src += length;
michael@0 448 }
michael@0 449 else
michael@0 450 {
michael@0 451 length = eol - *src;
michael@0 452 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
michael@0 453 *src = eol+2;
michael@0 454 else // Either CR or LF, so skip 1
michael@0 455 *src = eol+1;
michael@0 456 }
michael@0 457 return length;
michael@0 458 }
michael@0 459
michael@0 460 //-- The following #defines are used by ParseManifest()
michael@0 461 // and ParseOneFile(). The header strings are defined in the JAR specification.
michael@0 462 #define JAR_MF 1
michael@0 463 #define JAR_SF 2
michael@0 464 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
michael@0 465 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
michael@0 466 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
michael@0 467 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
michael@0 468
michael@0 469 nsresult
michael@0 470 nsJAR::ParseManifest()
michael@0 471 {
michael@0 472 //-- Verification Step 1
michael@0 473 if (mParsedManifest)
michael@0 474 return NS_OK;
michael@0 475 //-- (1)Manifest (MF) file
michael@0 476 nsCOMPtr<nsIUTF8StringEnumerator> files;
michael@0 477 nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
michael@0 478 if (!files) rv = NS_ERROR_FAILURE;
michael@0 479 if (NS_FAILED(rv)) return rv;
michael@0 480
michael@0 481 //-- Load the file into memory
michael@0 482 bool more;
michael@0 483 rv = files->HasMore(&more);
michael@0 484 NS_ENSURE_SUCCESS(rv, rv);
michael@0 485 if (!more)
michael@0 486 {
michael@0 487 mGlobalStatus = JAR_NO_MANIFEST;
michael@0 488 mParsedManifest = true;
michael@0 489 return NS_OK;
michael@0 490 }
michael@0 491
michael@0 492 nsAutoCString manifestFilename;
michael@0 493 rv = files->GetNext(manifestFilename);
michael@0 494 NS_ENSURE_SUCCESS(rv, rv);
michael@0 495
michael@0 496 // Check if there is more than one manifest, if so then error!
michael@0 497 rv = files->HasMore(&more);
michael@0 498 if (NS_FAILED(rv)) return rv;
michael@0 499 if (more)
michael@0 500 {
michael@0 501 mParsedManifest = true;
michael@0 502 return NS_ERROR_FILE_CORRUPTED; // More than one MF file
michael@0 503 }
michael@0 504
michael@0 505 nsXPIDLCString manifestBuffer;
michael@0 506 uint32_t manifestLen;
michael@0 507 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
michael@0 508 if (NS_FAILED(rv)) return rv;
michael@0 509
michael@0 510 //-- Parse it
michael@0 511 rv = ParseOneFile(manifestBuffer, JAR_MF);
michael@0 512 if (NS_FAILED(rv)) return rv;
michael@0 513
michael@0 514 //-- (2)Signature (SF) file
michael@0 515 // If there are multiple signatures, we select one.
michael@0 516 rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
michael@0 517 if (!files) rv = NS_ERROR_FAILURE;
michael@0 518 if (NS_FAILED(rv)) return rv;
michael@0 519 //-- Get an SF file
michael@0 520 rv = files->HasMore(&more);
michael@0 521 if (NS_FAILED(rv)) return rv;
michael@0 522 if (!more)
michael@0 523 {
michael@0 524 mGlobalStatus = JAR_NO_MANIFEST;
michael@0 525 mParsedManifest = true;
michael@0 526 return NS_OK;
michael@0 527 }
michael@0 528 rv = files->GetNext(manifestFilename);
michael@0 529 if (NS_FAILED(rv)) return rv;
michael@0 530
michael@0 531 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
michael@0 532 if (NS_FAILED(rv)) return rv;
michael@0 533
michael@0 534 //-- Get its corresponding signature file
michael@0 535 nsAutoCString sigFilename(manifestFilename);
michael@0 536 int32_t extension = sigFilename.RFindChar('.') + 1;
michael@0 537 NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
michael@0 538 (void)sigFilename.Cut(extension, 2);
michael@0 539 nsXPIDLCString sigBuffer;
michael@0 540 uint32_t sigLen;
michael@0 541 {
michael@0 542 nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
michael@0 543 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
michael@0 544 }
michael@0 545 if (NS_FAILED(rv))
michael@0 546 {
michael@0 547 nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
michael@0 548 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
michael@0 549 }
michael@0 550 if (NS_FAILED(rv))
michael@0 551 {
michael@0 552 mGlobalStatus = JAR_NO_MANIFEST;
michael@0 553 mParsedManifest = true;
michael@0 554 return NS_OK;
michael@0 555 }
michael@0 556
michael@0 557 //-- Get the signature verifier service
michael@0 558 nsCOMPtr<nsISignatureVerifier> verifier =
michael@0 559 do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv);
michael@0 560 if (NS_FAILED(rv)) // No signature verifier available
michael@0 561 {
michael@0 562 mGlobalStatus = JAR_NO_MANIFEST;
michael@0 563 mParsedManifest = true;
michael@0 564 return NS_OK;
michael@0 565 }
michael@0 566
michael@0 567 //-- Verify that the signature file is a valid signature of the SF file
michael@0 568 int32_t verifyError;
michael@0 569 rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
michael@0 570 &verifyError, getter_AddRefs(mPrincipal));
michael@0 571 if (NS_FAILED(rv)) return rv;
michael@0 572 if (mPrincipal && verifyError == 0)
michael@0 573 mGlobalStatus = JAR_VALID_MANIFEST;
michael@0 574 else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
michael@0 575 mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
michael@0 576 else
michael@0 577 mGlobalStatus = JAR_INVALID_SIG;
michael@0 578
michael@0 579 //-- Parse the SF file. If the verification above failed, principal
michael@0 580 // is null, and ParseOneFile will mark the relevant entries as invalid.
michael@0 581 // if ParseOneFile fails, then it has no effect, and we can safely
michael@0 582 // continue to the next SF file, or return.
michael@0 583 ParseOneFile(manifestBuffer, JAR_SF);
michael@0 584 mParsedManifest = true;
michael@0 585
michael@0 586 return NS_OK;
michael@0 587 }
michael@0 588
michael@0 589 nsresult
michael@0 590 nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
michael@0 591 {
michael@0 592 //-- Check file header
michael@0 593 const char* nextLineStart = filebuf;
michael@0 594 nsAutoCString curLine;
michael@0 595 int32_t linelen;
michael@0 596 linelen = ReadLine(&nextLineStart);
michael@0 597 curLine.Assign(filebuf, linelen);
michael@0 598
michael@0 599 if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
michael@0 600 ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) )
michael@0 601 return NS_ERROR_FILE_CORRUPTED;
michael@0 602
michael@0 603 //-- Skip header section
michael@0 604 do {
michael@0 605 linelen = ReadLine(&nextLineStart);
michael@0 606 } while (linelen > 0);
michael@0 607
michael@0 608 //-- Set up parsing variables
michael@0 609 const char* curPos;
michael@0 610 const char* sectionStart = nextLineStart;
michael@0 611
michael@0 612 nsJARManifestItem* curItemMF = nullptr;
michael@0 613 bool foundName = false;
michael@0 614 if (aFileType == JAR_MF) {
michael@0 615 curItemMF = new nsJARManifestItem();
michael@0 616 }
michael@0 617
michael@0 618 nsAutoCString curItemName;
michael@0 619 nsAutoCString storedSectionDigest;
michael@0 620
michael@0 621 for(;;)
michael@0 622 {
michael@0 623 curPos = nextLineStart;
michael@0 624 linelen = ReadLine(&nextLineStart);
michael@0 625 curLine.Assign(curPos, linelen);
michael@0 626 if (linelen == 0)
michael@0 627 // end of section (blank line or end-of-file)
michael@0 628 {
michael@0 629 if (aFileType == JAR_MF)
michael@0 630 {
michael@0 631 mTotalItemsInManifest++;
michael@0 632 if (curItemMF->mType != JAR_INVALID)
michael@0 633 {
michael@0 634 //-- Did this section have a name: line?
michael@0 635 if(!foundName)
michael@0 636 curItemMF->mType = JAR_INVALID;
michael@0 637 else
michael@0 638 {
michael@0 639 //-- If it's an internal item, it must correspond
michael@0 640 // to a valid jar entry
michael@0 641 if (curItemMF->mType == JAR_INTERNAL)
michael@0 642 {
michael@0 643 bool exists;
michael@0 644 nsresult rv = HasEntry(curItemName, &exists);
michael@0 645 if (NS_FAILED(rv) || !exists)
michael@0 646 curItemMF->mType = JAR_INVALID;
michael@0 647 }
michael@0 648 //-- Check for duplicates
michael@0 649 if (mManifestData.Contains(curItemName)) {
michael@0 650 curItemMF->mType = JAR_INVALID;
michael@0 651 }
michael@0 652 }
michael@0 653 }
michael@0 654
michael@0 655 if (curItemMF->mType == JAR_INVALID)
michael@0 656 delete curItemMF;
michael@0 657 else //-- calculate section digest
michael@0 658 {
michael@0 659 uint32_t sectionLength = curPos - sectionStart;
michael@0 660 CalculateDigest(sectionStart, sectionLength,
michael@0 661 curItemMF->calculatedSectionDigest);
michael@0 662 //-- Save item in the hashtable
michael@0 663 mManifestData.Put(curItemName, curItemMF);
michael@0 664 }
michael@0 665 if (nextLineStart == nullptr) // end-of-file
michael@0 666 break;
michael@0 667
michael@0 668 sectionStart = nextLineStart;
michael@0 669 curItemMF = new nsJARManifestItem();
michael@0 670 } // (aFileType == JAR_MF)
michael@0 671 else
michael@0 672 //-- file type is SF, compare digest with calculated
michael@0 673 // section digests from MF file.
michael@0 674 {
michael@0 675 if (foundName)
michael@0 676 {
michael@0 677 nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
michael@0 678 if(curItemSF)
michael@0 679 {
michael@0 680 NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
michael@0 681 "SECURITY ERROR: nsJARManifestItem not correctly initialized");
michael@0 682 curItemSF->status = mGlobalStatus;
michael@0 683 if (curItemSF->status == JAR_VALID_MANIFEST)
michael@0 684 { // Compare digests
michael@0 685 if (storedSectionDigest.IsEmpty())
michael@0 686 curItemSF->status = JAR_NOT_SIGNED;
michael@0 687 else
michael@0 688 {
michael@0 689 if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
michael@0 690 curItemSF->status = JAR_INVALID_MANIFEST;
michael@0 691 curItemSF->calculatedSectionDigest.Truncate();
michael@0 692 storedSectionDigest.Truncate();
michael@0 693 }
michael@0 694 } // (aPrincipal != nullptr)
michael@0 695 } // if(curItemSF)
michael@0 696 } // if(foundName)
michael@0 697
michael@0 698 if(nextLineStart == nullptr) // end-of-file
michael@0 699 break;
michael@0 700 } // aFileType == JAR_SF
michael@0 701 foundName = false;
michael@0 702 continue;
michael@0 703 } // if(linelen == 0)
michael@0 704
michael@0 705 //-- Look for continuations (beginning with a space) on subsequent lines
michael@0 706 // and append them to the current line.
michael@0 707 while(*nextLineStart == ' ')
michael@0 708 {
michael@0 709 curPos = nextLineStart;
michael@0 710 int32_t continuationLen = ReadLine(&nextLineStart) - 1;
michael@0 711 nsAutoCString continuation(curPos+1, continuationLen);
michael@0 712 curLine += continuation;
michael@0 713 linelen += continuationLen;
michael@0 714 }
michael@0 715
michael@0 716 //-- Find colon in current line, this separates name from value
michael@0 717 int32_t colonPos = curLine.FindChar(':');
michael@0 718 if (colonPos == -1) // No colon on line, ignore line
michael@0 719 continue;
michael@0 720 //-- Break down the line
michael@0 721 nsAutoCString lineName;
michael@0 722 curLine.Left(lineName, colonPos);
michael@0 723 nsAutoCString lineData;
michael@0 724 curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
michael@0 725
michael@0 726 //-- Lines to look for:
michael@0 727 // (1) Digest:
michael@0 728 if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
michael@0 729 //-- This is a digest line, save the data in the appropriate place
michael@0 730 {
michael@0 731 if(aFileType == JAR_MF)
michael@0 732 curItemMF->storedEntryDigest = lineData;
michael@0 733 else
michael@0 734 storedSectionDigest = lineData;
michael@0 735 continue;
michael@0 736 }
michael@0 737
michael@0 738 // (2) Name: associates this manifest section with a file in the jar.
michael@0 739 if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
michael@0 740 {
michael@0 741 curItemName = lineData;
michael@0 742 foundName = true;
michael@0 743 continue;
michael@0 744 }
michael@0 745
michael@0 746 // (3) Magic: this may be an inline Javascript.
michael@0 747 // We can't do any other kind of magic.
michael@0 748 if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
michael@0 749 {
michael@0 750 if (lineData.LowerCaseEqualsLiteral("javascript"))
michael@0 751 curItemMF->mType = JAR_EXTERNAL;
michael@0 752 else
michael@0 753 curItemMF->mType = JAR_INVALID;
michael@0 754 continue;
michael@0 755 }
michael@0 756
michael@0 757 } // for (;;)
michael@0 758 return NS_OK;
michael@0 759 } //ParseOneFile()
michael@0 760
michael@0 761 nsresult
michael@0 762 nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
michael@0 763 uint32_t aLen)
michael@0 764 {
michael@0 765 if (aManItem->status == JAR_VALID_MANIFEST)
michael@0 766 {
michael@0 767 if (aManItem->storedEntryDigest.IsEmpty())
michael@0 768 // No entry digests in manifest file. Entry is unsigned.
michael@0 769 aManItem->status = JAR_NOT_SIGNED;
michael@0 770 else
michael@0 771 { //-- Calculate and compare digests
michael@0 772 nsCString calculatedEntryDigest;
michael@0 773 nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
michael@0 774 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
michael@0 775 if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
michael@0 776 aManItem->status = JAR_INVALID_ENTRY;
michael@0 777 aManItem->storedEntryDigest.Truncate();
michael@0 778 }
michael@0 779 }
michael@0 780 aManItem->entryVerified = true;
michael@0 781 return NS_OK;
michael@0 782 }
michael@0 783
michael@0 784 void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
michael@0 785 {
michael@0 786 //-- Generate error message
michael@0 787 nsAutoString message;
michael@0 788 message.AssignLiteral("Signature Verification Error: the signature on ");
michael@0 789 if (!aFilename.IsEmpty())
michael@0 790 AppendASCIItoUTF16(aFilename, message);
michael@0 791 else
michael@0 792 message.AppendLiteral("this .jar archive");
michael@0 793 message.AppendLiteral(" is invalid because ");
michael@0 794 switch(errorCode)
michael@0 795 {
michael@0 796 case JAR_NOT_SIGNED:
michael@0 797 message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
michael@0 798 break;
michael@0 799 case JAR_INVALID_SIG:
michael@0 800 message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
michael@0 801 break;
michael@0 802 case JAR_INVALID_UNKNOWN_CA:
michael@0 803 message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
michael@0 804 break;
michael@0 805 case JAR_INVALID_MANIFEST:
michael@0 806 message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
michael@0 807 break;
michael@0 808 case JAR_INVALID_ENTRY:
michael@0 809 message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
michael@0 810 break;
michael@0 811 case JAR_NO_MANIFEST:
michael@0 812 message.AppendLiteral("the archive did not contain a manifest.");
michael@0 813 break;
michael@0 814 default:
michael@0 815 message.AppendLiteral("of an unknown problem.");
michael@0 816 }
michael@0 817
michael@0 818 // Report error in JS console
michael@0 819 nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
michael@0 820 if (console)
michael@0 821 {
michael@0 822 console->LogStringMessage(message.get());
michael@0 823 }
michael@0 824 #ifdef DEBUG
michael@0 825 char* messageCstr = ToNewCString(message);
michael@0 826 if (!messageCstr) return;
michael@0 827 fprintf(stderr, "%s\n", messageCstr);
michael@0 828 nsMemory::Free(messageCstr);
michael@0 829 #endif
michael@0 830 }
michael@0 831
michael@0 832
michael@0 833 nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
michael@0 834 nsCString& digest)
michael@0 835 {
michael@0 836 nsresult rv;
michael@0 837
michael@0 838 nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
michael@0 839 if (NS_FAILED(rv)) return rv;
michael@0 840
michael@0 841 rv = hasher->Init(nsICryptoHash::SHA1);
michael@0 842 if (NS_FAILED(rv)) return rv;
michael@0 843
michael@0 844 rv = hasher->Update((const uint8_t*) aInBuf, aLen);
michael@0 845 if (NS_FAILED(rv)) return rv;
michael@0 846
michael@0 847 return hasher->Finish(true, digest);
michael@0 848 }
michael@0 849
michael@0 850 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
michael@0 851
michael@0 852 //----------------------------------------------
michael@0 853 // nsJAREnumerator::HasMore
michael@0 854 //----------------------------------------------
michael@0 855 NS_IMETHODIMP
michael@0 856 nsJAREnumerator::HasMore(bool* aResult)
michael@0 857 {
michael@0 858 // try to get the next element
michael@0 859 if (!mName) {
michael@0 860 NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
michael@0 861 nsresult rv = mFind->FindNext( &mName, &mNameLen );
michael@0 862 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
michael@0 863 *aResult = false; // No more matches available
michael@0 864 return NS_OK;
michael@0 865 }
michael@0 866 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
michael@0 867 }
michael@0 868
michael@0 869 *aResult = true;
michael@0 870 return NS_OK;
michael@0 871 }
michael@0 872
michael@0 873 //----------------------------------------------
michael@0 874 // nsJAREnumerator::GetNext
michael@0 875 //----------------------------------------------
michael@0 876 NS_IMETHODIMP
michael@0 877 nsJAREnumerator::GetNext(nsACString& aResult)
michael@0 878 {
michael@0 879 // check if the current item is "stale"
michael@0 880 if (!mName) {
michael@0 881 bool bMore;
michael@0 882 nsresult rv = HasMore(&bMore);
michael@0 883 if (NS_FAILED(rv) || !bMore)
michael@0 884 return NS_ERROR_FAILURE; // no error translation
michael@0 885 }
michael@0 886 aResult.Assign(mName, mNameLen);
michael@0 887 mName = 0; // we just gave this one away
michael@0 888 return NS_OK;
michael@0 889 }
michael@0 890
michael@0 891
michael@0 892 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
michael@0 893
michael@0 894 nsJARItem::nsJARItem(nsZipItem* aZipItem)
michael@0 895 : mSize(aZipItem->Size()),
michael@0 896 mRealsize(aZipItem->RealSize()),
michael@0 897 mCrc32(aZipItem->CRC32()),
michael@0 898 mLastModTime(aZipItem->LastModTime()),
michael@0 899 mCompression(aZipItem->Compression()),
michael@0 900 mPermissions(aZipItem->Mode()),
michael@0 901 mIsDirectory(aZipItem->IsDirectory()),
michael@0 902 mIsSynthetic(aZipItem->isSynthetic)
michael@0 903 {
michael@0 904 }
michael@0 905
michael@0 906 //------------------------------------------
michael@0 907 // nsJARItem::GetCompression
michael@0 908 //------------------------------------------
michael@0 909 NS_IMETHODIMP
michael@0 910 nsJARItem::GetCompression(uint16_t *aCompression)
michael@0 911 {
michael@0 912 NS_ENSURE_ARG_POINTER(aCompression);
michael@0 913
michael@0 914 *aCompression = mCompression;
michael@0 915 return NS_OK;
michael@0 916 }
michael@0 917
michael@0 918 //------------------------------------------
michael@0 919 // nsJARItem::GetSize
michael@0 920 //------------------------------------------
michael@0 921 NS_IMETHODIMP
michael@0 922 nsJARItem::GetSize(uint32_t *aSize)
michael@0 923 {
michael@0 924 NS_ENSURE_ARG_POINTER(aSize);
michael@0 925
michael@0 926 *aSize = mSize;
michael@0 927 return NS_OK;
michael@0 928 }
michael@0 929
michael@0 930 //------------------------------------------
michael@0 931 // nsJARItem::GetRealSize
michael@0 932 //------------------------------------------
michael@0 933 NS_IMETHODIMP
michael@0 934 nsJARItem::GetRealSize(uint32_t *aRealsize)
michael@0 935 {
michael@0 936 NS_ENSURE_ARG_POINTER(aRealsize);
michael@0 937
michael@0 938 *aRealsize = mRealsize;
michael@0 939 return NS_OK;
michael@0 940 }
michael@0 941
michael@0 942 //------------------------------------------
michael@0 943 // nsJARItem::GetCrc32
michael@0 944 //------------------------------------------
michael@0 945 NS_IMETHODIMP
michael@0 946 nsJARItem::GetCRC32(uint32_t *aCrc32)
michael@0 947 {
michael@0 948 NS_ENSURE_ARG_POINTER(aCrc32);
michael@0 949
michael@0 950 *aCrc32 = mCrc32;
michael@0 951 return NS_OK;
michael@0 952 }
michael@0 953
michael@0 954 //------------------------------------------
michael@0 955 // nsJARItem::GetIsDirectory
michael@0 956 //------------------------------------------
michael@0 957 NS_IMETHODIMP
michael@0 958 nsJARItem::GetIsDirectory(bool *aIsDirectory)
michael@0 959 {
michael@0 960 NS_ENSURE_ARG_POINTER(aIsDirectory);
michael@0 961
michael@0 962 *aIsDirectory = mIsDirectory;
michael@0 963 return NS_OK;
michael@0 964 }
michael@0 965
michael@0 966 //------------------------------------------
michael@0 967 // nsJARItem::GetIsSynthetic
michael@0 968 //------------------------------------------
michael@0 969 NS_IMETHODIMP
michael@0 970 nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
michael@0 971 {
michael@0 972 NS_ENSURE_ARG_POINTER(aIsSynthetic);
michael@0 973
michael@0 974 *aIsSynthetic = mIsSynthetic;
michael@0 975 return NS_OK;
michael@0 976 }
michael@0 977
michael@0 978 //------------------------------------------
michael@0 979 // nsJARItem::GetLastModifiedTime
michael@0 980 //------------------------------------------
michael@0 981 NS_IMETHODIMP
michael@0 982 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
michael@0 983 {
michael@0 984 NS_ENSURE_ARG_POINTER(aLastModTime);
michael@0 985
michael@0 986 *aLastModTime = mLastModTime;
michael@0 987 return NS_OK;
michael@0 988 }
michael@0 989
michael@0 990 //------------------------------------------
michael@0 991 // nsJARItem::GetPermissions
michael@0 992 //------------------------------------------
michael@0 993 NS_IMETHODIMP
michael@0 994 nsJARItem::GetPermissions(uint32_t* aPermissions)
michael@0 995 {
michael@0 996 NS_ENSURE_ARG_POINTER(aPermissions);
michael@0 997
michael@0 998 *aPermissions = mPermissions;
michael@0 999 return NS_OK;
michael@0 1000 }
michael@0 1001
michael@0 1002 ////////////////////////////////////////////////////////////////////////////////
michael@0 1003 // nsIZipReaderCache
michael@0 1004
michael@0 1005 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
michael@0 1006
michael@0 1007 nsZipReaderCache::nsZipReaderCache()
michael@0 1008 : mLock("nsZipReaderCache.mLock")
michael@0 1009 , mZips(16)
michael@0 1010 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1011 ,
michael@0 1012 mZipCacheLookups(0),
michael@0 1013 mZipCacheHits(0),
michael@0 1014 mZipCacheFlushes(0),
michael@0 1015 mZipSyncMisses(0)
michael@0 1016 #endif
michael@0 1017 {
michael@0 1018 }
michael@0 1019
michael@0 1020 NS_IMETHODIMP
michael@0 1021 nsZipReaderCache::Init(uint32_t cacheSize)
michael@0 1022 {
michael@0 1023 mCacheSize = cacheSize;
michael@0 1024
michael@0 1025 // Register as a memory pressure observer
michael@0 1026 nsCOMPtr<nsIObserverService> os =
michael@0 1027 do_GetService("@mozilla.org/observer-service;1");
michael@0 1028 if (os)
michael@0 1029 {
michael@0 1030 os->AddObserver(this, "memory-pressure", true);
michael@0 1031 os->AddObserver(this, "chrome-flush-caches", true);
michael@0 1032 os->AddObserver(this, "flush-cache-entry", true);
michael@0 1033 }
michael@0 1034 // ignore failure of the observer registration.
michael@0 1035
michael@0 1036 return NS_OK;
michael@0 1037 }
michael@0 1038
michael@0 1039 static PLDHashOperator
michael@0 1040 DropZipReaderCache(const nsACString &aKey, nsJAR* aZip, void*)
michael@0 1041 {
michael@0 1042 aZip->SetZipReaderCache(nullptr);
michael@0 1043 return PL_DHASH_NEXT;
michael@0 1044 }
michael@0 1045
michael@0 1046 nsZipReaderCache::~nsZipReaderCache()
michael@0 1047 {
michael@0 1048 mZips.EnumerateRead(DropZipReaderCache, nullptr);
michael@0 1049
michael@0 1050 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1051 printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
michael@0 1052 mCacheSize, mZipCacheHits, mZipCacheLookups,
michael@0 1053 (float)mZipCacheHits / mZipCacheLookups,
michael@0 1054 mZipCacheFlushes, mZipSyncMisses);
michael@0 1055 #endif
michael@0 1056 }
michael@0 1057
michael@0 1058 NS_IMETHODIMP
michael@0 1059 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
michael@0 1060 {
michael@0 1061 NS_ENSURE_ARG_POINTER(zipFile);
michael@0 1062 nsresult rv;
michael@0 1063 nsCOMPtr<nsIZipReader> antiLockZipGrip;
michael@0 1064 MutexAutoLock lock(mLock);
michael@0 1065
michael@0 1066 nsAutoCString uri;
michael@0 1067 rv = zipFile->GetNativePath(uri);
michael@0 1068 if (NS_FAILED(rv))
michael@0 1069 return rv;
michael@0 1070
michael@0 1071 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
michael@0 1072
michael@0 1073 *aResult = mZips.Contains(uri);
michael@0 1074 return NS_OK;
michael@0 1075 }
michael@0 1076
michael@0 1077 NS_IMETHODIMP
michael@0 1078 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
michael@0 1079 {
michael@0 1080 NS_ENSURE_ARG_POINTER(zipFile);
michael@0 1081 nsresult rv;
michael@0 1082 nsCOMPtr<nsIZipReader> antiLockZipGrip;
michael@0 1083 MutexAutoLock lock(mLock);
michael@0 1084
michael@0 1085 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1086 mZipCacheLookups++;
michael@0 1087 #endif
michael@0 1088
michael@0 1089 nsAutoCString uri;
michael@0 1090 rv = zipFile->GetNativePath(uri);
michael@0 1091 if (NS_FAILED(rv)) return rv;
michael@0 1092
michael@0 1093 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
michael@0 1094
michael@0 1095 nsRefPtr<nsJAR> zip;
michael@0 1096 mZips.Get(uri, getter_AddRefs(zip));
michael@0 1097 if (zip) {
michael@0 1098 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1099 mZipCacheHits++;
michael@0 1100 #endif
michael@0 1101 zip->ClearReleaseTime();
michael@0 1102 } else {
michael@0 1103 zip = new nsJAR();
michael@0 1104 zip->SetZipReaderCache(this);
michael@0 1105
michael@0 1106 rv = zip->Open(zipFile);
michael@0 1107 if (NS_FAILED(rv)) {
michael@0 1108 return rv;
michael@0 1109 }
michael@0 1110
michael@0 1111 MOZ_ASSERT(!mZips.Contains(uri));
michael@0 1112 mZips.Put(uri, zip);
michael@0 1113 }
michael@0 1114 zip.forget(result);
michael@0 1115 return rv;
michael@0 1116 }
michael@0 1117
michael@0 1118 NS_IMETHODIMP
michael@0 1119 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
michael@0 1120 nsIZipReader* *result)
michael@0 1121 {
michael@0 1122 NS_ENSURE_ARG_POINTER(zipFile);
michael@0 1123
michael@0 1124 nsCOMPtr<nsIZipReader> outerZipReader;
michael@0 1125 nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
michael@0 1126 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1127
michael@0 1128 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1129 mZipCacheLookups++;
michael@0 1130 #endif
michael@0 1131
michael@0 1132 nsAutoCString uri;
michael@0 1133 rv = zipFile->GetNativePath(uri);
michael@0 1134 if (NS_FAILED(rv)) return rv;
michael@0 1135
michael@0 1136 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
michael@0 1137 uri.AppendLiteral("!/");
michael@0 1138 uri.Append(entry);
michael@0 1139
michael@0 1140 nsRefPtr<nsJAR> zip;
michael@0 1141 mZips.Get(uri, getter_AddRefs(zip));
michael@0 1142 if (zip) {
michael@0 1143 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1144 mZipCacheHits++;
michael@0 1145 #endif
michael@0 1146 zip->ClearReleaseTime();
michael@0 1147 } else {
michael@0 1148 zip = new nsJAR();
michael@0 1149 zip->SetZipReaderCache(this);
michael@0 1150
michael@0 1151 rv = zip->OpenInner(outerZipReader, entry);
michael@0 1152 if (NS_FAILED(rv)) {
michael@0 1153 return rv;
michael@0 1154 }
michael@0 1155
michael@0 1156 MOZ_ASSERT(!mZips.Contains(uri));
michael@0 1157 mZips.Put(uri, zip);
michael@0 1158 }
michael@0 1159 zip.forget(result);
michael@0 1160 return rv;
michael@0 1161 }
michael@0 1162
michael@0 1163 static PLDHashOperator
michael@0 1164 FindOldestZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
michael@0 1165 {
michael@0 1166 nsJAR** oldestPtr = static_cast<nsJAR**>(aClosure);
michael@0 1167 nsJAR* oldest = *oldestPtr;
michael@0 1168 nsJAR* current = aZip;
michael@0 1169 PRIntervalTime currentReleaseTime = current->GetReleaseTime();
michael@0 1170 if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
michael@0 1171 if (oldest == nullptr ||
michael@0 1172 currentReleaseTime < oldest->GetReleaseTime()) {
michael@0 1173 *oldestPtr = current;
michael@0 1174 }
michael@0 1175 }
michael@0 1176 return PL_DHASH_NEXT;
michael@0 1177 }
michael@0 1178
michael@0 1179 struct ZipFindData {nsJAR* zip; bool found;};
michael@0 1180
michael@0 1181 static PLDHashOperator
michael@0 1182 FindZip(const nsACString &aKey, nsJAR* aZip, void* aClosure)
michael@0 1183 {
michael@0 1184 ZipFindData* find_data = static_cast<ZipFindData*>(aClosure);
michael@0 1185
michael@0 1186 if (find_data->zip == aZip) {
michael@0 1187 find_data->found = true;
michael@0 1188 return PL_DHASH_STOP;
michael@0 1189 }
michael@0 1190 return PL_DHASH_NEXT;
michael@0 1191 }
michael@0 1192
michael@0 1193 nsresult
michael@0 1194 nsZipReaderCache::ReleaseZip(nsJAR* zip)
michael@0 1195 {
michael@0 1196 nsresult rv;
michael@0 1197 MutexAutoLock lock(mLock);
michael@0 1198
michael@0 1199 // It is possible that two thread compete for this zip. The dangerous
michael@0 1200 // case is where one thread Releases the zip and discovers that the ref
michael@0 1201 // count has gone to one. Before it can call this ReleaseZip method
michael@0 1202 // another thread calls our GetZip method. The ref count goes to two. That
michael@0 1203 // second thread then Releases the zip and the ref count goes to one. It
michael@0 1204 // then tries to enter this ReleaseZip method and blocks while the first
michael@0 1205 // thread is still here. The first thread continues and remove the zip from
michael@0 1206 // the cache and calls its Release method sending the ref count to 0 and
michael@0 1207 // deleting the zip. However, the second thread is still blocked at the
michael@0 1208 // start of ReleaseZip, but the 'zip' param now hold a reference to a
michael@0 1209 // deleted zip!
michael@0 1210 //
michael@0 1211 // So, we are going to try safeguarding here by searching our hashtable while
michael@0 1212 // locked here for the zip. We return fast if it is not found.
michael@0 1213
michael@0 1214 ZipFindData find_data = {zip, false};
michael@0 1215 mZips.EnumerateRead(FindZip, &find_data);
michael@0 1216 if (!find_data.found) {
michael@0 1217 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1218 mZipSyncMisses++;
michael@0 1219 #endif
michael@0 1220 return NS_OK;
michael@0 1221 }
michael@0 1222
michael@0 1223 zip->SetReleaseTime();
michael@0 1224
michael@0 1225 if (mZips.Count() <= mCacheSize)
michael@0 1226 return NS_OK;
michael@0 1227
michael@0 1228 nsJAR* oldest = nullptr;
michael@0 1229 mZips.EnumerateRead(FindOldestZip, &oldest);
michael@0 1230
michael@0 1231 // Because of the craziness above it is possible that there is no zip that
michael@0 1232 // needs removing.
michael@0 1233 if (!oldest)
michael@0 1234 return NS_OK;
michael@0 1235
michael@0 1236 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1237 mZipCacheFlushes++;
michael@0 1238 #endif
michael@0 1239
michael@0 1240 // remove from hashtable
michael@0 1241 nsAutoCString uri;
michael@0 1242 rv = oldest->GetJarPath(uri);
michael@0 1243 if (NS_FAILED(rv))
michael@0 1244 return rv;
michael@0 1245
michael@0 1246 if (oldest->mOuterZipEntry.IsEmpty()) {
michael@0 1247 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
michael@0 1248 } else {
michael@0 1249 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
michael@0 1250 uri.AppendLiteral("!/");
michael@0 1251 uri.Append(oldest->mOuterZipEntry);
michael@0 1252 }
michael@0 1253
michael@0 1254 // Retrieving and removing the JAR must be done without an extra AddRef
michael@0 1255 // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
michael@0 1256 // an extra time and trigger a deadlock.
michael@0 1257 nsRefPtr<nsJAR> removed;
michael@0 1258 mZips.Remove(uri, getter_AddRefs(removed));
michael@0 1259 NS_ASSERTION(removed, "botched");
michael@0 1260 NS_ASSERTION(oldest == removed, "removed wrong entry");
michael@0 1261
michael@0 1262 if (removed)
michael@0 1263 removed->SetZipReaderCache(nullptr);
michael@0 1264
michael@0 1265 return NS_OK;
michael@0 1266 }
michael@0 1267
michael@0 1268 static PLDHashOperator
michael@0 1269 FindFlushableZip(const nsACString &aKey, nsRefPtr<nsJAR>& aCurrent, void*)
michael@0 1270 {
michael@0 1271 if (aCurrent->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
michael@0 1272 aCurrent->SetZipReaderCache(nullptr);
michael@0 1273 return PL_DHASH_REMOVE;
michael@0 1274 }
michael@0 1275 return PL_DHASH_NEXT;
michael@0 1276 }
michael@0 1277
michael@0 1278 NS_IMETHODIMP
michael@0 1279 nsZipReaderCache::Observe(nsISupports *aSubject,
michael@0 1280 const char *aTopic,
michael@0 1281 const char16_t *aSomeData)
michael@0 1282 {
michael@0 1283 if (strcmp(aTopic, "memory-pressure") == 0) {
michael@0 1284 MutexAutoLock lock(mLock);
michael@0 1285 mZips.Enumerate(FindFlushableZip, nullptr);
michael@0 1286 }
michael@0 1287 else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
michael@0 1288 mZips.EnumerateRead(DropZipReaderCache, nullptr);
michael@0 1289 mZips.Clear();
michael@0 1290 }
michael@0 1291 else if (strcmp(aTopic, "flush-cache-entry") == 0) {
michael@0 1292 nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
michael@0 1293 if (!file)
michael@0 1294 return NS_OK;
michael@0 1295
michael@0 1296 nsAutoCString uri;
michael@0 1297 if (NS_FAILED(file->GetNativePath(uri)))
michael@0 1298 return NS_OK;
michael@0 1299
michael@0 1300 uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
michael@0 1301
michael@0 1302 MutexAutoLock lock(mLock);
michael@0 1303
michael@0 1304 nsRefPtr<nsJAR> zip;
michael@0 1305 mZips.Get(uri, getter_AddRefs(zip));
michael@0 1306 if (!zip)
michael@0 1307 return NS_OK;
michael@0 1308
michael@0 1309 #ifdef ZIP_CACHE_HIT_RATE
michael@0 1310 mZipCacheFlushes++;
michael@0 1311 #endif
michael@0 1312
michael@0 1313 zip->SetZipReaderCache(nullptr);
michael@0 1314
michael@0 1315 mZips.Remove(uri);
michael@0 1316 }
michael@0 1317 return NS_OK;
michael@0 1318 }
michael@0 1319
michael@0 1320 ////////////////////////////////////////////////////////////////////////////////

mercurial