Back to index

lightning-sunbird  0.9+nobinonly
nsJAR.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code, released
00016  * March 31, 1998.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Daniel Veditz <dveditz@netscape.com>
00025  *   Samir Gehani <sgehani@netscape.com>
00026  *   Mitch Stoltz <mstoltz@netsape.com>
00027  *   Pierre Phaneuf <pp@ludusdesign.com>
00028  *
00029  * Alternatively, the contents of this file may be used under the terms of
00030  * either the GNU General Public License Version 2 or later (the "GPL"), or
00031  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00032  * in which case the provisions of the GPL or the LGPL are applicable instead
00033  * of those above. If you wish to allow use of your version of this file only
00034  * under the terms of either the GPL or the LGPL, and not to allow others to
00035  * use your version of this file under the terms of the MPL, indicate your
00036  * decision by deleting the provisions above and replace them with the notice
00037  * and other provisions required by the GPL or the LGPL. If you do not delete
00038  * the provisions above, a recipient may use your version of this file under
00039  * the terms of any one of the MPL, the GPL or the LGPL.
00040  *
00041  * ***** END LICENSE BLOCK ***** */
00042 #include <string.h>
00043 #include "nsJARInputStream.h"
00044 #include "nsJAR.h"
00045 #include "nsILocalFile.h"
00046 #include "nsXPIDLString.h"
00047 #include "nsReadableUtils.h"
00048 #include "nsIServiceManager.h"
00049 #include "plbase64.h"
00050 #include "nsIConsoleService.h"
00051 #include "nscore.h"
00052 #include "nsCRT.h"
00053 #include "nsICryptoHash.h"
00054 
00055 #ifdef XP_UNIX
00056   #include <sys/stat.h>
00057 #elif defined (XP_WIN) || defined(XP_OS2)
00058   #include <io.h>
00059 #endif
00060 
00061 //----------------------------------------------
00062 // Errors and other utility definitions
00063 //----------------------------------------------
00064 #ifndef __gen_nsIFile_h__
00065 #define NS_ERROR_FILE_UNRECOGNIZED_PATH         NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 1)
00066 #define NS_ERROR_FILE_UNRESOLVABLE_SYMLINK      NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 2)
00067 #define NS_ERROR_FILE_EXECUTION_FAILED          NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 3)
00068 #define NS_ERROR_FILE_UNKNOWN_TYPE              NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 4)
00069 #define NS_ERROR_FILE_DESTINATION_NOT_DIR       NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 5)
00070 #define NS_ERROR_FILE_TARGET_DOES_NOT_EXIST     NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 6)
00071 #define NS_ERROR_FILE_COPY_OR_MOVE_FAILED       NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 7)
00072 #define NS_ERROR_FILE_ALREADY_EXISTS            NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 8)
00073 #define NS_ERROR_FILE_INVALID_PATH              NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 9)
00074 #define NS_ERROR_FILE_DISK_FULL                 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 10)
00075 #define NS_ERROR_FILE_CORRUPTED                 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 11)
00076 #endif
00077 
00078 static nsresult
00079 ziperr2nsresult(PRInt32 ziperr)
00080 {
00081   switch (ziperr) {
00082     case ZIP_OK:                return NS_OK;
00083     case ZIP_ERR_MEMORY:        return NS_ERROR_OUT_OF_MEMORY;
00084     case ZIP_ERR_DISK:          return NS_ERROR_FILE_DISK_FULL;
00085     case ZIP_ERR_CORRUPT:       return NS_ERROR_FILE_CORRUPTED;
00086     case ZIP_ERR_PARAM:         return NS_ERROR_ILLEGAL_VALUE;
00087     case ZIP_ERR_FNF:           return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
00088     case ZIP_ERR_UNSUPPORTED:   return NS_ERROR_NOT_IMPLEMENTED;
00089     default:                    return NS_ERROR_FAILURE;
00090   }
00091 }
00092 
00093 //-- PR_Free doesn't null the pointer. 
00094 //   This macro takes care of that.
00095 #define JAR_NULLFREE(_ptr) \
00096   {                        \
00097     PR_FREEIF(_ptr);       \
00098     _ptr = nsnull;         \
00099   }
00100 
00101 //----------------------------------------------
00102 // nsJARManifestItem declaration
00103 //----------------------------------------------
00104 /*
00105  * nsJARManifestItem contains meta-information pertaining 
00106  * to an individual JAR entry, taken from the 
00107  * META-INF/MANIFEST.MF and META-INF/ *.SF files.
00108  * This is security-critical information, defined here so it is not
00109  * accessible from anywhere else.
00110  */
00111 typedef enum
00112 {
00113   JAR_INVALID       = 1,
00114   JAR_INTERNAL      = 2,
00115   JAR_EXTERNAL      = 3
00116 } JARManifestItemType;
00117 
00118 class nsJARManifestItem
00119 {
00120 public:
00121   JARManifestItemType mType;
00122 
00123   // True if the second step of verification (VerifyEntry) 
00124   // has taken place:
00125   PRBool              entryVerified;
00126   
00127   // Not signed, valid, or failure code
00128   PRInt16             status;
00129   
00130   // Internal storage of digests
00131   char*               calculatedSectionDigest;
00132   char*               storedEntryDigest;
00133 
00134   nsJARManifestItem();
00135   virtual ~nsJARManifestItem();
00136 };
00137 
00138 //-------------------------------------------------
00139 // nsJARManifestItem constructors and destructor
00140 //-------------------------------------------------
00141 nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
00142                                         entryVerified(PR_FALSE),
00143                                         status(nsIJAR::NOT_SIGNED),
00144                                         calculatedSectionDigest(nsnull),
00145                                         storedEntryDigest(nsnull)
00146 {
00147 }
00148 
00149 nsJARManifestItem::~nsJARManifestItem()
00150 {
00151   // Delete digests if necessary
00152   PR_FREEIF(calculatedSectionDigest);
00153   PR_FREEIF(storedEntryDigest);
00154 }
00155 
00156 //----------------------------------------------
00157 // nsJAR constructor/destructor
00158 //----------------------------------------------
00159 PR_STATIC_CALLBACK(PRBool)
00160 DeleteManifestEntry(nsHashKey* aKey, void* aData, void* closure)
00161 {
00162 //-- deletes an entry in  mManifestData.
00163   delete (nsJARManifestItem*)aData;
00164   return PR_TRUE;
00165 }
00166 
00167 // The following initialization makes a guess of 10 entries per jarfile.
00168 nsJAR::nsJAR(): mManifestData(nsnull, nsnull, DeleteManifestEntry, nsnull, 10),
00169                 mParsedManifest(PR_FALSE), mGlobalStatus(nsIJAR::NOT_SIGNED),
00170                 mReleaseTime(PR_INTERVAL_NO_TIMEOUT), 
00171                 mCache(nsnull), 
00172                 mLock(nsnull),
00173                 mTotalItemsInManifest(0),
00174                 mFd(nsnull)
00175 {
00176 }
00177 
00178 nsJAR::~nsJAR()
00179 {
00180   Close();
00181   if (mLock) 
00182     PR_DestroyLock(mLock);
00183 }
00184 
00185 NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsJAR, nsIZipReader, nsIJAR)
00186 NS_IMPL_THREADSAFE_ADDREF(nsJAR)
00187 
00188 // Custom Release method works with nsZipReaderCache...
00189 nsrefcnt nsJAR::Release(void) 
00190 {
00191   nsrefcnt count; 
00192   NS_PRECONDITION(0 != mRefCnt, "dup release"); 
00193   count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); 
00194   NS_LOG_RELEASE(this, count, "nsJAR"); 
00195   if (0 == count) {
00196     mRefCnt = 1; /* stabilize */ 
00197     /* enable this to find non-threadsafe destructors: */ 
00198     /* NS_ASSERT_OWNINGTHREAD(nsJAR); */ 
00199     NS_DELETEXPCOM(this); 
00200     return 0; 
00201   }
00202   else if (1 == count && mCache) {
00203     nsresult rv = mCache->ReleaseZip(this);
00204     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file");
00205   }
00206   return count; 
00207 } 
00208 
00209 //----------------------------------------------
00210 // nsIZipReader implementation
00211 //----------------------------------------------
00212 
00213 NS_IMETHODIMP
00214 nsJAR::Init(nsIFile* zipFile)
00215 {
00216   mZipFile = zipFile;
00217   mLock = PR_NewLock();
00218   return mLock ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00219 }
00220 
00221 NS_IMETHODIMP
00222 nsJAR::GetFile(nsIFile* *result)
00223 {
00224   *result = mZipFile;
00225   NS_IF_ADDREF(*result);
00226   return NS_OK;
00227 }
00228 
00229 NS_IMETHODIMP
00230 nsJAR::Open()
00231 {
00232   nsresult rv;
00233   nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mZipFile, &rv);
00234   if (NS_FAILED(rv)) return rv;
00235 
00236   rv = localFile->OpenNSPRFileDesc(PR_RDONLY, 0000, &mFd);
00237   if (NS_FAILED(rv)) return rv;
00238 
00239   PRInt32 err = mZip.OpenArchiveWithFileDesc(mFd);
00240   
00241   return ziperr2nsresult(err);
00242 }
00243 
00244 NS_IMETHODIMP
00245 nsJAR::Close()
00246 {
00247 #ifdef STANDALONE
00248   // nsZipReadState::CloseArchive closes the file descriptor
00249 #else
00250   if (mFd)
00251     PR_Close(mFd);
00252 #endif
00253   mFd = nsnull;
00254   PRInt32 err = mZip.CloseArchive();
00255   return ziperr2nsresult(err);
00256 }
00257 
00258 NS_IMETHODIMP
00259 nsJAR::Test(const char *aEntryName)
00260 {
00261   NS_ASSERTION(mFd, "File isn't open!");
00262 
00263   PRInt32 err = mZip.Test(aEntryName, mFd);
00264   return ziperr2nsresult(err);
00265 }
00266 
00267 NS_IMETHODIMP
00268 nsJAR::Extract(const char *zipEntry, nsIFile* outFile)
00269 {
00270   // nsZipArchive and zlib are not thread safe
00271   // we need to use a lock to prevent bug #51267
00272   nsAutoLock lock(mLock);
00273 
00274   nsresult rv;
00275   nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(outFile, &rv);
00276   if (NS_FAILED(rv)) return rv;
00277 
00278   nsZipItem *item = 0;
00279   PRInt32 err = mZip.GetItem(zipEntry, &item);
00280   if (err != ZIP_OK)
00281     return ziperr2nsresult(err);
00282 
00283   // Remove existing file so we set permissions correctly.
00284   localFile->Remove(PR_FALSE);
00285 
00286   PRFileDesc* fd;
00287   rv = localFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->mode, &fd);
00288   if (NS_FAILED(rv)) return NS_ERROR_FILE_ACCESS_DENIED;
00289 
00290   err = mZip.ExtractItemToFileDesc(item, fd, mFd);
00291   PR_Close(fd);
00292 
00293   if (err != ZIP_OK)
00294     outFile->Remove(PR_FALSE);
00295   else
00296   {
00297 #if defined(XP_UNIX)
00298     if (item->isSymlink) 
00299     {
00300       nsCAutoString path;
00301       rv = outFile->GetNativePath(path);
00302       if (NS_SUCCEEDED(rv)) 
00303       {
00304         err = mZip.ResolveSymlink(path.get(),item);
00305       }
00306     }
00307 #endif
00308 
00309     PRTime prtime = item->GetModTime();
00310     // nsIFile needs usecs.
00311     PRTime conversion = LL_ZERO;
00312     PRTime newTime = LL_ZERO;
00313     LL_I2L(conversion, PR_USEC_PER_MSEC);
00314     LL_DIV(newTime, prtime, conversion);
00315     // non-fatal if this fails, ignore errors
00316     outFile->SetLastModifiedTime(newTime);
00317   }
00318 
00319   return ziperr2nsresult(err);
00320 }
00321 
00322 NS_IMETHODIMP    
00323 nsJAR::GetEntry(const char *zipEntry, nsIZipEntry* *result)
00324 {
00325   nsZipItem* zipItem;
00326   PRInt32 err = mZip.GetItem(zipEntry, &zipItem);
00327   if (err != ZIP_OK) return ziperr2nsresult(err);
00328 
00329   nsJARItem* jarItem = new nsJARItem();
00330   if (jarItem == nsnull)
00331     return NS_ERROR_OUT_OF_MEMORY;
00332   NS_ADDREF(jarItem);
00333   jarItem->Init(zipItem);
00334   *result = jarItem;
00335   return NS_OK;
00336 }
00337 
00338 NS_IMETHODIMP    
00339 nsJAR::FindEntries(const char *aPattern, nsISimpleEnumerator **result)
00340 {
00341   if (!result)
00342     return NS_ERROR_INVALID_POINTER;
00343     
00344   nsZipFind *find = mZip.FindInit(aPattern);
00345   if (!find)
00346     return NS_ERROR_OUT_OF_MEMORY;
00347 
00348   nsISimpleEnumerator *zipEnum = new nsJAREnumerator(find);
00349   if (!zipEnum)
00350     return NS_ERROR_OUT_OF_MEMORY;
00351   NS_ADDREF( zipEnum );
00352 
00353   *result = zipEnum;
00354   return NS_OK;
00355 }
00356 
00357 NS_IMETHODIMP
00358 nsJAR::GetInputStream(const char* aFilename, nsIInputStream** result)
00359 {
00360   // nsZipArchive and zlib are not thread safe
00361   // we need to use a lock to prevent bug #51267
00362   nsAutoLock lock(mLock);
00363 
00364   NS_ENSURE_ARG_POINTER(result);
00365   nsJARInputStream* jis = new nsJARInputStream();
00366   if (!jis) return NS_ERROR_FAILURE;
00367 
00368   // addref now so we can delete if the Init() fails
00369   *result = NS_STATIC_CAST(nsIInputStream*,jis);
00370   NS_ADDREF(*result);
00371   
00372   nsresult rv;
00373   rv = jis->Init(this, aFilename);
00374   
00375   if (NS_FAILED(rv)) {
00376     NS_RELEASE(*result);
00377     return NS_ERROR_FAILURE;
00378   }
00379 
00380   return NS_OK;
00381 }
00382 
00383 //----------------------------------------------
00384 // nsIJAR implementation
00385 //----------------------------------------------
00386 
00387 NS_IMETHODIMP
00388 nsJAR::GetCertificatePrincipal(const char* aFilename, nsIPrincipal** aPrincipal)
00389 {
00390   //-- Parameter check
00391   if (!aPrincipal)
00392     return NS_ERROR_NULL_POINTER;
00393   *aPrincipal = nsnull;
00394 
00395   //-- Get the signature verifier service
00396   nsresult rv;
00397   nsCOMPtr<nsISignatureVerifier> verifier = 
00398            do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv);
00399   if (NS_FAILED(rv)) // No signature verifier available
00400     return NS_OK;
00401 
00402   //-- Parse the manifest
00403   rv = ParseManifest(verifier);
00404   if (NS_FAILED(rv)) return rv;
00405   if (mGlobalStatus == nsIJAR::NO_MANIFEST)
00406     return NS_OK;
00407 
00408   PRInt16 requestedStatus;
00409   if (aFilename)
00410   {
00411     //-- Find the item
00412     nsCStringKey key(aFilename);
00413     nsJARManifestItem* manItem = NS_STATIC_CAST(nsJARManifestItem*, mManifestData.Get(&key));
00414     if (!manItem)
00415       return NS_OK;
00416     //-- Verify the item against the manifest
00417     if (!manItem->entryVerified)
00418     {
00419       nsXPIDLCString entryData;
00420       PRUint32 entryDataLen;
00421       rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
00422       if (NS_FAILED(rv)) return rv;
00423       rv = VerifyEntry(verifier, manItem, entryData, entryDataLen);
00424       if (NS_FAILED(rv)) return rv;
00425     }
00426     requestedStatus = manItem->status;
00427   }
00428   else // User wants identity of signer w/o verifying any entries
00429     requestedStatus = mGlobalStatus;
00430 
00431   if (requestedStatus != nsIJAR::VALID)
00432     ReportError(aFilename, requestedStatus);
00433   else // Valid signature
00434   {
00435     *aPrincipal = mPrincipal;
00436     NS_IF_ADDREF(*aPrincipal);
00437   }
00438   return NS_OK;
00439 }
00440 
00441 NS_IMETHODIMP 
00442 nsJAR::GetManifestEntriesCount(PRUint32* count)
00443 {
00444   *count = mTotalItemsInManifest;
00445   return NS_OK;
00446 }
00447 
00448 PRFileDesc*
00449 nsJAR::OpenFile()
00450 {
00451   nsresult rv;
00452   nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mZipFile, &rv);
00453   if (NS_FAILED(rv)) return nsnull;
00454 
00455   PRFileDesc* fd;
00456   rv = localFile->OpenNSPRFileDesc(PR_RDONLY, 0000, &fd);
00457   if (NS_FAILED(rv)) return nsnull;
00458 
00459   return fd;
00460 }
00461 
00462 //----------------------------------------------
00463 // nsJAR private implementation
00464 //----------------------------------------------
00465 nsresult 
00466 nsJAR::LoadEntry(const char* aFilename, char** aBuf, PRUint32* aBufLen)
00467 {
00468   //-- Get a stream for reading the file
00469   nsresult rv;
00470   nsCOMPtr<nsIInputStream> manifestStream;
00471   rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
00472   if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
00473   
00474   //-- Read the manifest file into memory
00475   char* buf;
00476   PRUint32 len;
00477   rv = manifestStream->Available(&len);
00478   if (NS_FAILED(rv)) return rv;
00479   if (len == PRUint32(-1))
00480     return NS_ERROR_FILE_CORRUPTED; // bug 164695
00481   buf = (char*)PR_MALLOC(len+1);
00482   if (!buf) return NS_ERROR_OUT_OF_MEMORY;
00483   PRUint32 bytesRead;
00484   rv = manifestStream->Read(buf, len, &bytesRead);
00485   if (bytesRead != len) 
00486     rv = NS_ERROR_FILE_CORRUPTED;
00487   if (NS_FAILED(rv)) {
00488     PR_FREEIF(buf);
00489     return rv;
00490   }
00491   buf[len] = '\0'; //Null-terminate the buffer
00492   *aBuf = buf;
00493   if (aBufLen)
00494     *aBufLen = len;
00495   return NS_OK;
00496 }
00497 
00498 
00499 PRInt32
00500 nsJAR::ReadLine(const char** src)
00501 {
00502   //--Moves pointer to beginning of next line and returns line length
00503   //  not including CR/LF.
00504   PRInt32 length;
00505   char* eol = PL_strpbrk(*src, "\r\n");
00506 
00507   if (eol == nsnull) // Probably reached end of file before newline
00508   {
00509     length = PL_strlen(*src);
00510     if (length == 0) // immediate end-of-file
00511       *src = nsnull;
00512     else             // some data left on this line
00513       *src += length;
00514   }
00515   else
00516   {
00517     length = eol - *src;
00518     if (eol[0] == '\r' && eol[1] == '\n')      // CR LF, so skip 2
00519       *src = eol+2;
00520     else                                       // Either CR or LF, so skip 1
00521       *src = eol+1;
00522   }
00523   return length;
00524 }
00525 
00526 //-- The following #defines are used by ParseManifest()
00527 //   and ParseOneFile(). The header strings are defined in the JAR specification.
00528 #define JAR_MF 1
00529 #define JAR_SF 2
00530 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
00531 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
00532 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
00533 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
00534 
00535 nsresult
00536 nsJAR::ParseManifest(nsISignatureVerifier* verifier)
00537 {
00538   //-- Verification Step 1
00539   if (mParsedManifest)
00540     return NS_OK;
00541   //-- (1)Manifest (MF) file
00542   nsresult rv;
00543   nsCOMPtr<nsISimpleEnumerator> files;
00544   rv = FindEntries(JAR_MF_SEARCH_STRING, getter_AddRefs(files));
00545   if (!files) rv = NS_ERROR_FAILURE;
00546   if (NS_FAILED(rv)) return rv;
00547 
00548   //-- Load the file into memory
00549   nsCOMPtr<nsJARItem> file;
00550   rv = files->GetNext(getter_AddRefs(file));
00551   if (NS_FAILED(rv)) return rv;
00552   if (!file)
00553   {
00554     mGlobalStatus = nsIJAR::NO_MANIFEST;
00555     mParsedManifest = PR_TRUE;
00556     return NS_OK;
00557   }
00558   PRBool more;
00559   rv = files->HasMoreElements(&more);
00560   if (NS_FAILED(rv)) return rv;
00561   if (more)
00562   {
00563     mParsedManifest = PR_TRUE;
00564     return NS_ERROR_FILE_CORRUPTED; // More than one MF file
00565   }
00566   nsXPIDLCString manifestFilename;
00567   rv = file->GetName(getter_Copies(manifestFilename));
00568   if (!manifestFilename || NS_FAILED(rv)) return rv;
00569   nsXPIDLCString manifestBuffer;
00570   rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer));
00571   if (NS_FAILED(rv)) return rv;
00572 
00573   //-- Parse it
00574   rv = ParseOneFile(verifier, manifestBuffer, JAR_MF);
00575   if (NS_FAILED(rv)) return rv;
00576 
00577   //-- (2)Signature (SF) file
00578   // If there are multiple signatures, we select one.
00579   rv = FindEntries(JAR_SF_SEARCH_STRING, getter_AddRefs(files));
00580   if (!files) rv = NS_ERROR_FAILURE;
00581   if (NS_FAILED(rv)) return rv;
00582   //-- Get an SF file
00583   rv = files->GetNext(getter_AddRefs(file));
00584   if (NS_FAILED(rv)) return rv;
00585   if (!file)
00586   {
00587     mGlobalStatus = nsIJAR::NO_MANIFEST;
00588     mParsedManifest = PR_TRUE;
00589     return NS_OK;
00590   }
00591   rv = file->GetName(getter_Copies(manifestFilename));
00592   if (NS_FAILED(rv)) return rv;
00593 
00594   PRUint32 manifestLen;
00595   rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
00596   if (NS_FAILED(rv)) return rv;
00597   
00598   //-- Get its corresponding signature file
00599   nsCAutoString sigFilename( NS_STATIC_CAST(const char*, manifestFilename) );
00600   PRInt32 extension = sigFilename.RFindChar('.') + 1;
00601   NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
00602   (void)sigFilename.Cut(extension, 2);
00603   nsXPIDLCString sigBuffer;
00604   PRUint32 sigLen;
00605   {
00606     nsCAutoString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
00607     rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
00608   }
00609   if (NS_FAILED(rv))
00610   {
00611     nsCAutoString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
00612     rv = LoadEntry(tempFilename.get(), getter_Copies(sigBuffer), &sigLen);
00613   }
00614   if (NS_FAILED(rv))
00615   {
00616     mGlobalStatus = nsIJAR::NO_MANIFEST;
00617     mParsedManifest = PR_TRUE;
00618     return NS_OK;
00619   }
00620 
00621   //-- Verify that the signature file is a valid signature of the SF file
00622   PRInt32 verifyError;
00623   rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen, 
00624                                  &verifyError, getter_AddRefs(mPrincipal));
00625   if (NS_FAILED(rv)) return rv;
00626   if (mPrincipal && verifyError == 0)
00627     mGlobalStatus = nsIJAR::VALID;
00628   else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
00629     mGlobalStatus = nsIJAR::INVALID_UNKNOWN_CA;
00630   else
00631     mGlobalStatus = nsIJAR::INVALID_SIG;
00632 
00633   //-- Parse the SF file. If the verification above failed, principal
00634   // is null, and ParseOneFile will mark the relevant entries as invalid.
00635   // if ParseOneFile fails, then it has no effect, and we can safely 
00636   // continue to the next SF file, or return. 
00637   ParseOneFile(verifier, manifestBuffer, JAR_SF);
00638   mParsedManifest = PR_TRUE;
00639 
00640   return NS_OK;
00641 }
00642 
00643 nsresult
00644 nsJAR::ParseOneFile(nsISignatureVerifier* verifier,
00645                     const char* filebuf, PRInt16 aFileType)
00646 {
00647   //-- Check file header
00648   const char* nextLineStart = filebuf;
00649   nsCAutoString curLine;
00650   PRInt32 linelen;
00651   linelen = ReadLine(&nextLineStart);
00652   curLine.Assign(filebuf, linelen);
00653 
00654   if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
00655        ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) )
00656      return NS_ERROR_FILE_CORRUPTED;
00657 
00658   //-- Skip header section
00659   do {
00660     linelen = ReadLine(&nextLineStart);
00661   } while (linelen > 0);
00662 
00663   //-- Set up parsing variables
00664   const char* curPos;
00665   const char* sectionStart = nextLineStart;
00666 
00667   nsJARManifestItem* curItemMF = nsnull;
00668   PRBool foundName = PR_FALSE;
00669   if (aFileType == JAR_MF)
00670     if (!(curItemMF = new nsJARManifestItem()))
00671       return NS_ERROR_OUT_OF_MEMORY;
00672 
00673   nsCAutoString curItemName;
00674   nsCAutoString storedSectionDigest;
00675 
00676   for(;;)
00677   {
00678     curPos = nextLineStart;
00679     linelen = ReadLine(&nextLineStart);
00680     curLine.Assign(curPos, linelen);
00681     if (linelen == 0) 
00682     // end of section (blank line or end-of-file)
00683     {
00684       if (aFileType == JAR_MF)
00685       {
00686         mTotalItemsInManifest++;
00687         if (curItemMF->mType != JAR_INVALID)
00688         { 
00689           //-- Did this section have a name: line?
00690           if(!foundName)
00691             curItemMF->mType = JAR_INVALID;
00692           else 
00693           {
00694             if (curItemMF->mType == JAR_INTERNAL)
00695             {
00696             //-- If it's an internal item, it must correspond 
00697             //   to a valid jar entry
00698               nsIZipEntry* entry;
00699               PRInt32 result = GetEntry(curItemName.get(), &entry);
00700               if (result != ZIP_OK || !entry)
00701                 curItemMF->mType = JAR_INVALID;
00702             }
00703             //-- Check for duplicates
00704             nsCStringKey key(curItemName);
00705             if (mManifestData.Exists(&key))
00706               curItemMF->mType = JAR_INVALID;
00707           }
00708         }
00709 
00710         if (curItemMF->mType == JAR_INVALID)
00711           delete curItemMF;
00712         else //-- calculate section digest
00713         {
00714           PRUint32 sectionLength = curPos - sectionStart;
00715           CalculateDigest(sectionStart, sectionLength,
00716                           &(curItemMF->calculatedSectionDigest));
00717           //-- Save item in the hashtable
00718           nsCStringKey itemKey(curItemName);
00719           mManifestData.Put(&itemKey, (void*)curItemMF);
00720         }
00721         if (nextLineStart == nsnull) // end-of-file
00722           break;
00723 
00724         sectionStart = nextLineStart;
00725         if (!(curItemMF = new nsJARManifestItem()))
00726           return NS_ERROR_OUT_OF_MEMORY;
00727       } // (aFileType == JAR_MF)
00728       else
00729         //-- file type is SF, compare digest with calculated 
00730         //   section digests from MF file.
00731       {
00732         if (foundName)
00733         {
00734           nsJARManifestItem* curItemSF;
00735           nsCStringKey key(curItemName);
00736           curItemSF = (nsJARManifestItem*)mManifestData.Get(&key);
00737           if(curItemSF)
00738           {
00739             NS_ASSERTION(curItemSF->status == nsJAR::NOT_SIGNED,
00740                          "SECURITY ERROR: nsJARManifestItem not correctly initialized");
00741             curItemSF->status = mGlobalStatus;
00742             if (curItemSF->status == nsIJAR::VALID)
00743             { // Compare digests
00744               if (storedSectionDigest.IsEmpty())
00745                 curItemSF->status = nsIJAR::NOT_SIGNED;
00746               else
00747               {
00748                 if (!storedSectionDigest.Equals((const char*)curItemSF->calculatedSectionDigest))
00749                   curItemSF->status = nsIJAR::INVALID_MANIFEST;
00750                 JAR_NULLFREE(curItemSF->calculatedSectionDigest)
00751                 storedSectionDigest = "";
00752               }
00753             } // (aPrincipal != nsnull)
00754           } // if(curItemSF)
00755         } // if(foundName)
00756 
00757         if(nextLineStart == nsnull) // end-of-file
00758           break;
00759       } // aFileType == JAR_SF
00760       foundName = PR_FALSE;
00761       continue;
00762     } // if(linelen == 0)
00763 
00764     //-- Look for continuations (beginning with a space) on subsequent lines
00765     //   and append them to the current line.
00766     while(*nextLineStart == ' ')
00767     {
00768       curPos = nextLineStart;
00769       PRInt32 continuationLen = ReadLine(&nextLineStart) - 1;
00770       nsCAutoString continuation(curPos+1, continuationLen);
00771       curLine += continuation;
00772       linelen += continuationLen;
00773     }
00774 
00775     //-- Find colon in current line, this separates name from value
00776     PRInt32 colonPos = curLine.FindChar(':');
00777     if (colonPos == -1)    // No colon on line, ignore line
00778       continue;
00779     //-- Break down the line
00780     nsCAutoString lineName;
00781     curLine.Left(lineName, colonPos);
00782     nsCAutoString lineData;
00783     curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
00784 
00785     //-- Lines to look for:
00786     // (1) Digest:
00787     if (lineName.Equals(NS_LITERAL_CSTRING("SHA1-Digest"),
00788                         nsCaseInsensitiveCStringComparator()))
00789     //-- This is a digest line, save the data in the appropriate place 
00790     {
00791       if(aFileType == JAR_MF)
00792       {
00793         curItemMF->storedEntryDigest = (char*)PR_MALLOC(lineData.Length()+1);
00794         if (!(curItemMF->storedEntryDigest))
00795           return NS_ERROR_OUT_OF_MEMORY;
00796         PL_strcpy(curItemMF->storedEntryDigest, lineData.get());
00797       }
00798       else
00799         storedSectionDigest = lineData;
00800       continue;
00801     }
00802     
00803     // (2) Name: associates this manifest section with a file in the jar.
00804     if (!foundName && lineName.Equals(NS_LITERAL_CSTRING("Name"),
00805                                       nsCaseInsensitiveCStringComparator())) 
00806     {
00807       curItemName = lineData;
00808       foundName = PR_TRUE;
00809       continue;
00810     }
00811 
00812     // (3) Magic: this may be an inline Javascript. 
00813     //     We can't do any other kind of magic.
00814     if ( aFileType == JAR_MF &&
00815          lineName.Equals(NS_LITERAL_CSTRING("Magic"),
00816                          nsCaseInsensitiveCStringComparator()))
00817     {
00818       if(lineData.Equals(NS_LITERAL_CSTRING("javascript"),
00819                          nsCaseInsensitiveCStringComparator()))
00820         curItemMF->mType = JAR_EXTERNAL;
00821       else
00822         curItemMF->mType = JAR_INVALID;
00823       continue;
00824     }
00825 
00826   } // for (;;)
00827   return NS_OK;
00828 } //ParseOneFile()
00829 
00830 nsresult
00831 nsJAR::VerifyEntry(nsISignatureVerifier* verifier,
00832                    nsJARManifestItem* aManItem, const char* aEntryData,
00833                    PRUint32 aLen)
00834 {
00835   if (aManItem->status == nsIJAR::VALID)
00836   {
00837     if(!aManItem->storedEntryDigest)
00838       // No entry digests in manifest file. Entry is unsigned.
00839       aManItem->status = nsIJAR::NOT_SIGNED;
00840     else
00841     { //-- Calculate and compare digests
00842       char* calculatedEntryDigest;
00843       nsresult rv = CalculateDigest(aEntryData, aLen, &calculatedEntryDigest);
00844       if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
00845       if (PL_strcmp(aManItem->storedEntryDigest, calculatedEntryDigest) != 0)
00846         aManItem->status = nsIJAR::INVALID_ENTRY;
00847       JAR_NULLFREE(calculatedEntryDigest)
00848       JAR_NULLFREE(aManItem->storedEntryDigest)
00849     }
00850   }
00851   aManItem->entryVerified = PR_TRUE;
00852   return NS_OK;
00853 }
00854 
00855 void nsJAR::ReportError(const char* aFilename, PRInt16 errorCode)
00856 {
00857   //-- Generate error message
00858   nsAutoString message; 
00859   message.AssignLiteral("Signature Verification Error: the signature on ");
00860   if (aFilename)
00861     message.AppendWithConversion(aFilename);
00862   else
00863     message.AppendLiteral("this .jar archive");
00864   message.AppendLiteral(" is invalid because ");
00865   switch(errorCode)
00866   {
00867   case nsIJAR::NOT_SIGNED:
00868     message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
00869     break;
00870   case nsIJAR::INVALID_SIG:
00871     message.Append(NS_LITERAL_STRING("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF)."));
00872     break;
00873   case nsIJAR::INVALID_UNKNOWN_CA:
00874     message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
00875     break;
00876   case nsIJAR::INVALID_MANIFEST:
00877     message.Append(NS_LITERAL_STRING("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file."));
00878     break;
00879   case nsIJAR::INVALID_ENTRY:
00880     message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
00881     break;
00882   default:
00883     message.AppendLiteral("of an unknown problem.");
00884   }
00885   
00886   // Report error in JS console
00887   nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
00888   if (console)
00889   {
00890     console->LogStringMessage(message.get());
00891   }
00892 #ifdef DEBUG
00893   char* messageCstr = ToNewCString(message);
00894   if (!messageCstr) return;
00895   fprintf(stderr, "%s\n", messageCstr);
00896   nsMemory::Free(messageCstr);
00897 #endif
00898 }
00899 
00900 
00901 nsresult nsJAR::CalculateDigest(const char* aInBuf, PRUint32 aLen,
00902                                 char** digest)
00903 {
00904   *digest = nsnull;
00905   nsresult rv;
00906   
00907 
00908   nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
00909   if (NS_FAILED(rv)) return rv;
00910 
00911   rv = hasher->Init(nsICryptoHash::SHA1);
00912   if (NS_FAILED(rv)) return rv;
00913 
00914   rv = hasher->Update((const PRUint8*) aInBuf, aLen);
00915   if (NS_FAILED(rv)) return rv;
00916 
00917   nsCAutoString hashString;
00918   rv = hasher->Finish(PR_TRUE, hashString);
00919   if (NS_FAILED(rv)) return rv;
00920 
00921   *digest = ToNewCString(hashString);
00922 
00923   return *digest ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
00924 }
00925 
00926 //----------------------------------------------
00927 // Debugging functions
00928 //----------------------------------------------
00929 #if 0
00930 PR_STATIC_CALLBACK(PRBool)
00931 PrintManItem(nsHashKey* aKey, void* aData, void* closure)
00932 {
00933   nsJARManifestItem* manItem = (nsJARManifestItem*)aData;
00934     if (manItem)
00935     {
00936       nsCStringKey* key2 = (nsCStringKey*)aKey;
00937       char* name = ToNewCString(key2->GetString());
00938       if (!(PL_strcmp(name, "") == 0))
00939         printf("%s s=%i\n",name, manItem->status);
00940     }
00941     return PR_TRUE;
00942 }
00943 #endif
00944 
00945 void nsJAR::DumpMetadata(const char* aMessage)
00946 {
00947 #if 0
00948   printf("### nsJAR::DumpMetadata at %s ###\n", aMessage);
00949   if (mPrincipal)
00950   {
00951     char* toStr;
00952     mPrincipal->ToString(&toStr);
00953     printf("Principal: %s.\n", toStr);
00954     PR_FREEIF(toStr);
00955   }
00956   else
00957     printf("No Principal. \n");
00958   mManifestData.Enumerate(PrintManItem);
00959   printf("\n");
00960 #endif
00961 } 
00962 
00963 //----------------------------------------------
00964 // nsJAREnumerator constructor and destructor
00965 //----------------------------------------------
00966 nsJAREnumerator::nsJAREnumerator(nsZipFind *aFind)
00967 : mFind(aFind),
00968   mCurr(nsnull),
00969   mIsCurrStale(PR_TRUE)
00970 {
00971     mArchive = mFind->GetArchive();
00972 }
00973 
00974 nsJAREnumerator::~nsJAREnumerator()
00975 {
00976     mArchive->FindFree(mFind);
00977 }
00978 
00979 NS_IMPL_ISUPPORTS1(nsJAREnumerator, nsISimpleEnumerator)
00980 
00981 //----------------------------------------------
00982 // nsJAREnumerator::HasMoreElements
00983 //----------------------------------------------
00984 NS_IMETHODIMP
00985 nsJAREnumerator::HasMoreElements(PRBool* aResult)
00986 {
00987     PRInt32 err;
00988 
00989     if (!mFind)
00990         return NS_ERROR_NOT_INITIALIZED;
00991 
00992     // try to get the next element
00993     if (mIsCurrStale)
00994     {
00995         err = mArchive->FindNext( mFind, &mCurr );
00996         if (err == ZIP_ERR_FNF)
00997         {
00998             *aResult = PR_FALSE;
00999             return NS_OK;
01000         }
01001         if (err != ZIP_OK)
01002             return NS_ERROR_FAILURE; // no error translation
01003 
01004         mIsCurrStale = PR_FALSE;
01005     }
01006 
01007     *aResult = PR_TRUE;
01008     return NS_OK;
01009 }
01010 
01011 //----------------------------------------------
01012 // nsJAREnumerator::GetNext
01013 //----------------------------------------------
01014 NS_IMETHODIMP
01015 nsJAREnumerator::GetNext(nsISupports** aResult)
01016 {
01017     nsresult rv;
01018     PRBool   bMore;
01019 
01020     // check if the current item is "stale"
01021     if (mIsCurrStale)
01022     {
01023         rv = HasMoreElements( &bMore );
01024         if (NS_FAILED(rv))
01025             return rv;
01026         if (bMore == PR_FALSE)
01027         {
01028             *aResult = nsnull;  // null return value indicates no more elements
01029             return NS_OK;
01030         }
01031     }
01032 
01033     // pack into an nsIJARItem
01034     nsJARItem* jarItem = new nsJARItem();
01035     if(jarItem)
01036     {
01037       NS_ADDREF(jarItem);
01038       jarItem->Init(mCurr);
01039       *aResult = jarItem;
01040       mIsCurrStale = PR_TRUE; // we just gave this one away
01041       return NS_OK;
01042     }
01043     else
01044       return NS_ERROR_OUT_OF_MEMORY;
01045 }
01046 
01047 //-------------------------------------------------
01048 // nsJARItem constructors and destructor
01049 //-------------------------------------------------
01050 nsJARItem::nsJARItem()
01051 {
01052 }
01053 
01054 nsJARItem::~nsJARItem()
01055 {
01056 }
01057 
01058 NS_IMPL_ISUPPORTS1(nsJARItem, nsIZipEntry)
01059 
01060 void nsJARItem::Init(nsZipItem* aZipItem)
01061 {
01062   mZipItem = aZipItem;
01063 }
01064 
01065 //------------------------------------------
01066 // nsJARItem::GetName
01067 //------------------------------------------
01068 NS_IMETHODIMP
01069 nsJARItem::GetName(char * *aName)
01070 {
01071     char *namedup;
01072 
01073     if ( !aName )
01074         return NS_ERROR_NULL_POINTER;
01075     if ( !mZipItem->name )
01076         return NS_ERROR_FAILURE;
01077 
01078     namedup = PL_strdup( mZipItem->name );
01079     if ( !namedup )
01080         return NS_ERROR_OUT_OF_MEMORY;
01081 
01082     *aName = namedup;
01083     return NS_OK;
01084 }
01085 
01086 //------------------------------------------
01087 // nsJARItem::GetCompression
01088 //------------------------------------------
01089 NS_IMETHODIMP 
01090 nsJARItem::GetCompression(PRUint16 *aCompression)
01091 {
01092     if (!aCompression)
01093         return NS_ERROR_NULL_POINTER;
01094     if (!mZipItem->compression)
01095         return NS_ERROR_FAILURE;
01096 
01097     *aCompression = mZipItem->compression;
01098     return NS_OK;
01099 }
01100 
01101 //------------------------------------------
01102 // nsJARItem::GetSize
01103 //------------------------------------------
01104 NS_IMETHODIMP 
01105 nsJARItem::GetSize(PRUint32 *aSize)
01106 {
01107     if (!aSize)
01108         return NS_ERROR_NULL_POINTER;
01109     if (!mZipItem->size)
01110         return NS_ERROR_FAILURE;
01111 
01112     *aSize = mZipItem->size;
01113     return NS_OK;
01114 }
01115 
01116 //------------------------------------------
01117 // nsJARItem::GetRealSize
01118 //------------------------------------------
01119 NS_IMETHODIMP 
01120 nsJARItem::GetRealSize(PRUint32 *aRealsize)
01121 {
01122     if (!aRealsize)
01123         return NS_ERROR_NULL_POINTER;
01124     if (!mZipItem->realsize)
01125         return NS_ERROR_FAILURE;
01126 
01127     *aRealsize = mZipItem->realsize;
01128     return NS_OK;
01129 }
01130 
01131 //------------------------------------------
01132 // nsJARItem::GetCrc32
01133 //------------------------------------------
01134 NS_IMETHODIMP 
01135 nsJARItem::GetCRC32(PRUint32 *aCrc32)
01136 {
01137     if (!aCrc32)
01138         return NS_ERROR_NULL_POINTER;
01139     if (!mZipItem->crc32)
01140         return NS_ERROR_FAILURE;
01141 
01142     *aCrc32 = mZipItem->crc32;
01143     return NS_OK;
01144 }
01145 
01147 // nsIZipReaderCache
01148 
01149 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
01150 
01151 nsZipReaderCache::nsZipReaderCache()
01152   : mLock(nsnull),
01153     mZips(16)
01154 #ifdef ZIP_CACHE_HIT_RATE
01155     ,
01156     mZipCacheLookups(0),
01157     mZipCacheHits(0),
01158     mZipCacheFlushes(0),
01159     mZipSyncMisses(0)
01160 #endif
01161 {
01162 }
01163 
01164 NS_IMETHODIMP
01165 nsZipReaderCache::Init(PRUint32 cacheSize)
01166 {
01167   mCacheSize = cacheSize; 
01168   
01169 // Register as a memory pressure observer 
01170   nsCOMPtr<nsIObserverService> os = 
01171            do_GetService("@mozilla.org/observer-service;1");
01172   if (os)
01173   {
01174     os->AddObserver(this, "memory-pressure", PR_TRUE);
01175     os->AddObserver(this, "chrome-flush-caches", PR_TRUE);
01176   }
01177 // ignore failure of the observer registration.
01178 
01179   mLock = PR_NewLock();
01180   return mLock ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
01181 }
01182 
01183 static PRBool PR_CALLBACK
01184 DropZipReaderCache(nsHashKey *aKey, void *aData, void* closure)
01185 {
01186   nsJAR* zip = (nsJAR*)aData;
01187   zip->SetZipReaderCache(nsnull);
01188   return PR_TRUE;
01189 }
01190 
01191 nsZipReaderCache::~nsZipReaderCache()
01192 {
01193   if (mLock)
01194     PR_DestroyLock(mLock);
01195   mZips.Enumerate(DropZipReaderCache, nsnull);
01196 
01197 #ifdef ZIP_CACHE_HIT_RATE
01198   printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
01199          mCacheSize, mZipCacheHits, mZipCacheLookups, 
01200          (float)mZipCacheHits / mZipCacheLookups, 
01201          mZipCacheFlushes, mZipSyncMisses);
01202 #endif
01203 }
01204 
01205 NS_IMETHODIMP
01206 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
01207 {
01208   nsresult rv;
01209   nsAutoLock lock(mLock);
01210 
01211 #ifdef ZIP_CACHE_HIT_RATE
01212   mZipCacheLookups++;
01213 #endif
01214 
01215   nsCAutoString path;
01216   rv = zipFile->GetNativePath(path);
01217   if (NS_FAILED(rv)) return rv;
01218 
01219   nsCStringKey key(path);
01220   nsJAR* zip = NS_STATIC_CAST(nsJAR*, NS_STATIC_CAST(nsIZipReader*,mZips.Get(&key))); // AddRefs
01221   if (zip) {
01222 #ifdef ZIP_CACHE_HIT_RATE
01223     mZipCacheHits++;
01224 #endif
01225     zip->ClearReleaseTime();
01226   }
01227   else {
01228     zip = new nsJAR();
01229     if (zip == nsnull)
01230         return NS_ERROR_OUT_OF_MEMORY;
01231     NS_ADDREF(zip);
01232     zip->SetZipReaderCache(this);
01233 
01234     rv = zip->Init(zipFile);
01235     if (NS_FAILED(rv)) {
01236       NS_RELEASE(zip);
01237       return rv;
01238     }
01239     rv = zip->Open();
01240     if (NS_FAILED(rv)) {
01241       NS_RELEASE(zip);
01242       return rv;
01243     }
01244 
01245     PRBool collision = mZips.Put(&key, NS_STATIC_CAST(nsIZipReader*, zip)); // AddRefs to 2
01246     NS_ASSERTION(!collision, "horked");
01247   }
01248   *result = zip;
01249   return rv;
01250 }
01251 
01252 static PRBool PR_CALLBACK
01253 FindOldestZip(nsHashKey *aKey, void *aData, void* closure)
01254 {
01255   nsJAR** oldestPtr = (nsJAR**)closure;
01256   nsJAR* oldest = *oldestPtr;
01257   nsJAR* current = (nsJAR*)aData;
01258   PRIntervalTime currentReleaseTime = current->GetReleaseTime();
01259   if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
01260     if (oldest == nsnull ||
01261         currentReleaseTime < oldest->GetReleaseTime()) {
01262       *oldestPtr = current;
01263     }    
01264   }
01265   return PR_TRUE;
01266 }
01267 
01268 struct ZipFindData {nsJAR* zip; PRBool found;}; 
01269 
01270 static PRBool PR_CALLBACK
01271 FindZip(nsHashKey *aKey, void *aData, void* closure)
01272 {
01273   ZipFindData* find_data = (ZipFindData*)closure;
01274 
01275   if (find_data->zip == (nsJAR*)aData) {
01276     find_data->found = PR_TRUE; 
01277     return PR_FALSE;
01278   }
01279   return PR_TRUE;
01280 }
01281 
01282 nsresult
01283 nsZipReaderCache::ReleaseZip(nsJAR* zip)
01284 {
01285   nsresult rv;
01286   nsAutoLock lock(mLock);
01287 
01288   // It is possible that two thread compete for this zip. The dangerous 
01289   // case is where one thread Releases the zip and discovers that the ref
01290   // count has gone to one. Before it can call this ReleaseZip method
01291   // another thread calls our GetZip method. The ref count goes to two. That
01292   // second thread then Releases the zip and the ref coutn goes to one. It
01293   // Then tries to enter this ReleaseZip method and blocks while the first
01294   // thread is still here. The first thread continues and remove the zip from 
01295   // the cache and calls its Release method sending the ref count to 0 and
01296   // deleting the zip. However, the second thread is still blocked at the
01297   // start of ReleaseZip, but the 'zip' param now hold a reference to a
01298   // deleted zip!
01299   // 
01300   // So, we are going to try safegaurding here by searching our hashtable while
01301   // locked here for the zip. We return fast if it is not found. 
01302 
01303   ZipFindData find_data = {zip, PR_FALSE};
01304   mZips.Enumerate(FindZip, &find_data);
01305   if (!find_data.found) {
01306 #ifdef ZIP_CACHE_HIT_RATE
01307     mZipSyncMisses++;
01308 #endif
01309     return NS_OK;
01310   }
01311 
01312   zip->SetReleaseTime();
01313 
01314   if (mZips.Count() <= mCacheSize)
01315     return NS_OK;
01316 
01317   nsJAR* oldest = nsnull;
01318   mZips.Enumerate(FindOldestZip, &oldest);
01319   
01320   // Because of the craziness above it is possible that there is no zip that
01321   // needs removing. 
01322   if (!oldest)
01323     return NS_OK;
01324 
01325 #ifdef ZIP_CACHE_HIT_RATE
01326     mZipCacheFlushes++;
01327 #endif
01328 
01329   // Clear the cache pointer in case we gave out this oldest guy while
01330   // his Release call was being made. Otherwise we could nest on ReleaseZip
01331   // when the second owner calls Release and we are still here in this lock.
01332   oldest->SetZipReaderCache(nsnull);
01333 
01334   // remove from hashtable
01335   nsCOMPtr<nsIFile> zipFile;
01336   rv = oldest->GetFile(getter_AddRefs(zipFile));
01337   if (NS_FAILED(rv)) return rv;
01338 
01339   nsCAutoString path;
01340   rv = zipFile->GetNativePath(path);
01341   if (NS_FAILED(rv)) return rv;
01342 
01343   nsCStringKey key(path);
01344   PRBool removed = mZips.Remove(&key);  // Releases
01345   NS_ASSERTION(removed, "botched");
01346 
01347   return NS_OK;
01348 }
01349 
01350 static PRBool PR_CALLBACK
01351 FindFlushableZip(nsHashKey *aKey, void *aData, void* closure)
01352 {
01353   nsHashKey** flushableKeyPtr = (nsHashKey**)closure;
01354   nsJAR* current = (nsJAR*)aData;
01355   
01356   if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
01357     *flushableKeyPtr = aKey;
01358     current->SetZipReaderCache(nsnull);
01359     return PR_FALSE;
01360   }
01361   return PR_TRUE;
01362 }
01363 
01364 NS_IMETHODIMP
01365 nsZipReaderCache::Observe(nsISupports *aSubject,
01366                           const char *aTopic, 
01367                           const PRUnichar *aSomeData)
01368 {
01369   if (strcmp(aTopic, "memory-pressure") == 0) {
01370     nsAutoLock lock(mLock);
01371     while (PR_TRUE) {
01372       nsHashKey* flushable = nsnull;
01373       mZips.Enumerate(FindFlushableZip, &flushable); 
01374       if ( ! flushable )
01375         break;
01376       PRBool removed = mZips.Remove(flushable);  // Releases
01377       NS_ASSERTION(removed, "botched");
01378 
01379 #ifdef xDEBUG_jband
01380       printf("flushed something from the jar cache\n");
01381 #endif
01382     }
01383   }
01384   else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
01385     mZips.Enumerate(DropZipReaderCache, nsnull);
01386     mZips.Reset();
01387   }
01388   return NS_OK;
01389 }
01390