Bug 533038 - 4. Fully support reading jars inside jars, r=taras a=blocking-beta6
authorMichael Wu <mwu@mozilla.com>
Wed, 08 Sep 2010 20:38:34 -0700
changeset 52251 3e6d29c4ab619e0806f581987f3afb1c631b705e
parent 52250 b32655a792347a55bd5831f8250805d3b936cb2f
child 52252 04f642fa69a2165507461cdc71d896f190a11d8a
push id15580
push usermwu@mozilla.com
push dateThu, 09 Sep 2010 03:42:33 +0000
treeherdermozilla-central@a26593abc145 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstaras, blocking-beta6
bugs533038
milestone2.0b6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 533038 - 4. Fully support reading jars inside jars, r=taras a=blocking-beta6
modules/libjar/nsIZipReader.idl
modules/libjar/nsJAR.cpp
modules/libjar/nsJAR.h
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
--- a/modules/libjar/nsIZipReader.idl
+++ b/modules/libjar/nsIZipReader.idl
@@ -86,27 +86,32 @@ interface nsIZipEntry : nsISupports
      * directory foo/ in a zip containing exactly one entry for foo/bar.txt
      * is synthetic.  If the zip file contains an actual entry for a directory,
      * this attribute will be false for the nsIZipEntry for that directory.
      * It is impossible for a file to be synthetic.
      */
     readonly attribute boolean          isSynthetic;
 };
 
