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