gecko/modules/libjar/nsZipArchive.cpp

1433 lines
38 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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 Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* 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>
*
* 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 ***** */
/*
* 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.
*/
#ifndef STANDALONE
#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"
/**
* Globals
*
* Global allocator used with zlib. Destroyed in module shutdown.
*/
#define NBUCKETS 6
#define BY4ALLOC_ITEMS 320
nsRecyclingAllocator *gZlibAllocator = NULL;
// For placement new used for arena allocations of zip file list
#include NEW_H
#define ZIP_ARENABLOCKSIZE (1*1024)
#else /* STANDALONE */
#ifdef XP_WIN
#include "windows.h"
#endif
#undef MOZILLA_CLIENT // undoes prtypes damage in zlib.h
#define ZFILE_CREATE "wb"
#define READTYPE PRUint32
#include "zlib.h"
#undef PR_PUBLIC_API
#include "zipstub.h"
#ifdef XP_MAC
#include <string.h>
#include <stdlib.h>
char * strdup(const char *src);
char * strdup(const char *src)
{
long len = strlen(src);
char *dup = (char *)malloc(len+1 * sizeof(char));
memcpy(dup, src, len+1);
return dup;
}
#endif
#ifdef WINCE
int remove(const char* inPath)
{
unsigned short wPath[MAX_PATH];
MultiByteToWideChar(CP_ACP,
0,
inPath,
-1,
wPath,
MAX_PATH);
if(FALSE != DeleteFileW(wPath))
return 0;
return -1;
}
#endif
#endif /* STANDALONE */
#ifdef XP_UNIX
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <unistd.h>
#elif defined(XP_WIN) || defined(XP_OS2)
#include <io.h>
#elif defined(XP_BEOS)
#include <unistd.h>
#endif
#ifdef __SYMBIAN32__
#include <sys/syslimits.h>
#endif /*__SYMBIAN32__*/
#ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */
# ifndef S_IFMT
# define S_IFMT 0170000
# endif
# ifndef S_IFLNK
# define S_IFLNK 0120000
# endif
# ifndef PATH_MAX
# define PATH_MAX 1024
# endif
#endif /* XP_UNIX */
#include "zipfile.h"
#include "zipstruct.h"
#include "nsZipArchive.h"
static PRUint16 xtoint(unsigned char *ii);
static PRUint32 xtolong(unsigned char *ll);
static PRUint16 ExtractMode(unsigned char *ll);
static PRUint32 HashName(const char* aName);
#if defined(XP_UNIX) || defined(XP_BEOS)
static PRBool IsSymlink(unsigned char *ll);
static nsresult ResolveSymlink(const char *path);
#endif
/*---------------------------------------------
* C API wrapper for nsZipArchive
*--------------------------------------------*/
#ifdef STANDALONE
/**
* ZIP_OpenArchive
*
* opens the named zip/jar archive and returns a handle that
* represents the archive in other ZIP_ calls.
*
* @param zipname archive filename
* @param hZip receives handle if archive opened OK
* @return status code
*/
PR_PUBLIC_API(PRInt32) ZIP_OpenArchive(const char * zipname, void** hZip)
{
PRInt32 status;
/*--- error check args ---*/
if (hZip == 0)
return ZIP_ERR_PARAM;
/*--- NULL output to prevent use by bozos who don't check errors ---*/
*hZip = 0;
/*--- create and open the archive ---*/
nsZipArchive* zip = new nsZipArchive();
if (zip == 0)
return ZIP_ERR_MEMORY;
PRFileDesc * fd = PR_Open(zipname, PR_RDONLY, 0400);
if (!fd) {
delete zip;
return ZIP_ERR_DISK;
}
status = zip->OpenArchive(fd);
if (status == ZIP_OK)
*hZip = static_cast<void*>(zip);
else {
delete zip;
PR_Close(fd);
}
return status;
}
/**
* ZIP_TestArchive
*
* Tests the integrity of this open zip archive by extracting each
* item to memory and performing a CRC check.
*
* @param hZip handle obtained from ZIP_OpenArchive
* @return status code (success indicated by ZIP_OK)
*/
PR_PUBLIC_API(PRInt32) ZIP_TestArchive(void *hZip)
{
/*--- error check args ---*/
if (hZip == 0)
return ZIP_ERR_PARAM;
nsZipArchive* zip = static_cast<nsZipArchive*>(hZip);
if (zip->kMagic != ZIP_MAGIC)
return ZIP_ERR_PARAM; /* whatever it is isn't one of ours! */
/*--- test the archive ---*/
return zip->Test(NULL);
}
/**
* ZIP_CloseArchive
*
* closes zip archive and frees memory
* @param hZip handle obtained from ZIP_OpenArchive
* @return status code
*/
PR_PUBLIC_API(PRInt32) ZIP_CloseArchive(void** hZip)
{
/*--- error check args ---*/
if (hZip == 0 || *hZip == 0)
return ZIP_ERR_PARAM;
nsZipArchive* zip = static_cast<nsZipArchive*>(*hZip);
if (zip->kMagic != ZIP_MAGIC)
return ZIP_ERR_PARAM; /* whatever it is isn't one of ours! */
/*--- close the archive ---*/
*hZip = 0;
delete zip;
return ZIP_OK;
}
/**
* ZIP_ExtractFile
*
* extracts named file from an opened archive
*
* @param hZip handle obtained from ZIP_OpenArchive
* @param filename name of file in archive
* @param outname filename to extract to
*/
PR_PUBLIC_API(PRInt32) ZIP_ExtractFile(void* hZip, const char * filename, const char * outname)
{
/*--- error check args ---*/
if (hZip == 0)
return ZIP_ERR_PARAM;
nsZipArchive* zip = static_cast<nsZipArchive*>(hZip);
if (zip->kMagic != ZIP_MAGIC)
return ZIP_ERR_PARAM; /* whatever it is isn't one of ours! */
//-- Find item in archive
nsZipItem* item = zip->GetItem(filename);
if (!item)
return ZIP_ERR_FNF;
// Can't extract a directory
if (item->isDirectory)
return ZIP_ERR_PARAM;
// delete any existing file so that we overwrite the file permissions
PR_Delete(outname);
PRFileDesc* fOut = PR_Open(outname, ZFILE_CREATE, item->mode);
if (!fOut)
return ZIP_ERR_DISK;
#if defined(XP_UNIX) && defined(STANDALONE)
// When STANDALONE is defined, PR_Open ignores its 3d argument.
mode_t msk = umask(0);
umask(msk);
chmod(outname, (item->mode | S_IRUSR) & ~msk);
#endif
// ExtractFile also closes the fOut handle and resolves the symlink if needed
return zip->ExtractFile(item, outname, fOut);
}
/**
* ZIP_FindInit
*
* Initializes an enumeration of files in the archive
*
* @param hZip handle obtained from ZIP_OpenArchive
* @param pattern regexp to match files in archive, the usual shell expressions.
* NULL pattern also matches all files, faster than "*"
*/
PR_PUBLIC_API(void*) ZIP_FindInit(void* hZip, const char * pattern)
{
/*--- error check args ---*/
if (hZip == 0)
return 0;
nsZipArchive* zip = static_cast<nsZipArchive*>(hZip);
if (zip->kMagic != ZIP_MAGIC)
return 0; /* whatever it is isn't one of ours! */
/*--- initialize the pattern search ---*/
nsZipFind* find;
PRInt32 rv = zip->FindInit(pattern, &find);
if (rv != ZIP_OK)
find = NULL;
return find;
}
/**
* ZIP_FindNext
*
* Puts the next name in the passed buffer. Returns ZIP_ERR_SMALLBUF when
* the name is too large for the buffer, and ZIP_ERR_FNF when there are no
* more files that match the pattern
*
* @param hFind handle obtained from ZIP_FindInit
* @param outbuf buffer to receive next filename
* @param bufsize size of allocated buffer
*/
PR_PUBLIC_API(PRInt32) ZIP_FindNext(void* hFind, char * outbuf, PRUint16 bufsize)
{
PRInt32 status;
/*--- error check args ---*/
if (hFind == 0)
return ZIP_ERR_PARAM;
nsZipFind* find = static_cast<nsZipFind*>(hFind);
if (find->kMagic != ZIPFIND_MAGIC)
return ZIP_ERR_PARAM; /* whatever it is isn't one of ours! */
/*--- return next filename file ---*/
const char* itemName;
status = find->FindNext(&itemName);
if (status == ZIP_OK)
{
PRUint16 namelen = (PRUint16)PL_strlen(itemName);
if (bufsize > namelen)
{
PL_strcpy(outbuf, itemName);
}
else
status = ZIP_ERR_SMALLBUF;
}
return status;
}
/**
* ZIP_FindFree
*
* Releases allocated memory associated with the find token
*
* @param hFind handle obtained from ZIP_FindInit
*/
PR_PUBLIC_API(PRInt32) ZIP_FindFree(void* hFind)
{
/*--- error check args ---*/
if (hFind == 0)
return ZIP_ERR_PARAM;
nsZipFind* find = static_cast<nsZipFind*>(hFind);
if (find->kMagic != ZIPFIND_MAGIC)
return ZIP_ERR_PARAM; /* whatever it is isn't one of ours! */
/* free the find structure */
delete find;
return ZIP_OK;
}
#if defined XP_WIN
void ProcessWindowsMessages()
{
MSG msg;
while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
#endif /* XP_WIN */
#else /* STANDALONE */
//***********************************************************
// Allocators for use with zlib
//
// These are allocators that are performance tuned for
// use with zlib. Our use of zlib for every file we read from
// the jar file when running navigator, we do these allocation.
// alloc 24
// alloc 64
// alloc 11520
// alloc 32768
// alloc 1216 [304x4] max
// alloc 76 [19x4]
// free 76 [19x4]
// alloc 1152 [288x4]
// free 1152 [288x4]
// free 1216 [304x4]
// alloc 28
// free 28
// free 32768
// free 11520
// free 64
// free 24
//
// The pool will allocate these as:
//
// 32,768
// 11,520
// 1,280 [320x4] - shared by first x4 alloc, 28
// 1,280 [320x4] - shared by second and third x4 alloc
// 64
// 24
// ------
// 46,936
//
// And almost all of the file reads happen serially. Hence this
// allocator tries to keep one set of memory needed for one file around
// and reused the same blocks for other file reads.
//
// The interesting question is when should be free this ?
// - memory pressure should be one.
// - after startup of navigator
// - after startup of mail
// In general, this allocator should be enabled before
// we startup and disabled after we startup if memory is a concern.
//***********************************************************
static void *
zlibAlloc(void *opaque, uInt items, uInt size)
{
nsRecyclingAllocator *zallocator = (nsRecyclingAllocator *)opaque;
if (zallocator) {
// Bump up x4 allocations
PRUint32 realitems = items;
if (size == 4 && items < BY4ALLOC_ITEMS)
realitems = BY4ALLOC_ITEMS;
return zallocator->Calloc(realitems, size);
}
else
return calloc(items, size);
}
static void
zlibFree(void *opaque, void *ptr)
{
nsRecyclingAllocator *zallocator = (nsRecyclingAllocator *)opaque;
if (zallocator)
zallocator->Free(ptr);
else
free(ptr);
return;
}
#endif /* STANDALONE */
nsresult gZlibInit(z_stream *zs)
{
memset(zs, 0, sizeof(z_stream));
#ifndef STANDALONE
//-- ensure we have our zlib allocator for better performance
if (!gZlibAllocator) {
gZlibAllocator = new nsRecyclingAllocator(NBUCKETS, NS_DEFAULT_RECYCLE_TIMEOUT, "libjar");
}
if (gZlibAllocator) {
zs->zalloc = zlibAlloc;
zs->zfree = zlibFree;
zs->opaque = gZlibAllocator;
}
#endif /* STANDALONE */
int zerr = inflateInit2(zs, -MAX_WBITS);
if (zerr != Z_OK) return ZIP_ERR_MEMORY;
return ZIP_OK;
}
//***********************************************************
// nsZipArchive -- public methods
//***********************************************************
//---------------------------------------------
// nsZipArchive::OpenArchive
//---------------------------------------------
nsresult nsZipArchive::OpenArchive(PRFileDesc * fd)
{
if (!fd)
return ZIP_ERR_PARAM;
#ifndef STANDALONE
// Initialize our arena
PL_INIT_ARENA_POOL(&mArena, "ZipArena", ZIP_ARENABLOCKSIZE);
#endif
//-- Keep the filedescriptor for further reading...
mFd = fd;
//-- get table of contents for archive
return BuildFileList();
}
//---------------------------------------------
// nsZipArchive::Test
//---------------------------------------------
nsresult nsZipArchive::Test(const char *aEntryName)
{
nsZipItem* currItem;
if (aEntryName) // only test specified item
{
currItem = GetItem(aEntryName);
if (!currItem)
return ZIP_ERR_FNF;
//-- don't test (synthetic) directory items
if (currItem->isDirectory)
return ZIP_OK;
return ExtractFile(currItem, 0, 0);
}
// test all items in archive
for (int i = 0; i < ZIP_TABSIZE; i++) {
for (currItem = mFiles[i]; currItem; currItem = currItem->next) {
//-- don't test (synthetic) directory items
if (currItem->isDirectory)
continue;
nsresult rv = ExtractFile(currItem, 0, 0);
if (rv != ZIP_OK)
return rv;
#if defined STANDALONE && defined XP_WIN
ProcessWindowsMessages();
#endif
}
}
return ZIP_OK;
}
//---------------------------------------------
// nsZipArchive::CloseArchive
//---------------------------------------------
nsresult nsZipArchive::CloseArchive()
{
#ifndef STANDALONE
if (mFd) {
PL_FinishArenaPool(&mArena);
}
// CAUTION:
// 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;
}
#else
// delete nsZipItems in table
nsZipItem* pItem;
for (int i = 0; i < ZIP_TABSIZE; ++i)
{
pItem = mFiles[i];
while (pItem != 0)
{
mFiles[i] = pItem->next;
free(pItem);
pItem = mFiles[i];
}
mFiles[i] = 0; // make sure we don't double-delete
}
#endif
if (mFd) {
PR_Close(mFd);
mFd = 0;
}
mBuiltSynthetics = PR_FALSE;
return ZIP_OK;
}
//---------------------------------------------
// nsZipArchive::GetItem
//---------------------------------------------
nsZipItem* nsZipArchive::GetItem(const char * aEntryName)
{
if (aEntryName) {
//-- If the request is for a directory, make sure that synthetic entries
//-- are created for the directories without their own entry.
if (!mBuiltSynthetics) {
PRUint32 len = strlen(aEntryName);
if ((len > 0) && (aEntryName[len-1] == '/')) {
if (BuildSynthetics() != ZIP_OK)
return 0;
}
}
nsZipItem* item = mFiles[ HashName(aEntryName) ];
while (item) {
if (!strcmp(aEntryName, item->name))
return item; //-- found it
item = item->next;
}
}
return 0;
}
//---------------------------------------------
// nsZipArchive::ExtractFile
// This extracts the item to the filehandle provided.
// If 'aFd' is null, it only tests the extraction.
// On extraction error(s) it removes the file.
// When needed, it also resolves the symlink.
//---------------------------------------------
nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname,
PRFileDesc* aFd)
{
if (!item)
return ZIP_ERR_PARAM;
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)
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);
break;
case DEFLATED:
rv = InflateItem(item, aFd);
break;
default:
//-- unsupported compression type
rv = ZIP_ERR_UNSUPPORTED;
}
//-- delete the file on errors, or resolve symlink if needed
if (aFd) {
PR_Close(aFd);
if (rv != ZIP_OK)
PR_Delete(outname);
#if defined(XP_UNIX) || defined(XP_BEOS)
else if (item->isSymlink)
rv = ResolveSymlink(outname);
#endif
}
return rv;
}
//---------------------------------------------
// nsZipArchive::FindInit
//---------------------------------------------
PRInt32
nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind)
{
if (!aFind)
return ZIP_ERR_PARAM;
// null out param in case an error happens
*aFind = NULL;
PRBool regExp = PR_FALSE;
char* pattern = 0;
// Create synthetic directory entries on demand
nsresult rv = BuildSynthetics();
if (rv != ZIP_OK)
return rv;
// validate the pattern
if (aPattern)
{
switch (NS_WildCardValid((char*)aPattern))
{
case INVALID_SXP:
return ZIP_ERR_PARAM;
case NON_SXP:
regExp = PR_FALSE;
break;
case VALID_SXP:
regExp = PR_TRUE;
break;
default:
// undocumented return value from RegExpValid!
PR_ASSERT(PR_FALSE);
return ZIP_ERR_PARAM;
}
pattern = PL_strdup(aPattern);
if (!pattern)
return ZIP_ERR_MEMORY;
}
*aFind = new nsZipFind(this, pattern, regExp);
if (!*aFind) {
PL_strfree(pattern);
return ZIP_ERR_MEMORY;
}
return ZIP_OK;
}
//---------------------------------------------
// nsZipFind::FindNext
//---------------------------------------------
nsresult nsZipFind::FindNext(const char ** aResult)
{
if (!mArchive || !aResult)
return ZIP_ERR_PARAM;
*aResult = 0;
// we start from last match, look for next
while (mSlot < ZIP_TABSIZE)
{
// move to next in current chain, or move to new slot
mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
PRBool found = PR_FALSE;
if (!mItem)
++mSlot; // no more in this chain, move to next slot
else if (!mPattern)
found = PR_TRUE; // always match
else if (mRegExp)
found = (NS_WildCardMatch(mItem->name, mPattern, PR_FALSE) == MATCH);
else
#if defined(STANDALONE) && defined(XP_MAC)
// simulate <regexp>* matches
found = (strncmp(mItem->name, mPattern, strlen(mPattern)) == 0);
#else
found = (PL_strcmp(mItem->name, mPattern) == 0);
#endif
if (found) {
*aResult = mItem->name;
return ZIP_OK;
}
}
return ZIP_ERR_FNF;
}
#if defined(XP_UNIX) || defined(XP_BEOS)
//---------------------------------------------
// ResolveSymlink
//---------------------------------------------
static nsresult ResolveSymlink(const char *path)
{
PRFileDesc * fIn = PR_Open(path, PR_RDONLY, 0000);
if (!fIn)
return ZIP_ERR_DISK;
char buf[PATH_MAX+1];
PRInt32 length = PR_Read(fIn, (void*)buf, PATH_MAX);
PR_Close(fIn);
if ( (length <= 0)
|| ((buf[length] = 0, PR_Delete(path)) != 0)
|| (symlink(buf, path) != 0))
{
return ZIP_ERR_DISK;
}
return ZIP_OK;
}
#endif
//***********************************************************
// nsZipArchive -- private implementation
//***********************************************************
#define BR_BUF_SIZE 1024 /* backward read buffer size */
//---------------------------------------------
// nsZipArchive::CreateZipItem
//---------------------------------------------
nsZipItem* nsZipArchive::CreateZipItem(PRUint16 namelen)
{
// sizeof(nsZipItem) includes space for name's null byte
#ifndef STANDALONE
// Arena allocate the nsZipItem
void *mem;
PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem)+namelen);
return (nsZipItem*)mem;
#else
return (nsZipItem*)malloc(sizeof(nsZipItem)+namelen);
#endif
}
//---------------------------------------------
// nsZipArchive::BuildFileList
//---------------------------------------------
nsresult nsZipArchive::BuildFileList()
{
PRUint8 buf[4*BR_BUF_SIZE];
//-----------------------------------------------------------------------
// locate the central directory via the End record
//-----------------------------------------------------------------------
//-- get archive size using end pos
PRInt32 pos = PR_Seek(mFd, 0, PR_SEEK_END);
#ifndef STANDALONE
if (pos <= 0)
#else
if (pos || ((pos = ftell(mFd)) <= 0))
#endif
return ZIP_ERR_CORRUPT;
PRBool bEndsigFound = PR_FALSE;
while (!bEndsigFound)
{
//-- 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;
//-- 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;
break;
}
}
if (bEndsigFound)
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));
pos = 0;
PRUint32 sig = xtolong(buf);
while (sig == CENTRALSIG) {
//-- make sure we've read enough
if (byteCount - pos < ZIPCENTRAL_SIZE)
return ZIP_ERR_CORRUPT;
//-------------------------------------------------------
// read the fixed-size data
//-------------------------------------------------------
ZipCentral* central = (ZipCentral*)(buf+pos);
PRUint16 namelen = xtoint(central->filename_len);
PRUint16 extralen = xtoint(central->extrafield_len);
PRUint16 commentlen = xtoint(central->commentfield_len);
//-- sanity check variable sizes and refuse to deal with
//-- anything too big: it's likely a corrupt archive
if (namelen > BR_BUF_SIZE || extralen > BR_BUF_SIZE || commentlen > 2*BR_BUF_SIZE)
return ZIP_ERR_CORRUPT;
nsZipItem* item = CreateZipItem(namelen);
if (!item)
return ZIP_ERR_MEMORY;
item->headerOffset = xtolong(central->localhdr_offset);
item->dataOffset = 0;
item->size = xtolong(central->size);
item->realsize = xtolong(central->orglen);
item->crc32 = xtolong(central->crc32);
item->time = xtoint(central->time);
item->date = xtoint(central->date);
item->isSynthetic = PR_FALSE;
item->hasDataOffset = PR_FALSE;
PRUint16 compression = xtoint(central->method);
item->compression = (compression < UNSUPPORTED) ? (PRUint8)compression
: UNSUPPORTED;
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
//-- an extra nsZipItem (and that only happens if we process a
//-- file inside that directory before processing the directory
//-- entry itself)
PRUint32 hash = HashName(item->name);
item->next = mFiles[hash];
mFiles[hash] = item;
//-------------------------------------------------------
// set up to process the next item at the top of loop
//-------------------------------------------------------
pos += namelen + extralen + commentlen;
sig = xtolong(buf+pos);
} /* while reading central directory records */
if (sig != ENDSIG)
return ZIP_ERR_CORRUPT;
return ZIP_OK;
}
//---------------------------------------------
// nsZipArchive::BuildSynthetics
//---------------------------------------------
nsresult nsZipArchive::BuildSynthetics()
{
if (mBuiltSynthetics)
return ZIP_OK;
mBuiltSynthetics = PR_TRUE;
// Create synthetic entries for any missing directories.
// Do this when all ziptable has scanned to prevent double entries.
for (int i = 0; i < ZIP_TABSIZE; ++i)
{
for (nsZipItem* item = mFiles[i]; item != 0; item = item->next)
{
if (item->isSynthetic)
continue;
//-- add entries for directories in the current item's path
//-- go from end to beginning, because then we can stop trying
//-- to create diritems if we find that the diritem we want to
//-- create already exists
//-- start just before the last char so as to not add the item
//-- twice if it's a directory
PRUint16 namelen = strlen(item->name);
for (char* p = item->name + namelen - 2; p >= item->name; p--)
{
if ('/' != *p)
continue;
// See whether we need to create any more implicit directories,
// because if we don't we can avoid a lot of work.
// We can even avoid (de)allocating space for a bogus dirname with
// a little trickery -- save the char at item->name[dirnamelen],
// set it to 0, compare the strings, and restore the saved
// char when done
const PRUint32 dirnamelen = p + 1 - item->name;
const char savedChar = item->name[dirnamelen];
item->name[dirnamelen] = 0;
// Is the directory in the file table?
PRUint32 hash = HashName(item->name);
PRBool found = PR_FALSE;
for (nsZipItem* zi = mFiles[hash]; zi != NULL; zi = zi->next)
{
if (0 == strcmp(item->name, zi->name))
{
// we've already added this dir and all its parents
found = PR_TRUE;
break;
}
}
// restore the char immediately
item->name[dirnamelen] = savedChar;
// if the directory was found, break out of the directory
// creation loop now that we know all implicit directories
// are there -- otherwise, start creating the zip item
if (found)
break;
nsZipItem* diritem = CreateZipItem(dirnamelen);
if (!diritem)
return ZIP_ERR_MEMORY;
memcpy(diritem->name, item->name, dirnamelen);
diritem->name[dirnamelen] = 0;
diritem->isDirectory = PR_TRUE;
diritem->isSynthetic = PR_TRUE;
diritem->compression = STORED;
diritem->size = diritem->realsize = 0;
diritem->crc32 = 0;
diritem->mode = 0755;
// Set an obviously wrong last-modified date/time, because
// finding something more accurate like the most recent
// last-modified date/time of the dir's contents is a lot
// of effort. The date/time corresponds to 1980-01-01 00:00.
diritem->time = 0;
diritem->date = 1 + (1 << 5) + (0 << 9);
// add diritem to the file table
diritem->next = mFiles[hash];
mFiles[hash] = diritem;
} /* end processing of dirs in item's name */
}
}
return ZIP_OK;
}
//---------------------------------------------
// nsZipArchive::SeekToItem
//---------------------------------------------
nsresult nsZipArchive::SeekToItem(nsZipItem* aItem, PRFileDesc* aFd)
{
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;
ZipLocal Local;
if ((PR_Read(aFd, (char*)&Local, ZIPLOCAL_SIZE) != (READTYPE) ZIPLOCAL_SIZE) ||
(xtolong(Local.signature) != LOCALSIG))
{
//-- read error or local header not found
return ZIP_ERR_CORRUPT;
}
aItem->dataOffset = aItem->headerOffset +
ZIPLOCAL_SIZE +
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;
}
//---------------------------------------------
// nsZipArchive::CopyItemToDisk
//---------------------------------------------
nsresult
nsZipArchive::CopyItemToDisk(PRUint32 itemSize, PRUint32 itemCrc, 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];
//-- 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)
{
//-- unexpected end of data in archive
return ZIP_ERR_CORRUPT;
}
//-- incrementally update crc32
crc = crc32(crc, (const unsigned char*)buf, chunk);
if (outFD && PR_Write(outFD, buf, chunk) < (READTYPE)chunk)
{
//-- Couldn't write all the data (disk full?)
return ZIP_ERR_DISK;
}
}
//-- verify crc32
if (crc != itemCrc)
return ZIP_ERR_CORRUPT;
return ZIP_OK;
}
//---------------------------------------------
// nsZipArchive::InflateItem
//---------------------------------------------
nsresult nsZipArchive::InflateItem(const nsZipItem* aItem, 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);
//-- allocate deflation buffers
Bytef inbuf[ZIP_BUFLEN];
Bytef outbuf[ZIP_BUFLEN];
//-- set up the inflate
z_stream zs;
nsresult status = gZlibInit(&zs);
if (status != ZIP_OK)
return ZIP_ERR_GENERAL;
//-- inflate loop
zs.next_out = outbuf;
zs.avail_out = ZIP_BUFLEN;
PRUint32 size = aItem->size;
PRUint32 outpos = 0;
PRUint32 crc = crc32(0L, Z_NULL, 0);
int zerr = Z_OK;
while (zerr == Z_OK)
{
PRBool bRead = PR_FALSE;
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)
{
//-- unexpected end of data
status = ZIP_ERR_CORRUPT;
break;
}
zs.next_in = inbuf;
zs.avail_in = chunk;
bRead = PR_TRUE;
}
if (zs.avail_out == 0)
{
//-- write inflated buffer to disk and make space
if (outFD && PR_Write(outFD, outbuf, ZIP_BUFLEN) < ZIP_BUFLEN)
{
//-- Couldn't write all the data (disk full?)
status = ZIP_ERR_DISK;
break;
}
outpos = zs.total_out;
zs.next_out = outbuf;
zs.avail_out = ZIP_BUFLEN;
bWrote = PR_TRUE;
}
if(bRead || bWrote)
{
Bytef* old_next_out = zs.next_out;
zerr = inflate(&zs, Z_PARTIAL_FLUSH);
//-- incrementally update crc32
crc = crc32(crc, (const unsigned char*)old_next_out, zs.next_out - old_next_out);
}
else
zerr = Z_STREAM_END;
#if defined STANDALONE && defined XP_WIN
ProcessWindowsMessages();
#endif
} // while
//-- verify crc32
if ((status == ZIP_OK) && (crc != aItem->crc32))
{
status = ZIP_ERR_CORRUPT;
goto cleanup;
}
//-- write last inflated bit to disk
if (zerr == Z_STREAM_END && outpos < zs.total_out)
{
PRUint32 chunk = zs.total_out - outpos;
if (outFD && PR_Write(outFD, outbuf, chunk) < (READTYPE)chunk)
status = ZIP_ERR_DISK;
}
//-- convert zlib error to return value
if (status == ZIP_OK && zerr != Z_OK && zerr != Z_STREAM_END)
{
status = (zerr == Z_MEM_ERROR) ? ZIP_ERR_MEMORY : ZIP_ERR_CORRUPT;
}
//-- if found no errors make sure we've converted the whole thing
PR_ASSERT(status != ZIP_OK || zs.total_in == aItem->size);
PR_ASSERT(status != ZIP_OK || zs.total_out == aItem->realsize);
cleanup:
//-- free zlib internal state
inflateEnd(&zs);
return status;
}
//------------------------------------------
// nsZipArchive constructor and destructor
//------------------------------------------
nsZipArchive::nsZipArchive() :
#ifdef STANDALONE
kMagic(ZIP_MAGIC),
#endif
mFd(0),
mBuiltSynthetics(PR_FALSE)
{
MOZ_COUNT_CTOR(nsZipArchive);
// initialize the table to NULL
memset(mFiles, 0, sizeof mFiles);
}
nsZipArchive::~nsZipArchive()
{
CloseArchive();
MOZ_COUNT_DTOR(nsZipArchive);
}
//------------------------------------------
// nsZipFind constructor and destructor
//------------------------------------------
nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, PRBool aRegExp) :
#ifdef STANDALONE
kMagic(ZIPFIND_MAGIC),
#endif
mArchive(aZip),
mPattern(aPattern),
mItem(0),
mSlot(0),
mRegExp(aRegExp)
{
MOZ_COUNT_CTOR(nsZipFind);
}
nsZipFind::~nsZipFind()
{
PL_strfree(mPattern);
MOZ_COUNT_DTOR(nsZipFind);
}
//------------------------------------------
// helper functions
//------------------------------------------
/*
* HashName
*
* returns a hash key for the entry name
*/
static PRUint32 HashName(const char* aName)
{
PR_ASSERT(aName != 0);
PRUint32 val = 0;
for (PRUint8* c = (PRUint8*)aName; *c != 0; c++) {
val = val*37 + *c;
}
return (val % ZIP_TABSIZE);
}
/*
* x t o i n t
*
* Converts a two byte ugly endianed integer
* to our platform's integer.
*/
static PRUint16 xtoint (unsigned char *ii)
{
return (PRUint16) ((ii [0]) | (ii [1] << 8));
}
/*
* x t o l o n g
*
* Converts a four byte ugly endianed integer
* to our platform's integer.
*/
static PRUint32 xtolong (unsigned char *ll)
{
return (PRUint32)( (ll [0] << 0) |
(ll [1] << 8) |
(ll [2] << 16) |
(ll [3] << 24) );
}
/*
* ExtractMode
*
* Extracts bits 17-24 from a 32-bit unsigned long
* representation of the external attributes field.
* Subsequently it tacks on the implicit user-read
* bit.
*/
static PRUint16 ExtractMode(unsigned char *ll)
{
return ((PRUint16)(ll[2])) | 0x0100;
}
#if defined(XP_UNIX) || defined(XP_BEOS)
/*
*
* Return true if the attributes are for a symbolic link
*
*/
static PRBool IsSymlink(unsigned char *ll)
{
return ((xtoint(ll+2) & S_IFMT) == S_IFLNK);
}
#endif