Remove TreeInfo (bug 525371, r=lw).
[mozilla-central.git] / modules / libjar / nsJAR.cpp
blob8cf28c65ba1c65aa6b501929dc834ef51068374f
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
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/
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.
15 * The Original Code is Mozilla Communicator client code, released
16 * March 31, 1998.
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.
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>
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.
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"
51 #ifdef XP_UNIX
52 #include <sys/stat.h>
53 #elif defined (XP_WIN) || defined(XP_OS2)
54 #include <io.h>
55 #endif
57 //----------------------------------------------
58 // nsJARManifestItem declaration
59 //----------------------------------------------
61 * nsJARManifestItem contains meta-information pertaining
62 * to an individual JAR entry, taken from the
63 * META-INF/MANIFEST.MF and META-INF/ *.SF files.
64 * This is security-critical information, defined here so it is not
65 * accessible from anywhere else.
67 typedef enum
69 JAR_INVALID = 1,
70 JAR_INTERNAL = 2,
71 JAR_EXTERNAL = 3
72 } JARManifestItemType;
74 class nsJARManifestItem
76 public:
77 JARManifestItemType mType;
79 // True if the second step of verification (VerifyEntry)
80 // has taken place:
81 PRBool entryVerified;
83 // Not signed, valid, or failure code
84 PRInt16 status;
86 // Internal storage of digests
87 nsCString calculatedSectionDigest;
88 nsCString storedEntryDigest;
90 nsJARManifestItem();
91 virtual ~nsJARManifestItem();
94 //-------------------------------------------------
95 // nsJARManifestItem constructors and destructor
96 //-------------------------------------------------
97 nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
98 entryVerified(PR_FALSE),
99 status(JAR_NOT_SIGNED)
103 nsJARManifestItem::~nsJARManifestItem()
107 //----------------------------------------------
108 // nsJAR constructor/destructor
109 //----------------------------------------------
110 static PRBool
111 DeleteManifestEntry(nsHashKey* aKey, void* aData, void* closure)
113 //-- deletes an entry in mManifestData.
114 delete (nsJARManifestItem*)aData;
115 return PR_TRUE;
118 // The following initialization makes a guess of 10 entries per jarfile.
119 nsJAR::nsJAR(): mManifestData(nsnull, nsnull, DeleteManifestEntry, nsnull, 10),
120 mParsedManifest(PR_FALSE),
121 mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
122 mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
123 mCache(nsnull),
124 mLock(nsnull),
125 mTotalItemsInManifest(0)
129 nsJAR::~nsJAR()
131 Close();
134 NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsJAR, nsIZipReader, nsIJAR)
135 NS_IMPL_THREADSAFE_ADDREF(nsJAR)
137 // Custom Release method works with nsZipReaderCache...
138 nsrefcnt nsJAR::Release(void)
140 nsrefcnt count;
141 NS_PRECONDITION(0 != mRefCnt, "dup release");
142 count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);
143 NS_LOG_RELEASE(this, count, "nsJAR");
144 if (0 == count) {
145 mRefCnt = 1; /* stabilize */
146 /* enable this to find non-threadsafe destructors: */
147 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
148 NS_DELETEXPCOM(this);
149 return 0;
151 else if (1 == count && mCache) {
152 nsresult rv = mCache->ReleaseZip(this);
153 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
155 return count;
158 //----------------------------------------------
159 // nsIZipReader implementation
160 //----------------------------------------------
162 NS_IMETHODIMP
163 nsJAR::Open(nsIFile* zipFile)
165 NS_ENSURE_ARG_POINTER(zipFile);
166 if (mLock) return NS_ERROR_FAILURE; // Already open!
168 mZipFile = zipFile;
170 mLock = PR_NewLock();
171 NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
173 PRFileDesc *fd = OpenFile();
174 NS_ENSURE_TRUE(fd, NS_ERROR_FAILURE);
176 nsresult rv = mZip.OpenArchive(fd);
177 if (NS_FAILED(rv)) Close();
179 return rv;
182 NS_IMETHODIMP
183 nsJAR::GetFile(nsIFile* *result)
185 *result = mZipFile;
186 NS_IF_ADDREF(*result);
187 return NS_OK;
190 NS_IMETHODIMP
191 nsJAR::Close()
193 if (mLock) {
194 PR_DestroyLock(mLock);
195 mLock = nsnull;
198 mParsedManifest = PR_FALSE;
199 mManifestData.Reset();
200 mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
201 mTotalItemsInManifest = 0;
203 return mZip.CloseArchive();
206 NS_IMETHODIMP
207 nsJAR::Test(const char *aEntryName)
209 return mZip.Test(aEntryName);
212 NS_IMETHODIMP
213 nsJAR::Extract(const char *zipEntry, nsIFile* outFile)
215 // nsZipArchive and zlib are not thread safe
216 // we need to use a lock to prevent bug #51267
217 nsAutoLock lock(mLock);
219 nsresult rv;
220 nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(outFile, &rv);
221 if (NS_FAILED(rv)) return rv;
223 nsZipItem *item = mZip.GetItem(zipEntry);
224 NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
226 // Remove existing file or directory so we set permissions correctly.
227 // If it's a directory that already exists and contains files, throw
228 // an exception and return.
230 //XXX Bug 332139:
231 //XXX If we guarantee that rv in the case of a non-empty directory
232 //XXX is always FILE_DIR_NOT_EMPTY, we can remove
233 //XXX |rv == NS_ERROR_FAILURE| - bug 322183 needs to be completely
234 //XXX fixed before that can happen
235 rv = localFile->Remove(PR_FALSE);
236 if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
237 rv == NS_ERROR_FAILURE)
238 return rv;
240 if (item->IsDirectory())
242 rv = localFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
243 //XXX Do this in nsZipArchive? It would be nice to keep extraction
244 //XXX code completely there, but that would require a way to get a
245 //XXX PRDir from localFile.
247 else
249 PRFileDesc* fd;
250 rv = localFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
251 if (NS_FAILED(rv)) return rv;
253 // ExtractFile also closes the fd handle and resolves the symlink if needed
254 nsCAutoString path;
255 rv = outFile->GetNativePath(path);
256 if (NS_FAILED(rv)) return rv;
258 rv = mZip.ExtractFile(item, path.get(), fd);
260 if (NS_FAILED(rv)) return rv;
262 PRTime prtime = GetModTime(item->Date(), item->Time());
263 // nsIFile needs milliseconds, while prtime is in microseconds.
264 PRTime conversion = LL_ZERO;
265 PRTime newTime = LL_ZERO;
266 LL_I2L(conversion, PR_USEC_PER_MSEC);
267 LL_DIV(newTime, prtime, conversion);
268 // non-fatal if this fails, ignore errors
269 outFile->SetLastModifiedTime(newTime);
271 return NS_OK;
274 NS_IMETHODIMP
275 nsJAR::GetEntry(const char *aEntryName, nsIZipEntry* *result)
277 nsZipItem* zipItem = mZip.GetItem(aEntryName);
278 NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
280 nsJARItem* jarItem = new nsJARItem(zipItem);
281 NS_ENSURE_TRUE(jarItem, NS_ERROR_OUT_OF_MEMORY);
283 NS_ADDREF(*result = jarItem);
284 return NS_OK;
287 NS_IMETHODIMP
288 nsJAR::HasEntry(const nsACString &aEntryName, PRBool *result)
290 *result = mZip.GetItem(PromiseFlatCString(aEntryName).get()) != nsnull;
291 return NS_OK;
294 NS_IMETHODIMP
295 nsJAR::FindEntries(const char *aPattern, nsIUTF8StringEnumerator **result)
297 NS_ENSURE_ARG_POINTER(result);
299 nsZipFind *find;
300 nsresult rv = mZip.FindInit(aPattern, &find);
301 NS_ENSURE_SUCCESS(rv, rv);
303 nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
304 if (!zipEnum) {
305 delete find;
306 return NS_ERROR_OUT_OF_MEMORY;
309 NS_ADDREF(*result = zipEnum);
310 return NS_OK;
313 NS_IMETHODIMP
314 nsJAR::GetInputStream(const char* aFilename, nsIInputStream** result)
316 return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
319 NS_IMETHODIMP
320 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
321 const char* aEntryName, nsIInputStream** result)
323 NS_ENSURE_ARG_POINTER(aEntryName);
324 NS_ENSURE_ARG_POINTER(result);
326 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
327 nsZipItem *item = nsnull;
328 if (*aEntryName) {
329 // First check if item exists in jar
330 item = mZip.GetItem(aEntryName);
331 if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
333 nsJARInputStream* jis = new nsJARInputStream();
334 // addref now so we can call InitFile/InitDirectory()
335 NS_ENSURE_TRUE(jis, NS_ERROR_OUT_OF_MEMORY);
336 NS_ADDREF(*result = jis);
338 nsresult rv = NS_OK;
339 if (!item || item->IsDirectory()) {
340 rv = jis->InitDirectory(this, aJarDirSpec, aEntryName);
341 } else {
342 rv = jis->InitFile(this, item);
344 if (NS_FAILED(rv)) {
345 NS_RELEASE(*result);
347 return rv;
350 //----------------------------------------------
351 // nsIJAR implementation
352 //----------------------------------------------
354 NS_IMETHODIMP
355 nsJAR::GetCertificatePrincipal(const char* aFilename, nsIPrincipal** aPrincipal)
357 //-- Parameter check
358 if (!aPrincipal)
359 return NS_ERROR_NULL_POINTER;
360 *aPrincipal = nsnull;
362 //-- Parse the manifest
363 nsresult rv = ParseManifest();
364 if (NS_FAILED(rv)) return rv;
365 if (mGlobalStatus == JAR_NO_MANIFEST)
366 return NS_OK;
368 PRInt16 requestedStatus;
369 if (aFilename)
371 //-- Find the item
372 nsCStringKey key(aFilename);
373 nsJARManifestItem* manItem = static_cast<nsJARManifestItem*>(mManifestData.Get(&key));
374 if (!manItem)
375 return NS_OK;
376 //-- Verify the item against the manifest
377 if (!manItem->entryVerified)
379 nsXPIDLCString entryData;
380 PRUint32 entryDataLen;
381 rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
382 if (NS_FAILED(rv)) return rv;
383 rv = VerifyEntry(manItem, entryData, entryDataLen);
384 if (NS_FAILED(rv)) return rv;
386 requestedStatus = manItem->status;
388 else // User wants identity of signer w/o verifying any entries
389 requestedStatus = mGlobalStatus;
391 if (requestedStatus != JAR_VALID_MANIFEST)
392 ReportError(aFilename, requestedStatus);
393 else // Valid signature
395 *aPrincipal = mPrincipal;
396 NS_IF_ADDREF(*aPrincipal);
398 return NS_OK;
401 NS_IMETHODIMP
402 nsJAR::GetManifestEntriesCount(PRUint32* count)
404 *count = mTotalItemsInManifest;
405 return NS_OK;
408 nsresult
409 nsJAR::GetJarPath(nsACString& aResult)
411 NS_ENSURE_ARG_POINTER(mZipFile);
413 return mZipFile->GetNativePath(aResult);
416 PRFileDesc*
417 nsJAR::OpenFile()
419 nsresult rv;
420 nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mZipFile, &rv);
421 if (NS_FAILED(rv)) return nsnull;
423 PRFileDesc* fd;
424 rv = localFile->OpenNSPRFileDesc(PR_RDONLY, 0000, &fd);
425 if (NS_FAILED(rv)) return nsnull;
427 return fd;
430 //----------------------------------------------
431 // nsJAR private implementation
432 //----------------------------------------------
433 nsresult
434 nsJAR::LoadEntry(const char* aFilename, char** aBuf, PRUint32* aBufLen)
436 //-- Get a stream for reading the file
437 nsresult rv;
438 nsCOMPtr<nsIInputStream> manifestStream;
439 rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
440 if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
442 //-- Read the manifest file into memory
443 char* buf;
444 PRUint32 len;
445 rv = manifestStream->Available(&len);
446 if (NS_FAILED(rv)) return rv;
447 if (len == PRUint32(-1))
448 return NS_ERROR_FILE_CORRUPTED; // bug 164695
449 buf = (char*)PR_MALLOC(len+1);
450 if (!buf) return NS_ERROR_OUT_OF_MEMORY;
451 PRUint32 bytesRead;
452 rv = manifestStream->Read(buf, len, &bytesRead);
453 if (bytesRead != len)
454 rv = NS_ERROR_FILE_CORRUPTED;
455 if (NS_FAILED(rv)) {
456 PR_FREEIF(buf);
457 return rv;
459 buf[len] = '\0'; //Null-terminate the buffer
460 *aBuf = buf;
461 if (aBufLen)
462 *aBufLen = len;
463 return NS_OK;
467 PRInt32
468 nsJAR::ReadLine(const char** src)
470 //--Moves pointer to beginning of next line and returns line length
471 // not including CR/LF.
472 PRInt32 length;
473 char* eol = PL_strpbrk(*src, "\r\n");
475 if (eol == nsnull) // Probably reached end of file before newline
477 length = PL_strlen(*src);
478 if (length == 0) // immediate end-of-file
479 *src = nsnull;
480 else // some data left on this line
481 *src += length;
483 else
485 length = eol - *src;
486 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
487 *src = eol+2;
488 else // Either CR or LF, so skip 1
489 *src = eol+1;
491 return length;
494 //-- The following #defines are used by ParseManifest()
495 // and ParseOneFile(). The header strings are defined in the JAR specification.
496 #define JAR_MF 1
497 #define JAR_SF 2
498 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
499 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
500 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
501 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
503 nsresult
504 nsJAR::ParseManifest()
506 //-- Verification Step 1
507 if (mParsedManifest)
508 return NS_OK;
509 //-- (1)Manifest (MF) file
510 nsCOMPtr<nsIUTF8StringEnumerator> files;
511 nsresult rv = FindEntries(JAR_MF_SEARCH_STRING, getter_AddRefs(files));
512 if (!files) rv = NS_ERROR_FAILURE;
513 if (NS_FAILED(rv)) return rv;
515 //-- Load the file into memory
516 PRBool more;
517 rv = files->HasMore(&more);
518 NS_ENSURE_SUCCESS(rv, rv);
519 if (!more)
521 mGlobalStatus = JAR_NO_MANIFEST;
522 mParsedManifest = PR_TRUE;
523 return NS_OK;
526 nsCAutoString manifestFilename;
527 rv = files->GetNext(manifestFilename);
528 NS_ENSURE_SUCCESS(rv, rv);
530 // Check if there is more than one manifest, if so then error!
531 rv = files->HasMore(&more);
532 if (NS_FAILED(rv)) return rv;
533 if (more)
535 mParsedManifest = PR_TRUE;
536 return NS_ERROR_FILE_CORRUPTED; // More than one MF file
539 nsXPIDLCString manifestBuffer;
540 PRUint32 manifestLen;
541 rv = LoadEntry(manifestFilename.get(), getter_Copies(manifestBuffer), &manifestLen);
542 if (NS_FAILED(rv)) return rv;
544 //-- Parse it
545 rv = ParseOneFile(manifestBuffer, JAR_MF);
546 if (NS_FAILED(rv)) return rv;
548 //-- (2)Signature (SF) file
549 // If there are multiple signatures, we select one.
550 rv = FindEntries(JAR_SF_SEARCH_STRING, getter_AddRefs(files));
551 if (!files) rv = NS_ERROR_FAILURE;
552 if (NS_FAILED(rv)) return rv;
553 //-- Get an SF file
554 rv = files->HasMore(&more);
555 if (NS_FAILED(rv)) return rv;
556 if (!more)
558 mGlobalStatus = JAR_NO_MANIFEST;
559 mParsedManifest = PR_TRUE;
560 return NS_OK;
562 rv = files->GetNext(manifestFilename);
563 if (NS_FAILED(rv)) return rv;
565 rv = LoadEntry(manifestFilename.get(), getter_Copies(manifestBuffer), &manifestLen);
566 if (NS_FAILED(rv)) return rv;
568 //-- Get its corresponding signature file
569 nsCAutoString sigFilename(manifestFilename);
570 PRInt32 extension = sigFilename.RFindChar('.') + 1;
571 NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
572 (void)sigFilename.Cut(extension, 2);
573 nsXPIDLCString sigBuffer;
574 PRUint32 sigLen;
576 nsCAutoString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
577 rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
579 if (NS_FAILED(rv))
581 nsCAutoString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
582 rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
584 if (NS_FAILED(rv))
586 mGlobalStatus = JAR_NO_MANIFEST;
587 mParsedManifest = PR_TRUE;
588 return NS_OK;
591 //-- Get the signature verifier service
592 nsCOMPtr<nsISignatureVerifier> verifier =
593 do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv);
594 if (NS_FAILED(rv)) // No signature verifier available
596 mGlobalStatus = JAR_NO_MANIFEST;
597 mParsedManifest = PR_TRUE;
598 return NS_OK;
601 //-- Verify that the signature file is a valid signature of the SF file
602 PRInt32 verifyError;
603 rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
604 &verifyError, getter_AddRefs(mPrincipal));
605 if (NS_FAILED(rv)) return rv;
606 if (mPrincipal && verifyError == 0)
607 mGlobalStatus = JAR_VALID_MANIFEST;
608 else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
609 mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
610 else
611 mGlobalStatus = JAR_INVALID_SIG;
613 //-- Parse the SF file. If the verification above failed, principal
614 // is null, and ParseOneFile will mark the relevant entries as invalid.
615 // if ParseOneFile fails, then it has no effect, and we can safely
616 // continue to the next SF file, or return.
617 ParseOneFile(manifestBuffer, JAR_SF);
618 mParsedManifest = PR_TRUE;
620 return NS_OK;
623 nsresult
624 nsJAR::ParseOneFile(const char* filebuf, PRInt16 aFileType)
626 //-- Check file header
627 const char* nextLineStart = filebuf;
628 nsCAutoString curLine;
629 PRInt32 linelen;
630 linelen = ReadLine(&nextLineStart);
631 curLine.Assign(filebuf, linelen);
633 if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
634 ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) )
635 return NS_ERROR_FILE_CORRUPTED;
637 //-- Skip header section
638 do {
639 linelen = ReadLine(&nextLineStart);
640 } while (linelen > 0);
642 //-- Set up parsing variables
643 const char* curPos;
644 const char* sectionStart = nextLineStart;
646 nsJARManifestItem* curItemMF = nsnull;
647 PRBool foundName = PR_FALSE;
648 if (aFileType == JAR_MF)
649 if (!(curItemMF = new nsJARManifestItem()))
650 return NS_ERROR_OUT_OF_MEMORY;
652 nsCAutoString curItemName;
653 nsCAutoString storedSectionDigest;
655 for(;;)
657 curPos = nextLineStart;
658 linelen = ReadLine(&nextLineStart);
659 curLine.Assign(curPos, linelen);
660 if (linelen == 0)
661 // end of section (blank line or end-of-file)
663 if (aFileType == JAR_MF)
665 mTotalItemsInManifest++;
666 if (curItemMF->mType != JAR_INVALID)
668 //-- Did this section have a name: line?
669 if(!foundName)
670 curItemMF->mType = JAR_INVALID;
671 else
673 //-- If it's an internal item, it must correspond
674 // to a valid jar entry
675 if (curItemMF->mType == JAR_INTERNAL)
677 PRBool exists;
678 nsresult rv = HasEntry(curItemName, &exists);
679 if (NS_FAILED(rv) || !exists)
680 curItemMF->mType = JAR_INVALID;
682 //-- Check for duplicates
683 nsCStringKey key(curItemName);
684 if (mManifestData.Exists(&key))
685 curItemMF->mType = JAR_INVALID;
689 if (curItemMF->mType == JAR_INVALID)
690 delete curItemMF;
691 else //-- calculate section digest
693 PRUint32 sectionLength = curPos - sectionStart;
694 CalculateDigest(sectionStart, sectionLength,
695 curItemMF->calculatedSectionDigest);
696 //-- Save item in the hashtable
697 nsCStringKey itemKey(curItemName);
698 mManifestData.Put(&itemKey, (void*)curItemMF);
700 if (nextLineStart == nsnull) // end-of-file
701 break;
703 sectionStart = nextLineStart;
704 if (!(curItemMF = new nsJARManifestItem()))
705 return NS_ERROR_OUT_OF_MEMORY;
706 } // (aFileType == JAR_MF)
707 else
708 //-- file type is SF, compare digest with calculated
709 // section digests from MF file.
711 if (foundName)
713 nsJARManifestItem* curItemSF;
714 nsCStringKey key(curItemName);
715 curItemSF = (nsJARManifestItem*)mManifestData.Get(&key);
716 if(curItemSF)
718 NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
719 "SECURITY ERROR: nsJARManifestItem not correctly initialized");
720 curItemSF->status = mGlobalStatus;
721 if (curItemSF->status == JAR_VALID_MANIFEST)
722 { // Compare digests
723 if (storedSectionDigest.IsEmpty())
724 curItemSF->status = JAR_NOT_SIGNED;
725 else
727 if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
728 curItemSF->status = JAR_INVALID_MANIFEST;
729 curItemSF->calculatedSectionDigest.Truncate();
730 storedSectionDigest.Truncate();
732 } // (aPrincipal != nsnull)
733 } // if(curItemSF)
734 } // if(foundName)
736 if(nextLineStart == nsnull) // end-of-file
737 break;
738 } // aFileType == JAR_SF
739 foundName = PR_FALSE;
740 continue;
741 } // if(linelen == 0)
743 //-- Look for continuations (beginning with a space) on subsequent lines
744 // and append them to the current line.
745 while(*nextLineStart == ' ')
747 curPos = nextLineStart;
748 PRInt32 continuationLen = ReadLine(&nextLineStart) - 1;
749 nsCAutoString continuation(curPos+1, continuationLen);
750 curLine += continuation;
751 linelen += continuationLen;
754 //-- Find colon in current line, this separates name from value
755 PRInt32 colonPos = curLine.FindChar(':');
756 if (colonPos == -1) // No colon on line, ignore line
757 continue;
758 //-- Break down the line
759 nsCAutoString lineName;
760 curLine.Left(lineName, colonPos);
761 nsCAutoString lineData;
762 curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
764 //-- Lines to look for:
765 // (1) Digest:
766 if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
767 //-- This is a digest line, save the data in the appropriate place
769 if(aFileType == JAR_MF)
770 curItemMF->storedEntryDigest = lineData;
771 else
772 storedSectionDigest = lineData;
773 continue;
776 // (2) Name: associates this manifest section with a file in the jar.
777 if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
779 curItemName = lineData;
780 foundName = PR_TRUE;
781 continue;
784 // (3) Magic: this may be an inline Javascript.
785 // We can't do any other kind of magic.
786 if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
788 if (lineData.LowerCaseEqualsLiteral("javascript"))
789 curItemMF->mType = JAR_EXTERNAL;
790 else
791 curItemMF->mType = JAR_INVALID;
792 continue;
795 } // for (;;)
796 return NS_OK;
797 } //ParseOneFile()
799 nsresult
800 nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
801 PRUint32 aLen)
803 if (aManItem->status == JAR_VALID_MANIFEST)
805 if (aManItem->storedEntryDigest.IsEmpty())
806 // No entry digests in manifest file. Entry is unsigned.
807 aManItem->status = JAR_NOT_SIGNED;
808 else
809 { //-- Calculate and compare digests
810 nsCString calculatedEntryDigest;
811 nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
812 if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
813 if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
814 aManItem->status = JAR_INVALID_ENTRY;
815 aManItem->storedEntryDigest.Truncate();
818 aManItem->entryVerified = PR_TRUE;
819 return NS_OK;
822 void nsJAR::ReportError(const char* aFilename, PRInt16 errorCode)
824 //-- Generate error message
825 nsAutoString message;
826 message.AssignLiteral("Signature Verification Error: the signature on ");
827 if (aFilename)
828 message.AppendWithConversion(aFilename);
829 else
830 message.AppendLiteral("this .jar archive");
831 message.AppendLiteral(" is invalid because ");
832 switch(errorCode)
834 case JAR_NOT_SIGNED:
835 message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
836 break;
837 case JAR_INVALID_SIG:
838 message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
839 break;
840 case JAR_INVALID_UNKNOWN_CA:
841 message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
842 break;
843 case JAR_INVALID_MANIFEST:
844 message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
845 break;
846 case JAR_INVALID_ENTRY:
847 message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
848 break;
849 case JAR_NO_MANIFEST:
850 message.AppendLiteral("the archive did not contain a manifest.");
851 break;
852 default:
853 message.AppendLiteral("of an unknown problem.");
856 // Report error in JS console
857 nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
858 if (console)
860 console->LogStringMessage(message.get());
862 #ifdef DEBUG
863 char* messageCstr = ToNewCString(message);
864 if (!messageCstr) return;
865 fprintf(stderr, "%s\n", messageCstr);
866 nsMemory::Free(messageCstr);
867 #endif
871 nsresult nsJAR::CalculateDigest(const char* aInBuf, PRUint32 aLen,
872 nsCString& digest)
874 nsresult rv;
876 nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
877 if (NS_FAILED(rv)) return rv;
879 rv = hasher->Init(nsICryptoHash::SHA1);
880 if (NS_FAILED(rv)) return rv;
882 rv = hasher->Update((const PRUint8*) aInBuf, aLen);
883 if (NS_FAILED(rv)) return rv;
885 return hasher->Finish(PR_TRUE, digest);
888 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJAREnumerator, nsIUTF8StringEnumerator)
890 //----------------------------------------------
891 // nsJAREnumerator::HasMore
892 //----------------------------------------------
893 NS_IMETHODIMP
894 nsJAREnumerator::HasMore(PRBool* aResult)
896 // try to get the next element
897 if (!mName) {
898 NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
899 nsresult rv = mFind->FindNext( &mName, &mNameLen );
900 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
901 *aResult = PR_FALSE; // No more matches available
902 return NS_OK;
904 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
907 *aResult = PR_TRUE;
908 return NS_OK;
911 //----------------------------------------------
912 // nsJAREnumerator::GetNext
913 //----------------------------------------------
914 NS_IMETHODIMP
915 nsJAREnumerator::GetNext(nsACString& aResult)
917 // check if the current item is "stale"
918 if (!mName) {
919 PRBool bMore;
920 nsresult rv = HasMore(&bMore);
921 if (NS_FAILED(rv) || !bMore)
922 return NS_ERROR_FAILURE; // no error translation
924 aResult.Assign(mName, mNameLen);
925 mName = 0; // we just gave this one away
926 return NS_OK;
930 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARItem, nsIZipEntry)
932 nsJARItem::nsJARItem(nsZipItem* aZipItem)
933 : mSize(aZipItem->Size()),
934 mRealsize(aZipItem->RealSize()),
935 mCrc32(aZipItem->CRC32()),
936 mDate(aZipItem->Date()),
937 mTime(aZipItem->Time()),
938 mCompression(aZipItem->Compression()),
939 mIsDirectory(aZipItem->IsDirectory()),
940 mIsSynthetic(aZipItem->isSynthetic)
944 //------------------------------------------
945 // nsJARItem::GetCompression
946 //------------------------------------------
947 NS_IMETHODIMP
948 nsJARItem::GetCompression(PRUint16 *aCompression)
950 NS_ENSURE_ARG_POINTER(aCompression);
952 *aCompression = mCompression;
953 return NS_OK;
956 //------------------------------------------
957 // nsJARItem::GetSize
958 //------------------------------------------
959 NS_IMETHODIMP
960 nsJARItem::GetSize(PRUint32 *aSize)
962 NS_ENSURE_ARG_POINTER(aSize);
964 *aSize = mSize;
965 return NS_OK;
968 //------------------------------------------
969 // nsJARItem::GetRealSize
970 //------------------------------------------
971 NS_IMETHODIMP
972 nsJARItem::GetRealSize(PRUint32 *aRealsize)
974 NS_ENSURE_ARG_POINTER(aRealsize);
976 *aRealsize = mRealsize;
977 return NS_OK;
980 //------------------------------------------
981 // nsJARItem::GetCrc32
982 //------------------------------------------
983 NS_IMETHODIMP
984 nsJARItem::GetCRC32(PRUint32 *aCrc32)
986 NS_ENSURE_ARG_POINTER(aCrc32);
988 *aCrc32 = mCrc32;
989 return NS_OK;
992 //------------------------------------------
993 // nsJARItem::GetIsDirectory
994 //------------------------------------------
995 NS_IMETHODIMP
996 nsJARItem::GetIsDirectory(PRBool *aIsDirectory)
998 NS_ENSURE_ARG_POINTER(aIsDirectory);
1000 *aIsDirectory = mIsDirectory;
1001 return NS_OK;
1004 //------------------------------------------
1005 // nsJARItem::GetIsSynthetic
1006 //------------------------------------------
1007 NS_IMETHODIMP
1008 nsJARItem::GetIsSynthetic(PRBool *aIsSynthetic)
1010 NS_ENSURE_ARG_POINTER(aIsSynthetic);
1012 *aIsSynthetic = mIsSynthetic;
1013 return NS_OK;
1016 //------------------------------------------
1017 // nsJARItem::GetLastModifiedTime
1018 //------------------------------------------
1019 NS_IMETHODIMP
1020 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
1022 NS_ENSURE_ARG_POINTER(aLastModTime);
1024 *aLastModTime = GetModTime(mDate, mTime);
1025 return NS_OK;
1028 ////////////////////////////////////////////////////////////////////////////////
1029 // nsIZipReaderCache
1031 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
1033 nsZipReaderCache::nsZipReaderCache()
1034 : mLock(nsnull),
1035 mZips(16)
1036 #ifdef ZIP_CACHE_HIT_RATE
1038 mZipCacheLookups(0),
1039 mZipCacheHits(0),
1040 mZipCacheFlushes(0),
1041 mZipSyncMisses(0)
1042 #endif
1046 NS_IMETHODIMP
1047 nsZipReaderCache::Init(PRUint32 cacheSize)
1049 mCacheSize = cacheSize;
1051 // Register as a memory pressure observer
1052 nsCOMPtr<nsIObserverService> os =
1053 do_GetService("@mozilla.org/observer-service;1");
1054 if (os)
1056 os->AddObserver(this, "memory-pressure", PR_TRUE);
1057 os->AddObserver(this, "chrome-flush-caches", PR_TRUE);
1059 // ignore failure of the observer registration.
1061 mLock = PR_NewLock();
1062 return mLock ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
1065 static PRBool
1066 DropZipReaderCache(nsHashKey *aKey, void *aData, void* closure)
1068 nsJAR* zip = (nsJAR*)aData;
1069 zip->SetZipReaderCache(nsnull);
1070 return PR_TRUE;
1073 nsZipReaderCache::~nsZipReaderCache()
1075 if (mLock)
1076 PR_DestroyLock(mLock);
1077 mZips.Enumerate(DropZipReaderCache, nsnull);
1079 #ifdef ZIP_CACHE_HIT_RATE
1080 printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
1081 mCacheSize, mZipCacheHits, mZipCacheLookups,
1082 (float)mZipCacheHits / mZipCacheLookups,
1083 mZipCacheFlushes, mZipSyncMisses);
1084 #endif
1087 NS_IMETHODIMP
1088 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
1090 NS_ENSURE_ARG_POINTER(zipFile);
1091 nsresult rv;
1092 nsCOMPtr<nsIJAR> antiLockZipGrip;
1093 nsAutoLock lock(mLock);
1095 #ifdef ZIP_CACHE_HIT_RATE
1096 mZipCacheLookups++;
1097 #endif
1099 nsCAutoString path;
1100 rv = zipFile->GetNativePath(path);
1101 if (NS_FAILED(rv)) return rv;
1103 nsCStringKey key(path);
1104 nsJAR* zip = static_cast<nsJAR*>(static_cast<nsIZipReader*>(mZips.Get(&key))); // AddRefs
1105 if (zip) {
1106 #ifdef ZIP_CACHE_HIT_RATE
1107 mZipCacheHits++;
1108 #endif
1109 zip->ClearReleaseTime();
1111 else {
1112 if (zip) {
1113 antiLockZipGrip = zip;
1114 mZips.Remove(&key);
1116 zip = new nsJAR();
1117 if (zip == nsnull)
1118 return NS_ERROR_OUT_OF_MEMORY;
1119 NS_ADDREF(zip);
1120 zip->SetZipReaderCache(this);
1122 rv = zip->Open(zipFile);
1123 if (NS_FAILED(rv)) {
1124 NS_RELEASE(zip);
1125 return rv;
1128 PRBool collision = mZips.Put(&key, static_cast<nsIZipReader*>(zip)); // AddRefs to 2
1129 NS_ASSERTION(!collision, "horked");
1131 *result = zip;
1132 return rv;
1135 static PRBool
1136 FindOldestZip(nsHashKey *aKey, void *aData, void* closure)
1138 nsJAR** oldestPtr = (nsJAR**)closure;
1139 nsJAR* oldest = *oldestPtr;
1140 nsJAR* current = (nsJAR*)aData;
1141 PRIntervalTime currentReleaseTime = current->GetReleaseTime();
1142 if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
1143 if (oldest == nsnull ||
1144 currentReleaseTime < oldest->GetReleaseTime()) {
1145 *oldestPtr = current;
1148 return PR_TRUE;
1151 struct ZipFindData {nsJAR* zip; PRBool found;};
1153 static PRBool
1154 FindZip(nsHashKey *aKey, void *aData, void* closure)
1156 ZipFindData* find_data = (ZipFindData*)closure;
1158 if (find_data->zip == (nsJAR*)aData) {
1159 find_data->found = PR_TRUE;
1160 return PR_FALSE;
1162 return PR_TRUE;
1165 nsresult
1166 nsZipReaderCache::ReleaseZip(nsJAR* zip)
1168 nsresult rv;
1169 nsAutoLock lock(mLock);
1171 // It is possible that two thread compete for this zip. The dangerous
1172 // case is where one thread Releases the zip and discovers that the ref
1173 // count has gone to one. Before it can call this ReleaseZip method
1174 // another thread calls our GetZip method. The ref count goes to two. That
1175 // second thread then Releases the zip and the ref count goes to one. It
1176 // then tries to enter this ReleaseZip method and blocks while the first
1177 // thread is still here. The first thread continues and remove the zip from
1178 // the cache and calls its Release method sending the ref count to 0 and
1179 // deleting the zip. However, the second thread is still blocked at the
1180 // start of ReleaseZip, but the 'zip' param now hold a reference to a
1181 // deleted zip!
1183 // So, we are going to try safeguarding here by searching our hashtable while
1184 // locked here for the zip. We return fast if it is not found.
1186 ZipFindData find_data = {zip, PR_FALSE};
1187 mZips.Enumerate(FindZip, &find_data);
1188 if (!find_data.found) {
1189 #ifdef ZIP_CACHE_HIT_RATE
1190 mZipSyncMisses++;
1191 #endif
1192 return NS_OK;
1195 zip->SetReleaseTime();
1197 if (mZips.Count() <= mCacheSize)
1198 return NS_OK;
1200 nsJAR* oldest = nsnull;
1201 mZips.Enumerate(FindOldestZip, &oldest);
1203 // Because of the craziness above it is possible that there is no zip that
1204 // needs removing.
1205 if (!oldest)
1206 return NS_OK;
1208 #ifdef ZIP_CACHE_HIT_RATE
1209 mZipCacheFlushes++;
1210 #endif
1212 // Clear the cache pointer in case we gave out this oldest guy while
1213 // his Release call was being made. Otherwise we could nest on ReleaseZip
1214 // when the second owner calls Release and we are still here in this lock.
1215 oldest->SetZipReaderCache(nsnull);
1217 // remove from hashtable
1218 nsCAutoString path;
1219 rv = oldest->GetJarPath(path);
1220 if (NS_FAILED(rv)) return rv;
1222 nsCStringKey key(path);
1223 PRBool removed = mZips.Remove(&key); // Releases
1224 NS_ASSERTION(removed, "botched");
1226 return NS_OK;
1229 static PRBool
1230 FindFlushableZip(nsHashKey *aKey, void *aData, void* closure)
1232 nsHashKey** flushableKeyPtr = (nsHashKey**)closure;
1233 nsJAR* current = (nsJAR*)aData;
1235 if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
1236 *flushableKeyPtr = aKey;
1237 current->SetZipReaderCache(nsnull);
1238 return PR_FALSE;
1240 return PR_TRUE;
1243 NS_IMETHODIMP
1244 nsZipReaderCache::Observe(nsISupports *aSubject,
1245 const char *aTopic,
1246 const PRUnichar *aSomeData)
1248 if (strcmp(aTopic, "memory-pressure") == 0) {
1249 nsAutoLock lock(mLock);
1250 while (PR_TRUE) {
1251 nsHashKey* flushable = nsnull;
1252 mZips.Enumerate(FindFlushableZip, &flushable);
1253 if ( ! flushable )
1254 break;
1255 PRBool removed = mZips.Remove(flushable); // Releases
1256 NS_ASSERTION(removed, "botched");
1258 #ifdef xDEBUG_jband
1259 printf("flushed something from the jar cache\n");
1260 #endif
1263 else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1264 mZips.Enumerate(DropZipReaderCache, nsnull);
1265 mZips.Reset();
1267 return NS_OK;
1270 PRTime GetModTime(PRUint16 aDate, PRUint16 aTime)
1272 PRExplodedTime time;
1274 time.tm_usec = 0;
1276 time.tm_hour = (aTime >> 11) & 0x1F;
1277 time.tm_min = (aTime >> 5) & 0x3F;
1278 time.tm_sec = (aTime & 0x1F) * 2;
1280 time.tm_year = (aDate >> 9) + 1980;
1281 time.tm_month = ((aDate >> 5) & 0x0F)-1;
1282 time.tm_mday = aDate & 0x1F;
1284 time.tm_params.tp_gmt_offset = 0;
1285 time.tm_params.tp_dst_offset = 0;
1287 PR_NormalizeTime(&time, PR_GMTParameters);
1288 time.tm_params = PR_LocalTimeParameters(&time);
1290 return PR_ImplodeTime(&time);
1293 ////////////////////////////////////////////////////////////////////////////////