Bug 566686 - Provide a decompression API in nsZipArchive, patch mostly by taras, r=alfredkayser

--HG--
extra : rebase_source : 0e45fb4b4a78fbd4337ef7f95ee338b58f50e29f
This commit is contained in:
Michael Wu 2010-06-02 14:46:48 -07:00
parent 76a20dbcfd
commit ec14bb00fe
3 changed files with 197 additions and 106 deletions

View File

@ -346,22 +346,22 @@ nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname,
// so the item to be extracted should never be a directory
PR_ASSERT(!item->IsDirectory());
Bytef outbuf[ZIP_BUFLEN];
nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true);
nsresult rv;
//-- extract the file using the appropriate method
switch(item->Compression())
{
case STORED:
rv = CopyItemToDisk(item, aFd);
while (true) {
PRUint32 count = 0;
PRUint8* buf = cursor.Read(&count);
if (!buf)
rv = NS_ERROR_FILE_CORRUPTED;
else if (count == 0)
break;
case DEFLATED:
rv = InflateItem(item, aFd);
break;
default:
//-- unsupported compression type
rv = NS_ERROR_NOT_IMPLEMENTED;
if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count)
rv = NS_ERROR_FILE_DISK_FULL;
}
//-- delete the file on errors, or resolve symlink if needed
@ -697,97 +697,6 @@ PRUint8* nsZipArchive::GetData(nsZipItem* aItem)
return data + offset;
}
//---------------------------------------------
// nsZipArchive::CopyItemToDisk
//---------------------------------------------
nsresult
nsZipArchive::CopyItemToDisk(nsZipItem *item, PRFileDesc* outFD)
{
PR_ASSERT(item);
//-- get to the start of file's data
const PRUint8* itemData = GetData(item);
if (!itemData)
return NS_ERROR_FILE_CORRUPTED;
if (outFD && PR_Write(outFD, itemData, item->Size()) < (READTYPE)item->Size())
{
//-- Couldn't write all the data (disk full?)
return NS_ERROR_FILE_DISK_FULL;
}
//-- Calculate crc
PRUint32 crc = crc32(0L, (const unsigned char*)itemData, item->Size());
//-- verify crc32
if (crc != item->CRC32())
return NS_ERROR_FILE_CORRUPTED;
return NS_OK;
}
//---------------------------------------------
// nsZipArchive::InflateItem
//---------------------------------------------
nsresult nsZipArchive::InflateItem(nsZipItem * item, 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(item);
//-- allocate deflation buffers
Bytef outbuf[ZIP_BUFLEN];
//-- set up the inflate
z_stream zs;
nsresult status = gZlibInit(&zs);
if (status != NS_OK)
return NS_ERROR_FAILURE;
//-- inflate loop
zs.avail_in = item->Size();
zs.next_in = (Bytef*)GetData(item);
if (!zs.next_in)
return NS_ERROR_FILE_CORRUPTED;
PRUint32 crc = crc32(0L, Z_NULL, 0);
int zerr = Z_OK;
while (zerr == Z_OK)
{
zs.next_out = outbuf;
zs.avail_out = ZIP_BUFLEN;
zerr = inflate(&zs, Z_PARTIAL_FLUSH);
if (zerr != Z_OK && zerr != Z_STREAM_END)
{
status = (zerr == Z_MEM_ERROR) ? NS_ERROR_OUT_OF_MEMORY : NS_ERROR_FILE_CORRUPTED;
break;
}
PRUint32 count = zs.next_out - outbuf;
//-- incrementally update crc32
crc = crc32(crc, (const unsigned char*)outbuf, count);
if (outFD && PR_Write(outFD, outbuf, count) < (READTYPE)count)
{
status = NS_ERROR_FILE_DISK_FULL;
break;
}
} // while
//-- free zlib internal state
inflateEnd(&zs);
//-- verify crc32
if ((status == NS_OK) && (crc != item->CRC32()))
{
status = NS_ERROR_FILE_CORRUPTED;
}
return status;
}
//------------------------------------------
// nsZipArchive constructor and destructor
//------------------------------------------
@ -1004,3 +913,94 @@ bool nsZipItem::IsSymlink()
}
#endif
nsZipCursor::nsZipCursor(nsZipItem *item, nsZipArchive *aZip, PRUint8* aBuf, PRUint32 aBufSize, bool doCRC) :
mItem(item),
mBuf(aBuf),
mBufSize(aBufSize),
mDoCRC(doCRC)
{
if (mItem->Compression() == DEFLATED) {
nsresult status = gZlibInit(&mZs);
NS_ASSERTION(status == NS_OK, "Zlib failed to initialize");
NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem");
}
mZs.avail_in = item->Size();
mZs.next_in = (Bytef*)aZip->GetData(item);
if (doCRC)
mCRC = crc32(0L, Z_NULL, 0);
}
nsZipCursor::~nsZipCursor()
{
if (mItem->Compression() == DEFLATED) {
inflateEnd(&mZs);
}
}
PRUint8* nsZipCursor::Read(PRUint32 *aBytesRead) {
int zerr;
PRUint8 *buf = nsnull;
bool verifyCRC = true;
if (!mZs.next_in)
return nsnull;
switch (mItem->Compression()) {
case STORED:
*aBytesRead = mZs.avail_in;
buf = mZs.next_in;
mZs.next_in += mZs.avail_in;
mZs.avail_in = 0;
break;
case DEFLATED:
buf = mBuf;
mZs.next_out = buf;
mZs.avail_out = mBufSize;
zerr = inflate(&mZs, Z_PARTIAL_FLUSH);
if (zerr != Z_OK && zerr != Z_STREAM_END)
return nsnull;
*aBytesRead = mZs.next_out - buf;
verifyCRC = (zerr == Z_STREAM_END);
break;
default:
return nsnull;
}
if (mDoCRC) {
mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
if (verifyCRC && mCRC != mItem->CRC32())
return nsnull;
}
return buf;
}
nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive *aZip, const char * aEntryName, bool doCRC) :
mReturnBuf(nsnull)
{
// make sure the ziparchive hangs around
mZipHandle = aZip->GetFD();
nsZipItem* item = aZip->GetItem(aEntryName);
if (!item)
return;
PRUint32 size = 0;
if (item->Compression() == DEFLATED) {
size = item->RealSize();
mAutoBuf = new PRUint8[size];
}
nsZipCursor cursor(item, aZip, mAutoBuf, size, doCRC);
mReturnBuf = cursor.Read(&mReadlen);
if (!mReturnBuf)
return;
if (mReadlen != item->RealSize()) {
NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow");
mReturnBuf = nsnull;
}
}

