Bug 504864: mmap io for JARs r=bsmedberg
authorTaras Glek <tglek@mozilla.com>
Wed, 12 Aug 2009 13:50:12 -0700
changeset 31399 ad1b7a04fbba0809240911d4156d61a0ce1817f8
parent 31398 bf5b19bf6439daedbbe74979a95cfa0b7aafccd0
child 31400 73dfd9f8b47f42be26322625b9568e4ea7862102
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs504864
milestone1.9.2a2pre
Bug 504864: mmap io for JARs r=bsmedberg
modules/libjar/nsJAR.cpp
modules/libjar/nsJAR.h
modules/libjar/nsJARInputStream.cpp
modules/libjar/nsJARInputStream.h
modules/libjar/nsZipArchive.cpp
modules/libjar/nsZipArchive.h
modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js
--- a/modules/libjar/nsJAR.cpp
+++ b/modules/libjar/nsJAR.cpp
@@ -117,17 +117,16 @@ DeleteManifestEntry(nsHashKey* aKey, voi
 
 // The following initialization makes a guess of 10 entries per jarfile.
 nsJAR::nsJAR(): mManifestData(nsnull, nsnull, DeleteManifestEntry, nsnull, 10),
                 mParsedManifest(PR_FALSE),
                 mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
                 mReleaseTime(PR_INTERVAL_NO_TIMEOUT), 
                 mCache(nsnull), 
                 mLock(nsnull),
-                mMtime(0),
                 mTotalItemsInManifest(0)
 {
 }
 
 nsJAR::~nsJAR()
 {
   Close();
 }
@@ -162,26 +161,24 @@ nsrefcnt nsJAR::Release(void)
 
 NS_IMETHODIMP
 nsJAR::Open(nsIFile* zipFile)
 {
   NS_ENSURE_ARG_POINTER(zipFile);
   if (mLock) return NS_ERROR_FAILURE; // Already open!
 
   mZipFile = zipFile;
-  nsresult rv = zipFile->GetLastModifiedTime(&mMtime);
-  if (NS_FAILED(rv)) return rv;
 
   mLock = PR_NewLock();
   NS_ENSURE_TRUE(mLock, NS_ERROR_OUT_OF_MEMORY);
 
   PRFileDesc *fd = OpenFile();
   NS_ENSURE_TRUE(fd, NS_ERROR_FAILURE);
 
-  rv = mZip.OpenArchive(fd);
+  nsresult rv = mZip.OpenArchive(fd);
   if (NS_FAILED(rv)) Close();
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsJAR::GetFile(nsIFile* *result)
 {
@@ -335,29 +332,19 @@ nsJAR::GetInputStreamWithSpec(const nsAC
   }
   nsJARInputStream* jis = new nsJARInputStream();
   // addref now so we can call InitFile/InitDirectory()
   NS_ENSURE_TRUE(jis, NS_ERROR_OUT_OF_MEMORY);
   NS_ADDREF(*result = jis);
 
   nsresult rv = NS_OK;
   if (!item || item->isDirectory) {
-    rv = jis->InitDirectory(&mZip, aJarDirSpec, aEntryName);
+    rv = jis->InitDirectory(this, aJarDirSpec, aEntryName);
   } else {
-    // Open jarfile, to get its own filedescriptor for the stream
-    // XXX The file may have been overwritten, so |item| might not be
-    // valid.  We really want to work from inode rather than file name.
-    PRFileDesc *fd = nsnull;
-    fd = OpenFile();
-    if (fd) {
-      rv = jis->InitFile(&mZip, item, fd);
-      // |jis| now owns |fd|
-    } else {
-      rv = NS_ERROR_FAILURE;
-    }
+    rv = jis->InitFile(mZip.GetFD(item), item);
   }
   if (NS_FAILED(rv)) {
     NS_RELEASE(*result);
   }
   return rv;
 }
 
 //----------------------------------------------
@@ -1108,23 +1095,19 @@ nsZipReaderCache::GetZip(nsIFile* zipFil
 #ifdef ZIP_CACHE_HIT_RATE
   mZipCacheLookups++;
 #endif
 
   nsCAutoString path;
   rv = zipFile->GetNativePath(path);
   if (NS_FAILED(rv)) return rv;
 
-  PRInt64 Mtime;
-  rv = zipFile->GetLastModifiedTime(&Mtime);
-  if (NS_FAILED(rv)) return rv;
-
   nsCStringKey key(path);
   nsJAR* zip = static_cast<nsJAR*>(static_cast<nsIZipReader*>(mZips.Get(&key))); // AddRefs
-  if (zip && Mtime == zip->GetMtime()) {
+  if (zip) {
 #ifdef ZIP_CACHE_HIT_RATE
     mZipCacheHits++;
 #endif
     zip->ClearReleaseTime();
   }
   else {
     if (zip) {
       antiLockZipGrip = zip;
--- a/modules/libjar/nsJAR.h
+++ b/modules/libjar/nsJAR.h
@@ -128,20 +128,16 @@ class nsJAR : public nsIZipReader, publi
     void ClearReleaseTime() {
       mReleaseTime = PR_INTERVAL_NO_TIMEOUT;
     }
     
     void SetZipReaderCache(nsZipReaderCache* cache) {
       mCache = cache;
     }
 
-    PRInt64 GetMtime() {
-      return mMtime;
-    }
-
   protected:
     //-- Private data members
     nsCOMPtr<nsIFile>        mZipFile;        // The zip/jar file on disk
     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
--- a/modules/libjar/nsJARInputStream.cpp
+++ b/modules/libjar/nsJARInputStream.cpp
@@ -18,16 +18,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1999
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mitch Stoltz <mstoltz@netscape.com>
+ *   Taras Glek <tglek@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -40,45 +41,40 @@
 
 #include "nsJARInputStream.h"
 #include "zipstruct.h"         // defines ZIP compression codes
 #include "nsZipArchive.h"
 
 #include "nsNetUtil.h"
 #include "nsEscape.h"
 #include "nsIFile.h"
+#include "nsDebug.h"
 
 /*---------------------------------------------
  *  nsISupports implementation
  *--------------------------------------------*/
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputStream, nsIInputStream)
 
 /*----------------------------------------------------------
  * nsJARInputStream implementation
  *--------------------------------------------------------*/
 
 nsresult
-nsJARInputStream::InitFile(nsZipArchive* aZip, nsZipItem *item, PRFileDesc *fd)
+nsJARInputStream::InitFile(nsZipHandle *aFd, nsZipItem *item)
 {
     nsresult rv;
-
-    // Keep the file handle, even on failure
-    mFd = fd;
-      
-    NS_ENSURE_ARG_POINTER(aZip);
-    NS_ENSURE_ARG_POINTER(item);
-    NS_ENSURE_ARG_POINTER(fd);
+    NS_ABORT_IF_FALSE(aFd, "Argument may not be null");
+    NS_ABORT_IF_FALSE(item, "Argument may not be null");
 
     // Mark it as closed, in case something fails in initialisation
     mClosed = PR_TRUE;
-
     // Keep the important bits of nsZipItem only
     mInSize = item->size;
- 
+
     //-- prepare for the compression type
     switch (item->compression) {
        case STORED: 
            break;
 
        case DEFLATED:
            mInflate = (InflateStruct *) PR_Malloc(sizeof(InflateStruct));
            NS_ENSURE_TRUE(mInflate, NS_ERROR_OUT_OF_MEMORY);
@@ -91,39 +87,38 @@ nsJARInputStream::InitFile(nsZipArchive*
            mInflate->mOutCrc = crc32(0L, Z_NULL, 0);
            break;
 
        default:
            return NS_ERROR_NOT_IMPLEMENTED;
     }
    
     //-- Set filepointer to start of item
-    rv = aZip->SeekToItem(item, mFd);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
+    mFd.Open(aFd, item->dataOffset, item->size);
         
     // Open for reading
     mClosed = PR_FALSE;
     mCurPos = 0;
     return NS_OK;
 }
 
 nsresult
-nsJARInputStream::InitDirectory(nsZipArchive* aZip,
+nsJARInputStream::InitDirectory(nsJAR* aJar,
                                 const nsACString& aJarDirSpec,
                                 const char* aDir)
 {
-    NS_ENSURE_ARG_POINTER(aZip);
-    NS_ENSURE_ARG_POINTER(aDir);
+    NS_ABORT_IF_FALSE(aJar, "Argument may not be null");
+    NS_ABORT_IF_FALSE(aDir, "Argument may not be null");
 
     // Mark it as closed, in case something fails in initialisation
     mClosed = PR_TRUE;
     mDirectory = PR_TRUE;
     
     // Keep the zipReader for getting the actual zipItems
-    mZip = aZip;
+    mJar = aJar;
     nsZipFind *find;
     nsresult rv;
     // We can get aDir's contents as strings via FindEntries
     // with the following pattern (see nsIZipReader.findEntries docs)
     // assuming dirName is properly escaped:
     //
     //   dirName + "?*~" + dirName + "?*/?*"
     nsDependentCString dirName(aDir);
@@ -151,17 +146,17 @@ nsJARInputStream::InitDirectory(nsZipArc
                 // fall through
             default:
                 escDirName.Append(*curr);
         }
         ++curr;
     }
     nsCAutoString pattern = escDirName + NS_LITERAL_CSTRING("?*~") +
                             escDirName + NS_LITERAL_CSTRING("?*/?*");
-    rv = aZip->FindInit(pattern.get(), &find);
+    rv = mJar->mZip.FindInit(pattern.get(), &find);
     if (NS_FAILED(rv)) return rv;
 
     const char *name;
     while ((rv = find->FindNext( &name )) == NS_OK) {
         // No need to copy string, just share the one from nsZipArchive
         mArray.AppendElement(nsDependentCString(name));
     }
     delete find;
@@ -215,37 +210,35 @@ nsJARInputStream::Read(char* aBuffer, PR
         rv = ReadDirectory(aBuffer, aCount, aBytesRead);
     } else {
         if (mInflate) {
             rv = ContinueInflate(aBuffer, aCount, aBytesRead);
         } else {
             PRInt32 bytesRead = 0;
             aCount = PR_MIN(aCount, mInSize - mCurPos);
             if (aCount) {
-                bytesRead = PR_Read(mFd, aBuffer, aCount);
+                bytesRead = mFd.Read(aBuffer, aCount);
                 if (bytesRead < 0)
                     return NS_ERROR_FILE_CORRUPTED;
                 mCurPos += bytesRead;
-                if (bytesRead != aCount) {
+                if ((PRUint32)bytesRead != aCount) {
                     // file is truncated or was lying about size, we're done
-                    PR_Close(mFd);
-                    mFd = nsnull;
+                    Close();
                     return NS_ERROR_FILE_CORRUPTED;
                 }
             }
             *aBytesRead = bytesRead;
         }
-
         // be aggressive about closing!
         // note that sometimes, we will close mFd before we've finished
         // deflating - this is because zlib buffers the input
         // So, don't free the ReadBuf/InflateStruct yet.
-        if (mCurPos >= mInSize && mFd) {
-            PR_Close(mFd);
-            mFd = nsnull;
+        // It is ok to close the fd multiple times (also in nsJARInputStream::Close())
+        if (mCurPos >= mInSize) {
+            mFd.Close();
         }
     }
     return rv;
 }
 
 NS_IMETHODIMP
 nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 count, PRUint32 *_retval)
 {
@@ -260,21 +253,18 @@ nsJARInputStream::IsNonBlocking(PRBool *
     *aNonBlocking = PR_FALSE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARInputStream::Close()
 {
     PR_FREEIF(mInflate);
-    if (mFd) {
-        PR_Close(mFd);
-        mFd = nsnull;
-    }
     mClosed = PR_TRUE;
+    mFd.Close();
     return NS_OK;
 }
 
 nsresult 
 nsJARInputStream::ContinueInflate(char* aBuffer, PRUint32 aCount,
                                   PRUint32* aBytesRead)
 {
     // No need to check the args, ::Read did that, but assert them at least
@@ -292,18 +282,17 @@ nsJARInputStream::ContinueInflate(char* 
     int zerr = Z_OK;
     //-- inflate loop
     while (mInflate->mZs.avail_out > 0 && zerr == Z_OK) {
 
         if (mInflate->mZs.avail_in == 0 && mCurPos < mInSize) {
             // time to fill the buffer!
             PRUint32 bytesToRead = PR_MIN(mInSize - mCurPos, ZIP_BUFLEN);
 
-            NS_ASSERTION(mFd, "File handle missing");
-            PRInt32 bytesRead = PR_Read(mFd, mInflate->mReadBuf, bytesToRead);
+            PRInt32 bytesRead = mFd.Read(mInflate->mReadBuf, bytesToRead);
             if (bytesRead < 0) {
                 zerr = Z_ERRNO;
                 break;
             }
             mCurPos += bytesRead;
 
             // now set the state for 'inflate'
             mInflate->mZs.next_in = mInflate->mReadBuf;
@@ -357,17 +346,17 @@ nsJARInputStream::ReadDirectory(char* aB
 
         for ( ;aCount > mBuffer.Length(); mArrPos++) {
             // have we consumed all the directory contents?
             if (arrayLen <= mArrPos)
                 break;
 
             const char * entryName = mArray[mArrPos].get();
             PRUint32 entryNameLen = mArray[mArrPos].Length();
-            nsZipItem* ze = mZip->GetItem(entryName);
+            nsZipItem* ze = mJar->mZip.GetItem(entryName);
             NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
 
             // Last Modified Time
             PRExplodedTime tm;
             PR_ExplodeTime(GetModTime(ze->date, ze->time), PR_GMTParameters, &tm);
             char itemLastModTime[65];
             PR_FormatTimeUSEnglish(itemLastModTime,
                                    sizeof(itemLastModTime),
--- a/modules/libjar/nsJARInputStream.h
+++ b/modules/libjar/nsJARInputStream.h
@@ -48,54 +48,55 @@
  * Class nsJARInputStream declaration. This class defines the type of the
  * object returned by calls to nsJAR::GetInputStream(filename) for the
  * purpose of reading a file item out of a JAR file. 
  *------------------------------------------------------------------------*/
 class nsJARInputStream : public nsIInputStream
 {
   public:
     nsJARInputStream() : 
-        mFd(nsnull), mInSize(0), mCurPos(0),
-        mInflate(nsnull), mDirectory(0), mClosed(PR_FALSE) { }
-    
-    ~nsJARInputStream() { Close(); }
+        mInSize(0), mCurPos(0), mInflate(nsnull), mDirectory(0), mClosed(PR_FALSE)
+  { }
+
+  ~nsJARInputStream() {
+    Close();
+  }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIINPUTSTREAM
    
     // takes ownership of |fd|, even on failure
-    nsresult InitFile(nsZipArchive* aZip, nsZipItem *item, PRFileDesc *fd);
+    nsresult InitFile(nsZipHandle *aFd, nsZipItem *item);
 
-    nsresult InitDirectory(nsZipArchive* aZip,
+    nsresult InitDirectory(nsJAR *aJar,
                            const nsACString& aJarDirSpec,
                            const char* aDir);
   
   private:
-    PRFileDesc*   mFd;              // My own file handle, for reading
     PRUint32      mInSize;          // Size in original file 
     PRUint32      mCurPos;          // Current position in input 
 
     struct InflateStruct {
         PRUint32      mOutSize;     // inflated size 
         PRUint32      mInCrc;       // CRC as provided by the zipentry
         PRUint32      mOutCrc;      // CRC as calculated by me
         z_stream      mZs;          // zip data structure
         unsigned char mReadBuf[ZIP_BUFLEN]; // Readbuffer to inflate from
     };
     struct InflateStruct *   mInflate;
 
     /* For directory reading */
-    nsZipArchive*           mZip;        // the zipReader
+    nsRefPtr<nsJAR>         mJar;     // string reference to zipreader
     PRUint32                mNameLen; // length of dirname
     nsCAutoString           mBuffer;  // storage for generated text of stream
     PRUint32                mArrPos;  // current position within mArray
     nsTArray<nsCString>     mArray;   // array of names in (zip) directory
-
-    PRPackedBool    mDirectory;
-    PRPackedBool    mClosed;          // Whether the stream is closed
+  PRPackedBool            mDirectory; // is this a directory?
+    PRPackedBool            mClosed;  // Whether the stream is closed
+    nsSeekableZipHandle     mFd;      // handle for reading
 
     nsresult ContinueInflate(char* aBuf, PRUint32 aCount, PRUint32* aBytesRead);
     nsresult ReadDirectory(char* aBuf, PRUint32 aCount, PRUint32* aBytesRead);
     PRUint32 CopyDataToBuffer(char* &aBuffer, PRUint32 &aCount);
 };
 
 #endif /* nsJARINPUTSTREAM_h__ */
 
--- a/modules/libjar/nsZipArchive.cpp
+++ b/modules/libjar/nsZipArchive.cpp
@@ -21,16 +21,17 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Daniel Veditz <dveditz@netscape.com>
  *   Samir Gehani <sgehani@netscape.com>
  *   Mitch Stoltz <mstoltz@netscape.com>
  *   Jeroen Dobbelaere <jeroen.dobbelaere@acunia.com>
  *   Jeff Walden <jwalden+code@mit.edu>
+ *   Taras Glek <tglek@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -44,27 +45,29 @@
 /*
  * This module implements a simple archive extractor for the PKZIP format.
  *
  * The underlying nsZipArchive is NOT thread-safe. Do not pass references
  * or pointers to it across thread boundaries.
  */
 
 
-#include "nsWildCard.h"
-#include "nscore.h"
-#include "prmem.h"
-#include "prio.h"
-#include "plstr.h"
-#include "prlog.h"
 #define ZFILE_CREATE    PR_WRONLY | PR_CREATE_FILE
 #define READTYPE  PRInt32
 #include "zlib.h"
 #include "nsISupportsUtils.h"
 #include "nsRecyclingAllocator.h"
+#include "prio.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "stdlib.h"
+#include "nsWildCard.h"
+#include "zipfile.h"
+#include "zipstruct.h"
+#include "nsZipArchive.h"
 
 /**
  * Globals
  *
  * Global allocator used with zlib. Destroyed in module shutdown.
  */
 #define NBUCKETS 6
 #define BY4ALLOC_ITEMS 320
@@ -200,35 +203,90 @@ nsresult gZlibInit(z_stream *zs)
     zs->opaque = gZlibAllocator;
   }
   int zerr = inflateInit2(zs, -MAX_WBITS);
   if (zerr != Z_OK) return ZIP_ERR_MEMORY;
 
   return ZIP_OK;
 }
 
+nsZipHandle::nsZipHandle()
+  : mFd(nsnull)
+  , mFileData(nsnull)
+  , mLen(0)
+  , mMap(nsnull)
+  , mRefCnt(0)
+{
+  MOZ_COUNT_CTOR(nsZipHandle);
+}
+
+NS_IMPL_THREADSAFE_ADDREF(nsZipHandle)
+NS_IMPL_THREADSAFE_RELEASE(nsZipHandle)
+
+nsresult nsZipHandle::Init(PRFileDesc *fd, nsZipHandle **ret)
+{
+  PRInt64 size = PR_Available64(fd);
+  if (size >= PR_INT32_MAX)
+    return NS_ERROR_FILE_TOO_BIG;
+
+  PRFileMap *map = PR_CreateFileMap(fd, size, PR_PROT_READONLY);
+
+  if (!map)
+    return NS_ERROR_FAILURE;
+
+  nsZipHandle *handle = new nsZipHandle();
+  if (!handle)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  handle->mFd = fd;
+  handle->mMap = map;
+  handle->mLen = (PRUint32) size;
+  handle->mFileData = (PRUint8*) PR_MemMap(map, 0, handle->mLen);
+  handle->AddRef();
+  *ret = handle;
+  return NS_OK;
+}
+
+PRInt32 nsZipHandle::Read(PRUint32 aPosition, void *aBuffer, PRUint32 aCount)
+{
+  if (mLen < 0 || aPosition+aCount > (PRUint32) mLen)
+    return -1;
+  PRInt32 count = PR_MIN(aCount, mLen - aPosition);
+  memcpy(aBuffer, mFileData + aPosition, count);
+  return count;
+}
+
+nsZipHandle::~nsZipHandle()
+{
+  if (mFd) {
+    PR_MemUnmap(mFileData, mLen);
+    PR_CloseFileMap(mMap);
+    PR_Close(mFd);
+    mFd = 0;
+  }
+  MOZ_COUNT_DTOR(nsZipHandle);
+}
+
 //***********************************************************
 //      nsZipArchive  --  public methods
 //***********************************************************
 
 
 //---------------------------------------------
 //  nsZipArchive::OpenArchive
 //---------------------------------------------
 nsresult nsZipArchive::OpenArchive(PRFileDesc * fd)
 {
-  if (!fd)
-    return ZIP_ERR_PARAM;
+  nsresult rv = nsZipHandle::Init(fd, getter_AddRefs(mFd));
+  if (NS_FAILED(rv))
+    return rv;
 
   // Initialize our arena
   PL_INIT_ARENA_POOL(&mArena, "ZipArena", ZIP_ARENABLOCKSIZE);
 
-  //-- Keep the filedescriptor for further reading...
-  mFd = fd;
-
   //-- get table of contents for archive
   return BuildFileList();
 }
 
 //---------------------------------------------
 //  nsZipArchive::Test
 //---------------------------------------------
 nsresult nsZipArchive::Test(const char *aEntryName)
@@ -274,21 +332,18 @@ nsresult nsZipArchive::CloseArchive()
   // We don't need to delete each of the nsZipItem as the memory for
   // the zip item and the filename it holds are both allocated from the Arena.
   // Hence, destroying the Arena is like destroying all the memory
   // for all the nsZipItem in one shot. But if the ~nsZipItem is doing
   // anything more than cleaning up memory, we should start calling it.
   // Let us also cleanup the mFiles table for re-use on the next 'open' call
   for (int i = 0; i < ZIP_TABSIZE; i++) {
     mFiles[i] = 0;
-  }  
-  if (mFd) {
-    PR_Close(mFd);
-    mFd = 0;
   }
+  mFd = NULL;
   mBuiltSynthetics = PR_FALSE;
   return ZIP_OK;
 }
 
 //---------------------------------------------
 // nsZipArchive::GetItem
 //---------------------------------------------
 nsZipItem*  nsZipArchive::GetItem(const char * aEntryName)
@@ -329,30 +384,34 @@ nsresult nsZipArchive::ExtractFile(nsZip
   if (!mFd)
     return ZIP_ERR_GENERAL;
 
   // Directory extraction is handled in nsJAR::Extract,
   // so the item to be extracted should never be a directory
   PR_ASSERT(!item->isDirectory);
 
   //-- move to the start of file's data
-  if (SeekToItem(item, mFd) != ZIP_OK)
+  if (!MaybeReadItem(item))
+    return ZIP_ERR_CORRUPT;
+
+  nsSeekableZipHandle fd;
+  if (!fd.Open(mFd.get(), item->dataOffset, item->size))
     return ZIP_ERR_CORRUPT;
 
   nsresult rv;
 
   //-- extract the file using the appropriate method
   switch(item->compression)
   {
     case STORED:
-      rv = CopyItemToDisk(item->size, item->crc32, aFd);
+      rv = CopyItemToDisk(item->size, item->crc32, fd, aFd);
       break;
 
     case DEFLATED:
-      rv = InflateItem(item, aFd);
+      rv = InflateItem(item, fd, aFd);
       break;
 
     default:
       //-- unsupported compression type
       rv = ZIP_ERR_UNSUPPORTED;
   }
 
   //-- delete the file on errors, or resolve symlink if needed
@@ -504,74 +563,64 @@ nsZipItem* nsZipArchive::CreateZipItem(P
   return (nsZipItem*)mem;
 }
 
 //---------------------------------------------
 //  nsZipArchive::BuildFileList
 //---------------------------------------------
 nsresult nsZipArchive::BuildFileList()
 {
-  PRUint8   buf[4*BR_BUF_SIZE];
-
+  PRUint8   *buf;
   //-----------------------------------------------------------------------
   // locate the central directory via the End record
   //-----------------------------------------------------------------------
 
   //-- get archive size using end pos
-  PRInt32  pos = PR_Seek(mFd, 0, PR_SEEK_END);
-  if (pos <= 0)
-    return ZIP_ERR_CORRUPT;
+  PRInt32  pos = (PRInt32) mFd->mLen;
 
-  PRBool bEndsigFound = PR_FALSE;
-  while (!bEndsigFound)
+  PRInt32 central = -1;
+  while (central == -1)
   {
     //-- read backwards in 1K-sized chunks (unless file is less than 1K)
     PRInt32  bufsize = pos > BR_BUF_SIZE ? BR_BUF_SIZE : pos;
     pos -= bufsize;
 
-    if (!ZIP_Seek(mFd, pos, PR_SEEK_SET))
-      return ZIP_ERR_CORRUPT;
-
-    if (PR_Read(mFd, buf, bufsize) != (READTYPE)bufsize)
-      return ZIP_ERR_CORRUPT;
+    buf = mFd->mFileData + pos;
 
     //-- scan for ENDSIG
     PRUint8 *endp = buf + bufsize;
     for (endp -= ZIPEND_SIZE; endp >= buf; endp--)
     {
       if (xtolong(endp) == ENDSIG)
       { 
         //-- Seek to start of central directory
-        PRInt32 central = xtolong(((ZipEnd *) endp)->offset_central_dir);
-        if (!ZIP_Seek(mFd, central, PR_SEEK_SET))
-          return ZIP_ERR_CORRUPT;
-
-        bEndsigFound = PR_TRUE;
+        central = xtolong(((ZipEnd *) endp)->offset_central_dir);
         break;
       }
     }
 
-    if (bEndsigFound)
+    if (central != -1)
       break;
 
     if (pos <= 0)
       //-- We're at the beginning of the file, and still no sign
       //-- of the end signature.  File must be corrupted!
       return ZIP_ERR_CORRUPT;
 
     //-- backward read must overlap ZipEnd length
     pos += ZIPEND_SIZE;
 
   } /* while looking for end signature */
 
 
   //-------------------------------------------------------
   // read the central directory headers
   //-------------------------------------------------------
-  PRInt32 byteCount = PR_Read(mFd, &buf, sizeof(buf));
+  PRInt32 byteCount = mFd->mLen - central;
+  buf = mFd->mFileData + central;
   pos = 0;
   PRUint32 sig = xtolong(buf);
   while (sig == CENTRALSIG) {
     //-- make sure we've read enough
     if (byteCount - pos < ZIPCENTRAL_SIZE)
       return ZIP_ERR_CORRUPT;
 
     //-------------------------------------------------------
@@ -608,41 +657,21 @@ nsresult nsZipArchive::BuildFileList()
 
     item->mode = ExtractMode(central->external_attributes);
 #if defined(XP_UNIX) || defined(XP_BEOS)
     // Check if item is a symlink
     item->isSymlink = IsSymlink(central->external_attributes);
 #endif
 
     pos += ZIPCENTRAL_SIZE;
-
-    //-------------------------------------------------------
-    // Make sure that remainder of this record (name, comments, extra)
-    // and the next ZipCentral is all in the buffer
-    //-------------------------------------------------------
-    PRInt32 leftover = byteCount - pos;
-    if (leftover < (namelen + extralen + commentlen + ZIPCENTRAL_SIZE)) {
-      //-- not enough data left to process at top of loop.
-      //-- move leftover and read more
-      memcpy(buf, buf+pos, leftover);
-      byteCount = leftover + PR_Read(mFd, buf+leftover, sizeof(buf)-leftover);
-      pos = 0;
-
-      if (byteCount < (namelen + extralen + commentlen + sizeof(sig))) {
-        // truncated file
-        return ZIP_ERR_CORRUPT;
-      }
-    }
-
     //-------------------------------------------------------
     // get the item name
     //-------------------------------------------------------
     memcpy(item->name, buf+pos, namelen);
     item->name[namelen] = 0;
-
     //-- an item whose name ends with '/' is a directory
     item->isDirectory = ('/' == item->name[namelen - 1]);
 
     //-- add item to file table
     //-- note that an explicit entry for a directory will override
     //-- a fake entry created for that directory (as in the case
     //-- of processing foo/bar.txt and then foo/) -- this will
     //-- preserve an explicit directory's metadata at the cost of
@@ -753,63 +782,66 @@ nsresult nsZipArchive::BuildSynthetics()
         diritem->next = mFiles[hash];
         mFiles[hash] = diritem;
       } /* end processing of dirs in item's name */
     }
   }
   return ZIP_OK;
 }
 
+nsZipHandle* nsZipArchive::GetFD(nsZipItem* aItem)
+{
+  if (!mFd || !MaybeReadItem(aItem))
+    return NULL;
+  return mFd.get();
+}
 
 //---------------------------------------------
-// nsZipArchive::SeekToItem
+// nsZipArchive::MaybeReadItem
 //---------------------------------------------
-nsresult  nsZipArchive::SeekToItem(nsZipItem* aItem, PRFileDesc* aFd)
+bool nsZipArchive::MaybeReadItem(nsZipItem* aItem)
 {
   PR_ASSERT (aItem);
 
   //-- the first time an item is used we need to calculate its offset
   if (!aItem->hasDataOffset)
   {
     //-- read local header to get variable length values and calculate
     //-- the real data offset
     //--
     //-- NOTE: extralen is different in central header and local header
     //--       for archives created using the Unix "zip" utility. To set
     //--       the offset accurately we need the _local_ extralen.
-    if (!ZIP_Seek(aFd, aItem->headerOffset, PR_SEEK_SET))
-      return ZIP_ERR_CORRUPT;
+    if (!mFd || !mFd->mLen > aItem->headerOffset + ZIPLOCAL_SIZE)
+      return false;
 
-    ZipLocal   Local;
-    if ((PR_Read(aFd, (char*)&Local, ZIPLOCAL_SIZE) != (READTYPE) ZIPLOCAL_SIZE) || 
-        (xtolong(Local.signature) != LOCALSIG))
+    ZipLocal   *Local =     (ZipLocal*)(mFd->mFileData + aItem->headerOffset);
+    //check limits here
+    if ((xtolong(Local->signature) != LOCALSIG))
     {
       //-- read error or local header not found
-      return ZIP_ERR_CORRUPT;
+      return false;
     }
 
     aItem->dataOffset = aItem->headerOffset +
                         ZIPLOCAL_SIZE +
-                        xtoint(Local.filename_len) +
-                        xtoint(Local.extrafield_len);
+                        xtoint(Local->filename_len) +
+                        xtoint(Local->extrafield_len);
     aItem->hasDataOffset = PR_TRUE;
   }
 
-  //-- move to start of file in archive
-  if (!ZIP_Seek(aFd, aItem->dataOffset, PR_SEEK_SET))
-    return  ZIP_ERR_CORRUPT;
-
-  return ZIP_OK;
+  return true;
 }
 
 //---------------------------------------------
 // nsZipArchive::CopyItemToDisk
 //---------------------------------------------
 nsresult
-nsZipArchive::CopyItemToDisk(PRUint32 itemSize, PRUint32 itemCrc, PRFileDesc* outFD)
+nsZipArchive::CopyItemToDisk(PRUint32 itemSize, PRUint32 itemCrc, 
+                             nsSeekableZipHandle &fd, PRFileDesc* outFD)
 /*
  * This function copies an archive item to disk, to the
  * file specified by outFD. If outFD is zero, the extracted data is
  * not written, only checked for CRC, so this is in effect same as 'Test'.
  */
 {
   PRUint32    chunk, pos, crc;
   char buf[ZIP_BUFLEN];
@@ -817,17 +849,17 @@ nsZipArchive::CopyItemToDisk(PRUint32 it
   //-- initialize crc
   crc = crc32(0L, Z_NULL, 0);
 
   //-- copy chunks until file is done
   for (pos = 0; pos < itemSize; pos += chunk)
   {
     chunk = (itemSize - pos < ZIP_BUFLEN) ? (itemSize - pos) : ZIP_BUFLEN;
     
-    if (PR_Read(mFd, buf, chunk) != (READTYPE)chunk)
+    if (fd.Read(buf, chunk) != (READTYPE)chunk)
     {
       //-- unexpected end of data in archive
       return ZIP_ERR_CORRUPT;
     }
 
     //-- incrementally update crc32
     crc = crc32(crc, (const unsigned char*)buf, chunk);
 
@@ -844,17 +876,17 @@ nsZipArchive::CopyItemToDisk(PRUint32 it
 
   return ZIP_OK;
 }
 
 
 //---------------------------------------------
 // nsZipArchive::InflateItem
 //---------------------------------------------
-nsresult nsZipArchive::InflateItem(const nsZipItem* aItem, PRFileDesc* outFD)
+nsresult nsZipArchive::InflateItem(const nsZipItem* aItem, nsSeekableZipHandle &fd, PRFileDesc* outFD)
 /*
  * This function inflates an archive item to disk, to the
  * file specified by outFD. If outFD is zero, the extracted data is
  * not written, only checked for CRC, so this is in effect same as 'Test'.
  */
 {
   PR_ASSERT(aItem);
 
@@ -882,17 +914,17 @@ nsresult nsZipArchive::InflateItem(const
     PRBool      bWrote= PR_FALSE;
 
     if (zs.avail_in == 0 && zs.total_in < size)
     {
       //-- no data to inflate yet still more in file:
       //-- read another chunk of compressed data
       PRUint32 chunk = (size-zs.total_in < ZIP_BUFLEN) ? size-zs.total_in : ZIP_BUFLEN;
 
-      if (PR_Read(mFd, inbuf, chunk) != (READTYPE)chunk)
+      if (fd.Read(inbuf, chunk) != (READTYPE)chunk)
       {
         //-- unexpected end of data
         status = ZIP_ERR_CORRUPT;
         break;
       }
 
       zs.next_in  = inbuf;
       zs.avail_in = chunk;
@@ -961,18 +993,17 @@ cleanup:
   return status;
 }
 
 //------------------------------------------
 // nsZipArchive constructor and destructor
 //------------------------------------------
 
 nsZipArchive::nsZipArchive() :
-    mFd(0),
-    mBuiltSynthetics(PR_FALSE)
+  mBuiltSynthetics(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsZipArchive);
 
   // initialize the table to NULL
   memset(mFiles, 0, sizeof mFiles);
 }
 
 nsZipArchive::~nsZipArchive()
@@ -1070,9 +1101,8 @@ static PRUint16 ExtractMode(unsigned cha
  *
  */
 
 static PRBool IsSymlink(unsigned char *ll)
 {
   return ((xtoint(ll+2) & S_IFMT) == S_IFLNK);
 }
 #endif
-
--- a/modules/libjar/nsZipArchive.h
+++ b/modules/libjar/nsZipArchive.h
@@ -19,16 +19,17 @@
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998-1999
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Daniel Veditz <dveditz@netscape.com>
  *   Samir Gehani <sgehani@netscape.com>
  *   Mitch Stoltz <mstoltz@netscape.com>
+ *   Taras Glek <tglek@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -48,16 +49,17 @@
 // Keep this odd. The -1 is significant.
 #define ZIP_BUFLEN    (4 * 1024 - 1)
 
 #define PL_ARENA_CONST_ALIGN_MASK 7
 #include "plarena.h"
 #define ZIP_Seek(fd,p,m) (PR_Seek((fd),((PROffset32)p),(m))==((PROffset32)p))
 
 #include "zlib.h"
+#include "nsAutoPtr.h"
 
 class nsZipFind;
 class nsZipReadState;
 class nsZipItemMetadata;
 
 struct PRFileDesc;
 
 /**
@@ -106,16 +108,18 @@ struct nsZipItem
                                     and no foo/ entry is synthetic */
 #if defined(XP_UNIX) || defined(XP_BEOS)
   PRPackedBool isSymlink : 1;
 #endif
 
   char        name[1]; // actually, bigger than 1
 };
 
+class nsZipHandle;
+class nsSeekableZipHandle;
 /** 
  * nsZipArchive -- a class for reading the PKZIP file format.
  *
  */
 class nsZipArchive 
 {
   friend class nsZipFind;
 
@@ -181,46 +185,134 @@ public:
    *                      (may be NULL to find all files in archive)
    * @param   aFind       a pointer to a pointer to a structure used
    *                      in FindNext.  In the case of an error this
    *                      will be set to NULL.
    * @return  status code
    */
   PRInt32 FindInit(const char * aPattern, nsZipFind** aFind);
 
-  /**
-   * Moves the filepointer aFd to the start of data of the aItem.
-   * @param   aItem       Pointer to nsZipItem
-   * @param   aFd         The filepointer to move
+  /* Gets an undependent handle to the jar
+   * Also ensures that aItem is fully filled
    */
-  nsresult  SeekToItem(nsZipItem* aItem, PRFileDesc* aFd);
+  nsZipHandle* GetFD(nsZipItem* aItem);
 
 private:
   //--- private members ---
 
   nsZipItem*    mFiles[ZIP_TABSIZE];
   PLArenaPool   mArena;
 
-  // Used for central directory reading, and for Test and Extract
-  PRFileDesc    *mFd;
+  /**
+   * Fills in nsZipItem fields that were not filled in by BuildFileList
+   * @param   aItem       Pointer to nsZipItem
+   * returns true if the item was filled in successfully
+   */
+  bool MaybeReadItem(nsZipItem* aItem);
 
   // Whether we synthesized the directory entries
   PRPackedBool  mBuiltSynthetics;
 
+  // file handle
+  nsRefPtr<nsZipHandle> mFd;
   //--- private methods ---
   
   nsZipArchive& operator=(const nsZipArchive& rhs); // prevent assignments
   nsZipArchive(const nsZipArchive& rhs);            // prevent copies
 
   nsZipItem*        CreateZipItem(PRUint16 namelen);
   nsresult          BuildFileList();
   nsresult          BuildSynthetics();
 
-  nsresult  CopyItemToDisk(PRUint32 size, PRUint32 crc, PRFileDesc* outFD);
-  nsresult  InflateItem(const nsZipItem* aItem, PRFileDesc* outFD);
+  nsresult  CopyItemToDisk(PRUint32 size, PRUint32 crc, nsSeekableZipHandle &fd, PRFileDesc* outFD);
+  nsresult  InflateItem(const nsZipItem* aItem, nsSeekableZipHandle &fd, PRFileDesc* outFD);
+};
+
+class nsZipHandle {
+friend class nsZipArchive;
+friend class nsSeekableZipHandle;
+public:
+  static nsresult Init(PRFileDesc *fd, nsZipHandle **ret NS_OUTPARAM);
+
+  /**
+   * Reads data at a certain point
+   * @param aPosition seek ofset
+   * @param aBuffer buffer
+   * @param aCount number of bytes to read */
+  PRInt32 Read(PRUint32 aPosition, void *aBuffer, PRUint32 aCount);
+
+  NS_METHOD_(nsrefcnt) AddRef(void);
+  NS_METHOD_(nsrefcnt) Release(void);
+
+protected:
+  PRFileDesc *mFd; // OS file-descriptor
+  PRUint8 *mFileData; // pointer to mmaped file
+  PRUint32 mLen; // length of file and memory mapped area
+
+private:
+  nsZipHandle();
+  ~nsZipHandle();
+
+  PRFileMap *mMap; // nspr datastructure for mmap
+  nsrefcnt mRefCnt; // ref count
+};
+
+
+/** nsSeekableZipHandle acts as a container for nsZipHandle,
+    emulates sequential file io */
+class nsSeekableZipHandle {
+  //   stick nsZipItem in here
+public:
+  nsSeekableZipHandle()
+    : mOffset(0)
+    , mRemaining(0)
+  {
+  }
+
+  /** Initializes nsSeekableZipHandle with
+   * @param aOffset byte offset of the file to start reading at
+   * @param length of this descriptor
+   */
+  bool Open(nsZipHandle *aHandle, PRUint32 aOffset, PRUint32 aLength) {
+    NS_ABORT_IF_FALSE (aHandle, "Argument must not be NULL");
+    if (aOffset > aHandle->mLen)
+      return false;
+    mFd = aHandle;
+    mOffset = aOffset;
+    mRemaining = aLength;
+    return true;
+  }
+
+  /** Releases the file handle. It is safe to call multiple times. */
+  void Close()
+  {
+    mFd = NULL;
+  }
+
+  /**
+   * Reads data at a certain point
+   * @param aBuffer input buffer
+   * @param aCount number of bytes to read */
+  PRInt32 Read(void *aBuffer, PRUint32 aCount)
+  {
+    if (!mFd.get())
+      return -1;
+    aCount = PR_MIN(mRemaining, aCount);
+    PRInt32 ret = mFd->Read(mOffset, aBuffer, aCount);
+    if (ret > 0) {
+      mOffset += ret;
+      mRemaining -= ret;
+    }
+    return ret;
+  }
+
+private:
+  nsRefPtr<nsZipHandle> mFd; // file handle
+  PRUint32 mOffset; // current reading offset
+  PRUint32 mRemaining; // bytes remaining
 };
 
 
 /** 
  * nsZipFind 
  *
  * a helper class for nsZipArchive, representing a search
  */
new file mode 100644
--- /dev/null
+++ b/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Taras Glek <tglek@mozilla.com>
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function wrapInputStream(input)
+{
+  var nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+  var factory = Components.classes["@mozilla.org/scriptableinputstream;1"];
+  var wrapper = factory.createInstance(nsIScriptableInputStream);
+  wrapper.init(input);
+  return wrapper;
+}
+
+// Check that files can be read from after closing zipreader
+function run_test() {
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+
+  // the build script have created the zip we can test on in the current dir.
+  var file = do_get_file("data/test_bug333423.zip");
+
+  var zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                  createInstance(Ci.nsIZipReader);
+  zipreader.open(file);
+  // do crc stuff
+  function check_archive_crc() {
+    zipreader.test(null);
+    return true;
+  }
+  do_check_true(check_archive_crc())
+  var entries = zipreader.findEntries(null);
+  var stream = wrapInputStream(zipreader.getInputStream("modules/libjar/test/Makefile.in"))
+  var dirstream= wrapInputStream(zipreader.getInputStream("modules/libjar/test/"))
+  zipreader.close();
+  zipreader = null;
+  Components.utils.forceGC();
+  do_check_true(stream.read(1024).length > 0);
+  do_check_true(dirstream.read(100).length > 0);
+}
+