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