-[scriptable, uuid(27067432-cb21-437e-99d1-f85858522bb1)]
+[scriptable, uuid(7bb925d6-833a-486c-8ef2-9bc15c670a60)]
 interface nsIZipReader : nsISupports
 {
     /**
      * Opens a zip file for reading.
      * It is allowed to open with another file, 
      * but it needs to be closed first with close().
      */
     void open(in nsIFile zipFile);
 
     /**
+     * Opens a zip file inside a zip file for reading.
+     */
+    void openInner(in nsIZipReader zipReader, in string zipEntry);
+
+    /**
      * The file that represents the zip with which this zip reader was
      * initialized.
      */
     readonly attribute nsIFile file;
 
     /**
      * Closes a zip reader. Subsequent attempts to extract files or read from
      * its input stream will result in an error.
@@ -235,16 +240,23 @@ interface nsIZipReaderCache : nsISupport
      * is created, initialized, and opened (see nsIZipReader::init and 
      * nsIZipReader::open). Otherwise the previously created zip reader is 
      * returned.
      *
      * @note If someone called close() on the shared nsIZipReader, this method 
      *       will return the closed zip reader.
      */
     nsIZipReader getZip(in nsIFile zipFile);
+
+    /**
+     * Returns a (possibly shared) nsIZipReader for a zip inside another zip
+     *
+     * See getZip
+     */
+    nsIZipReader getInnerZip(in nsIFile zipFile, in string zipEntry);
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
 %{C++
 
 #define NS_ZIPREADER_CID                             \
 { /* 7526a738-9632-11d3-8cd9-0060b0fc14a3 */         \
--- a/modules/libjar/nsJAR.cpp
+++ b/modules/libjar/nsJAR.cpp
@@ -169,16 +169,40 @@ nsJAR::Open(nsIFile* zipFile)
 
   mLock = PR_NewLock();
   NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
 
   return mZip.OpenArchive(zipFile);
 }
 
 NS_IMETHODIMP
+nsJAR::OpenInner(nsIZipReader *aZipReader, const char *aZipEntry)
+{
+  NS_ENSURE_ARG_POINTER(aZipReader);
+  NS_ENSURE_ARG_POINTER(aZipEntry);
+  if (mLock) return NS_ERROR_FAILURE; // Already open!
+
+  nsresult rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mLock = PR_NewLock();
+  NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
+
+  mOuterZipEntry.Assign(aZipEntry);
+
+  nsRefPtr<nsZipHandle> handle;
+  rv = nsZipHandle::Init(&static_cast<nsJAR*>(aZipReader)->mZip, aZipEntry,
+                         getter_AddRefs(handle));
+  if (NS_FAILED(rv))
+    return rv;
+
+  return mZip.OpenArchive(handle);
+}
+
+NS_IMETHODIMP
 nsJAR::GetFile(nsIFile* *result)
 {
   *result = mZipFile;
   NS_IF_ADDREF(*result);
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -188,16 +212,17 @@ nsJAR::Close()
     PR_DestroyLock(mLock);
     mLock = nsnull;
   }
 
   mParsedManifest = PR_FALSE;
   mManifestData.Reset();
   mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
   mTotalItemsInManifest = 0;
+  mOuterZipEntry.Truncate(0);
 
   return mZip.CloseArchive();
 }
 
 NS_IMETHODIMP
 nsJAR::Test(const char *aEntryName)
 {
   return mZip.Test(aEntryName);
@@ -996,18 +1021,18 @@ nsJARItem::GetLastModifiedTime(PRTime* a
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIZipReaderCache
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
 
 nsZipReaderCache::nsZipReaderCache()
-  : mLock(nsnull),
-    mZips(16)
+  : mLock(nsnull)
+  , mZips(16)
 #ifdef ZIP_CACHE_HIT_RATE
     ,
     mZipCacheLookups(0),
     mZipCacheHits(0),
     mZipCacheFlushes(0),
     mZipSyncMisses(0)
 #endif
 {
@@ -1061,21 +1086,23 @@ nsZipReaderCache::GetZip(nsIFile* zipFil
   nsresult rv;
   nsCOMPtr<nsIZipReader> antiLockZipGrip;
   nsAutoLock lock(mLock);
 
 #ifdef ZIP_CACHE_HIT_RATE
   mZipCacheLookups++;
 #endif
 
-  nsCAutoString path;
-  rv = zipFile->GetNativePath(path);
+  nsCAutoString uri;
+  rv = zipFile->GetNativePath(uri);
   if (NS_FAILED(rv)) return rv;
 
-  nsCStringKey key(path);
+  uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+  nsCStringKey key(uri);
   nsJAR* zip = static_cast<nsJAR*>(static_cast<nsIZipReader*>(mZips.Get(&key))); // AddRefs
   if (zip) {
 #ifdef ZIP_CACHE_HIT_RATE
     mZipCacheHits++;
 #endif
     zip->ClearReleaseTime();
   }
   else {
@@ -1093,16 +1120,66 @@ nsZipReaderCache::GetZip(nsIFile* zipFil
 
     PRBool collision = mZips.Put(&key, static_cast<nsIZipReader*>(zip)); // AddRefs to 2
     NS_ASSERTION(!collision, "horked");
   }
   *result = zip;
   return rv;
 }
 
+NS_IMETHODIMP
+nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const char *entry,
+                              nsIZipReader* *result)
+{
+  NS_ENSURE_ARG_POINTER(zipFile);
+
+  nsCOMPtr<nsIZipReader> outerZipReader;
+  nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef ZIP_CACHE_HIT_RATE
+  mZipCacheLookups++;
+#endif
+
+  nsCAutoString uri;
+  rv = zipFile->GetNativePath(uri);
+  if (NS_FAILED(rv)) return rv;
+
+  uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+  uri.AppendLiteral("!/");
+  uri.Append(entry);
+
+  nsCStringKey key(uri);
+  nsJAR* zip = static_cast<nsJAR*>(static_cast<nsIZipReader*>(mZips.Get(&key))); // AddRefs
+  if (zip) {
+#ifdef ZIP_CACHE_HIT_RATE
+    mZipCacheHits++;
+#endif
+    zip->ClearReleaseTime();
+  }
+  else {
+    zip = new nsJAR();
+    if (zip == nsnull)
+        return NS_ERROR_OUT_OF_MEMORY;
+    NS_ADDREF(zip);
+    zip->SetZipReaderCache(this);
+
+    rv = zip->OpenInner(outerZipReader, entry);
+    if (NS_FAILED(rv)) {
+      NS_RELEASE(zip);
+      return rv;
+    }
+
+    PRBool collision = mZips.Put(&key, static_cast<nsIZipReader*>(zip)); // AddRefs to 2
+    NS_ASSERTION(!collision, "horked");
+  }
+  *result = zip;
+  return rv;
+}
+
 static PRBool
 FindOldestZip(nsHashKey *aKey, void *aData, void* closure)
 {
   nsJAR** oldestPtr = (nsJAR**)closure;
   nsJAR* oldest = *oldestPtr;
   nsJAR* current = (nsJAR*)aData;
   PRIntervalTime currentReleaseTime = current->GetReleaseTime();
   if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
@@ -1176,22 +1253,32 @@ nsZipReaderCache::ReleaseZip(nsJAR* zip)
 #endif
 
   // Clear the cache pointer in case we gave out this oldest guy while
   // his Release call was being made. Otherwise we could nest on ReleaseZip
   // when the second owner calls Release and we are still here in this lock.
   oldest->SetZipReaderCache(nsnull);
 
   // remove from hashtable
-  nsCAutoString path;
-  rv = oldest->GetJarPath(path);
-  if (NS_FAILED(rv)) return rv;
+  nsCAutoString uri;
+  rv = oldest->GetJarPath(uri);
+  if (NS_FAILED(rv))
+    return rv;
 
-  nsCStringKey key(path);
-  PRBool removed = mZips.Remove(&key);  // Releases
+  if (zip->mOuterZipEntry.IsEmpty()) {
+    uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+  } else {
+    uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+    uri.AppendLiteral("!/");
+    uri.Append(zip->mOuterZipEntry);
+  }
+
+  nsCStringKey key(uri);
+  PRBool removed;
+  removed = mZips.Remove(&key);  // Releases
   NS_ASSERTION(removed, "botched");
 
   return NS_OK;
 }
 
 static PRBool
 FindFlushableZip(nsHashKey *aKey, void *aData, void* closure)
 {
--- a/modules/libjar/nsJAR.h
+++ b/modules/libjar/nsJAR.h
@@ -88,16 +88,18 @@ typedef enum
  * Class nsJAR declaration. 
  * nsJAR serves as an XPCOM wrapper for nsZipArchive with the addition of 
  * JAR manifest file parsing. 
  *------------------------------------------------------------------------*/
 class nsJAR : public nsIZipReader
 {
   // Allows nsJARInputStream to call the verification functions
   friend class nsJARInputStream;
+  // Allows nsZipReaderCache to access mOuterZipEntry
+  friend class nsZipReaderCache;
 
   public:
 
     nsJAR();
     virtual ~nsJAR();
     
     NS_DEFINE_STATIC_CID_ACCESSOR( NS_ZIPREADER_CID )
   
@@ -125,16 +127,17 @@ class nsJAR : public nsIZipReader
     
     void SetZipReaderCache(nsZipReaderCache* cache) {
       mCache = cache;
     }
 
   protected:
     //-- Private data members
     nsCOMPtr<nsIFile>        mZipFile;        // The zip/jar file on disk
+    nsCString                mOuterZipEntry;  // The entry in the zip this zip is reading from
     nsZipArchive             mZip;            // The underlying zip archive
     nsObjectHashtable        mManifestData;   // Stores metadata for each entry
     PRBool                   mParsedManifest; // True if manifest has been parsed
     nsCOMPtr<nsIPrincipal>   mPrincipal;      // The entity which signed this file
     PRInt16                  mGlobalStatus;   // Global signature verification status
     PRIntervalTime           mReleaseTime;    // used by nsZipReaderCache for flushing entries
     nsZipReaderCache*        mCache;          // if cached, this points to the cache it's contained in
     PRLock*                  mLock;	
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -79,27 +79,25 @@ static PRLogModuleInfo *gJarProtocolLog 
 //-----------------------------------------------------------------------------
 
 class nsJARInputThunk : public nsIInputStream
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
 
-    nsJARInputThunk(nsIFile *jarFile,
+    nsJARInputThunk(nsIZipReader *zipReader,
                     nsIURI* fullJarURI,
                     const nsACString &jarEntry,
                     nsIZipReaderCache *jarCache)
         : mJarCache(jarCache)
-        , mJarFile(jarFile)
+        , mJarReader(zipReader)
         , mJarEntry(jarEntry)
         , mContentLength(-1)
     {
-        NS_ASSERTION(mJarFile, "no jar file");
-
         if (fullJarURI) {
             nsresult rv = fullJarURI->GetAsciiSpec(mJarDirSpec);
             NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail");
         }
     }
 
     virtual ~nsJARInputThunk()
     {
@@ -118,43 +116,31 @@ public:
     }
 
     nsresult EnsureJarStream();
 
 private:
 
     nsCOMPtr<nsIZipReaderCache> mJarCache;
     nsCOMPtr<nsIZipReader>      mJarReader;
-    nsCOMPtr<nsIFile>           mJarFile;
     nsCString                   mJarDirSpec;
     nsCOMPtr<nsIInputStream>    mJarStream;
     nsCString                   mJarEntry;
     PRInt32                     mContentLength;
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputThunk, nsIInputStream)
 
 nsresult
 nsJARInputThunk::EnsureJarStream()
 {
     if (mJarStream)
         return NS_OK;
 
     nsresult rv;
-    if (mJarCache)
-        rv = mJarCache->GetZip(mJarFile, getter_AddRefs(mJarReader));
-    else {
-        // create an uncached jar reader
-        mJarReader = do_CreateInstance(kZipReaderCID, &rv);
-        if (NS_FAILED(rv)) return rv;
-
-        rv = mJarReader->Open(mJarFile);
-    }
-    if (NS_FAILED(rv)) return rv;
-
     if (ENTRY_IS_DIRECTORY(mJarEntry)) {
         // A directory stream also needs the Spec of the FullJarURI
         // because is included in the stream data itself.
 
         NS_ENSURE_STATE(!mJarDirSpec.IsEmpty());
 
         rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec,
                                                 mJarEntry.get(),
@@ -298,19 +284,50 @@ nsJARChannel::Init(nsIURI *uri)
 
 nsresult
 nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache)
 {
     // important to pass a clone of the file since the nsIFile impl is not
     // necessarily MT-safe
     nsCOMPtr<nsIFile> clonedFile;
     nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile));
-    if (NS_FAILED(rv)) return rv;
+    if (NS_FAILED(rv))
+        return rv;
+
+    nsCOMPtr<nsIZipReader> reader;
+    if (jarCache) {
+        if (mInnerJarEntry.IsEmpty())
+            rv = jarCache->GetZip(mJarFile, getter_AddRefs(reader));
+        else 
+            rv = jarCache->GetInnerZip(mJarFile, mInnerJarEntry.get(),
+                                       getter_AddRefs(reader));
+    } else {
+        // create an uncached jar reader
+        nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
+        if (NS_FAILED(rv))
+            return rv;
 
-    mJarInput = new nsJARInputThunk(clonedFile, mJarURI, mJarEntry, jarCache);
+        rv = outerReader->Open(mJarFile);
+        if (NS_FAILED(rv))
+            return rv;
+
+        if (mInnerJarEntry.IsEmpty())
+            reader = outerReader;
+        else {
+            reader = do_CreateInstance(kZipReaderCID, &rv);
+            if (NS_FAILED(rv))
+                return rv;
+
+            rv = reader->OpenInner(outerReader, mInnerJarEntry.get());
+        }
+    }
+    if (NS_FAILED(rv))
+        return rv;
+
+    mJarInput = new nsJARInputThunk(reader, mJarURI, mJarEntry, jarCache);
     if (!mJarInput)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(mJarInput);
     return NS_OK;
 }
 
 nsresult
 nsJARChannel::EnsureJarInput(PRBool blocking)
@@ -333,16 +350,31 @@ nsJARChannel::EnsureJarInput(PRBool bloc
     NS_UnescapeURL(mJarEntry);
 
     // try to get a nsIFile directly from the url, which will often succeed.
     {
         nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
         if (fileURL)
             fileURL->GetFile(getter_AddRefs(mJarFile));
     }
+    // try to handle a nested jar
+    if (!mJarFile) {
+        nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
+        if (jarURI) {
+            nsCOMPtr<nsIFileURL> fileURL;
+            nsCOMPtr<nsIURI> innerJarURI;
+            rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
+            if (NS_SUCCEEDED(rv))
+                fileURL = do_QueryInterface(innerJarURI);
+            if (fileURL) {
+                fileURL->GetFile(getter_AddRefs(mJarFile));
+                jarURI->GetJAREntry(mInnerJarEntry);
+            }
+        }
+    }
 
     if (mJarFile) {
         mIsUnsafe = PR_FALSE;
 
         // NOTE: we do not need to deal with mSecurityInfo here,
         // because we're loading from a local file
         rv = CreateJarInput(gJarHandler->JarCache());
     }
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -103,11 +103,12 @@ private:
     PRPackedBool                    mIsUnsafe;
 
     nsJARInputThunk                *mJarInput;
     nsCOMPtr<nsIStreamListener>     mDownloader;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
+    nsCString                       mInnerJarEntry;
 };
 
 #endif // nsJARChannel_h__