Line data Source code
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 "nsIX509Cert.h"
11 : #include "nsIConsoleService.h"
12 : #include "nsICryptoHash.h"
13 : #include "nsIDataSignatureVerifier.h"
14 : #include "mozilla/Omnijar.h"
15 : #include "mozilla/Unused.h"
16 :
17 : #ifdef XP_UNIX
18 : #include <sys/stat.h>
19 : #elif defined (XP_WIN)
20 : #include <io.h>
21 : #endif
22 :
23 : using namespace mozilla;
24 :
25 : //----------------------------------------------
26 : // nsJARManifestItem declaration
27 : //----------------------------------------------
28 : /*
29 : * nsJARManifestItem contains meta-information pertaining
30 : * to an individual JAR entry, taken from the
31 : * META-INF/MANIFEST.MF and META-INF/ *.SF files.
32 : * This is security-critical information, defined here so it is not
33 : * accessible from anywhere else.
34 : */
35 : typedef enum
36 : {
37 : JAR_INVALID = 1,
38 : JAR_INTERNAL = 2,
39 : JAR_EXTERNAL = 3
40 : } JARManifestItemType;
41 :
42 : class nsJARManifestItem
43 : {
44 : public:
45 : JARManifestItemType mType;
46 :
47 : // True if the second step of verification (VerifyEntry)
48 : // has taken place:
49 : bool entryVerified;
50 :
51 : // Not signed, valid, or failure code
52 : int16_t status;
53 :
54 : // Internal storage of digests
55 : nsCString calculatedSectionDigest;
56 : nsCString storedEntryDigest;
57 :
58 : nsJARManifestItem();
59 : virtual ~nsJARManifestItem();
60 : };
61 :
62 : //-------------------------------------------------
63 : // nsJARManifestItem constructors and destructor
64 : //-------------------------------------------------
65 0 : nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
66 : entryVerified(false),
67 0 : status(JAR_NOT_SIGNED)
68 : {
69 0 : }
70 :
71 0 : nsJARManifestItem::~nsJARManifestItem()
72 : {
73 0 : }
74 :
75 : //----------------------------------------------
76 : // nsJAR constructor/destructor
77 : //----------------------------------------------
78 :
79 : // The following initialization makes a guess of 10 entries per jarfile.
80 0 : nsJAR::nsJAR(): mZip(new nsZipArchive()),
81 : mManifestData(8),
82 : mParsedManifest(false),
83 : mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
84 : mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
85 : mCache(nullptr),
86 : mLock("nsJAR::mLock"),
87 : mMtime(0),
88 : mTotalItemsInManifest(0),
89 : mOpened(false),
90 0 : mIsOmnijar(false)
91 : {
92 0 : }
93 :
94 0 : nsJAR::~nsJAR()
95 : {
96 0 : Close();
97 0 : }
98 :
99 0 : NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
100 0 : NS_IMPL_ADDREF(nsJAR)
101 :
102 : // Custom Release method works with nsZipReaderCache...
103 0 : MozExternalRefCountType nsJAR::Release(void)
104 : {
105 : nsrefcnt count;
106 0 : NS_PRECONDITION(0 != mRefCnt, "dup release");
107 0 : count = --mRefCnt;
108 0 : NS_LOG_RELEASE(this, count, "nsJAR");
109 0 : if (0 == count) {
110 0 : mRefCnt = 1; /* stabilize */
111 : /* enable this to find non-threadsafe destructors: */
112 : /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
113 0 : delete this;
114 0 : return 0;
115 : }
116 0 : if (1 == count && mCache) {
117 : #ifdef DEBUG
118 : nsresult rv =
119 : #endif
120 0 : mCache->ReleaseZip(this);
121 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
122 : }
123 0 : return count;
124 : }
125 :
126 : //----------------------------------------------
127 : // nsIZipReader implementation
128 : //----------------------------------------------
129 :
130 : NS_IMETHODIMP
131 0 : nsJAR::Open(nsIFile* zipFile)
132 : {
133 0 : NS_ENSURE_ARG_POINTER(zipFile);
134 0 : if (mOpened) return NS_ERROR_FAILURE; // Already open!
135 :
136 0 : mZipFile = zipFile;
137 0 : mOuterZipEntry.Truncate();
138 0 : mOpened = true;
139 :
140 : // The omnijar is special, it is opened early on and closed late
141 : // this avoids reopening it
142 0 : RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
143 0 : if (zip) {
144 0 : mZip = zip;
145 0 : mIsOmnijar = true;
146 0 : return NS_OK;
147 : }
148 0 : return mZip->OpenArchive(zipFile);
149 : }
150 :
151 : NS_IMETHODIMP
152 0 : nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
153 : {
154 0 : NS_ENSURE_ARG_POINTER(aZipReader);
155 0 : if (mOpened) return NS_ERROR_FAILURE; // Already open!
156 :
157 : bool exist;
158 0 : nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
159 0 : NS_ENSURE_SUCCESS(rv, rv);
160 0 : NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
161 :
162 0 : rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
163 0 : NS_ENSURE_SUCCESS(rv, rv);
164 :
165 0 : mOpened = true;
166 :
167 0 : mOuterZipEntry.Assign(aZipEntry);
168 :
169 0 : RefPtr<nsZipHandle> handle;
170 0 : rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
171 0 : getter_AddRefs(handle));
172 0 : if (NS_FAILED(rv))
173 0 : return rv;
174 :
175 0 : return mZip->OpenArchive(handle);
176 : }
177 :
178 : NS_IMETHODIMP
179 0 : nsJAR::OpenMemory(void* aData, uint32_t aLength)
180 : {
181 0 : NS_ENSURE_ARG_POINTER(aData);
182 0 : if (mOpened) return NS_ERROR_FAILURE; // Already open!
183 :
184 0 : mOpened = true;
185 :
186 0 : RefPtr<nsZipHandle> handle;
187 0 : nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength,
188 0 : getter_AddRefs(handle));
189 0 : if (NS_FAILED(rv))
190 0 : return rv;
191 :
192 0 : return mZip->OpenArchive(handle);
193 : }
194 :
195 : NS_IMETHODIMP
196 0 : nsJAR::GetFile(nsIFile* *result)
197 : {
198 0 : *result = mZipFile;
199 0 : NS_IF_ADDREF(*result);
200 0 : return NS_OK;
201 : }
202 :
203 : NS_IMETHODIMP
204 0 : nsJAR::Close()
205 : {
206 0 : if (!mOpened) {
207 0 : return NS_ERROR_FAILURE; // Never opened or already closed.
208 : }
209 :
210 0 : mOpened = false;
211 0 : mParsedManifest = false;
212 0 : mManifestData.Clear();
213 0 : mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
214 0 : mTotalItemsInManifest = 0;
215 :
216 0 : if (mIsOmnijar) {
217 : // Reset state, but don't close the omnijar because we did not open it.
218 0 : mIsOmnijar = false;
219 0 : mZip = new nsZipArchive();
220 0 : return NS_OK;
221 : }
222 :
223 0 : return mZip->CloseArchive();
224 : }
225 :
226 : NS_IMETHODIMP
227 0 : nsJAR::Test(const nsACString &aEntryName)
228 : {
229 0 : return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
230 : }
231 :
232 : NS_IMETHODIMP
233 0 : nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
234 : {
235 : // nsZipArchive and zlib are not thread safe
236 : // we need to use a lock to prevent bug #51267
237 0 : MutexAutoLock lock(mLock);
238 :
239 0 : nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
240 0 : NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
241 :
242 : // Remove existing file or directory so we set permissions correctly.
243 : // If it's a directory that already exists and contains files, throw
244 : // an exception and return.
245 :
246 0 : nsresult rv = outFile->Remove(false);
247 0 : if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
248 : rv == NS_ERROR_FAILURE)
249 0 : return rv;
250 :
251 0 : if (item->IsDirectory())
252 : {
253 0 : rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
254 : //XXX Do this in nsZipArchive? It would be nice to keep extraction
255 : //XXX code completely there, but that would require a way to get a
256 : //XXX PRDir from outFile.
257 : }
258 : else
259 : {
260 : PRFileDesc* fd;
261 0 : rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
262 0 : if (NS_FAILED(rv)) return rv;
263 :
264 : // ExtractFile also closes the fd handle and resolves the symlink if needed
265 0 : nsAutoCString path;
266 0 : rv = outFile->GetNativePath(path);
267 0 : if (NS_FAILED(rv)) return rv;
268 :
269 0 : rv = mZip->ExtractFile(item, path.get(), fd);
270 : }
271 0 : if (NS_FAILED(rv)) return rv;
272 :
273 : // nsIFile needs milliseconds, while prtime is in microseconds.
274 : // non-fatal if this fails, ignore errors
275 0 : outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
276 :
277 0 : return NS_OK;
278 : }
279 :
280 : NS_IMETHODIMP
281 0 : nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
282 : {
283 0 : nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
284 0 : NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
285 :
286 0 : nsJARItem* jarItem = new nsJARItem(zipItem);
287 :
288 0 : NS_ADDREF(*result = jarItem);
289 0 : return NS_OK;
290 : }
291 :
292 : NS_IMETHODIMP
293 0 : nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
294 : {
295 0 : *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
296 0 : return NS_OK;
297 : }
298 :
299 : NS_IMETHODIMP
300 0 : nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
301 : {
302 0 : NS_ENSURE_ARG_POINTER(result);
303 :
304 : nsZipFind *find;
305 0 : nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
306 0 : NS_ENSURE_SUCCESS(rv, rv);
307 :
308 0 : nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
309 :
310 0 : NS_ADDREF(*result = zipEnum);
311 0 : return NS_OK;
312 : }
313 :
314 : NS_IMETHODIMP
315 0 : nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
316 : {
317 0 : return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
318 : }
319 :
320 : NS_IMETHODIMP
321 0 : nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
322 : const nsACString &aEntryName, nsIInputStream** result)
323 : {
324 0 : NS_ENSURE_ARG_POINTER(result);
325 :
326 : // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
327 0 : nsZipItem *item = nullptr;
328 0 : const char *entry = PromiseFlatCString(aEntryName).get();
329 0 : if (*entry) {
330 : // First check if item exists in jar
331 0 : item = mZip->GetItem(entry);
332 0 : if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
333 : }
334 0 : nsJARInputStream* jis = new nsJARInputStream();
335 : // addref now so we can call InitFile/InitDirectory()
336 0 : NS_ADDREF(*result = jis);
337 :
338 0 : nsresult rv = NS_OK;
339 0 : if (!item || item->IsDirectory()) {
340 0 : rv = jis->InitDirectory(this, aJarDirSpec, entry);
341 : } else {
342 0 : rv = jis->InitFile(this, item);
343 : }
344 0 : if (NS_FAILED(rv)) {
345 0 : NS_RELEASE(*result);
346 : }
347 0 : return rv;
348 : }
349 :
350 : NS_IMETHODIMP
351 0 : nsJAR::GetSigningCert(const nsACString& aFilename, nsIX509Cert** aSigningCert)
352 : {
353 : //-- Parameter check
354 0 : if (!aSigningCert) {
355 0 : return NS_ERROR_NULL_POINTER;
356 : }
357 0 : *aSigningCert = nullptr;
358 :
359 : // Don't check signatures in the omnijar - this is only
360 : // interesting for extensions/XPIs.
361 0 : RefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
362 0 : RefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
363 :
364 0 : if (mZip == greOmni || mZip == appOmni)
365 0 : return NS_OK;
366 :
367 : //-- Parse the manifest
368 0 : nsresult rv = ParseManifest();
369 0 : if (NS_FAILED(rv)) return rv;
370 0 : if (mGlobalStatus == JAR_NO_MANIFEST)
371 0 : return NS_OK;
372 :
373 : int16_t requestedStatus;
374 0 : if (!aFilename.IsEmpty())
375 : {
376 : //-- Find the item
377 0 : nsJARManifestItem* manItem = mManifestData.Get(aFilename);
378 0 : if (!manItem)
379 0 : return NS_OK;
380 : //-- Verify the item against the manifest
381 0 : if (!manItem->entryVerified)
382 : {
383 0 : nsXPIDLCString entryData;
384 0 : rv = LoadEntry(aFilename, entryData);
385 0 : if (NS_FAILED(rv)) return rv;
386 0 : rv = VerifyEntry(manItem, entryData, entryData.Length());
387 0 : if (NS_FAILED(rv)) return rv;
388 : }
389 0 : requestedStatus = manItem->status;
390 : }
391 : else // User wants identity of signer w/o verifying any entries
392 0 : requestedStatus = mGlobalStatus;
393 :
394 0 : if (requestedStatus != JAR_VALID_MANIFEST) {
395 0 : ReportError(aFilename, requestedStatus);
396 : } else { // Valid signature
397 0 : *aSigningCert = mSigningCert;
398 0 : NS_IF_ADDREF(*aSigningCert);
399 : }
400 0 : return NS_OK;
401 : }
402 :
403 : NS_IMETHODIMP
404 0 : nsJAR::GetManifestEntriesCount(uint32_t* count)
405 : {
406 0 : *count = mTotalItemsInManifest;
407 0 : return NS_OK;
408 : }
409 :
410 : nsresult
411 0 : nsJAR::GetJarPath(nsACString& aResult)
412 : {
413 0 : NS_ENSURE_ARG_POINTER(mZipFile);
414 :
415 0 : return mZipFile->GetNativePath(aResult);
416 : }
417 :
418 : nsresult
419 0 : nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
420 : {
421 0 : if (!aNSPRFileDesc) {
422 0 : return NS_ERROR_ILLEGAL_VALUE;
423 : }
424 0 : *aNSPRFileDesc = nullptr;
425 :
426 0 : if (!mZip) {
427 0 : return NS_ERROR_FAILURE;
428 : }
429 :
430 0 : RefPtr<nsZipHandle> handle = mZip->GetFD();
431 0 : if (!handle) {
432 0 : return NS_ERROR_FAILURE;
433 : }
434 :
435 0 : return handle->GetNSPRFileDesc(aNSPRFileDesc);
436 : }
437 :
438 : //----------------------------------------------
439 : // nsJAR private implementation
440 : //----------------------------------------------
441 : nsresult
442 0 : nsJAR::LoadEntry(const nsACString& aFilename, nsCString& aBuf)
443 : {
444 : //-- Get a stream for reading the file
445 : nsresult rv;
446 0 : nsCOMPtr<nsIInputStream> manifestStream;
447 0 : rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
448 0 : if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
449 :
450 : //-- Read the manifest file into memory
451 : char* buf;
452 : uint64_t len64;
453 0 : rv = manifestStream->Available(&len64);
454 0 : if (NS_FAILED(rv)) return rv;
455 0 : if (len64 >= UINT32_MAX) { // bug 164695
456 0 : nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest size";
457 0 : return NS_ERROR_FILE_CORRUPTED;
458 : }
459 0 : uint32_t len = (uint32_t)len64;
460 0 : buf = (char*)malloc(len+1);
461 0 : if (!buf) return NS_ERROR_OUT_OF_MEMORY;
462 : uint32_t bytesRead;
463 0 : rv = manifestStream->Read(buf, len, &bytesRead);
464 0 : if (bytesRead != len) {
465 0 : nsZipArchive::sFileCorruptedReason = "nsJAR: manifest too small";
466 0 : rv = NS_ERROR_FILE_CORRUPTED;
467 : }
468 0 : if (NS_FAILED(rv)) {
469 0 : free(buf);
470 0 : return rv;
471 : }
472 0 : buf[len] = '\0'; //Null-terminate the buffer
473 0 : aBuf.Adopt(buf, len);
474 0 : return NS_OK;
475 : }
476 :
477 :
478 : int32_t
479 0 : nsJAR::ReadLine(const char** src)
480 : {
481 0 : if (!*src) {
482 0 : return 0;
483 : }
484 :
485 : //--Moves pointer to beginning of next line and returns line length
486 : // not including CR/LF.
487 : int32_t length;
488 0 : char* eol = PL_strpbrk(*src, "\r\n");
489 :
490 0 : if (eol == nullptr) // Probably reached end of file before newline
491 : {
492 0 : length = strlen(*src);
493 0 : if (length == 0) // immediate end-of-file
494 0 : *src = nullptr;
495 : else // some data left on this line
496 0 : *src += length;
497 : }
498 : else
499 : {
500 0 : length = eol - *src;
501 0 : if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
502 0 : *src = eol+2;
503 : else // Either CR or LF, so skip 1
504 0 : *src = eol+1;
505 : }
506 0 : return length;
507 : }
508 :
509 : //-- The following #defines are used by ParseManifest()
510 : // and ParseOneFile(). The header strings are defined in the JAR specification.
511 : #define JAR_MF 1
512 : #define JAR_SF 2
513 : #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
514 : #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
515 : #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
516 : #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
517 :
518 : nsresult
519 0 : nsJAR::ParseManifest()
520 : {
521 : //-- Verification Step 1
522 0 : if (mParsedManifest)
523 0 : return NS_OK;
524 : //-- (1)Manifest (MF) file
525 0 : nsCOMPtr<nsIUTF8StringEnumerator> files;
526 0 : nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
527 0 : if (!files) rv = NS_ERROR_FAILURE;
528 0 : if (NS_FAILED(rv)) return rv;
529 :
530 : //-- Load the file into memory
531 : bool more;
532 0 : rv = files->HasMore(&more);
533 0 : NS_ENSURE_SUCCESS(rv, rv);
534 0 : if (!more)
535 : {
536 0 : mGlobalStatus = JAR_NO_MANIFEST;
537 0 : mParsedManifest = true;
538 0 : return NS_OK;
539 : }
540 :
541 0 : nsAutoCString manifestFilename;
542 0 : rv = files->GetNext(manifestFilename);
543 0 : NS_ENSURE_SUCCESS(rv, rv);
544 :
545 : // Check if there is more than one manifest, if so then error!
546 0 : rv = files->HasMore(&more);
547 0 : if (NS_FAILED(rv)) return rv;
548 0 : if (more)
549 : {
550 0 : mParsedManifest = true;
551 0 : nsZipArchive::sFileCorruptedReason = "nsJAR: duplicate manifests";
552 0 : return NS_ERROR_FILE_CORRUPTED; // More than one MF file
553 : }
554 :
555 0 : nsXPIDLCString manifestBuffer;
556 0 : rv = LoadEntry(manifestFilename, manifestBuffer);
557 0 : if (NS_FAILED(rv)) return rv;
558 :
559 : //-- Parse it
560 0 : rv = ParseOneFile(manifestBuffer, JAR_MF);
561 0 : if (NS_FAILED(rv)) return rv;
562 :
563 : //-- (2)Signature (SF) file
564 : // If there are multiple signatures, we select one.
565 0 : rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
566 0 : if (!files) rv = NS_ERROR_FAILURE;
567 0 : if (NS_FAILED(rv)) return rv;
568 : //-- Get an SF file
569 0 : rv = files->HasMore(&more);
570 0 : if (NS_FAILED(rv)) return rv;
571 0 : if (!more)
572 : {
573 0 : mGlobalStatus = JAR_NO_MANIFEST;
574 0 : mParsedManifest = true;
575 0 : return NS_OK;
576 : }
577 0 : rv = files->GetNext(manifestFilename);
578 0 : if (NS_FAILED(rv)) return rv;
579 :
580 0 : rv = LoadEntry(manifestFilename, manifestBuffer);
581 0 : if (NS_FAILED(rv)) return rv;
582 :
583 : //-- Get its corresponding signature file
584 0 : nsAutoCString sigFilename(manifestFilename);
585 0 : int32_t extension = sigFilename.RFindChar('.') + 1;
586 0 : NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
587 0 : (void)sigFilename.Cut(extension, 2);
588 0 : nsXPIDLCString sigBuffer;
589 : {
590 0 : nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
591 0 : rv = LoadEntry(tempFilename, sigBuffer);
592 : }
593 0 : if (NS_FAILED(rv))
594 : {
595 0 : nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
596 0 : rv = LoadEntry(tempFilename, sigBuffer);
597 : }
598 0 : if (NS_FAILED(rv))
599 : {
600 0 : mGlobalStatus = JAR_NO_MANIFEST;
601 0 : mParsedManifest = true;
602 0 : return NS_OK;
603 : }
604 :
605 : //-- Get the signature verifier service
606 : nsCOMPtr<nsIDataSignatureVerifier> verifier(
607 0 : do_GetService("@mozilla.org/security/datasignatureverifier;1", &rv));
608 0 : if (NS_FAILED(rv)) // No signature verifier available
609 : {
610 0 : mGlobalStatus = JAR_NO_MANIFEST;
611 0 : mParsedManifest = true;
612 0 : return NS_OK;
613 : }
614 :
615 : //-- Verify that the signature file is a valid signature of the SF file
616 : int32_t verifyError;
617 0 : rv = verifier->VerifySignature(sigBuffer, manifestBuffer,
618 0 : &verifyError, getter_AddRefs(mSigningCert));
619 0 : if (NS_FAILED(rv)) return rv;
620 0 : if (mSigningCert && verifyError == nsIDataSignatureVerifier::VERIFY_OK) {
621 0 : mGlobalStatus = JAR_VALID_MANIFEST;
622 0 : } else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER) {
623 0 : mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
624 : } else {
625 0 : mGlobalStatus = JAR_INVALID_SIG;
626 : }
627 :
628 : //-- Parse the SF file. If the verification above failed, principal
629 : // is null, and ParseOneFile will mark the relevant entries as invalid.
630 : // if ParseOneFile fails, then it has no effect, and we can safely
631 : // continue to the next SF file, or return.
632 0 : ParseOneFile(manifestBuffer, JAR_SF);
633 0 : mParsedManifest = true;
634 :
635 0 : return NS_OK;
636 : }
637 :
638 : nsresult
639 0 : nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
640 : {
641 : //-- Check file header
642 0 : const char* nextLineStart = filebuf;
643 0 : nsAutoCString curLine;
644 : int32_t linelen;
645 0 : linelen = ReadLine(&nextLineStart);
646 0 : curLine.Assign(filebuf, linelen);
647 :
648 0 : if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
649 0 : ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) {
650 0 : nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest header";
651 0 : return NS_ERROR_FILE_CORRUPTED;
652 : }
653 :
654 : //-- Skip header section
655 0 : do {
656 0 : linelen = ReadLine(&nextLineStart);
657 0 : } while (linelen > 0);
658 :
659 : //-- Set up parsing variables
660 : const char* curPos;
661 0 : const char* sectionStart = nextLineStart;
662 :
663 0 : nsJARManifestItem* curItemMF = nullptr;
664 0 : bool foundName = false;
665 0 : if (aFileType == JAR_MF) {
666 0 : curItemMF = new nsJARManifestItem();
667 : }
668 :
669 0 : nsAutoCString curItemName;
670 0 : nsAutoCString storedSectionDigest;
671 :
672 : for(;;)
673 : {
674 0 : curPos = nextLineStart;
675 0 : linelen = ReadLine(&nextLineStart);
676 0 : curLine.Assign(curPos, linelen);
677 0 : if (linelen == 0)
678 : // end of section (blank line or end-of-file)
679 : {
680 0 : if (aFileType == JAR_MF)
681 : {
682 0 : mTotalItemsInManifest++;
683 0 : if (curItemMF->mType != JAR_INVALID)
684 : {
685 : //-- Did this section have a name: line?
686 0 : if(!foundName)
687 0 : curItemMF->mType = JAR_INVALID;
688 : else
689 : {
690 : //-- If it's an internal item, it must correspond
691 : // to a valid jar entry
692 0 : if (curItemMF->mType == JAR_INTERNAL)
693 : {
694 : bool exists;
695 0 : nsresult rv = HasEntry(curItemName, &exists);
696 0 : if (NS_FAILED(rv) || !exists)
697 0 : curItemMF->mType = JAR_INVALID;
698 : }
699 : //-- Check for duplicates
700 0 : if (mManifestData.Contains(curItemName)) {
701 0 : curItemMF->mType = JAR_INVALID;
702 : }
703 : }
704 : }
705 :
706 0 : if (curItemMF->mType == JAR_INVALID)
707 0 : delete curItemMF;
708 : else //-- calculate section digest
709 : {
710 0 : uint32_t sectionLength = curPos - sectionStart;
711 0 : CalculateDigest(sectionStart, sectionLength,
712 0 : curItemMF->calculatedSectionDigest);
713 : //-- Save item in the hashtable
714 0 : mManifestData.Put(curItemName, curItemMF);
715 : }
716 0 : if (nextLineStart == nullptr) // end-of-file
717 0 : break;
718 :
719 0 : sectionStart = nextLineStart;
720 0 : curItemMF = new nsJARManifestItem();
721 : } // (aFileType == JAR_MF)
722 : else
723 : //-- file type is SF, compare digest with calculated
724 : // section digests from MF file.
725 : {
726 0 : if (foundName)
727 : {
728 0 : nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
729 0 : if(curItemSF)
730 : {
731 0 : NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
732 : "SECURITY ERROR: nsJARManifestItem not correctly initialized");
733 0 : curItemSF->status = mGlobalStatus;
734 0 : if (curItemSF->status == JAR_VALID_MANIFEST)
735 : { // Compare digests
736 0 : if (storedSectionDigest.IsEmpty())
737 0 : curItemSF->status = JAR_NOT_SIGNED;
738 : else
739 : {
740 0 : if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
741 0 : curItemSF->status = JAR_INVALID_MANIFEST;
742 0 : curItemSF->calculatedSectionDigest.Truncate();
743 0 : storedSectionDigest.Truncate();
744 : }
745 : } // (aPrincipal != nullptr)
746 : } // if(curItemSF)
747 : } // if(foundName)
748 :
749 0 : if(nextLineStart == nullptr) // end-of-file
750 0 : break;
751 : } // aFileType == JAR_SF
752 0 : foundName = false;
753 0 : continue;
754 : } // if(linelen == 0)
755 :
756 : //-- Look for continuations (beginning with a space) on subsequent lines
757 : // and append them to the current line.
758 0 : while(*nextLineStart == ' ')
759 : {
760 0 : curPos = nextLineStart;
761 0 : int32_t continuationLen = ReadLine(&nextLineStart) - 1;
762 0 : nsAutoCString continuation(curPos+1, continuationLen);
763 0 : curLine += continuation;
764 0 : linelen += continuationLen;
765 : }
766 :
767 : //-- Find colon in current line, this separates name from value
768 0 : int32_t colonPos = curLine.FindChar(':');
769 0 : if (colonPos == -1) // No colon on line, ignore line
770 0 : continue;
771 : //-- Break down the line
772 0 : nsAutoCString lineName;
773 0 : curLine.Left(lineName, colonPos);
774 0 : nsAutoCString lineData;
775 0 : curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
776 :
777 : //-- Lines to look for:
778 : // (1) Digest:
779 0 : if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
780 : //-- This is a digest line, save the data in the appropriate place
781 : {
782 0 : if(aFileType == JAR_MF)
783 0 : curItemMF->storedEntryDigest = lineData;
784 : else
785 0 : storedSectionDigest = lineData;
786 0 : continue;
787 : }
788 :
789 : // (2) Name: associates this manifest section with a file in the jar.
790 0 : if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
791 : {
792 0 : curItemName = lineData;
793 0 : foundName = true;
794 0 : continue;
795 : }
796 :
797 : // (3) Magic: this may be an inline Javascript.
798 : // We can't do any other kind of magic.
799 0 : if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
800 : {
801 0 : if (lineData.LowerCaseEqualsLiteral("javascript"))
802 0 : curItemMF->mType = JAR_EXTERNAL;
803 : else
804 0 : curItemMF->mType = JAR_INVALID;
805 0 : continue;
806 : }
807 :
808 0 : } // for (;;)
809 0 : return NS_OK;
810 : } //ParseOneFile()
811 :
812 : nsresult
813 0 : nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
814 : uint32_t aLen)
815 : {
816 0 : if (aManItem->status == JAR_VALID_MANIFEST)
817 : {
818 0 : if (aManItem->storedEntryDigest.IsEmpty())
819 : // No entry digests in manifest file. Entry is unsigned.
820 0 : aManItem->status = JAR_NOT_SIGNED;
821 : else
822 : { //-- Calculate and compare digests
823 0 : nsCString calculatedEntryDigest;
824 0 : nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
825 0 : if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
826 0 : if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
827 0 : aManItem->status = JAR_INVALID_ENTRY;
828 0 : aManItem->storedEntryDigest.Truncate();
829 : }
830 : }
831 0 : aManItem->entryVerified = true;
832 0 : return NS_OK;
833 : }
834 :
835 0 : void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
836 : {
837 : //-- Generate error message
838 0 : nsAutoString message;
839 0 : message.AssignLiteral("Signature Verification Error: the signature on ");
840 0 : if (!aFilename.IsEmpty())
841 0 : AppendASCIItoUTF16(aFilename, message);
842 : else
843 0 : message.AppendLiteral("this .jar archive");
844 0 : message.AppendLiteral(" is invalid because ");
845 0 : switch(errorCode)
846 : {
847 : case JAR_NOT_SIGNED:
848 0 : message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
849 0 : break;
850 : case JAR_INVALID_SIG:
851 0 : message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
852 0 : break;
853 : case JAR_INVALID_UNKNOWN_CA:
854 0 : message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
855 0 : break;
856 : case JAR_INVALID_MANIFEST:
857 0 : message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
858 0 : break;
859 : case JAR_INVALID_ENTRY:
860 0 : message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
861 0 : break;
862 : case JAR_NO_MANIFEST:
863 0 : message.AppendLiteral("the archive did not contain a manifest.");
864 0 : break;
865 : default:
866 0 : message.AppendLiteral("of an unknown problem.");
867 : }
868 :
869 : // Report error in JS console
870 0 : nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
871 0 : if (console)
872 : {
873 0 : console->LogStringMessage(message.get());
874 : }
875 : #ifdef DEBUG
876 0 : char* messageCstr = ToNewCString(message);
877 0 : if (!messageCstr) return;
878 0 : fprintf(stderr, "%s\n", messageCstr);
879 0 : free(messageCstr);
880 : #endif
881 : }
882 :
883 :
884 0 : nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
885 : nsCString& digest)
886 : {
887 : nsresult rv;
888 :
889 0 : nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
890 0 : if (NS_FAILED(rv)) return rv;
891 :
892 0 : rv = hasher->Init(nsICryptoHash::SHA1);
893 0 : if (NS_FAILED(rv)) return rv;
894 :
895 0 : rv = hasher->Update((const uint8_t*) aInBuf, aLen);
896 0 : if (NS_FAILED(rv)) return rv;
897 :
898 0 : return hasher->Finish(true, digest);
899 : }
900 :
901 0 : NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
902 :
903 : //----------------------------------------------
904 : // nsJAREnumerator::HasMore
905 : //----------------------------------------------
906 : NS_IMETHODIMP
907 0 : nsJAREnumerator::HasMore(bool* aResult)
908 : {
909 : // try to get the next element
910 0 : if (!mName) {
911 0 : NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
912 0 : nsresult rv = mFind->FindNext( &mName, &mNameLen );
913 0 : if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
914 0 : *aResult = false; // No more matches available
915 0 : return NS_OK;
916 : }
917 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
918 : }
919 :
920 0 : *aResult = true;
921 0 : return NS_OK;
922 : }
923 :
924 : //----------------------------------------------
925 : // nsJAREnumerator::GetNext
926 : //----------------------------------------------
927 : NS_IMETHODIMP
928 0 : nsJAREnumerator::GetNext(nsACString& aResult)
929 : {
930 : // check if the current item is "stale"
931 0 : if (!mName) {
932 : bool bMore;
933 0 : nsresult rv = HasMore(&bMore);
934 0 : if (NS_FAILED(rv) || !bMore)
935 0 : return NS_ERROR_FAILURE; // no error translation
936 : }
937 0 : aResult.Assign(mName, mNameLen);
938 0 : mName = 0; // we just gave this one away
939 0 : return NS_OK;
940 : }
941 :
942 :
943 0 : NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
944 :
945 0 : nsJARItem::nsJARItem(nsZipItem* aZipItem)
946 0 : : mSize(aZipItem->Size()),
947 0 : mRealsize(aZipItem->RealSize()),
948 0 : mCrc32(aZipItem->CRC32()),
949 0 : mLastModTime(aZipItem->LastModTime()),
950 0 : mCompression(aZipItem->Compression()),
951 0 : mPermissions(aZipItem->Mode()),
952 0 : mIsDirectory(aZipItem->IsDirectory()),
953 0 : mIsSynthetic(aZipItem->isSynthetic)
954 : {
955 0 : }
956 :
957 : //------------------------------------------
958 : // nsJARItem::GetCompression
959 : //------------------------------------------
960 : NS_IMETHODIMP
961 0 : nsJARItem::GetCompression(uint16_t *aCompression)
962 : {
963 0 : NS_ENSURE_ARG_POINTER(aCompression);
964 :
965 0 : *aCompression = mCompression;
966 0 : return NS_OK;
967 : }
968 :
969 : //------------------------------------------
970 : // nsJARItem::GetSize
971 : //------------------------------------------
972 : NS_IMETHODIMP
973 0 : nsJARItem::GetSize(uint32_t *aSize)
974 : {
975 0 : NS_ENSURE_ARG_POINTER(aSize);
976 :
977 0 : *aSize = mSize;
978 0 : return NS_OK;
979 : }
980 :
981 : //------------------------------------------
982 : // nsJARItem::GetRealSize
983 : //------------------------------------------
984 : NS_IMETHODIMP
985 0 : nsJARItem::GetRealSize(uint32_t *aRealsize)
986 : {
987 0 : NS_ENSURE_ARG_POINTER(aRealsize);
988 :
989 0 : *aRealsize = mRealsize;
990 0 : return NS_OK;
991 : }
992 :
993 : //------------------------------------------
994 : // nsJARItem::GetCrc32
995 : //------------------------------------------
996 : NS_IMETHODIMP
997 0 : nsJARItem::GetCRC32(uint32_t *aCrc32)
998 : {
999 0 : NS_ENSURE_ARG_POINTER(aCrc32);
1000 :
1001 0 : *aCrc32 = mCrc32;
1002 0 : return NS_OK;
1003 : }
1004 :
1005 : //------------------------------------------
1006 : // nsJARItem::GetIsDirectory
1007 : //------------------------------------------
1008 : NS_IMETHODIMP
1009 0 : nsJARItem::GetIsDirectory(bool *aIsDirectory)
1010 : {
1011 0 : NS_ENSURE_ARG_POINTER(aIsDirectory);
1012 :
1013 0 : *aIsDirectory = mIsDirectory;
1014 0 : return NS_OK;
1015 : }
1016 :
1017 : //------------------------------------------
1018 : // nsJARItem::GetIsSynthetic
1019 : //------------------------------------------
1020 : NS_IMETHODIMP
1021 0 : nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
1022 : {
1023 0 : NS_ENSURE_ARG_POINTER(aIsSynthetic);
1024 :
1025 0 : *aIsSynthetic = mIsSynthetic;
1026 0 : return NS_OK;
1027 : }
1028 :
1029 : //------------------------------------------
1030 : // nsJARItem::GetLastModifiedTime
1031 : //------------------------------------------
1032 : NS_IMETHODIMP
1033 0 : nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
1034 : {
1035 0 : NS_ENSURE_ARG_POINTER(aLastModTime);
1036 :
1037 0 : *aLastModTime = mLastModTime;
1038 0 : return NS_OK;
1039 : }
1040 :
1041 : //------------------------------------------
1042 : // nsJARItem::GetPermissions
1043 : //------------------------------------------
1044 : NS_IMETHODIMP
1045 0 : nsJARItem::GetPermissions(uint32_t* aPermissions)
1046 : {
1047 0 : NS_ENSURE_ARG_POINTER(aPermissions);
1048 :
1049 0 : *aPermissions = mPermissions;
1050 0 : return NS_OK;
1051 : }
1052 :
1053 : ////////////////////////////////////////////////////////////////////////////////
1054 : // nsIZipReaderCache
1055 :
1056 16 : NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
1057 :
1058 1 : nsZipReaderCache::nsZipReaderCache()
1059 : : mLock("nsZipReaderCache.mLock")
1060 : , mCacheSize(0)
1061 1 : , mZips()
1062 : #ifdef ZIP_CACHE_HIT_RATE
1063 : ,
1064 : mZipCacheLookups(0),
1065 : mZipCacheHits(0),
1066 : mZipCacheFlushes(0),
1067 : mZipSyncMisses(0)
1068 : #endif
1069 : {
1070 1 : }
1071 :
1072 : NS_IMETHODIMP
1073 1 : nsZipReaderCache::Init(uint32_t cacheSize)
1074 : {
1075 1 : mCacheSize = cacheSize;
1076 :
1077 : // Register as a memory pressure observer
1078 : nsCOMPtr<nsIObserverService> os =
1079 2 : do_GetService("@mozilla.org/observer-service;1");
1080 1 : if (os)
1081 : {
1082 1 : os->AddObserver(this, "memory-pressure", true);
1083 1 : os->AddObserver(this, "chrome-flush-caches", true);
1084 1 : os->AddObserver(this, "flush-cache-entry", true);
1085 : }
1086 : // ignore failure of the observer registration.
1087 :
1088 2 : return NS_OK;
1089 : }
1090 :
1091 0 : nsZipReaderCache::~nsZipReaderCache()
1092 : {
1093 0 : for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
1094 0 : iter.UserData()->SetZipReaderCache(nullptr);
1095 : }
1096 :
1097 : #ifdef ZIP_CACHE_HIT_RATE
1098 : printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
1099 : mCacheSize, mZipCacheHits, mZipCacheLookups,
1100 : (float)mZipCacheHits / mZipCacheLookups,
1101 : mZipCacheFlushes, mZipSyncMisses);
1102 : #endif
1103 0 : }
1104 :
1105 : NS_IMETHODIMP
1106 0 : nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
1107 : {
1108 0 : NS_ENSURE_ARG_POINTER(zipFile);
1109 : nsresult rv;
1110 0 : MutexAutoLock lock(mLock);
1111 :
1112 0 : nsAutoCString uri;
1113 0 : rv = zipFile->GetNativePath(uri);
1114 0 : if (NS_FAILED(rv))
1115 0 : return rv;
1116 :
1117 0 : uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1118 :
1119 0 : *aResult = mZips.Contains(uri);
1120 0 : return NS_OK;
1121 : }
1122 :
1123 : NS_IMETHODIMP
1124 0 : nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
1125 : {
1126 0 : NS_ENSURE_ARG_POINTER(zipFile);
1127 : nsresult rv;
1128 0 : MutexAutoLock lock(mLock);
1129 :
1130 : #ifdef ZIP_CACHE_HIT_RATE
1131 : mZipCacheLookups++;
1132 : #endif
1133 :
1134 0 : nsAutoCString uri;
1135 0 : rv = zipFile->GetNativePath(uri);
1136 0 : if (NS_FAILED(rv)) return rv;
1137 :
1138 0 : uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1139 :
1140 0 : RefPtr<nsJAR> zip;
1141 0 : mZips.Get(uri, getter_AddRefs(zip));
1142 0 : if (zip) {
1143 : #ifdef ZIP_CACHE_HIT_RATE
1144 : mZipCacheHits++;
1145 : #endif
1146 0 : zip->ClearReleaseTime();
1147 : } else {
1148 0 : zip = new nsJAR();
1149 0 : zip->SetZipReaderCache(this);
1150 0 : rv = zip->Open(zipFile);
1151 0 : if (NS_FAILED(rv)) {
1152 0 : return rv;
1153 : }
1154 :
1155 0 : MOZ_ASSERT(!mZips.Contains(uri));
1156 0 : mZips.Put(uri, zip);
1157 : }
1158 0 : zip.forget(result);
1159 0 : return rv;
1160 : }
1161 :
1162 : NS_IMETHODIMP
1163 0 : nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
1164 : nsIZipReader* *result)
1165 : {
1166 0 : NS_ENSURE_ARG_POINTER(zipFile);
1167 :
1168 0 : nsCOMPtr<nsIZipReader> outerZipReader;
1169 0 : nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
1170 0 : NS_ENSURE_SUCCESS(rv, rv);
1171 :
1172 0 : MutexAutoLock lock(mLock);
1173 :
1174 : #ifdef ZIP_CACHE_HIT_RATE
1175 : mZipCacheLookups++;
1176 : #endif
1177 :
1178 0 : nsAutoCString uri;
1179 0 : rv = zipFile->GetNativePath(uri);
1180 0 : if (NS_FAILED(rv)) return rv;
1181 :
1182 0 : uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
1183 0 : uri.AppendLiteral("!/");
1184 0 : uri.Append(entry);
1185 :
1186 0 : RefPtr<nsJAR> zip;
1187 0 : mZips.Get(uri, getter_AddRefs(zip));
1188 0 : if (zip) {
1189 : #ifdef ZIP_CACHE_HIT_RATE
1190 : mZipCacheHits++;
1191 : #endif
1192 0 : zip->ClearReleaseTime();
1193 : } else {
1194 0 : zip = new nsJAR();
1195 0 : zip->SetZipReaderCache(this);
1196 :
1197 0 : rv = zip->OpenInner(outerZipReader, entry);
1198 0 : if (NS_FAILED(rv)) {
1199 0 : return rv;
1200 : }
1201 :
1202 0 : MOZ_ASSERT(!mZips.Contains(uri));
1203 0 : mZips.Put(uri, zip);
1204 : }
1205 0 : zip.forget(result);
1206 0 : return rv;
1207 : }
1208 :
1209 : NS_IMETHODIMP
1210 0 : nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal)
1211 : {
1212 : #if defined(XP_WIN)
1213 : MOZ_CRASH("Not implemented");
1214 : return NS_ERROR_NOT_IMPLEMENTED;
1215 : #else
1216 0 : if (!zipFile) {
1217 0 : return NS_ERROR_FAILURE;
1218 : }
1219 :
1220 : nsresult rv;
1221 0 : nsAutoCString uri;
1222 0 : rv = zipFile->GetNativePath(uri);
1223 0 : if (NS_FAILED(rv)) {
1224 0 : return rv;
1225 : }
1226 0 : uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1227 :
1228 0 : MutexAutoLock lock(mLock);
1229 0 : RefPtr<nsJAR> zip;
1230 0 : mZips.Get(uri, getter_AddRefs(zip));
1231 0 : if (!zip) {
1232 0 : return NS_ERROR_FAILURE;
1233 : }
1234 :
1235 0 : zip->ClearReleaseTime();
1236 0 : rv = zip->GetNSPRFileDesc(aRetVal);
1237 : // Do this to avoid possible deadlock on mLock with ReleaseZip().
1238 0 : MutexAutoUnlock unlock(mLock);
1239 0 : RefPtr<nsJAR> zipTemp = zip.forget();
1240 0 : return rv;
1241 : #endif /* XP_WIN */
1242 : }
1243 :
1244 : nsresult
1245 0 : nsZipReaderCache::ReleaseZip(nsJAR* zip)
1246 : {
1247 : nsresult rv;
1248 0 : MutexAutoLock lock(mLock);
1249 :
1250 : // It is possible that two thread compete for this zip. The dangerous
1251 : // case is where one thread Releases the zip and discovers that the ref
1252 : // count has gone to one. Before it can call this ReleaseZip method
1253 : // another thread calls our GetZip method. The ref count goes to two. That
1254 : // second thread then Releases the zip and the ref count goes to one. It
1255 : // then tries to enter this ReleaseZip method and blocks while the first
1256 : // thread is still here. The first thread continues and remove the zip from
1257 : // the cache and calls its Release method sending the ref count to 0 and
1258 : // deleting the zip. However, the second thread is still blocked at the
1259 : // start of ReleaseZip, but the 'zip' param now hold a reference to a
1260 : // deleted zip!
1261 : //
1262 : // So, we are going to try safeguarding here by searching our hashtable while
1263 : // locked here for the zip. We return fast if it is not found.
1264 :
1265 0 : bool found = false;
1266 0 : for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
1267 0 : if (zip == iter.UserData()) {
1268 0 : found = true;
1269 0 : break;
1270 : }
1271 : }
1272 :
1273 0 : if (!found) {
1274 : #ifdef ZIP_CACHE_HIT_RATE
1275 : mZipSyncMisses++;
1276 : #endif
1277 0 : return NS_OK;
1278 : }
1279 :
1280 0 : zip->SetReleaseTime();
1281 :
1282 0 : if (mZips.Count() <= mCacheSize)
1283 0 : return NS_OK;
1284 :
1285 : // Find the oldest zip.
1286 0 : nsJAR* oldest = nullptr;
1287 0 : for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
1288 0 : nsJAR* current = iter.UserData();
1289 0 : PRIntervalTime currentReleaseTime = current->GetReleaseTime();
1290 0 : if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
1291 0 : if (oldest == nullptr ||
1292 0 : currentReleaseTime < oldest->GetReleaseTime()) {
1293 0 : oldest = current;
1294 : }
1295 : }
1296 : }
1297 :
1298 : // Because of the craziness above it is possible that there is no zip that
1299 : // needs removing.
1300 0 : if (!oldest)
1301 0 : return NS_OK;
1302 :
1303 : #ifdef ZIP_CACHE_HIT_RATE
1304 : mZipCacheFlushes++;
1305 : #endif
1306 :
1307 : // remove from hashtable
1308 0 : nsAutoCString uri;
1309 0 : rv = oldest->GetJarPath(uri);
1310 0 : if (NS_FAILED(rv))
1311 0 : return rv;
1312 :
1313 0 : if (oldest->mOuterZipEntry.IsEmpty()) {
1314 0 : uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1315 : } else {
1316 0 : uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
1317 0 : uri.AppendLiteral("!/");
1318 0 : uri.Append(oldest->mOuterZipEntry);
1319 : }
1320 :
1321 : // Retrieving and removing the JAR must be done without an extra AddRef
1322 : // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
1323 : // an extra time and trigger a deadlock.
1324 0 : RefPtr<nsJAR> removed;
1325 0 : mZips.Remove(uri, getter_AddRefs(removed));
1326 0 : NS_ASSERTION(removed, "botched");
1327 0 : NS_ASSERTION(oldest == removed, "removed wrong entry");
1328 :
1329 0 : if (removed)
1330 0 : removed->SetZipReaderCache(nullptr);
1331 :
1332 0 : return NS_OK;
1333 : }
1334 :
1335 : NS_IMETHODIMP
1336 0 : nsZipReaderCache::Observe(nsISupports *aSubject,
1337 : const char *aTopic,
1338 : const char16_t *aSomeData)
1339 : {
1340 0 : if (strcmp(aTopic, "memory-pressure") == 0) {
1341 0 : MutexAutoLock lock(mLock);
1342 0 : for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
1343 0 : RefPtr<nsJAR>& current = iter.Data();
1344 0 : if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
1345 0 : current->SetZipReaderCache(nullptr);
1346 0 : iter.Remove();
1347 : }
1348 : }
1349 : }
1350 0 : else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1351 0 : MutexAutoLock lock(mLock);
1352 0 : for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
1353 0 : iter.UserData()->SetZipReaderCache(nullptr);
1354 : }
1355 0 : mZips.Clear();
1356 : }
1357 0 : else if (strcmp(aTopic, "flush-cache-entry") == 0) {
1358 0 : nsCOMPtr<nsIFile> file;
1359 0 : if (aSubject) {
1360 0 : file = do_QueryInterface(aSubject);
1361 0 : } else if (aSomeData) {
1362 0 : nsDependentString fileName(aSomeData);
1363 0 : Unused << NS_NewLocalFile(fileName, false, getter_AddRefs(file));
1364 : }
1365 :
1366 0 : if (!file)
1367 0 : return NS_OK;
1368 :
1369 0 : nsAutoCString uri;
1370 0 : if (NS_FAILED(file->GetNativePath(uri)))
1371 0 : return NS_OK;
1372 :
1373 0 : uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
1374 :
1375 0 : MutexAutoLock lock(mLock);
1376 :
1377 0 : RefPtr<nsJAR> zip;
1378 0 : mZips.Get(uri, getter_AddRefs(zip));
1379 0 : if (!zip)
1380 0 : return NS_OK;
1381 :
1382 : #ifdef ZIP_CACHE_HIT_RATE
1383 : mZipCacheFlushes++;
1384 : #endif
1385 :
1386 0 : zip->SetZipReaderCache(nullptr);
1387 :
1388 0 : mZips.Remove(uri);
1389 : }
1390 0 : return NS_OK;
1391 : }
1392 :
1393 : ////////////////////////////////////////////////////////////////////////////////
|