b=450088 backing out (new reftest failed)
[wine-gecko.git] / modules / libjar / nsJAR.cpp
blob15f84f7bb956d94f7e6fde3d5c0fa1e8f161d5a5
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 char* calculatedSectionDigest;
88 char* 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),
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 //----------------------------------------------
115 static PRBool
116 DeleteManifestEntry(nsHashKey* aKey, void* aData, void* closure)
118 //-- deletes an entry in mManifestData.
119 delete (nsJARManifestItem*)aData;
120 return PR_TRUE;
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),
128 mCache(nsnull),
129 mLock(nsnull),
130 mMtime(0),
131 mTotalItemsInManifest(0)
135 nsJAR::~nsJAR()
137 Close();
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)
146 nsrefcnt count;
147 NS_PRECONDITION(0 != mRefCnt, "dup release");
148 count = PR_AtomicDecrement((PRInt32 *)&mRefCnt);
149 NS_LOG_RELEASE(this, count, "nsJAR");
150 if (0 == count) {
151 mRefCnt = 1; /* stabilize */
152 /* enable this to find non-threadsafe destructors: */
153 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
154 NS_DELETEXPCOM(this);
155 return 0;
157 else if (1 == count && mCache) {
158 nsresult rv = mCache->ReleaseZip(this);
159 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
161 return count;
164 //----------------------------------------------
165 // nsIZipReader implementation
166 //----------------------------------------------
168 NS_IMETHODIMP
169 nsJAR::Open(nsIFile* zipFile)
171 if (mLock) return NS_ERROR_FAILURE; // Already open!
173 mZipFile = zipFile;
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();
186 return rv;
189 NS_IMETHODIMP
190 nsJAR::GetFile(nsIFile* *result)
192 *result = mZipFile;
193 NS_IF_ADDREF(*result);
194 return NS_OK;
197 NS_IMETHODIMP
198 nsJAR::Close()
200 if (mLock) {
201 PR_DestroyLock(mLock);
202 mLock = nsnull;
205 mParsedManifest = PR_FALSE;
206 mManifestData.Reset();
207 mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
208 mTotalItemsInManifest = 0;
210 return mZip.CloseArchive();
213 NS_IMETHODIMP
214 nsJAR::Test(const char *aEntryName)
216 return mZip.Test(aEntryName);
219 NS_IMETHODIMP
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);
226 nsresult rv;
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.
237 //XXX Bug 332139:
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)
245 return rv;
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.
254 else
256 PRFileDesc* fd;
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
261 nsCAutoString path;
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);
278 return NS_OK;
281 NS_IMETHODIMP
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);
291 return NS_OK;
294 NS_IMETHODIMP
295 nsJAR::HasEntry(const nsACString &aEntryName, PRBool *result)
297 *result = mZip.GetItem(PromiseFlatCString(aEntryName).get()) != nsnull;
298 return NS_OK;
301 NS_IMETHODIMP
302 nsJAR::FindEntries(const char *aPattern, nsIUTF8StringEnumerator **result)
304 NS_ENSURE_ARG_POINTER(result);
306 nsZipFind *find;
307 nsresult rv = mZip.FindInit(aPattern, &find);
308 NS_ENSURE_SUCCESS(rv, rv);
310 nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
311 if (!zipEnum) {
312 delete find;
313 return NS_ERROR_OUT_OF_MEMORY;
316 NS_ADDREF(*result = zipEnum);
317 return NS_OK;
320 NS_IMETHODIMP
321 nsJAR::GetInputStream(const char* aFilename, nsIInputStream** result)
323 return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
326 NS_IMETHODIMP
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;
335 if (*aEntryName) {
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);
345 nsresult rv = NS_OK;
346 if (!item || item->isDirectory) {
347 rv = jis->InitDirectory(&mZip, aJarDirSpec, aEntryName);
348 } else {
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;
353 fd = OpenFile();
354 if (fd) {
355 rv = jis->InitFile(&mZip, item, fd);
356 // |jis| now owns |fd|
357 } else {
358 rv = NS_ERROR_FAILURE;
361 if (NS_FAILED(rv)) {
362 NS_RELEASE(*result);
364 return rv;
367 //----------------------------------------------
368 // nsIJAR implementation
369 //----------------------------------------------
371 NS_IMETHODIMP
372 nsJAR::GetCertificatePrincipal(const char* aFilename, nsIPrincipal** aPrincipal)
374 //-- Parameter check
375 if (!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)
383 return NS_OK;
385 PRInt16 requestedStatus;
386 if (aFilename)
388 //-- Find the item
389 nsCStringKey key(aFilename);
390 nsJARManifestItem* manItem = static_cast<nsJARManifestItem*>(mManifestData.Get(&key));
391 if (!manItem)
392 return NS_OK;
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);
415 return NS_OK;
418 NS_IMETHODIMP
419 nsJAR::GetManifestEntriesCount(PRUint32* count)
421 *count = mTotalItemsInManifest;
422 return NS_OK;
425 nsresult
426 nsJAR::GetJarPath(nsACString& aResult)
428 NS_ENSURE_ARG_POINTER(mZipFile);
430 return mZipFile->GetNativePath(aResult);
433 PRFileDesc*
434 nsJAR::OpenFile()
436 nsresult rv;
437 nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mZipFile, &rv);
438 if (NS_FAILED(rv)) return nsnull;
440 PRFileDesc* fd;
441 rv = localFile->OpenNSPRFileDesc(PR_RDONLY, 0000, &fd);
442 if (NS_FAILED(rv)) return nsnull;
444 return fd;
447 //----------------------------------------------
448 // nsJAR private implementation
449 //----------------------------------------------
450 nsresult
451 nsJAR::LoadEntry(const char* aFilename, char** aBuf, PRUint32* aBufLen)
453 //-- Get a stream for reading the file
454 nsresult rv;
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
460 char* buf;
461 PRUint32 len;
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;
468 PRUint32 bytesRead;
469 rv = manifestStream->Read(buf, len, &bytesRead);
470 if (bytesRead != len)
471 rv = NS_ERROR_FILE_CORRUPTED;
472 if (NS_FAILED(rv)) {
473 PR_FREEIF(buf);
474 return rv;
476 buf[len] = '\0'; //Null-terminate the buffer
477 *aBuf = buf;
478 if (aBufLen)
479 *aBufLen = len;
480 return NS_OK;
484 PRInt32
485 nsJAR::ReadLine(const char** src)
487 //--Moves pointer to beginning of next line and returns line length
488 // not including CR/LF.
489 PRInt32 length;
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
496 *src = nsnull;
497 else // some data left on this line
498 *src += length;
500 else
502 length = eol - *src;
503 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
504 *src = eol+2;
505 else // Either CR or LF, so skip 1
506 *src = eol+1;
508 return length;
511 //-- The following #defines are used by ParseManifest()
512 // and ParseOneFile(). The header strings are defined in the JAR specification.
513 #define JAR_MF 1
514 #define JAR_SF 2
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"
520 nsresult
521 nsJAR::ParseManifest()
523 //-- Verification Step 1
524 if (mParsedManifest)
525 return NS_OK;
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
533 PRBool more;
534 rv = files->HasMore(&more);
535 NS_ENSURE_SUCCESS(rv, rv);
536 if (!more)
538 mGlobalStatus = JAR_NO_MANIFEST;
539 mParsedManifest = PR_TRUE;
540 return NS_OK;
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;
550 if (more)
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;
561 //-- Parse it
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;
570 //-- Get an SF file
571 rv = files->HasMore(&more);
572 if (NS_FAILED(rv)) return rv;
573 if (!more)
575 mGlobalStatus = JAR_NO_MANIFEST;
576 mParsedManifest = PR_TRUE;
577 return NS_OK;
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;
591 PRUint32 sigLen;
593 nsCAutoString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
594 rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
596 if (NS_FAILED(rv))
598 nsCAutoString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
599 rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
601 if (NS_FAILED(rv))
603 mGlobalStatus = JAR_NO_MANIFEST;
604 mParsedManifest = PR_TRUE;
605 return NS_OK;
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;
615 return NS_OK;
618 //-- Verify that the signature file is a valid signature of the SF file
619 PRInt32 verifyError;
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;
627 else
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;
637 return NS_OK;
640 nsresult
641 nsJAR::ParseOneFile(const char* filebuf, PRInt16 aFileType)
643 //-- Check file header
644 const char* nextLineStart = filebuf;
645 nsCAutoString curLine;
646 PRInt32 linelen;
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
655 do {
656 linelen = ReadLine(&nextLineStart);
657 } while (linelen > 0);
659 //-- Set up parsing variables
660 const char* curPos;
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;
672 for(;;)
674 curPos = nextLineStart;
675 linelen = ReadLine(&nextLineStart);
676 curLine.Assign(curPos, linelen);
677 if (linelen == 0)
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?
686 if(!foundName)
687 curItemMF->mType = JAR_INVALID;
688 else
690 //-- If it's an internal item, it must correspond
691 // to a valid jar entry
692 if (curItemMF->mType == JAR_INTERNAL)
694 PRBool exists;
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)
707 delete curItemMF;
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
718 break;
720 sectionStart = nextLineStart;
721 if (!(curItemMF = new nsJARManifestItem()))
722 return NS_ERROR_OUT_OF_MEMORY;
723 } // (aFileType == JAR_MF)
724 else
725 //-- file type is SF, compare digest with calculated
726 // section digests from MF file.
728 if (foundName)
730 nsJARManifestItem* curItemSF;
731 nsCStringKey key(curItemName);
732 curItemSF = (nsJARManifestItem*)mManifestData.Get(&key);
733 if(curItemSF)
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)
739 { // Compare digests
740 if (storedSectionDigest.IsEmpty())
741 curItemSF->status = JAR_NOT_SIGNED;
742 else
744 if (!storedSectionDigest.Equals((const char*)curItemSF->calculatedSectionDigest))
745 curItemSF->status = JAR_INVALID_MANIFEST;
746 PR_FREEIF(curItemSF->calculatedSectionDigest)
747 storedSectionDigest = "";
749 } // (aPrincipal != nsnull)
750 } // if(curItemSF)
751 } // if(foundName)
753 if(nextLineStart == nsnull) // end-of-file
754 break;
755 } // aFileType == JAR_SF
756 foundName = PR_FALSE;
757 continue;
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
774 continue;
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:
782 // (1) Digest:
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());
794 else
795 storedSectionDigest = lineData;
796 continue;
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;
804 foundName = PR_TRUE;
805 continue;
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;
817 else
818 curItemMF->mType = JAR_INVALID;
819 continue;
822 } // for (;;)
823 return NS_OK;
824 } //ParseOneFile()
826 nsresult
827 nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
828 PRUint32 aLen)
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;
835 else
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;
847 return NS_OK;
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 ");
855 if (aFilename)
856 message.AppendWithConversion(aFilename);
857 else
858 message.AppendLiteral("this .jar archive");
859 message.AppendLiteral(" is invalid because ");
860 switch(errorCode)
862 case JAR_NOT_SIGNED:
863 message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
864 break;
865 case JAR_INVALID_SIG:
866 message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
867 break;
868 case JAR_INVALID_UNKNOWN_CA:
869 message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
870 break;
871 case JAR_INVALID_MANIFEST:
872 message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
873 break;
874 case JAR_INVALID_ENTRY:
875 message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
876 break;
877 case JAR_NO_MANIFEST:
878 message.AppendLiteral("the archive did not contain a manifest.");
879 break;
880 default:
881 message.AppendLiteral("of an unknown problem.");
884 // Report error in JS console
885 nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
886 if (console)
888 console->LogStringMessage(message.get());
890 #ifdef DEBUG
891 char* messageCstr = ToNewCString(message);
892 if (!messageCstr) return;
893 fprintf(stderr, "%s\n", messageCstr);
894 nsMemory::Free(messageCstr);
895 #endif
899 nsresult nsJAR::CalculateDigest(const char* aInBuf, PRUint32 aLen,
900 char** digest)
902 *digest = nsnull;
903 nsresult rv;
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 //----------------------------------------------
929 NS_IMETHODIMP
930 nsJAREnumerator::HasMore(PRBool* aResult)
932 // try to get the next element
933 if (!mCurr) {
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
938 return NS_OK;
940 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
943 *aResult = PR_TRUE;
944 return NS_OK;
947 //----------------------------------------------
948 // nsJAREnumerator::GetNext
949 //----------------------------------------------
950 NS_IMETHODIMP
951 nsJAREnumerator::GetNext(nsACString& aResult)
953 // check if the current item is "stale"
954 if (!mCurr) {
955 PRBool bMore;
956 nsresult rv = HasMore(&bMore);
957 if (NS_FAILED(rv) || !bMore)
958 return NS_ERROR_FAILURE; // no error translation
960 aResult = mCurr;
961 mCurr = 0; // we just gave this one away
962 return NS_OK;
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 //------------------------------------------
983 NS_IMETHODIMP
984 nsJARItem::GetCompression(PRUint16 *aCompression)
986 NS_ENSURE_ARG_POINTER(aCompression);
988 *aCompression = mCompression;
989 return NS_OK;
992 //------------------------------------------
993 // nsJARItem::GetSize
994 //------------------------------------------
995 NS_IMETHODIMP
996 nsJARItem::GetSize(PRUint32 *aSize)
998 NS_ENSURE_ARG_POINTER(aSize);
1000 *aSize = mSize;
1001 return NS_OK;
1004 //------------------------------------------
1005 // nsJARItem::GetRealSize
1006 //------------------------------------------
1007 NS_IMETHODIMP
1008 nsJARItem::GetRealSize(PRUint32 *aRealsize)
1010 NS_ENSURE_ARG_POINTER(aRealsize);
1012 *aRealsize = mRealsize;
1013 return NS_OK;
1016 //------------------------------------------
1017 // nsJARItem::GetCrc32
1018 //------------------------------------------
1019 NS_IMETHODIMP
1020 nsJARItem::GetCRC32(PRUint32 *aCrc32)
1022 NS_ENSURE_ARG_POINTER(aCrc32);
1024 *aCrc32 = mCrc32;
1025 return NS_OK;
1028 //------------------------------------------
1029 // nsJARItem::GetIsDirectory
1030 //------------------------------------------
1031 NS_IMETHODIMP
1032 nsJARItem::GetIsDirectory(PRBool *aIsDirectory)
1034 NS_ENSURE_ARG_POINTER(aIsDirectory);
1036 *aIsDirectory = mIsDirectory;
1037 return NS_OK;
1040 //------------------------------------------
1041 // nsJARItem::GetIsSynthetic
1042 //------------------------------------------
1043 NS_IMETHODIMP
1044 nsJARItem::GetIsSynthetic(PRBool *aIsSynthetic)
1046 NS_ENSURE_ARG_POINTER(aIsSynthetic);
1048 *aIsSynthetic = mIsSynthetic;
1049 return NS_OK;
1052 //------------------------------------------
1053 // nsJARItem::GetLastModifiedTime
1054 //------------------------------------------
1055 NS_IMETHODIMP
1056 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
1058 NS_ENSURE_ARG_POINTER(aLastModTime);
1060 *aLastModTime = GetModTime(mDate, mTime);
1061 return NS_OK;
1064 ////////////////////////////////////////////////////////////////////////////////
1065 // nsIZipReaderCache
1067 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
1069 nsZipReaderCache::nsZipReaderCache()
1070 : mLock(nsnull),
1071 mZips(16)
1072 #ifdef ZIP_CACHE_HIT_RATE
1074 mZipCacheLookups(0),
1075 mZipCacheHits(0),
1076 mZipCacheFlushes(0),
1077 mZipSyncMisses(0)
1078 #endif
1082 NS_IMETHODIMP
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");
1090 if (os)
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;
1101 static PRBool
1102 DropZipReaderCache(nsHashKey *aKey, void *aData, void* closure)
1104 nsJAR* zip = (nsJAR*)aData;
1105 zip->SetZipReaderCache(nsnull);
1106 return PR_TRUE;
1109 nsZipReaderCache::~nsZipReaderCache()
1111 if (mLock)
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);
1120 #endif
1123 NS_IMETHODIMP
1124 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
1126 NS_ENSURE_ARG_POINTER(zipFile);
1127 nsresult rv;
1128 nsCOMPtr<nsIJAR> antiLockZipGrip;
1129 nsAutoLock lock(mLock);
1131 #ifdef ZIP_CACHE_HIT_RATE
1132 mZipCacheLookups++;
1133 #endif
1135 nsCAutoString path;
1136 rv = zipFile->GetNativePath(path);
1137 if (NS_FAILED(rv)) return rv;
1139 PRInt64 Mtime;
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
1147 mZipCacheHits++;
1148 #endif
1149 zip->ClearReleaseTime();
1151 else {
1152 if (zip) {
1153 antiLockZipGrip = zip;
1154 mZips.Remove(&key);
1156 zip = new nsJAR();
1157 if (zip == nsnull)
1158 return NS_ERROR_OUT_OF_MEMORY;
1159 NS_ADDREF(zip);
1160 zip->SetZipReaderCache(this);
1162 rv = zip->Open(zipFile);
1163 if (NS_FAILED(rv)) {
1164 NS_RELEASE(zip);
1165 return rv;
1168 PRBool collision = mZips.Put(&key, static_cast<nsIZipReader*>(zip)); // AddRefs to 2
1169 NS_ASSERTION(!collision, "horked");
1171 *result = zip;
1172 return rv;
1175 static PRBool
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;
1188 return PR_TRUE;
1191 struct ZipFindData {nsJAR* zip; PRBool found;};
1193 static PRBool
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;
1200 return PR_FALSE;
1202 return PR_TRUE;
1205 nsresult
1206 nsZipReaderCache::ReleaseZip(nsJAR* zip)
1208 nsresult rv;
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
1221 // deleted zip!
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
1230 mZipSyncMisses++;
1231 #endif
1232 return NS_OK;
1235 zip->SetReleaseTime();
1237 if (mZips.Count() <= mCacheSize)
1238 return NS_OK;
1240 nsJAR* oldest = nsnull;
1241 mZips.Enumerate(FindOldestZip, &oldest);
1243 // Because of the craziness above it is possible that there is no zip that
1244 // needs removing.
1245 if (!oldest)
1246 return NS_OK;
1248 #ifdef ZIP_CACHE_HIT_RATE
1249 mZipCacheFlushes++;
1250 #endif
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
1258 nsCAutoString path;
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");
1266 return NS_OK;
1269 static PRBool
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);
1278 return PR_FALSE;
1280 return PR_TRUE;
1283 NS_IMETHODIMP
1284 nsZipReaderCache::Observe(nsISupports *aSubject,
1285 const char *aTopic,
1286 const PRUnichar *aSomeData)
1288 if (strcmp(aTopic, "memory-pressure") == 0) {
1289 nsAutoLock lock(mLock);
1290 while (PR_TRUE) {
1291 nsHashKey* flushable = nsnull;
1292 mZips.Enumerate(FindFlushableZip, &flushable);
1293 if ( ! flushable )
1294 break;
1295 PRBool removed = mZips.Remove(flushable); // Releases
1296 NS_ASSERTION(removed, "botched");
1298 #ifdef xDEBUG_jband
1299 printf("flushed something from the jar cache\n");
1300 #endif
1303 else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
1304 mZips.Enumerate(DropZipReaderCache, nsnull);
1305 mZips.Reset();
1307 return NS_OK;
1310 PRTime GetModTime(PRUint16 aDate, PRUint16 aTime)
1312 PRExplodedTime time;
1314 time.tm_usec = 0;
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 ////////////////////////////////////////////////////////////////////////////////