michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include michael@0: #include "nsJARInputStream.h" michael@0: #include "nsJAR.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsICryptoHash.h" michael@0: #include "prprf.h" michael@0: #include "mozilla/Omnijar.h" michael@0: michael@0: #ifdef XP_UNIX michael@0: #include michael@0: #elif defined (XP_WIN) michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: //---------------------------------------------- michael@0: // nsJARManifestItem declaration michael@0: //---------------------------------------------- michael@0: /* michael@0: * nsJARManifestItem contains meta-information pertaining michael@0: * to an individual JAR entry, taken from the michael@0: * META-INF/MANIFEST.MF and META-INF/ *.SF files. michael@0: * This is security-critical information, defined here so it is not michael@0: * accessible from anywhere else. michael@0: */ michael@0: typedef enum michael@0: { michael@0: JAR_INVALID = 1, michael@0: JAR_INTERNAL = 2, michael@0: JAR_EXTERNAL = 3 michael@0: } JARManifestItemType; michael@0: michael@0: class nsJARManifestItem michael@0: { michael@0: public: michael@0: JARManifestItemType mType; michael@0: michael@0: // True if the second step of verification (VerifyEntry) michael@0: // has taken place: michael@0: bool entryVerified; michael@0: michael@0: // Not signed, valid, or failure code michael@0: int16_t status; michael@0: michael@0: // Internal storage of digests michael@0: nsCString calculatedSectionDigest; michael@0: nsCString storedEntryDigest; michael@0: michael@0: nsJARManifestItem(); michael@0: virtual ~nsJARManifestItem(); michael@0: }; michael@0: michael@0: //------------------------------------------------- michael@0: // nsJARManifestItem constructors and destructor michael@0: //------------------------------------------------- michael@0: nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL), michael@0: entryVerified(false), michael@0: status(JAR_NOT_SIGNED) michael@0: { michael@0: } michael@0: michael@0: nsJARManifestItem::~nsJARManifestItem() michael@0: { michael@0: } michael@0: michael@0: //---------------------------------------------- michael@0: // nsJAR constructor/destructor michael@0: //---------------------------------------------- michael@0: michael@0: // The following initialization makes a guess of 10 entries per jarfile. michael@0: nsJAR::nsJAR(): mZip(new nsZipArchive()), michael@0: mManifestData(10), michael@0: mParsedManifest(false), michael@0: mGlobalStatus(JAR_MANIFEST_NOT_PARSED), michael@0: mReleaseTime(PR_INTERVAL_NO_TIMEOUT), michael@0: mCache(nullptr), michael@0: mLock("nsJAR::mLock"), michael@0: mTotalItemsInManifest(0), michael@0: mOpened(false) michael@0: { michael@0: } michael@0: michael@0: nsJAR::~nsJAR() michael@0: { michael@0: Close(); michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader) michael@0: NS_IMPL_ADDREF(nsJAR) michael@0: michael@0: // Custom Release method works with nsZipReaderCache... michael@0: MozExternalRefCountType nsJAR::Release(void) michael@0: { michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "nsJAR"); michael@0: if (0 == count) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: /* enable this to find non-threadsafe destructors: */ michael@0: /* NS_ASSERT_OWNINGTHREAD(nsJAR); */ michael@0: delete this; michael@0: return 0; michael@0: } michael@0: else if (1 == count && mCache) { michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: mCache->ReleaseZip(this); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file"); michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: //---------------------------------------------- michael@0: // nsIZipReader implementation michael@0: //---------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::Open(nsIFile* zipFile) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(zipFile); michael@0: if (mOpened) return NS_ERROR_FAILURE; // Already open! michael@0: michael@0: mZipFile = zipFile; michael@0: mOuterZipEntry.Truncate(); michael@0: mOpened = true; michael@0: michael@0: // The omnijar is special, it is opened early on and closed late michael@0: // this avoids reopening it michael@0: nsRefPtr zip = mozilla::Omnijar::GetReader(zipFile); michael@0: if (zip) { michael@0: mZip = zip; michael@0: return NS_OK; michael@0: } michael@0: return mZip->OpenArchive(zipFile); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aZipReader); michael@0: if (mOpened) return NS_ERROR_FAILURE; // Already open! michael@0: michael@0: bool exist; michael@0: nsresult rv = aZipReader->HasEntry(aZipEntry, &exist); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND); michael@0: michael@0: rv = aZipReader->GetFile(getter_AddRefs(mZipFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mOpened = true; michael@0: michael@0: mOuterZipEntry.Assign(aZipEntry); michael@0: michael@0: nsRefPtr handle; michael@0: rv = nsZipHandle::Init(static_cast(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(), michael@0: getter_AddRefs(handle)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return mZip->OpenArchive(handle); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetFile(nsIFile* *result) michael@0: { michael@0: *result = mZipFile; michael@0: NS_IF_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::Close() michael@0: { michael@0: mOpened = false; michael@0: mParsedManifest = false; michael@0: mManifestData.Clear(); michael@0: mGlobalStatus = JAR_MANIFEST_NOT_PARSED; michael@0: mTotalItemsInManifest = 0; michael@0: michael@0: nsRefPtr greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); michael@0: nsRefPtr appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); michael@0: michael@0: if (mZip == greOmni || mZip == appOmni) { michael@0: mZip = new nsZipArchive(); michael@0: return NS_OK; michael@0: } michael@0: return mZip->CloseArchive(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::Test(const nsACString &aEntryName) michael@0: { michael@0: return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile) michael@0: { michael@0: // nsZipArchive and zlib are not thread safe michael@0: // we need to use a lock to prevent bug #51267 michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get()); michael@0: NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); michael@0: michael@0: // Remove existing file or directory so we set permissions correctly. michael@0: // If it's a directory that already exists and contains files, throw michael@0: // an exception and return. michael@0: michael@0: nsresult rv = outFile->Remove(false); michael@0: if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || michael@0: rv == NS_ERROR_FAILURE) michael@0: return rv; michael@0: michael@0: if (item->IsDirectory()) michael@0: { michael@0: rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode()); michael@0: //XXX Do this in nsZipArchive? It would be nice to keep extraction michael@0: //XXX code completely there, but that would require a way to get a michael@0: //XXX PRDir from outFile. michael@0: } michael@0: else michael@0: { michael@0: PRFileDesc* fd; michael@0: rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // ExtractFile also closes the fd handle and resolves the symlink if needed michael@0: nsAutoCString path; michael@0: rv = outFile->GetNativePath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mZip->ExtractFile(item, path.get(), fd); michael@0: } michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // nsIFile needs milliseconds, while prtime is in microseconds. michael@0: // non-fatal if this fails, ignore errors michael@0: outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result) michael@0: { michael@0: nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get()); michael@0: NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); michael@0: michael@0: nsJARItem* jarItem = new nsJARItem(zipItem); michael@0: michael@0: NS_ADDREF(*result = jarItem); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::HasEntry(const nsACString &aEntryName, bool *result) michael@0: { michael@0: *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: nsZipFind *find; michael@0: nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find); michael@0: michael@0: NS_ADDREF(*result = zipEnum); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result) michael@0: { michael@0: return GetInputStreamWithSpec(EmptyCString(), aFilename, result); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec, michael@0: const nsACString &aEntryName, nsIInputStream** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: michael@0: // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case! michael@0: nsZipItem *item = nullptr; michael@0: const char *entry = PromiseFlatCString(aEntryName).get(); michael@0: if (*entry) { michael@0: // First check if item exists in jar michael@0: item = mZip->GetItem(entry); michael@0: if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; michael@0: } michael@0: nsJARInputStream* jis = new nsJARInputStream(); michael@0: // addref now so we can call InitFile/InitDirectory() michael@0: NS_ADDREF(*result = jis); michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (!item || item->IsDirectory()) { michael@0: rv = jis->InitDirectory(this, aJarDirSpec, entry); michael@0: } else { michael@0: rv = jis->InitFile(this, item); michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(*result); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal) michael@0: { michael@0: //-- Parameter check michael@0: if (!aPrincipal) michael@0: return NS_ERROR_NULL_POINTER; michael@0: *aPrincipal = nullptr; michael@0: michael@0: // Don't check signatures in the omnijar - this is only michael@0: // interesting for extensions/XPIs. michael@0: nsRefPtr greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); michael@0: nsRefPtr appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); michael@0: michael@0: if (mZip == greOmni || mZip == appOmni) michael@0: return NS_OK; michael@0: michael@0: //-- Parse the manifest michael@0: nsresult rv = ParseManifest(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (mGlobalStatus == JAR_NO_MANIFEST) michael@0: return NS_OK; michael@0: michael@0: int16_t requestedStatus; michael@0: if (!aFilename.IsEmpty()) michael@0: { michael@0: //-- Find the item michael@0: nsJARManifestItem* manItem = mManifestData.Get(aFilename); michael@0: if (!manItem) michael@0: return NS_OK; michael@0: //-- Verify the item against the manifest michael@0: if (!manItem->entryVerified) michael@0: { michael@0: nsXPIDLCString entryData; michael@0: uint32_t entryDataLen; michael@0: rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = VerifyEntry(manItem, entryData, entryDataLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: requestedStatus = manItem->status; michael@0: } michael@0: else // User wants identity of signer w/o verifying any entries michael@0: requestedStatus = mGlobalStatus; michael@0: michael@0: if (requestedStatus != JAR_VALID_MANIFEST) michael@0: ReportError(aFilename, requestedStatus); michael@0: else // Valid signature michael@0: { michael@0: *aPrincipal = mPrincipal; michael@0: NS_IF_ADDREF(*aPrincipal); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJAR::GetManifestEntriesCount(uint32_t* count) michael@0: { michael@0: *count = mTotalItemsInManifest; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJAR::GetJarPath(nsACString& aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(mZipFile); michael@0: michael@0: return mZipFile->GetNativePath(aResult); michael@0: } michael@0: michael@0: //---------------------------------------------- michael@0: // nsJAR private implementation michael@0: //---------------------------------------------- michael@0: nsresult michael@0: nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen) michael@0: { michael@0: //-- Get a stream for reading the file michael@0: nsresult rv; michael@0: nsCOMPtr manifestStream; michael@0: rv = GetInputStream(aFilename, getter_AddRefs(manifestStream)); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; michael@0: michael@0: //-- Read the manifest file into memory michael@0: char* buf; michael@0: uint64_t len64; michael@0: rv = manifestStream->Available(&len64); michael@0: if (NS_FAILED(rv)) return rv; michael@0: NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695 michael@0: uint32_t len = (uint32_t)len64; michael@0: buf = (char*)malloc(len+1); michael@0: if (!buf) return NS_ERROR_OUT_OF_MEMORY; michael@0: uint32_t bytesRead; michael@0: rv = manifestStream->Read(buf, len, &bytesRead); michael@0: if (bytesRead != len) michael@0: rv = NS_ERROR_FILE_CORRUPTED; michael@0: if (NS_FAILED(rv)) { michael@0: free(buf); michael@0: return rv; michael@0: } michael@0: buf[len] = '\0'; //Null-terminate the buffer michael@0: *aBuf = buf; michael@0: if (aBufLen) michael@0: *aBufLen = len; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: nsJAR::ReadLine(const char** src) michael@0: { michael@0: if (!*src) { michael@0: return 0; michael@0: } michael@0: michael@0: //--Moves pointer to beginning of next line and returns line length michael@0: // not including CR/LF. michael@0: int32_t length; michael@0: char* eol = PL_strpbrk(*src, "\r\n"); michael@0: michael@0: if (eol == nullptr) // Probably reached end of file before newline michael@0: { michael@0: length = strlen(*src); michael@0: if (length == 0) // immediate end-of-file michael@0: *src = nullptr; michael@0: else // some data left on this line michael@0: *src += length; michael@0: } michael@0: else michael@0: { michael@0: length = eol - *src; michael@0: if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2 michael@0: *src = eol+2; michael@0: else // Either CR or LF, so skip 1 michael@0: *src = eol+1; michael@0: } michael@0: return length; michael@0: } michael@0: michael@0: //-- The following #defines are used by ParseManifest() michael@0: // and ParseOneFile(). The header strings are defined in the JAR specification. michael@0: #define JAR_MF 1 michael@0: #define JAR_SF 2 michael@0: #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" michael@0: #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" michael@0: #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0" michael@0: #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0" michael@0: michael@0: nsresult michael@0: nsJAR::ParseManifest() michael@0: { michael@0: //-- Verification Step 1 michael@0: if (mParsedManifest) michael@0: return NS_OK; michael@0: //-- (1)Manifest (MF) file michael@0: nsCOMPtr files; michael@0: nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files)); michael@0: if (!files) rv = NS_ERROR_FAILURE; michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: //-- Load the file into memory michael@0: bool more; michael@0: rv = files->HasMore(&more); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!more) michael@0: { michael@0: mGlobalStatus = JAR_NO_MANIFEST; michael@0: mParsedManifest = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString manifestFilename; michael@0: rv = files->GetNext(manifestFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Check if there is more than one manifest, if so then error! michael@0: rv = files->HasMore(&more); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (more) michael@0: { michael@0: mParsedManifest = true; michael@0: return NS_ERROR_FILE_CORRUPTED; // More than one MF file michael@0: } michael@0: michael@0: nsXPIDLCString manifestBuffer; michael@0: uint32_t manifestLen; michael@0: rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: //-- Parse it michael@0: rv = ParseOneFile(manifestBuffer, JAR_MF); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: //-- (2)Signature (SF) file michael@0: // If there are multiple signatures, we select one. michael@0: rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files)); michael@0: if (!files) rv = NS_ERROR_FAILURE; michael@0: if (NS_FAILED(rv)) return rv; michael@0: //-- Get an SF file michael@0: rv = files->HasMore(&more); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (!more) michael@0: { michael@0: mGlobalStatus = JAR_NO_MANIFEST; michael@0: mParsedManifest = true; michael@0: return NS_OK; michael@0: } michael@0: rv = files->GetNext(manifestFilename); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: //-- Get its corresponding signature file michael@0: nsAutoCString sigFilename(manifestFilename); michael@0: int32_t extension = sigFilename.RFindChar('.') + 1; michael@0: NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension."); michael@0: (void)sigFilename.Cut(extension, 2); michael@0: nsXPIDLCString sigBuffer; michael@0: uint32_t sigLen; michael@0: { michael@0: nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3); michael@0: rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3); michael@0: rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: mGlobalStatus = JAR_NO_MANIFEST; michael@0: mParsedManifest = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //-- Get the signature verifier service michael@0: nsCOMPtr verifier = michael@0: do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) // No signature verifier available michael@0: { michael@0: mGlobalStatus = JAR_NO_MANIFEST; michael@0: mParsedManifest = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //-- Verify that the signature file is a valid signature of the SF file michael@0: int32_t verifyError; michael@0: rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen, michael@0: &verifyError, getter_AddRefs(mPrincipal)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (mPrincipal && verifyError == 0) michael@0: mGlobalStatus = JAR_VALID_MANIFEST; michael@0: else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA) michael@0: mGlobalStatus = JAR_INVALID_UNKNOWN_CA; michael@0: else michael@0: mGlobalStatus = JAR_INVALID_SIG; michael@0: michael@0: //-- Parse the SF file. If the verification above failed, principal michael@0: // is null, and ParseOneFile will mark the relevant entries as invalid. michael@0: // if ParseOneFile fails, then it has no effect, and we can safely michael@0: // continue to the next SF file, or return. michael@0: ParseOneFile(manifestBuffer, JAR_SF); michael@0: mParsedManifest = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType) michael@0: { michael@0: //-- Check file header michael@0: const char* nextLineStart = filebuf; michael@0: nsAutoCString curLine; michael@0: int32_t linelen; michael@0: linelen = ReadLine(&nextLineStart); michael@0: curLine.Assign(filebuf, linelen); michael@0: michael@0: if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) || michael@0: ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: michael@0: //-- Skip header section michael@0: do { michael@0: linelen = ReadLine(&nextLineStart); michael@0: } while (linelen > 0); michael@0: michael@0: //-- Set up parsing variables michael@0: const char* curPos; michael@0: const char* sectionStart = nextLineStart; michael@0: michael@0: nsJARManifestItem* curItemMF = nullptr; michael@0: bool foundName = false; michael@0: if (aFileType == JAR_MF) { michael@0: curItemMF = new nsJARManifestItem(); michael@0: } michael@0: michael@0: nsAutoCString curItemName; michael@0: nsAutoCString storedSectionDigest; michael@0: michael@0: for(;;) michael@0: { michael@0: curPos = nextLineStart; michael@0: linelen = ReadLine(&nextLineStart); michael@0: curLine.Assign(curPos, linelen); michael@0: if (linelen == 0) michael@0: // end of section (blank line or end-of-file) michael@0: { michael@0: if (aFileType == JAR_MF) michael@0: { michael@0: mTotalItemsInManifest++; michael@0: if (curItemMF->mType != JAR_INVALID) michael@0: { michael@0: //-- Did this section have a name: line? michael@0: if(!foundName) michael@0: curItemMF->mType = JAR_INVALID; michael@0: else michael@0: { michael@0: //-- If it's an internal item, it must correspond michael@0: // to a valid jar entry michael@0: if (curItemMF->mType == JAR_INTERNAL) michael@0: { michael@0: bool exists; michael@0: nsresult rv = HasEntry(curItemName, &exists); michael@0: if (NS_FAILED(rv) || !exists) michael@0: curItemMF->mType = JAR_INVALID; michael@0: } michael@0: //-- Check for duplicates michael@0: if (mManifestData.Contains(curItemName)) { michael@0: curItemMF->mType = JAR_INVALID; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (curItemMF->mType == JAR_INVALID) michael@0: delete curItemMF; michael@0: else //-- calculate section digest michael@0: { michael@0: uint32_t sectionLength = curPos - sectionStart; michael@0: CalculateDigest(sectionStart, sectionLength, michael@0: curItemMF->calculatedSectionDigest); michael@0: //-- Save item in the hashtable michael@0: mManifestData.Put(curItemName, curItemMF); michael@0: } michael@0: if (nextLineStart == nullptr) // end-of-file michael@0: break; michael@0: michael@0: sectionStart = nextLineStart; michael@0: curItemMF = new nsJARManifestItem(); michael@0: } // (aFileType == JAR_MF) michael@0: else michael@0: //-- file type is SF, compare digest with calculated michael@0: // section digests from MF file. michael@0: { michael@0: if (foundName) michael@0: { michael@0: nsJARManifestItem* curItemSF = mManifestData.Get(curItemName); michael@0: if(curItemSF) michael@0: { michael@0: NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED, michael@0: "SECURITY ERROR: nsJARManifestItem not correctly initialized"); michael@0: curItemSF->status = mGlobalStatus; michael@0: if (curItemSF->status == JAR_VALID_MANIFEST) michael@0: { // Compare digests michael@0: if (storedSectionDigest.IsEmpty()) michael@0: curItemSF->status = JAR_NOT_SIGNED; michael@0: else michael@0: { michael@0: if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest)) michael@0: curItemSF->status = JAR_INVALID_MANIFEST; michael@0: curItemSF->calculatedSectionDigest.Truncate(); michael@0: storedSectionDigest.Truncate(); michael@0: } michael@0: } // (aPrincipal != nullptr) michael@0: } // if(curItemSF) michael@0: } // if(foundName) michael@0: michael@0: if(nextLineStart == nullptr) // end-of-file michael@0: break; michael@0: } // aFileType == JAR_SF michael@0: foundName = false; michael@0: continue; michael@0: } // if(linelen == 0) michael@0: michael@0: //-- Look for continuations (beginning with a space) on subsequent lines michael@0: // and append them to the current line. michael@0: while(*nextLineStart == ' ') michael@0: { michael@0: curPos = nextLineStart; michael@0: int32_t continuationLen = ReadLine(&nextLineStart) - 1; michael@0: nsAutoCString continuation(curPos+1, continuationLen); michael@0: curLine += continuation; michael@0: linelen += continuationLen; michael@0: } michael@0: michael@0: //-- Find colon in current line, this separates name from value michael@0: int32_t colonPos = curLine.FindChar(':'); michael@0: if (colonPos == -1) // No colon on line, ignore line michael@0: continue; michael@0: //-- Break down the line michael@0: nsAutoCString lineName; michael@0: curLine.Left(lineName, colonPos); michael@0: nsAutoCString lineData; michael@0: curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2)); michael@0: michael@0: //-- Lines to look for: michael@0: // (1) Digest: michael@0: if (lineName.LowerCaseEqualsLiteral("sha1-digest")) michael@0: //-- This is a digest line, save the data in the appropriate place michael@0: { michael@0: if(aFileType == JAR_MF) michael@0: curItemMF->storedEntryDigest = lineData; michael@0: else michael@0: storedSectionDigest = lineData; michael@0: continue; michael@0: } michael@0: michael@0: // (2) Name: associates this manifest section with a file in the jar. michael@0: if (!foundName && lineName.LowerCaseEqualsLiteral("name")) michael@0: { michael@0: curItemName = lineData; michael@0: foundName = true; michael@0: continue; michael@0: } michael@0: michael@0: // (3) Magic: this may be an inline Javascript. michael@0: // We can't do any other kind of magic. michael@0: if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic")) michael@0: { michael@0: if (lineData.LowerCaseEqualsLiteral("javascript")) michael@0: curItemMF->mType = JAR_EXTERNAL; michael@0: else michael@0: curItemMF->mType = JAR_INVALID; michael@0: continue; michael@0: } michael@0: michael@0: } // for (;;) michael@0: return NS_OK; michael@0: } //ParseOneFile() michael@0: michael@0: nsresult michael@0: nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData, michael@0: uint32_t aLen) michael@0: { michael@0: if (aManItem->status == JAR_VALID_MANIFEST) michael@0: { michael@0: if (aManItem->storedEntryDigest.IsEmpty()) michael@0: // No entry digests in manifest file. Entry is unsigned. michael@0: aManItem->status = JAR_NOT_SIGNED; michael@0: else michael@0: { //-- Calculate and compare digests michael@0: nsCString calculatedEntryDigest; michael@0: nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest)) michael@0: aManItem->status = JAR_INVALID_ENTRY; michael@0: aManItem->storedEntryDigest.Truncate(); michael@0: } michael@0: } michael@0: aManItem->entryVerified = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode) michael@0: { michael@0: //-- Generate error message michael@0: nsAutoString message; michael@0: message.AssignLiteral("Signature Verification Error: the signature on "); michael@0: if (!aFilename.IsEmpty()) michael@0: AppendASCIItoUTF16(aFilename, message); michael@0: else michael@0: message.AppendLiteral("this .jar archive"); michael@0: message.AppendLiteral(" is invalid because "); michael@0: switch(errorCode) michael@0: { michael@0: case JAR_NOT_SIGNED: michael@0: message.AppendLiteral("the archive did not contain a valid PKCS7 signature."); michael@0: break; michael@0: case JAR_INVALID_SIG: michael@0: message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF)."); michael@0: break; michael@0: case JAR_INVALID_UNKNOWN_CA: michael@0: message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer."); michael@0: break; michael@0: case JAR_INVALID_MANIFEST: michael@0: message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file."); michael@0: break; michael@0: case JAR_INVALID_ENTRY: michael@0: message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified."); michael@0: break; michael@0: case JAR_NO_MANIFEST: michael@0: message.AppendLiteral("the archive did not contain a manifest."); michael@0: break; michael@0: default: michael@0: message.AppendLiteral("of an unknown problem."); michael@0: } michael@0: michael@0: // Report error in JS console michael@0: nsCOMPtr console(do_GetService("@mozilla.org/consoleservice;1")); michael@0: if (console) michael@0: { michael@0: console->LogStringMessage(message.get()); michael@0: } michael@0: #ifdef DEBUG michael@0: char* messageCstr = ToNewCString(message); michael@0: if (!messageCstr) return; michael@0: fprintf(stderr, "%s\n", messageCstr); michael@0: nsMemory::Free(messageCstr); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen, michael@0: nsCString& digest) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = hasher->Init(nsICryptoHash::SHA1); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = hasher->Update((const uint8_t*) aInBuf, aLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return hasher->Finish(true, digest); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator) michael@0: michael@0: //---------------------------------------------- michael@0: // nsJAREnumerator::HasMore michael@0: //---------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsJAREnumerator::HasMore(bool* aResult) michael@0: { michael@0: // try to get the next element michael@0: if (!mName) { michael@0: NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind."); michael@0: nsresult rv = mFind->FindNext( &mName, &mNameLen ); michael@0: if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { michael@0: *aResult = false; // No more matches available michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation michael@0: } michael@0: michael@0: *aResult = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------- michael@0: // nsJAREnumerator::GetNext michael@0: //---------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsJAREnumerator::GetNext(nsACString& aResult) michael@0: { michael@0: // check if the current item is "stale" michael@0: if (!mName) { michael@0: bool bMore; michael@0: nsresult rv = HasMore(&bMore); michael@0: if (NS_FAILED(rv) || !bMore) michael@0: return NS_ERROR_FAILURE; // no error translation michael@0: } michael@0: aResult.Assign(mName, mNameLen); michael@0: mName = 0; // we just gave this one away michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry) michael@0: michael@0: nsJARItem::nsJARItem(nsZipItem* aZipItem) michael@0: : mSize(aZipItem->Size()), michael@0: mRealsize(aZipItem->RealSize()), michael@0: mCrc32(aZipItem->CRC32()), michael@0: mLastModTime(aZipItem->LastModTime()), michael@0: mCompression(aZipItem->Compression()), michael@0: mPermissions(aZipItem->Mode()), michael@0: mIsDirectory(aZipItem->IsDirectory()), michael@0: mIsSynthetic(aZipItem->isSynthetic) michael@0: { michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetCompression michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetCompression(uint16_t *aCompression) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCompression); michael@0: michael@0: *aCompression = mCompression; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetSize michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetSize(uint32_t *aSize) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aSize); michael@0: michael@0: *aSize = mSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetRealSize michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetRealSize(uint32_t *aRealsize) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRealsize); michael@0: michael@0: *aRealsize = mRealsize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetCrc32 michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetCRC32(uint32_t *aCrc32) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCrc32); michael@0: michael@0: *aCrc32 = mCrc32; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetIsDirectory michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetIsDirectory(bool *aIsDirectory) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsDirectory); michael@0: michael@0: *aIsDirectory = mIsDirectory; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetIsSynthetic michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetIsSynthetic(bool *aIsSynthetic) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsSynthetic); michael@0: michael@0: *aIsSynthetic = mIsSynthetic; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetLastModifiedTime michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aLastModTime); michael@0: michael@0: *aLastModTime = mLastModTime; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsJARItem::GetPermissions michael@0: //------------------------------------------ michael@0: NS_IMETHODIMP michael@0: nsJARItem::GetPermissions(uint32_t* aPermissions) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aPermissions); michael@0: michael@0: *aPermissions = mPermissions; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIZipReaderCache michael@0: michael@0: NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference) michael@0: michael@0: nsZipReaderCache::nsZipReaderCache() michael@0: : mLock("nsZipReaderCache.mLock") michael@0: , mZips(16) michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: , michael@0: mZipCacheLookups(0), michael@0: mZipCacheHits(0), michael@0: mZipCacheFlushes(0), michael@0: mZipSyncMisses(0) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsZipReaderCache::Init(uint32_t cacheSize) michael@0: { michael@0: mCacheSize = cacheSize; michael@0: michael@0: // Register as a memory pressure observer michael@0: nsCOMPtr os = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (os) michael@0: { michael@0: os->AddObserver(this, "memory-pressure", true); michael@0: os->AddObserver(this, "chrome-flush-caches", true); michael@0: os->AddObserver(this, "flush-cache-entry", true); michael@0: } michael@0: // ignore failure of the observer registration. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: DropZipReaderCache(const nsACString &aKey, nsJAR* aZip, void*) michael@0: { michael@0: aZip->SetZipReaderCache(nullptr); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsZipReaderCache::~nsZipReaderCache() michael@0: { michael@0: mZips.EnumerateRead(DropZipReaderCache, nullptr); michael@0: michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n", michael@0: mCacheSize, mZipCacheHits, mZipCacheLookups, michael@0: (float)mZipCacheHits / mZipCacheLookups, michael@0: mZipCacheFlushes, mZipSyncMisses); michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(zipFile); michael@0: nsresult rv; michael@0: nsCOMPtr antiLockZipGrip; michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsAutoCString uri; michael@0: rv = zipFile->GetNativePath(uri); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: uri.Insert(NS_LITERAL_CSTRING("file:"), 0); michael@0: michael@0: *aResult = mZips.Contains(uri); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(zipFile); michael@0: nsresult rv; michael@0: nsCOMPtr antiLockZipGrip; michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheLookups++; michael@0: #endif michael@0: michael@0: nsAutoCString uri; michael@0: rv = zipFile->GetNativePath(uri); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uri.Insert(NS_LITERAL_CSTRING("file:"), 0); michael@0: michael@0: nsRefPtr zip; michael@0: mZips.Get(uri, getter_AddRefs(zip)); michael@0: if (zip) { michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheHits++; michael@0: #endif michael@0: zip->ClearReleaseTime(); michael@0: } else { michael@0: zip = new nsJAR(); michael@0: zip->SetZipReaderCache(this); michael@0: michael@0: rv = zip->Open(zipFile); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mZips.Contains(uri)); michael@0: mZips.Put(uri, zip); michael@0: } michael@0: zip.forget(result); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry, michael@0: nsIZipReader* *result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(zipFile); michael@0: michael@0: nsCOMPtr outerZipReader; michael@0: nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheLookups++; michael@0: #endif michael@0: michael@0: nsAutoCString uri; michael@0: rv = zipFile->GetNativePath(uri); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); michael@0: uri.AppendLiteral("!/"); michael@0: uri.Append(entry); michael@0: michael@0: nsRefPtr zip; michael@0: mZips.Get(uri, getter_AddRefs(zip)); michael@0: if (zip) { michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheHits++; michael@0: #endif michael@0: zip->ClearReleaseTime(); michael@0: } else { michael@0: zip = new nsJAR(); michael@0: zip->SetZipReaderCache(this); michael@0: michael@0: rv = zip->OpenInner(outerZipReader, entry); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: MOZ_ASSERT(!mZips.Contains(uri)); michael@0: mZips.Put(uri, zip); michael@0: } michael@0: zip.forget(result); michael@0: return rv; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: FindOldestZip(const nsACString &aKey, nsJAR* aZip, void* aClosure) michael@0: { michael@0: nsJAR** oldestPtr = static_cast(aClosure); michael@0: nsJAR* oldest = *oldestPtr; michael@0: nsJAR* current = aZip; michael@0: PRIntervalTime currentReleaseTime = current->GetReleaseTime(); michael@0: if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) { michael@0: if (oldest == nullptr || michael@0: currentReleaseTime < oldest->GetReleaseTime()) { michael@0: *oldestPtr = current; michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: struct ZipFindData {nsJAR* zip; bool found;}; michael@0: michael@0: static PLDHashOperator michael@0: FindZip(const nsACString &aKey, nsJAR* aZip, void* aClosure) michael@0: { michael@0: ZipFindData* find_data = static_cast(aClosure); michael@0: michael@0: if (find_data->zip == aZip) { michael@0: find_data->found = true; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult michael@0: nsZipReaderCache::ReleaseZip(nsJAR* zip) michael@0: { michael@0: nsresult rv; michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: // It is possible that two thread compete for this zip. The dangerous michael@0: // case is where one thread Releases the zip and discovers that the ref michael@0: // count has gone to one. Before it can call this ReleaseZip method michael@0: // another thread calls our GetZip method. The ref count goes to two. That michael@0: // second thread then Releases the zip and the ref count goes to one. It michael@0: // then tries to enter this ReleaseZip method and blocks while the first michael@0: // thread is still here. The first thread continues and remove the zip from michael@0: // the cache and calls its Release method sending the ref count to 0 and michael@0: // deleting the zip. However, the second thread is still blocked at the michael@0: // start of ReleaseZip, but the 'zip' param now hold a reference to a michael@0: // deleted zip! michael@0: // michael@0: // So, we are going to try safeguarding here by searching our hashtable while michael@0: // locked here for the zip. We return fast if it is not found. michael@0: michael@0: ZipFindData find_data = {zip, false}; michael@0: mZips.EnumerateRead(FindZip, &find_data); michael@0: if (!find_data.found) { michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipSyncMisses++; michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: zip->SetReleaseTime(); michael@0: michael@0: if (mZips.Count() <= mCacheSize) michael@0: return NS_OK; michael@0: michael@0: nsJAR* oldest = nullptr; michael@0: mZips.EnumerateRead(FindOldestZip, &oldest); michael@0: michael@0: // Because of the craziness above it is possible that there is no zip that michael@0: // needs removing. michael@0: if (!oldest) michael@0: return NS_OK; michael@0: michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheFlushes++; michael@0: #endif michael@0: michael@0: // remove from hashtable michael@0: nsAutoCString uri; michael@0: rv = oldest->GetJarPath(uri); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (oldest->mOuterZipEntry.IsEmpty()) { michael@0: uri.Insert(NS_LITERAL_CSTRING("file:"), 0); michael@0: } else { michael@0: uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); michael@0: uri.AppendLiteral("!/"); michael@0: uri.Append(oldest->mOuterZipEntry); michael@0: } michael@0: michael@0: // Retrieving and removing the JAR must be done without an extra AddRef michael@0: // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case michael@0: // an extra time and trigger a deadlock. michael@0: nsRefPtr removed; michael@0: mZips.Remove(uri, getter_AddRefs(removed)); michael@0: NS_ASSERTION(removed, "botched"); michael@0: NS_ASSERTION(oldest == removed, "removed wrong entry"); michael@0: michael@0: if (removed) michael@0: removed->SetZipReaderCache(nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: FindFlushableZip(const nsACString &aKey, nsRefPtr& aCurrent, void*) michael@0: { michael@0: if (aCurrent->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) { michael@0: aCurrent->SetZipReaderCache(nullptr); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsZipReaderCache::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aSomeData) michael@0: { michael@0: if (strcmp(aTopic, "memory-pressure") == 0) { michael@0: MutexAutoLock lock(mLock); michael@0: mZips.Enumerate(FindFlushableZip, nullptr); michael@0: } michael@0: else if (strcmp(aTopic, "chrome-flush-caches") == 0) { michael@0: mZips.EnumerateRead(DropZipReaderCache, nullptr); michael@0: mZips.Clear(); michael@0: } michael@0: else if (strcmp(aTopic, "flush-cache-entry") == 0) { michael@0: nsCOMPtr file = do_QueryInterface(aSubject); michael@0: if (!file) michael@0: return NS_OK; michael@0: michael@0: nsAutoCString uri; michael@0: if (NS_FAILED(file->GetNativePath(uri))) michael@0: return NS_OK; michael@0: michael@0: uri.Insert(NS_LITERAL_CSTRING("file:"), 0); michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: nsRefPtr zip; michael@0: mZips.Get(uri, getter_AddRefs(zip)); michael@0: if (!zip) michael@0: return NS_OK; michael@0: michael@0: #ifdef ZIP_CACHE_HIT_RATE michael@0: mZipCacheFlushes++; michael@0: #endif michael@0: michael@0: zip->SetZipReaderCache(nullptr); michael@0: michael@0: mZips.Remove(uri); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////////