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/Omnijar.h"
13 #include "mozilla/Unused.h"
14 
15 #ifdef XP_UNIX
16 #  include <sys/stat.h>
17 #elif defined(XP_WIN)
18 #  include <io.h>
19 #endif
20 
21 using namespace mozilla;
22 
23 //----------------------------------------------
24 // nsJAR constructor/destructor
25 //----------------------------------------------
26 
27 // The following initialization makes a guess of 10 entries per jarfile.
nsJAR()28 nsJAR::nsJAR()
29     : mZip(new nsZipArchive()),
30       mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
31       mCache(nullptr),
32       mLock("nsJAR::mLock"),
33       mMtime(0),
34       mOpened(false),
35       mSkipArchiveClosing(false) {}
36 
~nsJAR()37 nsJAR::~nsJAR() { Close(); }
38 
NS_IMPL_QUERY_INTERFACE(nsJAR,nsIZipReader)39 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
40 NS_IMPL_ADDREF(nsJAR)
41 
42 // Custom Release method works with nsZipReaderCache...
43 // Release might be called from multi-thread, we have to
44 // take this function carefully to avoid delete-after-use.
45 MozExternalRefCountType nsJAR::Release(void) {
46   nsrefcnt count;
47   MOZ_ASSERT(0 != mRefCnt, "dup release");
48 
49   RefPtr<nsZipReaderCache> cache;
50   if (mRefCnt == 2) {  // don't use a lock too frequently
51     // Use a mutex here to guarantee mCache is not racing and the target
52     // instance is still valid to increase ref-count.
53     MutexAutoLock lock(mLock);
54     cache = mCache;
55     mCache = nullptr;
56   }
57   if (cache) {
58     DebugOnly<nsresult> rv = cache->ReleaseZip(this);
59     MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file");
60   }
61 
62   count = --mRefCnt;  // don't access any member variable after this line
63   NS_LOG_RELEASE(this, count, "nsJAR");
64   if (0 == count) {
65     mRefCnt = 1; /* stabilize */
66     /* enable this to find non-threadsafe destructors: */
67     /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
68     delete this;
69     return 0;
70   }
71 
72   return count;
73 }
74 
75 //----------------------------------------------
76 // nsIZipReader implementation
77 //----------------------------------------------
78 
79 NS_IMETHODIMP
Open(nsIFile * zipFile)80 nsJAR::Open(nsIFile* zipFile) {
81   NS_ENSURE_ARG_POINTER(zipFile);
82   if (mOpened) return NS_ERROR_FAILURE;  // Already open!
83 
84   mZipFile = zipFile;
85   mOuterZipEntry.Truncate();
86   mOpened = true;
87 
88   // The omnijar is special, it is opened early on and closed late
89   // this avoids reopening it
90   RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
91   if (zip) {
92     mZip = zip;
93     mSkipArchiveClosing = true;
94     return NS_OK;
95   }
96   return mZip->OpenArchive(zipFile);
97 }
98 
99 NS_IMETHODIMP
OpenInner(nsIZipReader * aZipReader,const nsACString & aZipEntry)100 nsJAR::OpenInner(nsIZipReader* aZipReader, const nsACString& aZipEntry) {
101   NS_ENSURE_ARG_POINTER(aZipReader);
102   if (mOpened) return NS_ERROR_FAILURE;  // Already open!
103 
104   nsJAR* outerJAR = static_cast<nsJAR*>(aZipReader);
105   RefPtr<nsZipArchive> innerZip =
106       mozilla::Omnijar::GetInnerReader(outerJAR->mZipFile, aZipEntry);
107   if (innerZip) {
108     mOpened = true;
109     mZip = innerZip;
110     mSkipArchiveClosing = true;
111     return NS_OK;
112   }
113 
114   bool exist;
115   nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
116   NS_ENSURE_SUCCESS(rv, rv);
117   NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
118 
119   rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
120   NS_ENSURE_SUCCESS(rv, rv);
121 
122   mOpened = true;
123 
124   mOuterZipEntry.Assign(aZipEntry);
125 
126   RefPtr<nsZipHandle> handle;
127   rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(),
128                          PromiseFlatCString(aZipEntry).get(),
129                          getter_AddRefs(handle));
130   if (NS_FAILED(rv)) return rv;
131 
132   return mZip->OpenArchive(handle);
133 }
134 
135 NS_IMETHODIMP
OpenMemory(void * aData,uint32_t aLength)136 nsJAR::OpenMemory(void* aData, uint32_t aLength) {
137   NS_ENSURE_ARG_POINTER(aData);
138   if (mOpened) return NS_ERROR_FAILURE;  // Already open!
139 
140   mOpened = true;
141 
142   RefPtr<nsZipHandle> handle;
143   nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength,
144                                   getter_AddRefs(handle));
145   if (NS_FAILED(rv)) return rv;
146 
147   return mZip->OpenArchive(handle);
148 }
149 
150 NS_IMETHODIMP
GetFile(nsIFile ** result)151 nsJAR::GetFile(nsIFile** result) {
152   *result = mZipFile;
153   NS_IF_ADDREF(*result);
154   return NS_OK;
155 }
156 
157 NS_IMETHODIMP
Close()158 nsJAR::Close() {
159   if (!mOpened) {
160     return NS_ERROR_FAILURE;  // Never opened or already closed.
161   }
162 
163   mOpened = false;
164 
165   if (mSkipArchiveClosing) {
166     // Reset state, but don't close the omnijar because we did not open it.
167     mSkipArchiveClosing = false;
168     mZip = new nsZipArchive();
169     return NS_OK;
170   }
171 
172   return mZip->CloseArchive();
173 }
174 
175 NS_IMETHODIMP
Test(const nsACString & aEntryName)176 nsJAR::Test(const nsACString& aEntryName) {
177   return mZip->Test(
178       aEntryName.IsEmpty() ? nullptr : PromiseFlatCString(aEntryName).get());
179 }
180 
181 NS_IMETHODIMP
Extract(const nsACString & aEntryName,nsIFile * outFile)182 nsJAR::Extract(const nsACString& aEntryName, nsIFile* outFile) {
183   // nsZipArchive and zlib are not thread safe
184   // we need to use a lock to prevent bug #51267
185   MutexAutoLock lock(mLock);
186 
187   nsZipItem* item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
188   NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
189 
190   // Remove existing file or directory so we set permissions correctly.
191   // If it's a directory that already exists and contains files, throw
192   // an exception and return.
193 
194   nsresult rv = outFile->Remove(false);
195   if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || rv == NS_ERROR_FAILURE) return rv;
196 
197   if (item->IsDirectory()) {
198     rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
199     // XXX Do this in nsZipArchive?  It would be nice to keep extraction
200     // XXX code completely there, but that would require a way to get a
201     // XXX PRDir from outFile.
202   } else {
203     PRFileDesc* fd;
204     rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(),
205                                    &fd);
206     if (NS_FAILED(rv)) return rv;
207 
208     // ExtractFile also closes the fd handle and resolves the symlink if needed
209     rv = mZip->ExtractFile(item, outFile, fd);
210   }
211   if (NS_FAILED(rv)) return rv;
212 
213   // nsIFile needs milliseconds, while prtime is in microseconds.
214   // non-fatal if this fails, ignore errors
215   outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
216 
217   return NS_OK;
218 }
219 
220 NS_IMETHODIMP
GetEntry(const nsACString & aEntryName,nsIZipEntry ** result)221 nsJAR::GetEntry(const nsACString& aEntryName, nsIZipEntry** result) {
222   nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
223   NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
224 
225   nsJARItem* jarItem = new nsJARItem(zipItem);
226 
227   NS_ADDREF(*result = jarItem);
228   return NS_OK;
229 }
230 
231 NS_IMETHODIMP
HasEntry(const nsACString & aEntryName,bool * result)232 nsJAR::HasEntry(const nsACString& aEntryName, bool* result) {
233   *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
234   return NS_OK;
235 }
236 
237 NS_IMETHODIMP
FindEntries(const nsACString & aPattern,nsIUTF8StringEnumerator ** result)238 nsJAR::FindEntries(const nsACString& aPattern,
239                    nsIUTF8StringEnumerator** result) {
240   NS_ENSURE_ARG_POINTER(result);
241 
242   nsZipFind* find;
243   nsresult rv = mZip->FindInit(
244       aPattern.IsEmpty() ? nullptr : PromiseFlatCString(aPattern).get(), &find);
245   NS_ENSURE_SUCCESS(rv, rv);
246 
247   nsIUTF8StringEnumerator* zipEnum = new nsJAREnumerator(find);
248 
249   NS_ADDREF(*result = zipEnum);
250   return NS_OK;
251 }
252 
253 NS_IMETHODIMP
GetInputStream(const nsACString & aFilename,nsIInputStream ** result)254 nsJAR::GetInputStream(const nsACString& aFilename, nsIInputStream** result) {
255   return GetInputStreamWithSpec(""_ns, aFilename, result);
256 }
257 
258 NS_IMETHODIMP
GetInputStreamWithSpec(const nsACString & aJarDirSpec,const nsACString & aEntryName,nsIInputStream ** result)259 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
260                               const nsACString& aEntryName,
261                               nsIInputStream** result) {
262   NS_ENSURE_ARG_POINTER(result);
263 
264   // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
265   nsZipItem* item = nullptr;
266   const nsCString& entry = PromiseFlatCString(aEntryName);
267   if (*entry.get()) {
268     // First check if item exists in jar
269     item = mZip->GetItem(entry.get());
270     if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
271   }
272   nsJARInputStream* jis = new nsJARInputStream();
273   // addref now so we can call InitFile/InitDirectory()
274   NS_ADDREF(*result = jis);
275 
276   nsresult rv = NS_OK;
277   if (!item || item->IsDirectory()) {
278     rv = jis->InitDirectory(this, aJarDirSpec, entry.get());
279   } else {
280     rv = jis->InitFile(this, item);
281   }
282   if (NS_FAILED(rv)) {
283     NS_RELEASE(*result);
284   }
285   return rv;
286 }
287 
GetJarPath(nsACString & aResult)288 nsresult nsJAR::GetJarPath(nsACString& aResult) {
289   NS_ENSURE_ARG_POINTER(mZipFile);
290 
291   return mZipFile->GetPersistentDescriptor(aResult);
292 }
293 
GetNSPRFileDesc(PRFileDesc ** aNSPRFileDesc)294 nsresult nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) {
295   if (!aNSPRFileDesc) {
296     return NS_ERROR_ILLEGAL_VALUE;
297   }
298   *aNSPRFileDesc = nullptr;
299 
300   if (!mZip) {
301     return NS_ERROR_FAILURE;
302   }
303 
304   RefPtr<nsZipHandle> handle = mZip->GetFD();
305   if (!handle) {
306     return NS_ERROR_FAILURE;
307   }
308 
309   return handle->GetNSPRFileDesc(aNSPRFileDesc);
310 }
311 
312 //----------------------------------------------
313 // nsJAR private implementation
314 //----------------------------------------------
LoadEntry(const nsACString & aFilename,nsCString & aBuf)315 nsresult nsJAR::LoadEntry(const nsACString& aFilename, nsCString& aBuf) {
316   //-- Get a stream for reading the file
317   nsresult rv;
318   nsCOMPtr<nsIInputStream> manifestStream;
319   rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
320   if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
321 
322   //-- Read the manifest file into memory
323   char* buf;
324   uint64_t len64;
325   rv = manifestStream->Available(&len64);
326   if (NS_FAILED(rv)) return rv;
327   NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED);  // bug 164695
328   uint32_t len = (uint32_t)len64;
329   buf = (char*)malloc(len + 1);
330   if (!buf) return NS_ERROR_OUT_OF_MEMORY;
331   uint32_t bytesRead;
332   rv = manifestStream->Read(buf, len, &bytesRead);
333   if (bytesRead != len) {
334     rv = NS_ERROR_FILE_CORRUPTED;
335   }
336   if (NS_FAILED(rv)) {
337     free(buf);
338     return rv;
339   }
340   buf[len] = '\0';  // Null-terminate the buffer
341   aBuf.Adopt(buf, len);
342   return NS_OK;
343 }
344 
ReadLine(const char ** src)345 int32_t nsJAR::ReadLine(const char** src) {
346   if (!*src) {
347     return 0;
348   }
349 
350   //--Moves pointer to beginning of next line and returns line length
351   //  not including CR/LF.
352   int32_t length;
353   char* eol = PL_strpbrk(*src, "\r\n");
354 
355   if (eol == nullptr)  // Probably reached end of file before newline
356   {
357     length = strlen(*src);
358     if (length == 0)  // immediate end-of-file
359       *src = nullptr;
360     else  // some data left on this line
361       *src += length;
362   } else {
363     length = eol - *src;
364     if (eol[0] == '\r' && eol[1] == '\n')  // CR LF, so skip 2
365       *src = eol + 2;
366     else  // Either CR or LF, so skip 1
367       *src = eol + 1;
368   }
369   return length;
370 }
371 
NS_IMPL_ISUPPORTS(nsJAREnumerator,nsIUTF8StringEnumerator,nsIStringEnumerator)372 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator, nsIStringEnumerator)
373 
374 //----------------------------------------------
375 // nsJAREnumerator::HasMore
376 //----------------------------------------------
377 NS_IMETHODIMP
378 nsJAREnumerator::HasMore(bool* aResult) {
379   // try to get the next element
380   if (!mName) {
381     NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
382     nsresult rv = mFind->FindNext(&mName, &mNameLen);
383     if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
384       *aResult = false;  // No more matches available
385       return NS_OK;
386     }
387     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);  // no error translation
388   }
389 
390   *aResult = true;
391   return NS_OK;
392 }
393 
394 //----------------------------------------------
395 // nsJAREnumerator::GetNext
396 //----------------------------------------------
397 NS_IMETHODIMP
GetNext(nsACString & aResult)398 nsJAREnumerator::GetNext(nsACString& aResult) {
399   // check if the current item is "stale"
400   if (!mName) {
401     bool bMore;
402     nsresult rv = HasMore(&bMore);
403     if (NS_FAILED(rv) || !bMore)
404       return NS_ERROR_FAILURE;  // no error translation
405   }
406   aResult.Assign(mName, mNameLen);
407   mName = 0;  // we just gave this one away
408   return NS_OK;
409 }
410 
NS_IMPL_ISUPPORTS(nsJARItem,nsIZipEntry)411 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
412 
413 nsJARItem::nsJARItem(nsZipItem* aZipItem)
414     : mSize(aZipItem->Size()),
415       mRealsize(aZipItem->RealSize()),
416       mCrc32(aZipItem->CRC32()),
417       mLastModTime(aZipItem->LastModTime()),
418       mCompression(aZipItem->Compression()),
419       mPermissions(aZipItem->Mode()),
420       mIsDirectory(aZipItem->IsDirectory()),
421       mIsSynthetic(aZipItem->isSynthetic) {}
422 
423 //------------------------------------------
424 // nsJARItem::GetCompression
425 //------------------------------------------
426 NS_IMETHODIMP
GetCompression(uint16_t * aCompression)427 nsJARItem::GetCompression(uint16_t* aCompression) {
428   NS_ENSURE_ARG_POINTER(aCompression);
429 
430   *aCompression = mCompression;
431   return NS_OK;
432 }
433 
434 //------------------------------------------
435 // nsJARItem::GetSize
436 //------------------------------------------
437 NS_IMETHODIMP
GetSize(uint32_t * aSize)438 nsJARItem::GetSize(uint32_t* aSize) {
439   NS_ENSURE_ARG_POINTER(aSize);
440 
441   *aSize = mSize;
442   return NS_OK;
443 }
444 
445 //------------------------------------------
446 // nsJARItem::GetRealSize
447 //------------------------------------------
448 NS_IMETHODIMP
GetRealSize(uint32_t * aRealsize)449 nsJARItem::GetRealSize(uint32_t* aRealsize) {
450   NS_ENSURE_ARG_POINTER(aRealsize);
451 
452   *aRealsize = mRealsize;
453   return NS_OK;
454 }
455 
456 //------------------------------------------
457 // nsJARItem::GetCrc32
458 //------------------------------------------
459 NS_IMETHODIMP
GetCRC32(uint32_t * aCrc32)460 nsJARItem::GetCRC32(uint32_t* aCrc32) {
461   NS_ENSURE_ARG_POINTER(aCrc32);
462 
463   *aCrc32 = mCrc32;
464   return NS_OK;
465 }
466 
467 //------------------------------------------
468 // nsJARItem::GetIsDirectory
469 //------------------------------------------
470 NS_IMETHODIMP
GetIsDirectory(bool * aIsDirectory)471 nsJARItem::GetIsDirectory(bool* aIsDirectory) {
472   NS_ENSURE_ARG_POINTER(aIsDirectory);
473 
474   *aIsDirectory = mIsDirectory;
475   return NS_OK;
476 }
477 
478 //------------------------------------------
479 // nsJARItem::GetIsSynthetic
480 //------------------------------------------
481 NS_IMETHODIMP
GetIsSynthetic(bool * aIsSynthetic)482 nsJARItem::GetIsSynthetic(bool* aIsSynthetic) {
483   NS_ENSURE_ARG_POINTER(aIsSynthetic);
484 
485   *aIsSynthetic = mIsSynthetic;
486   return NS_OK;
487 }
488 
489 //------------------------------------------
490 // nsJARItem::GetLastModifiedTime
491 //------------------------------------------
492 NS_IMETHODIMP
GetLastModifiedTime(PRTime * aLastModTime)493 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) {
494   NS_ENSURE_ARG_POINTER(aLastModTime);
495 
496   *aLastModTime = mLastModTime;
497   return NS_OK;
498 }
499 
500 //------------------------------------------
501 // nsJARItem::GetPermissions
502 //------------------------------------------
503 NS_IMETHODIMP
GetPermissions(uint32_t * aPermissions)504 nsJARItem::GetPermissions(uint32_t* aPermissions) {
505   NS_ENSURE_ARG_POINTER(aPermissions);
506 
507   *aPermissions = mPermissions;
508   return NS_OK;
509 }
510 
511 ////////////////////////////////////////////////////////////////////////////////
512 // nsIZipReaderCache
513 
NS_IMPL_ISUPPORTS(nsZipReaderCache,nsIZipReaderCache,nsIObserver,nsISupportsWeakReference)514 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver,
515                   nsISupportsWeakReference)
516 
517 nsZipReaderCache::nsZipReaderCache()
518     : mLock("nsZipReaderCache.mLock"),
519       mCacheSize(0),
520       mZips()
521 #ifdef ZIP_CACHE_HIT_RATE
522       ,
523       mZipCacheLookups(0),
524       mZipCacheHits(0),
525       mZipCacheFlushes(0),
526       mZipSyncMisses(0)
527 #endif
528 {
529 }
530 
531 NS_IMETHODIMP
Init(uint32_t cacheSize)532 nsZipReaderCache::Init(uint32_t cacheSize) {
533   mCacheSize = cacheSize;
534 
535   // Register as a memory pressure observer
536   nsCOMPtr<nsIObserverService> os =
537       do_GetService("@mozilla.org/observer-service;1");
538   if (os) {
539     os->AddObserver(this, "memory-pressure", true);
540     os->AddObserver(this, "chrome-flush-caches", true);
541     os->AddObserver(this, "flush-cache-entry", true);
542   }
543   // ignore failure of the observer registration.
544 
545   return NS_OK;
546 }
547 
~nsZipReaderCache()548 nsZipReaderCache::~nsZipReaderCache() {
549   for (const auto& zip : mZips.Values()) {
550     zip->SetZipReaderCache(nullptr);
551   }
552 
553 #ifdef ZIP_CACHE_HIT_RATE
554   printf(
555       "nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed "
556       "%d\n",
557       mCacheSize, mZipCacheHits, mZipCacheLookups,
558       (float)mZipCacheHits / mZipCacheLookups, mZipCacheFlushes,
559       mZipSyncMisses);
560 #endif
561 }
562 
563 NS_IMETHODIMP
IsCached(nsIFile * zipFile,bool * aResult)564 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) {
565   NS_ENSURE_ARG_POINTER(zipFile);
566   nsresult rv;
567   MutexAutoLock lock(mLock);
568 
569   nsAutoCString uri;
570   rv = zipFile->GetPersistentDescriptor(uri);
571   if (NS_FAILED(rv)) return rv;
572 
573   uri.InsertLiteral("file:", 0);
574 
575   *aResult = mZips.Contains(uri);
576   return NS_OK;
577 }
578 
GetZip(nsIFile * zipFile,nsIZipReader ** result,bool failOnMiss)579 nsresult nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result,
580                                   bool failOnMiss) {
581   NS_ENSURE_ARG_POINTER(zipFile);
582   nsresult rv;
583   MutexAutoLock lock(mLock);
584 
585 #ifdef ZIP_CACHE_HIT_RATE
586   mZipCacheLookups++;
587 #endif
588 
589   nsAutoCString uri;
590   rv = zipFile->GetPersistentDescriptor(uri);
591   if (NS_FAILED(rv)) return rv;
592 
593   uri.InsertLiteral("file:", 0);
594 
595   RefPtr<nsJAR> zip;
596   mZips.Get(uri, getter_AddRefs(zip));
597   if (zip) {
598 #ifdef ZIP_CACHE_HIT_RATE
599     mZipCacheHits++;
600 #endif
601     zip->ClearReleaseTime();
602   } else {
603     if (failOnMiss) {
604       return NS_ERROR_CACHE_KEY_NOT_FOUND;
605     }
606 
607     zip = new nsJAR();
608     zip->SetZipReaderCache(this);
609     rv = zip->Open(zipFile);
610     if (NS_FAILED(rv)) {
611       return rv;
612     }
613 
614     MOZ_ASSERT(!mZips.Contains(uri));
615     mZips.InsertOrUpdate(uri, RefPtr{zip});
616   }
617   zip.forget(result);
618   return rv;
619 }
620 
621 NS_IMETHODIMP
GetZipIfCached(nsIFile * zipFile,nsIZipReader ** result)622 nsZipReaderCache::GetZipIfCached(nsIFile* zipFile, nsIZipReader** result) {
623   return GetZip(zipFile, result, true);
624 }
625 
626 NS_IMETHODIMP
GetZip(nsIFile * zipFile,nsIZipReader ** result)627 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader** result) {
628   return GetZip(zipFile, result, false);
629 }
630 
631 NS_IMETHODIMP
GetInnerZip(nsIFile * zipFile,const nsACString & entry,nsIZipReader ** result)632 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString& entry,
633                               nsIZipReader** result) {
634   NS_ENSURE_ARG_POINTER(zipFile);
635 
636   nsCOMPtr<nsIZipReader> outerZipReader;
637   nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
638   NS_ENSURE_SUCCESS(rv, rv);
639 
640   MutexAutoLock lock(mLock);
641 
642 #ifdef ZIP_CACHE_HIT_RATE
643   mZipCacheLookups++;
644 #endif
645 
646   nsAutoCString uri;
647   rv = zipFile->GetPersistentDescriptor(uri);
648   if (NS_FAILED(rv)) return rv;
649 
650   uri.InsertLiteral("jar:", 0);
651   uri.AppendLiteral("!/");
652   uri.Append(entry);
653 
654   RefPtr<nsJAR> zip;
655   mZips.Get(uri, getter_AddRefs(zip));
656   if (zip) {
657 #ifdef ZIP_CACHE_HIT_RATE
658     mZipCacheHits++;
659 #endif
660     zip->ClearReleaseTime();
661   } else {
662     zip = new nsJAR();
663     zip->SetZipReaderCache(this);
664 
665     rv = zip->OpenInner(outerZipReader, entry);
666     if (NS_FAILED(rv)) {
667       return rv;
668     }
669 
670     MOZ_ASSERT(!mZips.Contains(uri));
671     mZips.InsertOrUpdate(uri, RefPtr{zip});
672   }
673   zip.forget(result);
674   return rv;
675 }
676 
677 NS_IMETHODIMP
GetFd(nsIFile * zipFile,PRFileDesc ** aRetVal)678 nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal) {
679 #if defined(XP_WIN)
680   MOZ_CRASH("Not implemented");
681   return NS_ERROR_NOT_IMPLEMENTED;
682 #else
683   if (!zipFile) {
684     return NS_ERROR_FAILURE;
685   }
686 
687   nsresult rv;
688   nsAutoCString uri;
689   rv = zipFile->GetPersistentDescriptor(uri);
690   if (NS_FAILED(rv)) {
691     return rv;
692   }
693   uri.InsertLiteral("file:", 0);
694 
695   MutexAutoLock lock(mLock);
696   RefPtr<nsJAR> zip;
697   mZips.Get(uri, getter_AddRefs(zip));
698   if (!zip) {
699     return NS_ERROR_FAILURE;
700   }
701 
702   zip->ClearReleaseTime();
703   rv = zip->GetNSPRFileDesc(aRetVal);
704   // Do this to avoid possible deadlock on mLock with ReleaseZip().
705   {
706     MutexAutoUnlock unlock(mLock);
707     zip = nullptr;
708   }
709   return rv;
710 #endif /* XP_WIN */
711 }
712 
ReleaseZip(nsJAR * zip)713 nsresult nsZipReaderCache::ReleaseZip(nsJAR* zip) {
714   nsresult rv;
715   MutexAutoLock lock(mLock);
716 
717   // It is possible that two thread compete for this zip. The dangerous
718   // case is where one thread Releases the zip and discovers that the ref
719   // count has gone to one. Before it can call this ReleaseZip method
720   // another thread calls our GetZip method. The ref count goes to two. That
721   // second thread then Releases the zip and the ref count goes to one. It
722   // then tries to enter this ReleaseZip method and blocks while the first
723   // thread is still here. The first thread continues and remove the zip from
724   // the cache and calls its Release method sending the ref count to 0 and
725   // deleting the zip. However, the second thread is still blocked at the
726   // start of ReleaseZip, but the 'zip' param now hold a reference to a
727   // deleted zip!
728   //
729   // So, we are going to try safeguarding here by searching our hashtable while
730   // locked here for the zip. We return fast if it is not found.
731 
732   bool found = false;
733   for (const auto& current : mZips.Values()) {
734     if (zip == current) {
735       found = true;
736       break;
737     }
738   }
739 
740   if (!found) {
741 #ifdef ZIP_CACHE_HIT_RATE
742     mZipSyncMisses++;
743 #endif
744     return NS_OK;
745   }
746 
747   zip->SetReleaseTime();
748 
749   if (mZips.Count() <= mCacheSize) return NS_OK;
750 
751   // Find the oldest zip.
752   nsJAR* oldest = nullptr;
753   for (const auto& current : mZips.Values()) {
754     PRIntervalTime currentReleaseTime = current->GetReleaseTime();
755     if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
756       if (oldest == nullptr || currentReleaseTime < oldest->GetReleaseTime()) {
757         oldest = current;
758       }
759     }
760   }
761 
762   // Because of the craziness above it is possible that there is no zip that
763   // needs removing.
764   if (!oldest) return NS_OK;
765 
766 #ifdef ZIP_CACHE_HIT_RATE
767   mZipCacheFlushes++;
768 #endif
769 
770   // remove from hashtable
771   nsAutoCString uri;
772   rv = oldest->GetJarPath(uri);
773   if (NS_FAILED(rv)) return rv;
774 
775   if (oldest->mOuterZipEntry.IsEmpty()) {
776     uri.InsertLiteral("file:", 0);
777   } else {
778     uri.InsertLiteral("jar:", 0);
779     uri.AppendLiteral("!/");
780     uri.Append(oldest->mOuterZipEntry);
781   }
782 
783   // Retrieving and removing the JAR must be done without an extra AddRef
784   // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
785   // an extra time and trigger a deadlock.
786   RefPtr<nsJAR> removed;
787   mZips.Remove(uri, getter_AddRefs(removed));
788   NS_ASSERTION(removed, "botched");
789   NS_ASSERTION(oldest == removed, "removed wrong entry");
790 
791   if (removed) removed->SetZipReaderCache(nullptr);
792 
793   return NS_OK;
794 }
795 
796 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aSomeData)797 nsZipReaderCache::Observe(nsISupports* aSubject, const char* aTopic,
798                           const char16_t* aSomeData) {
799   if (strcmp(aTopic, "memory-pressure") == 0) {
800     MutexAutoLock lock(mLock);
801     for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
802       RefPtr<nsJAR>& current = iter.Data();
803       if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
804         current->SetZipReaderCache(nullptr);
805         iter.Remove();
806       }
807     }
808   } else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
809     MutexAutoLock lock(mLock);
810     for (const auto& current : mZips.Values()) {
811       current->SetZipReaderCache(nullptr);
812     }
813     mZips.Clear();
814   } else if (strcmp(aTopic, "flush-cache-entry") == 0) {
815     nsCOMPtr<nsIFile> file;
816     if (aSubject) {
817       file = do_QueryInterface(aSubject);
818     } else if (aSomeData) {
819       nsDependentString fileName(aSomeData);
820       Unused << NS_NewLocalFile(fileName, false, getter_AddRefs(file));
821     }
822 
823     if (!file) return NS_OK;
824 
825     nsAutoCString uri;
826     if (NS_FAILED(file->GetPersistentDescriptor(uri))) return NS_OK;
827 
828     uri.InsertLiteral("file:", 0);
829 
830     MutexAutoLock lock(mLock);
831 
832     RefPtr<nsJAR> zip;
833     mZips.Get(uri, getter_AddRefs(zip));
834     if (!zip) return NS_OK;
835 
836 #ifdef ZIP_CACHE_HIT_RATE
837     mZipCacheFlushes++;
838 #endif
839 
840     zip->SetZipReaderCache(nullptr);
841 
842     mZips.Remove(uri);
843   }
844   return NS_OK;
845 }
846 
847 ////////////////////////////////////////////////////////////////////////////////
848