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
15 * The Original Code is Mozilla Communicator client code, released
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.
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 ***** */
44 #include "nsJARInputStream.h"
46 #include "nsILocalFile.h"
47 #include "nsIConsoleService.h"
48 #include "nsICryptoHash.h"
53 #elif defined (XP_WIN) || defined(XP_OS2)
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.
72 } JARManifestItemType
;
74 class nsJARManifestItem
77 JARManifestItemType mType
;
79 // True if the second step of verification (VerifyEntry)
83 // Not signed, valid, or failure code
86 // Internal storage of digests
87 char* calculatedSectionDigest
;
88 char* storedEntryDigest
;
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
),
100 calculatedSectionDigest(nsnull
),
101 storedEntryDigest(nsnull
)
105 nsJARManifestItem::~nsJARManifestItem()
107 // Delete digests if necessary
108 PR_FREEIF(calculatedSectionDigest
);
109 PR_FREEIF(storedEntryDigest
);
112 //----------------------------------------------
113 // nsJAR constructor/destructor
114 //----------------------------------------------
116 DeleteManifestEntry(nsHashKey
* aKey
, void* aData
, void* closure
)
118 //-- deletes an entry in mManifestData.
119 delete (nsJARManifestItem
*)aData
;
123 // The following initialization makes a guess of 10 entries per jarfile.
124 nsJAR::nsJAR(): mManifestData(nsnull
, nsnull
, DeleteManifestEntry
, nsnull
, 10),
125 mParsedManifest(PR_FALSE
),
126 mGlobalStatus(JAR_MANIFEST_NOT_PARSED
),
127 mReleaseTime(PR_INTERVAL_NO_TIMEOUT
),
131 mTotalItemsInManifest(0)
140 NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsJAR
, nsIZipReader
, nsIJAR
)
141 NS_IMPL_THREADSAFE_ADDREF(nsJAR
)
143 // Custom Release method works with nsZipReaderCache...
144 nsrefcnt
nsJAR::Release(void)
147 NS_PRECONDITION(0 != mRefCnt
, "dup release");
148 count
= PR_AtomicDecrement((PRInt32
*)&mRefCnt
);
149 NS_LOG_RELEASE(this, count
, "nsJAR");
151 mRefCnt
= 1; /* stabilize */
152 /* enable this to find non-threadsafe destructors: */
153 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
154 NS_DELETEXPCOM(this);
157 else if (1 == count
&& mCache
) {
158 nsresult rv
= mCache
->ReleaseZip(this);
159 NS_ASSERTION(NS_SUCCEEDED(rv
), "failed to release zip file");
164 //----------------------------------------------
165 // nsIZipReader implementation
166 //----------------------------------------------
169 nsJAR::Open(nsIFile
* zipFile
)
171 if (mLock
) return NS_ERROR_FAILURE
; // Already open!
174 nsresult rv
= zipFile
->GetLastModifiedTime(&mMtime
);
175 if (NS_FAILED(rv
)) return rv
;
177 mLock
= PR_NewLock();
178 NS_ENSURE_TRUE(mLock
, NS_ERROR_OUT_OF_MEMORY
);
180 PRFileDesc
*fd
= OpenFile();
181 NS_ENSURE_TRUE(fd
, NS_ERROR_FAILURE
);
183 rv
= mZip
.OpenArchive(fd
);
184 if (NS_FAILED(rv
)) Close();
190 nsJAR::GetFile(nsIFile
* *result
)
193 NS_IF_ADDREF(*result
);
201 PR_DestroyLock(mLock
);
205 mParsedManifest
= PR_FALSE
;
206 mManifestData
.Reset();
207 mGlobalStatus
= JAR_MANIFEST_NOT_PARSED
;
208 mTotalItemsInManifest
= 0;
210 return mZip
.CloseArchive();
214 nsJAR::Test(const char *aEntryName
)
216 return mZip
.Test(aEntryName
);
220 nsJAR::Extract(const char *zipEntry
, nsIFile
* outFile
)
222 // nsZipArchive and zlib are not thread safe
223 // we need to use a lock to prevent bug #51267
224 nsAutoLock
lock(mLock
);
227 nsCOMPtr
<nsILocalFile
> localFile
= do_QueryInterface(outFile
, &rv
);
228 if (NS_FAILED(rv
)) return rv
;
230 nsZipItem
*item
= mZip
.GetItem(zipEntry
);
231 NS_ENSURE_TRUE(item
, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
);
233 // Remove existing file or directory so we set permissions correctly.
234 // If it's a directory that already exists and contains files, throw
235 // an exception and return.
238 //XXX If we guarantee that rv in the case of a non-empty directory
239 //XXX is always FILE_DIR_NOT_EMPTY, we can remove
240 //XXX |rv == NS_ERROR_FAILURE| - bug 322183 needs to be completely
241 //XXX fixed before that can happen
242 rv
= localFile
->Remove(PR_FALSE
);
243 if (rv
== NS_ERROR_FILE_DIR_NOT_EMPTY
||
244 rv
== NS_ERROR_FAILURE
)
247 if (item
->isDirectory
)
249 rv
= localFile
->Create(nsIFile::DIRECTORY_TYPE
, item
->mode
);
250 //XXX Do this in nsZipArchive? It would be nice to keep extraction
251 //XXX code completely there, but that would require a way to get a
252 //XXX PRDir from localFile.
257 rv
= localFile
->OpenNSPRFileDesc(PR_WRONLY
| PR_CREATE_FILE
, item
->mode
, &fd
);
258 if (NS_FAILED(rv
)) return rv
;
260 // ExtractFile also closes the fd handle and resolves the symlink if needed
262 rv
= outFile
->GetNativePath(path
);
263 if (NS_FAILED(rv
)) return rv
;
265 rv
= mZip
.ExtractFile(item
, path
.get(), fd
);
267 if (NS_FAILED(rv
)) return rv
;
269 PRTime prtime
= GetModTime(item
->date
, item
->time
);
270 // nsIFile needs milliseconds, while prtime is in microseconds.
271 PRTime conversion
= LL_ZERO
;
272 PRTime newTime
= LL_ZERO
;
273 LL_I2L(conversion
, PR_USEC_PER_MSEC
);
274 LL_DIV(newTime
, prtime
, conversion
);
275 // non-fatal if this fails, ignore errors
276 outFile
->SetLastModifiedTime(newTime
);
282 nsJAR::GetEntry(const char *aEntryName
, nsIZipEntry
* *result
)
284 nsZipItem
* zipItem
= mZip
.GetItem(aEntryName
);
285 NS_ENSURE_TRUE(zipItem
, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
);
287 nsJARItem
* jarItem
= new nsJARItem(zipItem
);
288 NS_ENSURE_TRUE(jarItem
, NS_ERROR_OUT_OF_MEMORY
);
290 NS_ADDREF(*result
= jarItem
);
295 nsJAR::HasEntry(const nsACString
&aEntryName
, PRBool
*result
)
297 *result
= mZip
.GetItem(PromiseFlatCString(aEntryName
).get()) != nsnull
;
302 nsJAR::FindEntries(const char *aPattern
, nsIUTF8StringEnumerator
**result
)
304 NS_ENSURE_ARG_POINTER(result
);
307 nsresult rv
= mZip
.FindInit(aPattern
, &find
);
308 NS_ENSURE_SUCCESS(rv
, rv
);
310 nsIUTF8StringEnumerator
*zipEnum
= new nsJAREnumerator(find
);
313 return NS_ERROR_OUT_OF_MEMORY
;
316 NS_ADDREF(*result
= zipEnum
);
321 nsJAR::GetInputStream(const char* aFilename
, nsIInputStream
** result
)
323 return GetInputStreamWithSpec(EmptyCString(), aFilename
, result
);
327 nsJAR::GetInputStreamWithSpec(const nsACString
& aJarDirSpec
,
328 const char* aEntryName
, nsIInputStream
** result
)
330 NS_ENSURE_ARG_POINTER(aEntryName
);
331 NS_ENSURE_ARG_POINTER(result
);
333 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
334 nsZipItem
*item
= nsnull
;
336 // First check if item exists in jar
337 item
= mZip
.GetItem(aEntryName
);
338 if (!item
) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
;
340 nsJARInputStream
* jis
= new nsJARInputStream();
341 // addref now so we can call InitFile/InitDirectory()
342 NS_ENSURE_TRUE(jis
, NS_ERROR_OUT_OF_MEMORY
);
343 NS_ADDREF(*result
= jis
);
346 if (!item
|| item
->isDirectory
) {
347 rv
= jis
->InitDirectory(&mZip
, aJarDirSpec
, aEntryName
);
349 // Open jarfile, to get its own filedescriptor for the stream
350 // XXX The file may have been overwritten, so |item| might not be
351 // valid. We really want to work from inode rather than file name.
352 PRFileDesc
*fd
= nsnull
;
355 rv
= jis
->InitFile(&mZip
, item
, fd
);
356 // |jis| now owns |fd|
358 rv
= NS_ERROR_FAILURE
;
367 //----------------------------------------------
368 // nsIJAR implementation
369 //----------------------------------------------
372 nsJAR::GetCertificatePrincipal(const char* aFilename
, nsIPrincipal
** aPrincipal
)
376 return NS_ERROR_NULL_POINTER
;
377 *aPrincipal
= nsnull
;
379 //-- Parse the manifest
380 nsresult rv
= ParseManifest();
381 if (NS_FAILED(rv
)) return rv
;
382 if (mGlobalStatus
== JAR_NO_MANIFEST
)
385 PRInt16 requestedStatus
;
389 nsCStringKey
key(aFilename
);
390 nsJARManifestItem
* manItem
= static_cast<nsJARManifestItem
*>(mManifestData
.Get(&key
));
393 //-- Verify the item against the manifest
394 if (!manItem
->entryVerified
)
396 nsXPIDLCString entryData
;
397 PRUint32 entryDataLen
;
398 rv
= LoadEntry(aFilename
, getter_Copies(entryData
), &entryDataLen
);
399 if (NS_FAILED(rv
)) return rv
;
400 rv
= VerifyEntry(manItem
, entryData
, entryDataLen
);
401 if (NS_FAILED(rv
)) return rv
;
403 requestedStatus
= manItem
->status
;
405 else // User wants identity of signer w/o verifying any entries
406 requestedStatus
= mGlobalStatus
;
408 if (requestedStatus
!= JAR_VALID_MANIFEST
)
409 ReportError(aFilename
, requestedStatus
);
410 else // Valid signature
412 *aPrincipal
= mPrincipal
;
413 NS_IF_ADDREF(*aPrincipal
);
419 nsJAR::GetManifestEntriesCount(PRUint32
* count
)
421 *count
= mTotalItemsInManifest
;
426 nsJAR::GetJarPath(nsACString
& aResult
)
428 NS_ENSURE_ARG_POINTER(mZipFile
);
430 return mZipFile
->GetNativePath(aResult
);
437 nsCOMPtr
<nsILocalFile
> localFile
= do_QueryInterface(mZipFile
, &rv
);
438 if (NS_FAILED(rv
)) return nsnull
;
441 rv
= localFile
->OpenNSPRFileDesc(PR_RDONLY
, 0000, &fd
);
442 if (NS_FAILED(rv
)) return nsnull
;
447 //----------------------------------------------
448 // nsJAR private implementation
449 //----------------------------------------------
451 nsJAR::LoadEntry(const char* aFilename
, char** aBuf
, PRUint32
* aBufLen
)
453 //-- Get a stream for reading the file
455 nsCOMPtr
<nsIInputStream
> manifestStream
;
456 rv
= GetInputStream(aFilename
, getter_AddRefs(manifestStream
));
457 if (NS_FAILED(rv
)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
;
459 //-- Read the manifest file into memory
462 rv
= manifestStream
->Available(&len
);
463 if (NS_FAILED(rv
)) return rv
;
464 if (len
== PRUint32(-1))
465 return NS_ERROR_FILE_CORRUPTED
; // bug 164695
466 buf
= (char*)PR_MALLOC(len
+1);
467 if (!buf
) return NS_ERROR_OUT_OF_MEMORY
;
469 rv
= manifestStream
->Read(buf
, len
, &bytesRead
);
470 if (bytesRead
!= len
)
471 rv
= NS_ERROR_FILE_CORRUPTED
;
476 buf
[len
] = '\0'; //Null-terminate the buffer
485 nsJAR::ReadLine(const char** src
)
487 //--Moves pointer to beginning of next line and returns line length
488 // not including CR/LF.
490 char* eol
= PL_strpbrk(*src
, "\r\n");
492 if (eol
== nsnull
) // Probably reached end of file before newline
494 length
= PL_strlen(*src
);
495 if (length
== 0) // immediate end-of-file
497 else // some data left on this line
503 if (eol
[0] == '\r' && eol
[1] == '\n') // CR LF, so skip 2
505 else // Either CR or LF, so skip 1
511 //-- The following #defines are used by ParseManifest()
512 // and ParseOneFile(). The header strings are defined in the JAR specification.
515 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
516 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
517 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
518 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
521 nsJAR::ParseManifest()
523 //-- Verification Step 1
526 //-- (1)Manifest (MF) file
527 nsCOMPtr
<nsIUTF8StringEnumerator
> files
;
528 nsresult rv
= FindEntries(JAR_MF_SEARCH_STRING
, getter_AddRefs(files
));
529 if (!files
) rv
= NS_ERROR_FAILURE
;
530 if (NS_FAILED(rv
)) return rv
;
532 //-- Load the file into memory
534 rv
= files
->HasMore(&more
);
535 NS_ENSURE_SUCCESS(rv
, rv
);
538 mGlobalStatus
= JAR_NO_MANIFEST
;
539 mParsedManifest
= PR_TRUE
;
543 nsCAutoString manifestFilename
;
544 rv
= files
->GetNext(manifestFilename
);
545 NS_ENSURE_SUCCESS(rv
, rv
);
547 // Check if there is more than one manifest, if so then error!
548 rv
= files
->HasMore(&more
);
549 if (NS_FAILED(rv
)) return rv
;
552 mParsedManifest
= PR_TRUE
;
553 return NS_ERROR_FILE_CORRUPTED
; // More than one MF file
556 nsXPIDLCString manifestBuffer
;
557 PRUint32 manifestLen
;
558 rv
= LoadEntry(manifestFilename
.get(), getter_Copies(manifestBuffer
), &manifestLen
);
559 if (NS_FAILED(rv
)) return rv
;
562 rv
= ParseOneFile(manifestBuffer
, JAR_MF
);
563 if (NS_FAILED(rv
)) return rv
;
565 //-- (2)Signature (SF) file
566 // If there are multiple signatures, we select one.
567 rv
= FindEntries(JAR_SF_SEARCH_STRING
, getter_AddRefs(files
));
568 if (!files
) rv
= NS_ERROR_FAILURE
;
569 if (NS_FAILED(rv
)) return rv
;
571 rv
= files
->HasMore(&more
);
572 if (NS_FAILED(rv
)) return rv
;
575 mGlobalStatus
= JAR_NO_MANIFEST
;
576 mParsedManifest
= PR_TRUE
;
579 rv
= files
->GetNext(manifestFilename
);
580 if (NS_FAILED(rv
)) return rv
;
582 rv
= LoadEntry(manifestFilename
.get(), getter_Copies(manifestBuffer
), &manifestLen
);
583 if (NS_FAILED(rv
)) return rv
;
585 //-- Get its corresponding signature file
586 nsCAutoString
sigFilename(manifestFilename
);
587 PRInt32 extension
= sigFilename
.RFindChar('.') + 1;
588 NS_ASSERTION(extension
!= 0, "Manifest Parser: Missing file extension.");
589 (void)sigFilename
.Cut(extension
, 2);
590 nsXPIDLCString sigBuffer
;
593 nsCAutoString
tempFilename(sigFilename
); tempFilename
.Append("rsa", 3);
594 rv
= LoadEntry(tempFilename
.get(), getter_Copies(sigBuffer
), &sigLen
);
598 nsCAutoString
tempFilename(sigFilename
); tempFilename
.Append("RSA", 3);
599 rv
= LoadEntry(tempFilename
.get(), getter_Copies(sigBuffer
), &sigLen
);
603 mGlobalStatus
= JAR_NO_MANIFEST
;
604 mParsedManifest
= PR_TRUE
;
608 //-- Get the signature verifier service
609 nsCOMPtr
<nsISignatureVerifier
> verifier
=
610 do_GetService(SIGNATURE_VERIFIER_CONTRACTID
, &rv
);
611 if (NS_FAILED(rv
)) // No signature verifier available
613 mGlobalStatus
= JAR_NO_MANIFEST
;
614 mParsedManifest
= PR_TRUE
;
618 //-- Verify that the signature file is a valid signature of the SF file
620 rv
= verifier
->VerifySignature(sigBuffer
, sigLen
, manifestBuffer
, manifestLen
,
621 &verifyError
, getter_AddRefs(mPrincipal
));
622 if (NS_FAILED(rv
)) return rv
;
623 if (mPrincipal
&& verifyError
== 0)
624 mGlobalStatus
= JAR_VALID_MANIFEST
;
625 else if (verifyError
== nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA
)
626 mGlobalStatus
= JAR_INVALID_UNKNOWN_CA
;
628 mGlobalStatus
= JAR_INVALID_SIG
;
630 //-- Parse the SF file. If the verification above failed, principal
631 // is null, and ParseOneFile will mark the relevant entries as invalid.
632 // if ParseOneFile fails, then it has no effect, and we can safely
633 // continue to the next SF file, or return.
634 ParseOneFile(manifestBuffer
, JAR_SF
);
635 mParsedManifest
= PR_TRUE
;
641 nsJAR::ParseOneFile(const char* filebuf
, PRInt16 aFileType
)
643 //-- Check file header
644 const char* nextLineStart
= filebuf
;
645 nsCAutoString curLine
;
647 linelen
= ReadLine(&nextLineStart
);
648 curLine
.Assign(filebuf
, linelen
);
650 if ( ((aFileType
== JAR_MF
) && !curLine
.Equals(JAR_MF_HEADER
) ) ||
651 ((aFileType
== JAR_SF
) && !curLine
.Equals(JAR_SF_HEADER
) ) )
652 return NS_ERROR_FILE_CORRUPTED
;
654 //-- Skip header section
656 linelen
= ReadLine(&nextLineStart
);
657 } while (linelen
> 0);
659 //-- Set up parsing variables
661 const char* sectionStart
= nextLineStart
;
663 nsJARManifestItem
* curItemMF
= nsnull
;
664 PRBool foundName
= PR_FALSE
;
665 if (aFileType
== JAR_MF
)
666 if (!(curItemMF
= new nsJARManifestItem()))
667 return NS_ERROR_OUT_OF_MEMORY
;
669 nsCAutoString curItemName
;
670 nsCAutoString storedSectionDigest
;
674 curPos
= nextLineStart
;
675 linelen
= ReadLine(&nextLineStart
);
676 curLine
.Assign(curPos
, linelen
);
678 // end of section (blank line or end-of-file)
680 if (aFileType
== JAR_MF
)
682 mTotalItemsInManifest
++;
683 if (curItemMF
->mType
!= JAR_INVALID
)
685 //-- Did this section have a name: line?
687 curItemMF
->mType
= JAR_INVALID
;
690 //-- If it's an internal item, it must correspond
691 // to a valid jar entry
692 if (curItemMF
->mType
== JAR_INTERNAL
)
695 PRInt32 result
= HasEntry(curItemName
, &exists
);
696 if (result
!= ZIP_OK
|| !exists
)
697 curItemMF
->mType
= JAR_INVALID
;
699 //-- Check for duplicates
700 nsCStringKey
key(curItemName
);
701 if (mManifestData
.Exists(&key
))
702 curItemMF
->mType
= JAR_INVALID
;
706 if (curItemMF
->mType
== JAR_INVALID
)
708 else //-- calculate section digest
710 PRUint32 sectionLength
= curPos
- sectionStart
;
711 CalculateDigest(sectionStart
, sectionLength
,
712 &(curItemMF
->calculatedSectionDigest
));
713 //-- Save item in the hashtable
714 nsCStringKey
itemKey(curItemName
);
715 mManifestData
.Put(&itemKey
, (void*)curItemMF
);
717 if (nextLineStart
== nsnull
) // end-of-file
720 sectionStart
= nextLineStart
;
721 if (!(curItemMF
= new nsJARManifestItem()))
722 return NS_ERROR_OUT_OF_MEMORY
;
723 } // (aFileType == JAR_MF)
725 //-- file type is SF, compare digest with calculated
726 // section digests from MF file.
730 nsJARManifestItem
* curItemSF
;
731 nsCStringKey
key(curItemName
);
732 curItemSF
= (nsJARManifestItem
*)mManifestData
.Get(&key
);
735 NS_ASSERTION(curItemSF
->status
== JAR_NOT_SIGNED
,
736 "SECURITY ERROR: nsJARManifestItem not correctly initialized");
737 curItemSF
->status
= mGlobalStatus
;
738 if (curItemSF
->status
== JAR_VALID_MANIFEST
)
740 if (storedSectionDigest
.IsEmpty())
741 curItemSF
->status
= JAR_NOT_SIGNED
;
744 if (!storedSectionDigest
.Equals((const char*)curItemSF
->calculatedSectionDigest
))
745 curItemSF
->status
= JAR_INVALID_MANIFEST
;
746 PR_FREEIF(curItemSF
->calculatedSectionDigest
)
747 storedSectionDigest
= "";
749 } // (aPrincipal != nsnull)
753 if(nextLineStart
== nsnull
) // end-of-file
755 } // aFileType == JAR_SF
756 foundName
= PR_FALSE
;
758 } // if(linelen == 0)
760 //-- Look for continuations (beginning with a space) on subsequent lines
761 // and append them to the current line.
762 while(*nextLineStart
== ' ')
764 curPos
= nextLineStart
;
765 PRInt32 continuationLen
= ReadLine(&nextLineStart
) - 1;
766 nsCAutoString
continuation(curPos
+1, continuationLen
);
767 curLine
+= continuation
;
768 linelen
+= continuationLen
;
771 //-- Find colon in current line, this separates name from value
772 PRInt32 colonPos
= curLine
.FindChar(':');
773 if (colonPos
== -1) // No colon on line, ignore line
775 //-- Break down the line
776 nsCAutoString lineName
;
777 curLine
.Left(lineName
, colonPos
);
778 nsCAutoString lineData
;
779 curLine
.Mid(lineData
, colonPos
+2, linelen
- (colonPos
+2));
781 //-- Lines to look for:
783 if (lineName
.Equals(NS_LITERAL_CSTRING("SHA1-Digest"),
784 nsCaseInsensitiveCStringComparator()))
785 //-- This is a digest line, save the data in the appropriate place
787 if(aFileType
== JAR_MF
)
789 curItemMF
->storedEntryDigest
= (char*)PR_MALLOC(lineData
.Length()+1);
790 if (!(curItemMF
->storedEntryDigest
))
791 return NS_ERROR_OUT_OF_MEMORY
;
792 PL_strcpy(curItemMF
->storedEntryDigest
, lineData
.get());
795 storedSectionDigest
= lineData
;
799 // (2) Name: associates this manifest section with a file in the jar.
800 if (!foundName
&& lineName
.Equals(NS_LITERAL_CSTRING("Name"),
801 nsCaseInsensitiveCStringComparator()))
803 curItemName
= lineData
;
808 // (3) Magic: this may be an inline Javascript.
809 // We can't do any other kind of magic.
810 if ( aFileType
== JAR_MF
&&
811 lineName
.Equals(NS_LITERAL_CSTRING("Magic"),
812 nsCaseInsensitiveCStringComparator()))
814 if(lineData
.Equals(NS_LITERAL_CSTRING("javascript"),
815 nsCaseInsensitiveCStringComparator()))
816 curItemMF
->mType
= JAR_EXTERNAL
;
818 curItemMF
->mType
= JAR_INVALID
;
827 nsJAR::VerifyEntry(nsJARManifestItem
* aManItem
, const char* aEntryData
,
830 if (aManItem
->status
== JAR_VALID_MANIFEST
)
832 if(!aManItem
->storedEntryDigest
)
833 // No entry digests in manifest file. Entry is unsigned.
834 aManItem
->status
= JAR_NOT_SIGNED
;
836 { //-- Calculate and compare digests
837 char* calculatedEntryDigest
;
838 nsresult rv
= CalculateDigest(aEntryData
, aLen
, &calculatedEntryDigest
);
839 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
840 if (PL_strcmp(aManItem
->storedEntryDigest
, calculatedEntryDigest
) != 0)
841 aManItem
->status
= JAR_INVALID_ENTRY
;
842 PR_FREEIF(calculatedEntryDigest
)
843 PR_FREEIF(aManItem
->storedEntryDigest
)
846 aManItem
->entryVerified
= PR_TRUE
;
850 void nsJAR::ReportError(const char* aFilename
, PRInt16 errorCode
)
852 //-- Generate error message
853 nsAutoString message
;
854 message
.AssignLiteral("Signature Verification Error: the signature on ");
856 message
.AppendWithConversion(aFilename
);
858 message
.AppendLiteral("this .jar archive");
859 message
.AppendLiteral(" is invalid because ");
863 message
.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
865 case JAR_INVALID_SIG
:
866 message
.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
868 case JAR_INVALID_UNKNOWN_CA
:
869 message
.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
871 case JAR_INVALID_MANIFEST
:
872 message
.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
874 case JAR_INVALID_ENTRY
:
875 message
.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
877 case JAR_NO_MANIFEST
:
878 message
.AppendLiteral("the archive did not contain a manifest.");
881 message
.AppendLiteral("of an unknown problem.");
884 // Report error in JS console
885 nsCOMPtr
<nsIConsoleService
> console(do_GetService("@mozilla.org/consoleservice;1"));
888 console
->LogStringMessage(message
.get());
891 char* messageCstr
= ToNewCString(message
);
892 if (!messageCstr
) return;
893 fprintf(stderr
, "%s\n", messageCstr
);
894 nsMemory::Free(messageCstr
);
899 nsresult
nsJAR::CalculateDigest(const char* aInBuf
, PRUint32 aLen
,
906 nsCOMPtr
<nsICryptoHash
> hasher
= do_CreateInstance("@mozilla.org/security/hash;1", &rv
);
907 if (NS_FAILED(rv
)) return rv
;
909 rv
= hasher
->Init(nsICryptoHash::SHA1
);
910 if (NS_FAILED(rv
)) return rv
;
912 rv
= hasher
->Update((const PRUint8
*) aInBuf
, aLen
);
913 if (NS_FAILED(rv
)) return rv
;
915 nsCAutoString hashString
;
916 rv
= hasher
->Finish(PR_TRUE
, hashString
);
917 if (NS_FAILED(rv
)) return rv
;
919 *digest
= ToNewCString(hashString
);
921 return *digest
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
924 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJAREnumerator
, nsIUTF8StringEnumerator
)
926 //----------------------------------------------
927 // nsJAREnumerator::HasMore
928 //----------------------------------------------
930 nsJAREnumerator::HasMore(PRBool
* aResult
)
932 // try to get the next element
934 NS_ASSERTION(mFind
, "nsJAREnumerator: Missing zipFind.");
935 nsresult rv
= mFind
->FindNext( &mCurr
);
936 if (rv
== NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
) {
937 *aResult
= PR_FALSE
; // No more matches available
940 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
); // no error translation
947 //----------------------------------------------
948 // nsJAREnumerator::GetNext
949 //----------------------------------------------
951 nsJAREnumerator::GetNext(nsACString
& aResult
)
953 // check if the current item is "stale"
956 nsresult rv
= HasMore(&bMore
);
957 if (NS_FAILED(rv
) || !bMore
)
958 return NS_ERROR_FAILURE
; // no error translation
961 mCurr
= 0; // we just gave this one away
966 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARItem
, nsIZipEntry
)
968 nsJARItem::nsJARItem(nsZipItem
* aZipItem
)
969 : mSize(aZipItem
->size
),
970 mRealsize(aZipItem
->realsize
),
971 mCrc32(aZipItem
->crc32
),
972 mDate(aZipItem
->date
),
973 mTime(aZipItem
->time
),
974 mCompression(aZipItem
->compression
),
975 mIsDirectory(aZipItem
->isDirectory
),
976 mIsSynthetic(aZipItem
->isSynthetic
)
980 //------------------------------------------
981 // nsJARItem::GetCompression
982 //------------------------------------------
984 nsJARItem::GetCompression(PRUint16
*aCompression
)
986 NS_ENSURE_ARG_POINTER(aCompression
);
988 *aCompression
= mCompression
;
992 //------------------------------------------
993 // nsJARItem::GetSize
994 //------------------------------------------
996 nsJARItem::GetSize(PRUint32
*aSize
)
998 NS_ENSURE_ARG_POINTER(aSize
);
1004 //------------------------------------------
1005 // nsJARItem::GetRealSize
1006 //------------------------------------------
1008 nsJARItem::GetRealSize(PRUint32
*aRealsize
)
1010 NS_ENSURE_ARG_POINTER(aRealsize
);
1012 *aRealsize
= mRealsize
;
1016 //------------------------------------------
1017 // nsJARItem::GetCrc32
1018 //------------------------------------------
1020 nsJARItem::GetCRC32(PRUint32
*aCrc32
)
1022 NS_ENSURE_ARG_POINTER(aCrc32
);
1028 //------------------------------------------
1029 // nsJARItem::GetIsDirectory
1030 //------------------------------------------
1032 nsJARItem::GetIsDirectory(PRBool
*aIsDirectory
)
1034 NS_ENSURE_ARG_POINTER(aIsDirectory
);
1036 *aIsDirectory
= mIsDirectory
;
1040 //------------------------------------------
1041 // nsJARItem::GetIsSynthetic
1042 //------------------------------------------
1044 nsJARItem::GetIsSynthetic(PRBool
*aIsSynthetic
)
1046 NS_ENSURE_ARG_POINTER(aIsSynthetic
);
1048 *aIsSynthetic
= mIsSynthetic
;
1052 //------------------------------------------
1053 // nsJARItem::GetLastModifiedTime
1054 //------------------------------------------
1056 nsJARItem::GetLastModifiedTime(PRTime
* aLastModTime
)
1058 NS_ENSURE_ARG_POINTER(aLastModTime
);
1060 *aLastModTime
= GetModTime(mDate
, mTime
);
1064 ////////////////////////////////////////////////////////////////////////////////
1065 // nsIZipReaderCache
1067 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache
, nsIZipReaderCache
, nsIObserver
, nsISupportsWeakReference
)
1069 nsZipReaderCache::nsZipReaderCache()
1072 #ifdef ZIP_CACHE_HIT_RATE
1074 mZipCacheLookups(0),
1076 mZipCacheFlushes(0),
1083 nsZipReaderCache::Init(PRUint32 cacheSize
)
1085 mCacheSize
= cacheSize
;
1087 // Register as a memory pressure observer
1088 nsCOMPtr
<nsIObserverService
> os
=
1089 do_GetService("@mozilla.org/observer-service;1");
1092 os
->AddObserver(this, "memory-pressure", PR_TRUE
);
1093 os
->AddObserver(this, "chrome-flush-caches", PR_TRUE
);
1095 // ignore failure of the observer registration.
1097 mLock
= PR_NewLock();
1098 return mLock
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
1102 DropZipReaderCache(nsHashKey
*aKey
, void *aData
, void* closure
)
1104 nsJAR
* zip
= (nsJAR
*)aData
;
1105 zip
->SetZipReaderCache(nsnull
);
1109 nsZipReaderCache::~nsZipReaderCache()
1112 PR_DestroyLock(mLock
);
1113 mZips
.Enumerate(DropZipReaderCache
, nsnull
);
1115 #ifdef ZIP_CACHE_HIT_RATE
1116 printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
1117 mCacheSize
, mZipCacheHits
, mZipCacheLookups
,
1118 (float)mZipCacheHits
/ mZipCacheLookups
,
1119 mZipCacheFlushes
, mZipSyncMisses
);
1124 nsZipReaderCache::GetZip(nsIFile
* zipFile
, nsIZipReader
* *result
)
1126 NS_ENSURE_ARG_POINTER(zipFile
);
1128 nsCOMPtr
<nsIJAR
> antiLockZipGrip
;
1129 nsAutoLock
lock(mLock
);
1131 #ifdef ZIP_CACHE_HIT_RATE
1136 rv
= zipFile
->GetNativePath(path
);
1137 if (NS_FAILED(rv
)) return rv
;
1140 rv
= zipFile
->GetLastModifiedTime(&Mtime
);
1141 if (NS_FAILED(rv
)) return rv
;
1143 nsCStringKey
key(path
);
1144 nsJAR
* zip
= static_cast<nsJAR
*>(static_cast<nsIZipReader
*>(mZips
.Get(&key
))); // AddRefs
1145 if (zip
&& Mtime
== zip
->GetMtime()) {
1146 #ifdef ZIP_CACHE_HIT_RATE
1149 zip
->ClearReleaseTime();
1153 antiLockZipGrip
= zip
;
1158 return NS_ERROR_OUT_OF_MEMORY
;
1160 zip
->SetZipReaderCache(this);
1162 rv
= zip
->Open(zipFile
);
1163 if (NS_FAILED(rv
)) {
1168 PRBool collision
= mZips
.Put(&key
, static_cast<nsIZipReader
*>(zip
)); // AddRefs to 2
1169 NS_ASSERTION(!collision
, "horked");
1176 FindOldestZip(nsHashKey
*aKey
, void *aData
, void* closure
)
1178 nsJAR
** oldestPtr
= (nsJAR
**)closure
;
1179 nsJAR
* oldest
= *oldestPtr
;
1180 nsJAR
* current
= (nsJAR
*)aData
;
1181 PRIntervalTime currentReleaseTime
= current
->GetReleaseTime();
1182 if (currentReleaseTime
!= PR_INTERVAL_NO_TIMEOUT
) {
1183 if (oldest
== nsnull
||
1184 currentReleaseTime
< oldest
->GetReleaseTime()) {
1185 *oldestPtr
= current
;
1191 struct ZipFindData
{nsJAR
* zip
; PRBool found
;};
1194 FindZip(nsHashKey
*aKey
, void *aData
, void* closure
)
1196 ZipFindData
* find_data
= (ZipFindData
*)closure
;
1198 if (find_data
->zip
== (nsJAR
*)aData
) {
1199 find_data
->found
= PR_TRUE
;
1206 nsZipReaderCache::ReleaseZip(nsJAR
* zip
)
1209 nsAutoLock
lock(mLock
);
1211 // It is possible that two thread compete for this zip. The dangerous
1212 // case is where one thread Releases the zip and discovers that the ref
1213 // count has gone to one. Before it can call this ReleaseZip method
1214 // another thread calls our GetZip method. The ref count goes to two. That
1215 // second thread then Releases the zip and the ref count goes to one. It
1216 // then tries to enter this ReleaseZip method and blocks while the first
1217 // thread is still here. The first thread continues and remove the zip from
1218 // the cache and calls its Release method sending the ref count to 0 and
1219 // deleting the zip. However, the second thread is still blocked at the
1220 // start of ReleaseZip, but the 'zip' param now hold a reference to a
1223 // So, we are going to try safeguarding here by searching our hashtable while
1224 // locked here for the zip. We return fast if it is not found.
1226 ZipFindData find_data
= {zip
, PR_FALSE
};
1227 mZips
.Enumerate(FindZip
, &find_data
);
1228 if (!find_data
.found
) {
1229 #ifdef ZIP_CACHE_HIT_RATE
1235 zip
->SetReleaseTime();
1237 if (mZips
.Count() <= mCacheSize
)
1240 nsJAR
* oldest
= nsnull
;
1241 mZips
.Enumerate(FindOldestZip
, &oldest
);
1243 // Because of the craziness above it is possible that there is no zip that
1248 #ifdef ZIP_CACHE_HIT_RATE
1252 // Clear the cache pointer in case we gave out this oldest guy while
1253 // his Release call was being made. Otherwise we could nest on ReleaseZip
1254 // when the second owner calls Release and we are still here in this lock.
1255 oldest
->SetZipReaderCache(nsnull
);
1257 // remove from hashtable
1259 rv
= oldest
->GetJarPath(path
);
1260 if (NS_FAILED(rv
)) return rv
;
1262 nsCStringKey
key(path
);
1263 PRBool removed
= mZips
.Remove(&key
); // Releases
1264 NS_ASSERTION(removed
, "botched");
1270 FindFlushableZip(nsHashKey
*aKey
, void *aData
, void* closure
)
1272 nsHashKey
** flushableKeyPtr
= (nsHashKey
**)closure
;
1273 nsJAR
* current
= (nsJAR
*)aData
;
1275 if (current
->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT
) {
1276 *flushableKeyPtr
= aKey
;
1277 current
->SetZipReaderCache(nsnull
);
1284 nsZipReaderCache::Observe(nsISupports
*aSubject
,
1286 const PRUnichar
*aSomeData
)
1288 if (strcmp(aTopic
, "memory-pressure") == 0) {
1289 nsAutoLock
lock(mLock
);
1291 nsHashKey
* flushable
= nsnull
;
1292 mZips
.Enumerate(FindFlushableZip
, &flushable
);
1295 PRBool removed
= mZips
.Remove(flushable
); // Releases
1296 NS_ASSERTION(removed
, "botched");
1299 printf("flushed something from the jar cache\n");
1303 else if (strcmp(aTopic
, "chrome-flush-caches") == 0) {
1304 mZips
.Enumerate(DropZipReaderCache
, nsnull
);
1310 PRTime
GetModTime(PRUint16 aDate
, PRUint16 aTime
)
1312 PRExplodedTime time
;
1316 time
.tm_hour
= (aTime
>> 11) & 0x1F;
1317 time
.tm_min
= (aTime
>> 5) & 0x3F;
1318 time
.tm_sec
= (aTime
& 0x1F) * 2;
1320 time
.tm_year
= (aDate
>> 9) + 1980;
1321 time
.tm_month
= ((aDate
>> 5) & 0x0F)-1;
1322 time
.tm_mday
= aDate
& 0x1F;
1324 time
.tm_params
.tp_gmt_offset
= 0;
1325 time
.tm_params
.tp_dst_offset
= 0;
1327 PR_NormalizeTime(&time
, PR_GMTParameters
);
1328 time
.tm_params
= PR_LocalTimeParameters(&time
);
1330 return PR_ImplodeTime(&time
);
1333 ////////////////////////////////////////////////////////////////////////////////