From ed2f94b3a597c1c7442b5acfeca93d5b04109582 Mon Sep 17 00:00:00 2001 From: Taras Glek Date: Sat, 8 Aug 2009 12:07:39 +0200 Subject: [PATCH] Bug 504864 - mmap io for JARs; r=benjamin --- modules/libjar/nsJAR.cpp | 25 +-- modules/libjar/nsJAR.h | 4 - modules/libjar/nsJARInputStream.cpp | 53 ++--- modules/libjar/nsJARInputStream.h | 23 +-- modules/libjar/nsZipArchive.cpp | 185 ++++++++++-------- modules/libjar/nsZipArchive.h | 110 ++++++++++- ...est_jarinput_stream_zipreader_reference.js | 66 +++++++ 7 files changed, 310 insertions(+), 156 deletions(-) create mode 100644 modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js diff --git a/modules/libjar/nsJAR.cpp b/modules/libjar/nsJAR.cpp index ec8ba48167c..6d013281fb0 100644 --- a/modules/libjar/nsJAR.cpp +++ b/modules/libjar/nsJAR.cpp @@ -122,7 +122,6 @@ nsJAR::nsJAR(): mManifestData(nsnull, nsnull, DeleteManifestEntry, nsnull, 10), mReleaseTime(PR_INTERVAL_NO_TIMEOUT), mCache(nsnull), mLock(nsnull), - mMtime(0), mTotalItemsInManifest(0) { } @@ -167,8 +166,6 @@ nsJAR::Open(nsIFile* 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); @@ -176,7 +173,7 @@ nsJAR::Open(nsIFile* zipFile) 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; @@ -340,19 +337,9 @@ nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec, 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); @@ -1113,13 +1100,9 @@ nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result) 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(static_cast(mZips.Get(&key))); // AddRefs - if (zip && Mtime == zip->GetMtime()) { + if (zip) { #ifdef ZIP_CACHE_HIT_RATE mZipCacheHits++; #endif diff --git a/modules/libjar/nsJAR.h b/modules/libjar/nsJAR.h index 073b5cac06c..af59811b98a 100644 --- a/modules/libjar/nsJAR.h +++ b/modules/libjar/nsJAR.h @@ -133,10 +133,6 @@ class nsJAR : public nsIZipReader, public nsIJAR mCache = cache; } - PRInt64 GetMtime() { - return mMtime; - } - protected: //-- Private data members nsCOMPtr mZipFile; // The zip/jar file on disk diff --git a/modules/libjar/nsJARInputStream.cpp b/modules/libjar/nsJARInputStream.cpp index ea56d7bcbd7..50b392da4c7 100644 --- a/modules/libjar/nsJARInputStream.cpp +++ b/modules/libjar/nsJARInputStream.cpp @@ -23,6 +23,7 @@ * * Contributor(s): * Mitch Stoltz + * Taras Glek * * 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 @@ -45,6 +46,7 @@ #include "nsNetUtil.h" #include "nsEscape.h" #include "nsIFile.h" +#include "nsDebug.h" /*--------------------------------------------- * nsISupports implementation @@ -57,23 +59,17 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputStream, nsIInputStream) *--------------------------------------------------------*/ 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: @@ -96,8 +92,7 @@ nsJARInputStream::InitFile(nsZipArchive* aZip, nsZipItem *item, PRFileDesc *fd) } //-- 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; @@ -106,19 +101,19 @@ nsJARInputStream::InitFile(nsZipArchive* aZip, nsZipItem *item, PRFileDesc *fd) } 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 @@ -156,7 +151,7 @@ nsJARInputStream::InitDirectory(nsZipArchive* aZip, } 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; @@ -220,27 +215,25 @@ nsJARInputStream::Read(char* aBuffer, PRUint32 aCount, PRUint32 *aBytesRead) 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; @@ -265,11 +258,8 @@ NS_IMETHODIMP nsJARInputStream::Close() { PR_FREEIF(mInflate); - if (mFd) { - PR_Close(mFd); - mFd = nsnull; - } mClosed = PR_TRUE; + mFd.Close(); return NS_OK; } @@ -297,8 +287,7 @@ nsJARInputStream::ContinueInflate(char* aBuffer, PRUint32 aCount, // 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; @@ -362,7 +351,7 @@ nsJARInputStream::ReadDirectory(char* aBuffer, PRUint32 aCount, PRUint32 *aBytes 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 diff --git a/modules/libjar/nsJARInputStream.h b/modules/libjar/nsJARInputStream.h index b8404c971f7..d626e96f4de 100644 --- a/modules/libjar/nsJARInputStream.h +++ b/modules/libjar/nsJARInputStream.h @@ -53,23 +53,24 @@ 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 @@ -83,14 +84,14 @@ class nsJARInputStream : public nsIInputStream struct InflateStruct * mInflate; /* For directory reading */ - nsZipArchive* mZip; // the zipReader + nsRefPtr 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 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); diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp index 6886273fe43..3d9747fb927 100644 --- a/modules/libjar/nsZipArchive.cpp +++ b/modules/libjar/nsZipArchive.cpp @@ -26,6 +26,7 @@ * Mitch Stoltz * Jeroen Dobbelaere * Jeff Walden + * Taras Glek * * 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 @@ -49,17 +50,19 @@ */ -#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 @@ -205,6 +208,60 @@ nsresult gZlibInit(z_stream *zs) return ZIP_OK; } +nsZipHandle::nsZipHandle() + : mFd(nsnull) + , mFileData(nsnull) + , mLen(0) + , mMap(nsnull) + , mRefCnt(1) +{ +} + +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); + *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; + } +} + //*********************************************************** // nsZipArchive -- public methods //*********************************************************** @@ -215,15 +272,13 @@ nsresult gZlibInit(z_stream *zs) //--------------------------------------------- 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(); } @@ -279,11 +334,8 @@ nsresult nsZipArchive::CloseArchive() // 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; } @@ -334,7 +386,11 @@ nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname, PR_ASSERT(!item->isDirectory); //-- move to the start of file's data - if (SeekToItem(item, mFd) != ZIP_OK) + if (MaybeReadItem(item) != ZIP_OK) + return ZIP_ERR_CORRUPT; + + nsSeekableZipHandle fd; + if (!fd.Open(mFd.get(), item->headerOffset, item->size)) return ZIP_ERR_CORRUPT; nsresult rv; @@ -343,11 +399,11 @@ nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname, 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: @@ -509,29 +565,22 @@ nsZipItem* nsZipArchive::CreateZipItem(PRUint16 namelen) //--------------------------------------------- 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; @@ -540,16 +589,12 @@ nsresult nsZipArchive::BuildFileList() 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) @@ -566,7 +611,8 @@ nsresult nsZipArchive::BuildFileList() //------------------------------------------------------- // 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) { @@ -613,31 +659,11 @@ nsresult nsZipArchive::BuildFileList() #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]); @@ -758,11 +784,17 @@ nsresult nsZipArchive::BuildSynthetics() 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); @@ -775,36 +807,33 @@ nsresult nsZipArchive::SeekToItem(nsZipItem* aItem, PRFileDesc* aFd) //-- 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 @@ -822,7 +851,7 @@ nsZipArchive::CopyItemToDisk(PRUint32 itemSize, PRUint32 itemCrc, PRFileDesc* ou { 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; @@ -849,7 +878,7 @@ nsZipArchive::CopyItemToDisk(PRUint32 itemSize, PRUint32 itemCrc, PRFileDesc* ou //--------------------------------------------- // 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 @@ -887,7 +916,7 @@ nsresult nsZipArchive::InflateItem(const nsZipItem* aItem, PRFileDesc* outFD) //-- 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; @@ -966,8 +995,7 @@ cleanup: //------------------------------------------ nsZipArchive::nsZipArchive() : - mFd(0), - mBuiltSynthetics(PR_FALSE) + mBuiltSynthetics(PR_FALSE) { MOZ_COUNT_CTOR(nsZipArchive); @@ -1075,4 +1103,3 @@ static PRBool IsSymlink(unsigned char *ll) return ((xtoint(ll+2) & S_IFMT) == S_IFLNK); } #endif - diff --git a/modules/libjar/nsZipArchive.h b/modules/libjar/nsZipArchive.h index b1f1626b00a..d37c0ca6ef1 100644 --- a/modules/libjar/nsZipArchive.h +++ b/modules/libjar/nsZipArchive.h @@ -24,6 +24,7 @@ * Daniel Veditz * Samir Gehani * Mitch Stoltz + * Taras Glek * * 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 @@ -53,6 +54,7 @@ #define ZIP_Seek(fd,p,m) (PR_Seek((fd),((PROffset32)p),(m))==((PROffset32)p)) #include "zlib.h" +#include "nsAutoPtr.h" class nsZipFind; class nsZipReadState; @@ -111,6 +113,8 @@ struct nsZipItem char name[1]; // actually, bigger than 1 }; +class nsZipHandle; +class nsSeekableZipHandle; /** * nsZipArchive -- a class for reading the PKZIP file format. * @@ -186,12 +190,10 @@ public: */ 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 --- @@ -199,12 +201,18 @@ private: 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 mFd; //--- private methods --- nsZipArchive& operator=(const nsZipArchive& rhs); // prevent assignments @@ -214,8 +222,92 @@ private: 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); + + nsrefcnt AddRef(void); + 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 mFd; // file handle + PRUint32 mOffset; // current reading offset + PRUint32 mRemaining; // bytes remaining }; diff --git a/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js b/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js new file mode 100644 index 00000000000..c8c49218853 --- /dev/null +++ b/modules/libjar/test/unit/test_jarinput_stream_zipreader_reference.js @@ -0,0 +1,66 @@ +/* -*- 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 + * 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); + 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); +} +