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