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