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 nsCString calculatedSectionDigest
;
88 nsCString 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
)
103 nsJARManifestItem::~nsJARManifestItem()
107 //----------------------------------------------
108 // nsJAR constructor/destructor
109 //----------------------------------------------
111 DeleteManifestEntry(nsHashKey
* aKey
, void* aData
, void* closure
)
113 //-- deletes an entry in mManifestData.
114 delete (nsJARManifestItem
*)aData
;
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
),
125 mTotalItemsInManifest(0)
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)
141 NS_PRECONDITION(0 != mRefCnt
, "dup release");
142 count
= PR_AtomicDecrement((PRInt32
*)&mRefCnt
);
143 NS_LOG_RELEASE(this, count
, "nsJAR");
145 mRefCnt
= 1; /* stabilize */
146 /* enable this to find non-threadsafe destructors: */
147 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
148 NS_DELETEXPCOM(this);
151 else if (1 == count
&& mCache
) {
152 nsresult rv
= mCache
->ReleaseZip(this);
153 NS_ASSERTION(NS_SUCCEEDED(rv
), "failed to release zip file");
158 //----------------------------------------------
159 // nsIZipReader implementation
160 //----------------------------------------------
163 nsJAR::Open(nsIFile
* zipFile
)
165 NS_ENSURE_ARG_POINTER(zipFile
);
166 if (mLock
) return NS_ERROR_FAILURE
; // Already open!
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();
183 nsJAR::GetFile(nsIFile
* *result
)
186 NS_IF_ADDREF(*result
);
194 PR_DestroyLock(mLock
);
198 mParsedManifest
= PR_FALSE
;
199 mManifestData
.Reset();
200 mGlobalStatus
= JAR_MANIFEST_NOT_PARSED
;
201 mTotalItemsInManifest
= 0;
203 return mZip
.CloseArchive();
207 nsJAR::Test(const char *aEntryName
)
209 return mZip
.Test(aEntryName
);
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
);
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.
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
)
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.
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
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
);
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
);
288 nsJAR::HasEntry(const nsACString
&aEntryName
, PRBool
*result
)
290 *result
= mZip
.GetItem(PromiseFlatCString(aEntryName
).get()) != nsnull
;
295 nsJAR::FindEntries(const char *aPattern
, nsIUTF8StringEnumerator
**result
)
297 NS_ENSURE_ARG_POINTER(result
);
300 nsresult rv
= mZip
.FindInit(aPattern
, &find
);
301 NS_ENSURE_SUCCESS(rv
, rv
);
303 nsIUTF8StringEnumerator
*zipEnum
= new nsJAREnumerator(find
);
306 return NS_ERROR_OUT_OF_MEMORY
;
309 NS_ADDREF(*result
= zipEnum
);
314 nsJAR::GetInputStream(const char* aFilename
, nsIInputStream
** result
)
316 return GetInputStreamWithSpec(EmptyCString(), aFilename
, result
);
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
;
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
);
339 if (!item
|| item
->IsDirectory()) {
340 rv
= jis
->InitDirectory(this, aJarDirSpec
, aEntryName
);
342 rv
= jis
->InitFile(this, item
);
350 //----------------------------------------------
351 // nsIJAR implementation
352 //----------------------------------------------
355 nsJAR::GetCertificatePrincipal(const char* aFilename
, nsIPrincipal
** 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
)
368 PRInt16 requestedStatus
;
372 nsCStringKey
key(aFilename
);
373 nsJARManifestItem
* manItem
= static_cast<nsJARManifestItem
*>(mManifestData
.Get(&key
));
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
);
402 nsJAR::GetManifestEntriesCount(PRUint32
* count
)
404 *count
= mTotalItemsInManifest
;
409 nsJAR::GetJarPath(nsACString
& aResult
)
411 NS_ENSURE_ARG_POINTER(mZipFile
);
413 return mZipFile
->GetNativePath(aResult
);
420 nsCOMPtr
<nsILocalFile
> localFile
= do_QueryInterface(mZipFile
, &rv
);
421 if (NS_FAILED(rv
)) return nsnull
;
424 rv
= localFile
->OpenNSPRFileDesc(PR_RDONLY
, 0000, &fd
);
425 if (NS_FAILED(rv
)) return nsnull
;
430 //----------------------------------------------
431 // nsJAR private implementation
432 //----------------------------------------------
434 nsJAR::LoadEntry(const char* aFilename
, char** aBuf
, PRUint32
* aBufLen
)
436 //-- Get a stream for reading the file
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
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
;
452 rv
= manifestStream
->Read(buf
, len
, &bytesRead
);
453 if (bytesRead
!= len
)
454 rv
= NS_ERROR_FILE_CORRUPTED
;
459 buf
[len
] = '\0'; //Null-terminate the buffer
468 nsJAR::ReadLine(const char** src
)
470 //--Moves pointer to beginning of next line and returns line length
471 // not including CR/LF.
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
480 else // some data left on this line
486 if (eol
[0] == '\r' && eol
[1] == '\n') // CR LF, so skip 2
488 else // Either CR or LF, so skip 1
494 //-- The following #defines are used by ParseManifest()
495 // and ParseOneFile(). The header strings are defined in the JAR specification.
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"
504 nsJAR::ParseManifest()
506 //-- Verification Step 1
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
517 rv
= files
->HasMore(&more
);
518 NS_ENSURE_SUCCESS(rv
, rv
);
521 mGlobalStatus
= JAR_NO_MANIFEST
;
522 mParsedManifest
= PR_TRUE
;
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
;
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
;
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
;
554 rv
= files
->HasMore(&more
);
555 if (NS_FAILED(rv
)) return rv
;
558 mGlobalStatus
= JAR_NO_MANIFEST
;
559 mParsedManifest
= PR_TRUE
;
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
;
576 nsCAutoString
tempFilename(sigFilename
); tempFilename
.Append("rsa", 3);
577 rv
= LoadEntry(tempFilename
.get(), getter_Copies(sigBuffer
), &sigLen
);
581 nsCAutoString
tempFilename(sigFilename
); tempFilename
.Append("RSA", 3);
582 rv
= LoadEntry(tempFilename
.get(), getter_Copies(sigBuffer
), &sigLen
);
586 mGlobalStatus
= JAR_NO_MANIFEST
;
587 mParsedManifest
= PR_TRUE
;
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
;
601 //-- Verify that the signature file is a valid signature of the SF file
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
;
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
;
624 nsJAR::ParseOneFile(const char* filebuf
, PRInt16 aFileType
)
626 //-- Check file header
627 const char* nextLineStart
= filebuf
;
628 nsCAutoString curLine
;
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
639 linelen
= ReadLine(&nextLineStart
);
640 } while (linelen
> 0);
642 //-- Set up parsing variables
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
;
657 curPos
= nextLineStart
;
658 linelen
= ReadLine(&nextLineStart
);
659 curLine
.Assign(curPos
, linelen
);
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?
670 curItemMF
->mType
= JAR_INVALID
;
673 //-- If it's an internal item, it must correspond
674 // to a valid jar entry
675 if (curItemMF
->mType
== JAR_INTERNAL
)
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
)
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
703 sectionStart
= nextLineStart
;
704 if (!(curItemMF
= new nsJARManifestItem()))
705 return NS_ERROR_OUT_OF_MEMORY
;
706 } // (aFileType == JAR_MF)
708 //-- file type is SF, compare digest with calculated
709 // section digests from MF file.
713 nsJARManifestItem
* curItemSF
;
714 nsCStringKey
key(curItemName
);
715 curItemSF
= (nsJARManifestItem
*)mManifestData
.Get(&key
);
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
)
723 if (storedSectionDigest
.IsEmpty())
724 curItemSF
->status
= JAR_NOT_SIGNED
;
727 if (!storedSectionDigest
.Equals(curItemSF
->calculatedSectionDigest
))
728 curItemSF
->status
= JAR_INVALID_MANIFEST
;
729 curItemSF
->calculatedSectionDigest
.Truncate();
730 storedSectionDigest
.Truncate();
732 } // (aPrincipal != nsnull)
736 if(nextLineStart
== nsnull
) // end-of-file
738 } // aFileType == JAR_SF
739 foundName
= PR_FALSE
;
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
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:
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
;
772 storedSectionDigest
= lineData
;
776 // (2) Name: associates this manifest section with a file in the jar.
777 if (!foundName
&& lineName
.LowerCaseEqualsLiteral("name"))
779 curItemName
= lineData
;
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
;
791 curItemMF
->mType
= JAR_INVALID
;
800 nsJAR::VerifyEntry(nsJARManifestItem
* aManItem
, const char* aEntryData
,
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
;
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
;
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 ");
828 message
.AppendWithConversion(aFilename
);
830 message
.AppendLiteral("this .jar archive");
831 message
.AppendLiteral(" is invalid because ");
835 message
.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
837 case JAR_INVALID_SIG
:
838 message
.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
840 case JAR_INVALID_UNKNOWN_CA
:
841 message
.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
843 case JAR_INVALID_MANIFEST
:
844 message
.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
846 case JAR_INVALID_ENTRY
:
847 message
.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
849 case JAR_NO_MANIFEST
:
850 message
.AppendLiteral("the archive did not contain a manifest.");
853 message
.AppendLiteral("of an unknown problem.");
856 // Report error in JS console
857 nsCOMPtr
<nsIConsoleService
> console(do_GetService("@mozilla.org/consoleservice;1"));
860 console
->LogStringMessage(message
.get());
863 char* messageCstr
= ToNewCString(message
);
864 if (!messageCstr
) return;
865 fprintf(stderr
, "%s\n", messageCstr
);
866 nsMemory::Free(messageCstr
);
871 nsresult
nsJAR::CalculateDigest(const char* aInBuf
, PRUint32 aLen
,
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 //----------------------------------------------
894 nsJAREnumerator::HasMore(PRBool
* aResult
)
896 // try to get the next element
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
904 NS_ENSURE_SUCCESS(rv
, NS_ERROR_FAILURE
); // no error translation
911 //----------------------------------------------
912 // nsJAREnumerator::GetNext
913 //----------------------------------------------
915 nsJAREnumerator::GetNext(nsACString
& aResult
)
917 // check if the current item is "stale"
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
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 //------------------------------------------
948 nsJARItem::GetCompression(PRUint16
*aCompression
)
950 NS_ENSURE_ARG_POINTER(aCompression
);
952 *aCompression
= mCompression
;
956 //------------------------------------------
957 // nsJARItem::GetSize
958 //------------------------------------------
960 nsJARItem::GetSize(PRUint32
*aSize
)
962 NS_ENSURE_ARG_POINTER(aSize
);
968 //------------------------------------------
969 // nsJARItem::GetRealSize
970 //------------------------------------------
972 nsJARItem::GetRealSize(PRUint32
*aRealsize
)
974 NS_ENSURE_ARG_POINTER(aRealsize
);
976 *aRealsize
= mRealsize
;
980 //------------------------------------------
981 // nsJARItem::GetCrc32
982 //------------------------------------------
984 nsJARItem::GetCRC32(PRUint32
*aCrc32
)
986 NS_ENSURE_ARG_POINTER(aCrc32
);
992 //------------------------------------------
993 // nsJARItem::GetIsDirectory
994 //------------------------------------------
996 nsJARItem::GetIsDirectory(PRBool
*aIsDirectory
)
998 NS_ENSURE_ARG_POINTER(aIsDirectory
);
1000 *aIsDirectory
= mIsDirectory
;
1004 //------------------------------------------
1005 // nsJARItem::GetIsSynthetic
1006 //------------------------------------------
1008 nsJARItem::GetIsSynthetic(PRBool
*aIsSynthetic
)
1010 NS_ENSURE_ARG_POINTER(aIsSynthetic
);
1012 *aIsSynthetic
= mIsSynthetic
;
1016 //------------------------------------------
1017 // nsJARItem::GetLastModifiedTime
1018 //------------------------------------------
1020 nsJARItem::GetLastModifiedTime(PRTime
* aLastModTime
)
1022 NS_ENSURE_ARG_POINTER(aLastModTime
);
1024 *aLastModTime
= GetModTime(mDate
, mTime
);
1028 ////////////////////////////////////////////////////////////////////////////////
1029 // nsIZipReaderCache
1031 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache
, nsIZipReaderCache
, nsIObserver
, nsISupportsWeakReference
)
1033 nsZipReaderCache::nsZipReaderCache()
1036 #ifdef ZIP_CACHE_HIT_RATE
1038 mZipCacheLookups(0),
1040 mZipCacheFlushes(0),
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");
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
;
1066 DropZipReaderCache(nsHashKey
*aKey
, void *aData
, void* closure
)
1068 nsJAR
* zip
= (nsJAR
*)aData
;
1069 zip
->SetZipReaderCache(nsnull
);
1073 nsZipReaderCache::~nsZipReaderCache()
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
);
1088 nsZipReaderCache::GetZip(nsIFile
* zipFile
, nsIZipReader
* *result
)
1090 NS_ENSURE_ARG_POINTER(zipFile
);
1092 nsCOMPtr
<nsIJAR
> antiLockZipGrip
;
1093 nsAutoLock
lock(mLock
);
1095 #ifdef ZIP_CACHE_HIT_RATE
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
1106 #ifdef ZIP_CACHE_HIT_RATE
1109 zip
->ClearReleaseTime();
1113 antiLockZipGrip
= zip
;
1118 return NS_ERROR_OUT_OF_MEMORY
;
1120 zip
->SetZipReaderCache(this);
1122 rv
= zip
->Open(zipFile
);
1123 if (NS_FAILED(rv
)) {
1128 PRBool collision
= mZips
.Put(&key
, static_cast<nsIZipReader
*>(zip
)); // AddRefs to 2
1129 NS_ASSERTION(!collision
, "horked");
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
;
1151 struct ZipFindData
{nsJAR
* zip
; PRBool found
;};
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
;
1166 nsZipReaderCache::ReleaseZip(nsJAR
* zip
)
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
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
1195 zip
->SetReleaseTime();
1197 if (mZips
.Count() <= mCacheSize
)
1200 nsJAR
* oldest
= nsnull
;
1201 mZips
.Enumerate(FindOldestZip
, &oldest
);
1203 // Because of the craziness above it is possible that there is no zip that
1208 #ifdef ZIP_CACHE_HIT_RATE
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
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");
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
);
1244 nsZipReaderCache::Observe(nsISupports
*aSubject
,
1246 const PRUnichar
*aSomeData
)
1248 if (strcmp(aTopic
, "memory-pressure") == 0) {
1249 nsAutoLock
lock(mLock
);
1251 nsHashKey
* flushable
= nsnull
;
1252 mZips
.Enumerate(FindFlushableZip
, &flushable
);
1255 PRBool removed
= mZips
.Remove(flushable
); // Releases
1256 NS_ASSERTION(removed
, "botched");
1259 printf("flushed something from the jar cache\n");
1263 else if (strcmp(aTopic
, "chrome-flush-caches") == 0) {
1264 mZips
.Enumerate(DropZipReaderCache
, nsnull
);
1270 PRTime
GetModTime(PRUint16 aDate
, PRUint16 aTime
)
1272 PRExplodedTime time
;
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 ////////////////////////////////////////////////////////////////////////////////