View File

@ -214,9 +214,6 @@ private:
nsZipItem* CreateZipItem();
nsresult BuildFileList();
nsresult BuildSynthetics();
nsresult CopyItemToDisk(nsZipItem* item, PRFileDesc* outFD);
nsresult InflateItem(nsZipItem* item, PRFileDesc* outFD);
};
class nsZipHandle {
@ -265,6 +262,88 @@ private:
nsZipFind(const nsZipFind& rhs);
};
/**
* nsZipCursor -- a low-level class for reading the individual items in a zip.
*/
class nsZipCursor {
public:
/**
* Initializes the cursor
*
* @param aItem Item of interest
* @param aZip Archive
* @param aBuf Buffer used for decompression.
* This determines the maximum Read() size in the compressed case.
* @param aBufSize Buffer size
* @param doCRC When set to true Read() will check crc
*/
nsZipCursor(nsZipItem *aItem, nsZipArchive *aZip, PRUint8* aBuf = NULL, PRUint32 aBufSize = 0, bool doCRC = false);
~nsZipCursor();
/**
* Performs reads. In the compressed case it uses aBuf(passed in constructor), for stored files
* it returns a zero-copy buffer.
*
* @param aBytesRead Outparam for number of bytes read.
* @return data read or NULL if item is corrupted.
*/
PRUint8* Read(PRUint32 *aBytesRead);
private:
nsZipItem *mItem;
PRUint8 *mBuf;
PRUint32 mBufSize;
z_stream mZs;
PRUint32 mCRC;
bool mDoCRC;
};
/**
* nsZipItemPtr - a RAII convenience class for reading the individual items in a zip.
* It reads whole files and does zero-copy IO for stored files. A buffer is allocated
* for decompression.
* Do not use when the file may be very large.
*/
class nsZipItemPtr_base {
public:
/**
* Initializes the reader
*
* @param aZip Archive
* @param aEntryName Archive membername
* @param doCRC When set to true Read() will check crc
*/
nsZipItemPtr_base(nsZipArchive *aZip, const char *aEntryName, bool doCRC);
PRUint32 Length() const {
return mReadlen;
}
protected:
nsRefPtr<nsZipHandle> mZipHandle;
nsAutoArrayPtr<PRUint8> mAutoBuf;
PRUint8 *mReturnBuf;
PRUint32 mReadlen;
};
template <class T>
class nsZipItemPtr : public nsZipItemPtr_base {
public:
nsZipItemPtr(nsZipArchive *aZip, const char *aEntryName, bool doCRC = false) : nsZipItemPtr_base(aZip, aEntryName, doCRC) { }
/**
* @return buffer containing the whole zip member or NULL on error.
* The returned buffer is owned by nsZipItemReader.
*/
const T* Buffer() const {
return (const T*)mReturnBuf;
}
operator const T*() const {
return Buffer();
}
};
nsresult gZlibInit(z_stream *zs);
#endif /* nsZipArchive_h_ */

View File

@ -0,0 +1,12 @@
// Make sure uncompressed files pass crc
const Cc = Components.classes;
const Ci = Components.interfaces;
function run_test() {
var file = do_get_file("data/uncompressed.zip");
var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
zipReader.open(file);
zipReader.test("hello");
zipReader.close();
}