ciderpress/diskimg/OuterWrapper.cpp
2007-03-27 17:47:10 +00:00

1536 lines
41 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Code for handling "outer wrappers" like ZIP and gzip.
*
* TODO: for safety, these should compress into a temp file and then rename
* the temp file over the original. The current implementation just
* truncates the open file descriptor or reopens the original file. Both
* risk data loss if the program or system crashes while the data is being
* written to disk.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
#define DEF_MEM_LEVEL 8 // normally in zutil.h
/*
* ===========================================================================
* OuterGzip
* ===========================================================================
*/
/*
* Test to see if this is a gzip file.
*
* This test is pretty weak, so we shouldn't even be looking at this
* unless the file ends in ".gz". A better test would scan the entire
* header.
*
* Would be nice to just gzopen the file, but unfortunately that tries
* to be "helpful" and reads the file whether it's in gz format or not.
* Some days I could do with a little less "help".
*/
/*static*/ DIError
OuterGzip::Test(GenericFD* pGFD, di_off_t outerLength)
{
const int kGzipMagic = 0x8b1f; // 0x1f 0x8b
unsigned short magic, magicBuf;
const char* imagePath;
WMSG0("Testing for gzip\n");
/* don't need this here, but we will later on */
imagePath = pGFD->GetPathName();
if (imagePath == nil) {
WMSG0("Can't test gzip on non-file\n");
return kDIErrNotSupported;
}
pGFD->Rewind();
if (pGFD->Read(&magicBuf, 2) != kDIErrNone)
return kDIErrGeneric;
magic = GetShortLE((unsigned char*) &magicBuf);
if (magic == kGzipMagic)
return kDIErrNone;
else
return kDIErrGeneric;
}
/*
* The gzip file format has a length embedded in the footer, but
* unfortunately there is no interface to access it. So, we have
* to keep reading until we run out of data, extending the buffer
* to accommodate the new data each time. (We could also just read
* the footer directly, but that requires that there are no garbage
* bytes at the end of the file, which is a real concern on some FTP sites.)
*
* Start out by trying sizes that we think will work (140K, 800K),
* then grow quickly.
*
* The largest possible ProDOS image is 32MB, but it's possible to
* have an HFS volume or a partitioned image larger than that. We currently
* cap the limit to avoid nasty behavior when encountering really
* large .gz files. This isn't great -- we ought to support extracting
* to a temp file, or allowing the caller to specify what the largest
* size they can handle is.
*/
DIError
OuterGzip::ExtractGzipImage(gzFile gzfp, char** pBuf, di_off_t* pLength)
{
DIError dierr = kDIErrNone;
const int kMinEmpty = 256 * 1024;
const int kStartSize = 141 * 1024;
const int kNextSize1 = 801 * 1024;
const int kNextSize2 = 1024 * 1024;
const int kMaxIncr = 4096 * 1024;
const int kAbsoluteMax = kMaxUncompressedSize;
char* buf = nil;
char* newBuf = nil;
long curSize, maxSize;
assert(gzfp != nil);
assert(pBuf != nil);
assert(pLength != nil);
curSize = 0;
maxSize = kStartSize;
buf = new char[maxSize];
if (buf == nil) {
dierr = kDIErrMalloc;
goto bail;
}
while (1) {
long len;
/*
* Try to fill the buffer.
*
* It appears that zlib v1.1.4 was more tolerant of certain kinds
* of broken archives than v1.2.1. Both give you a pile of data
* on the first read, with no error reported, but the next read
* attempt returns with z_err=-3 (Z_DATA_ERROR) and z_eof set. I'm
* not sure exactly what the flaw is, but I'm guessing something
* got lopped off the end of the archives. gzip v1.3.3 won't touch
* them either.
*
* It would be easy enough to access them, if they were accessible.
* Unfortunately the implementation is buried. Instead, we do
* a quick test against known unadorned floppy disk sizes to see
* if we can salvage the contents. (Our read attempts are all
* slightly *over* the standard disk sizes, so if it comes back right
* on one we're *probably* okay.)
*/
len = gzread(gzfp, buf + curSize, maxSize - curSize);
if (len < 0) {
WMSG1(" ExGZ Call to gzread failed, errno=%d\n", errno);
if (curSize == 140*1024 || curSize == 800*1024) {
WMSG0("WARNING: accepting damaged gzip file\n");
fWrapperDamaged = true;
break; // sleazy, but currently necessary
}
dierr = kDIErrReadFailed;
goto bail;
} else if (len == 0) {
/* EOF reached */
break;
} else if (len < (maxSize - curSize)) {
/* we've probably reached the end, but we can't be sure,
so let's go around again */
WMSG2(" ExGZ gzread(%ld) returned %ld, letting it ride\n",
maxSize - curSize, len);
curSize += len;
} else {
/* update buffer, and grow it if it's not big enough */
curSize += len;
WMSG2(" max=%ld cur=%ld\n", maxSize, curSize);
if (maxSize - curSize < kMinEmpty) {
/* not enough room, grow it */
if (maxSize == kStartSize)
maxSize = kNextSize1;
else if (maxSize == kNextSize1)
maxSize = kNextSize2;
else {
if (maxSize < kMaxIncr)
maxSize = maxSize * 2;
else
maxSize += kMaxIncr;
}
newBuf = new char[maxSize];
if (newBuf == nil) {
WMSG1(" ExGZ failed buffer alloc (%ld)\n",
maxSize);
dierr = kDIErrMalloc;
goto bail;
}
memcpy(newBuf, buf, curSize);
delete[] buf;
buf = newBuf;
newBuf = nil;
WMSG1(" ExGZ grew buffer to %ld\n", maxSize);
} else {
/* don't need to grow buffer yet */
WMSG3(" ExGZ read %ld bytes, cur=%ld max=%ld\n",
len, curSize, maxSize);
}
}
assert(curSize < maxSize);
if (curSize > kAbsoluteMax) {
WMSG0(" ExGZ excessive size, probably not a disk image\n");
dierr = kDIErrTooBig; // close enough
goto bail;
}
}
if (curSize + (1024*1024) < maxSize) {
/* shrink it down so it fits */
WMSG2(" Down-sizing buffer from %ld to %ld\n", maxSize, curSize);
newBuf = new char[curSize];
if (newBuf == nil)
goto bail;
memcpy(newBuf, buf, curSize);
delete[] buf;
buf = newBuf;
newBuf = nil;
}
*pBuf = buf;
*pLength = curSize;
WMSG1(" ExGZ final size = %ld\n", curSize);
buf = nil;
bail:
delete[] buf;
delete[] newBuf;
return dierr;
}
/*
* Open the archive, and extract the disk image into a memory buffer.
*/
DIError
OuterGzip::Load(GenericFD* pOuterGFD, di_off_t outerLength, bool readOnly,
di_off_t* pWrapperLength, GenericFD** ppWrapperGFD)
{
DIError dierr = kDIErrNone;
GFDBuffer* pNewGFD = nil;
char* buf = nil;
di_off_t length = -1;
const char* imagePath;
gzFile gzfp = nil;
imagePath = pOuterGFD->GetPathName();
if (imagePath == nil) {
assert(false); // should've been caught in Test
return kDIErrNotSupported;
}
gzfp = gzopen(imagePath, "rb"); // use "readOnly" here
if (gzfp == nil) { // DON'T retry RO -- should be done at higher level?
WMSG1("gzopen failed, errno=%d\n", errno);
dierr = kDIErrGeneric;
goto bail;
}
dierr = ExtractGzipImage(gzfp, &buf, &length);
if (dierr != kDIErrNone)
goto bail;
/*
* Everything is going well. Now we substitute a memory-based GenericFD
* for the existing GenericFD.
*/
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, readOnly);
if (dierr != kDIErrNone)
goto bail;
buf = nil; // now owned by pNewGFD;
/*
* Success!
*/
assert(dierr == kDIErrNone);
*ppWrapperGFD = pNewGFD;
pNewGFD = nil;
*pWrapperLength = length;
bail:
if (dierr != kDIErrNone) {
delete pNewGFD;
}
if (gzfp != nil)
gzclose(gzfp);
return dierr;
}
/*
* Save the contents of "pWrapperGFD" to the file pointed to by
* "pOuterGFD".
*
* "pOuterGFD" isn't disturbed (same as Load). All we want is to get the
* filename and then do everything through gzio.
*/
DIError
OuterGzip::Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD,
di_off_t wrapperLength)
{
DIError dierr = kDIErrNone;
const char* imagePath;
gzFile gzfp = nil;
WMSG1(" GZ save (wrapperLen=%ld)\n", (long) wrapperLength);
assert(wrapperLength > 0);
/*
* Reopen the file.
*/
imagePath = pOuterGFD->GetPathName();
if (imagePath == nil) {
assert(false); // should've been caught long ago
return kDIErrNotSupported;
}
gzfp = gzopen(imagePath, "wb");
if (gzfp == nil) {
WMSG1("gzopen for write failed, errno=%d\n", errno);
dierr = kDIErrGeneric;
goto bail;
}
char buf[16384];
size_t actual;
long written, totalWritten;
pWrapperGFD->Rewind();
totalWritten = 0;
while (wrapperLength > 0) {
dierr = pWrapperGFD->Read(buf, sizeof(buf), &actual);
if (dierr == kDIErrEOF) {
dierr = kDIErrNone;
break;
}
if (dierr != kDIErrNone) {
WMSG1("Error reading source GFD during gzip save (err=%d)\n",dierr);
goto bail;
}
assert(actual > 0);
written = gzwrite(gzfp, buf, actual);
if (written == 0) {
WMSG1("Failed writing %d bytes to gzio\n", actual);
dierr = kDIErrGeneric;
goto bail;
}
totalWritten += written;
wrapperLength -= actual;
}
assert(wrapperLength == 0); // not expecting any slop
WMSG1(" GZ wrote %ld bytes\n", totalWritten);
/*
* Success!
*/
assert(dierr == kDIErrNone);
bail:
if (gzfp != nil)
gzclose(gzfp);
return dierr;
}
/*
* ===========================================================================
* OuterZip
* ===========================================================================
*/
/*
* Test to see if this is a ZIP archive.
*/
/*static*/ DIError
OuterZip::Test(GenericFD* pGFD, di_off_t outerLength)
{
DIError dierr = kDIErrNone;
CentralDirEntry cde;
WMSG0("Testing for zip\n");
dierr = ReadCentralDir(pGFD, outerLength, &cde);
if (dierr != kDIErrNone)
goto bail;
/*
* Make sure it's a compression method we support.
*/
if (cde.fCompressionMethod != kCompressStored &&
cde.fCompressionMethod != kCompressDeflated)
{
WMSG1(" ZIP compression method %d not supported\n",
cde.fCompressionMethod);
dierr = kDIErrGeneric;
goto bail;
}
/*
* Limit the size to something reasonable.
*/
if (cde.fUncompressedSize < 512 ||
cde.fUncompressedSize > kMaxUncompressedSize)
{
WMSG1(" ZIP uncompressed size %lu is outside range\n",
cde.fUncompressedSize);
dierr = kDIErrGeneric;
goto bail;
}
assert(dierr == kDIErrNone);
bail:
return dierr;
}
/*
* Open the archive, and extract the disk image into a memory buffer.
*/
DIError
OuterZip::Load(GenericFD* pOuterGFD, di_off_t outerLength, bool readOnly,
di_off_t* pWrapperLength, GenericFD** ppWrapperGFD)
{
DIError dierr = kDIErrNone;
GFDBuffer* pNewGFD = nil;
CentralDirEntry cde;
unsigned char* buf = nil;
di_off_t length = -1;
const char* pExt;
dierr = ReadCentralDir(pOuterGFD, outerLength, &cde);
if (dierr != kDIErrNone)
goto bail;
if (cde.fFileNameLength > 0) {
pExt = FindExtension((const char*) cde.fFileName, kZipFssep);
if (pExt != nil) {
assert(*pExt == '.');
SetExtension(pExt+1);
WMSG1("OuterZip using extension '%s'\n", GetExtension());
}
SetStoredFileName((const char*) cde.fFileName);
}
dierr = ExtractZipEntry(pOuterGFD, &cde, &buf, &length);
if (dierr != kDIErrNone)
goto bail;
/*
* Everything is going well. Now we substitute a memory-based GenericFD
* for the existing GenericFD.
*/
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, readOnly);
if (dierr != kDIErrNone)
goto bail;
buf = nil; // now owned by pNewGFD;
/*
* Success!
*/
assert(dierr == kDIErrNone);
*ppWrapperGFD = pNewGFD;
pNewGFD = nil;
*pWrapperLength = length;
bail:
if (dierr != kDIErrNone) {
delete pNewGFD;
}
return dierr;
}
/*
* Save the contents of "pWrapperGFD" to the file pointed to by
* "pOuterGFD".
*/
DIError
OuterZip::Save(GenericFD* pOuterGFD, GenericFD* pWrapperGFD,
di_off_t wrapperLength)
{
DIError dierr = kDIErrNone;
LocalFileHeader lfh;
CentralDirEntry cde;
EndOfCentralDir eocd;
di_off_t lfhOffset;
WMSG1(" ZIP save (wrapperLen=%ld)\n", (long) wrapperLength);
assert(wrapperLength > 0);
dierr = pOuterGFD->Rewind();
if (dierr != kDIErrNone)
goto bail;
dierr = pOuterGFD->Truncate();
if (dierr != kDIErrNone)
goto bail;
dierr = pWrapperGFD->Rewind();
if (dierr != kDIErrNone)
goto bail;
lfhOffset = pOuterGFD->Tell(); // always 0 with only one file
/*
* Don't store an empty filename. Some applications, e.g. Info-ZIP's
* "unzip", get confused. Ideally the DiskImg image creation code
* will have set the actual filename, with an extension that matches
* the file contents.
*/
if (fStoredFileName == nil || fStoredFileName[0] == '\0')
SetStoredFileName("disk");
/*
* Write the ZIP local file header. We don't have file lengths or
* CRCs yet, so we have to go back and fill those in later.
*/
lfh.fVersionToExtract = kDefaultVersion;
#if NO_ZIP_COMPRESS
lfh.fGPBitFlag = 0;
lfh.fCompressionMethod = 0;
#else
lfh.fGPBitFlag = 0x0002; // indicates maximum compression used
lfh.fCompressionMethod = 8; // when compressionMethod == deflate
#endif
GetMSDOSTime(&lfh.fLastModFileDate, &lfh.fLastModFileTime);
lfh.SetFileName(fStoredFileName);
dierr = lfh.Write(pOuterGFD);
if (dierr != kDIErrNone)
goto bail;
/*
* Write the compressed data.
*/
unsigned long crc;
di_off_t compressedLen;
if (lfh.fCompressionMethod == kCompressDeflated) {
dierr = DeflateGFDToGFD(pOuterGFD, pWrapperGFD, wrapperLength,
&compressedLen, &crc);
if (dierr != kDIErrNone)
goto bail;
} else if (lfh.fCompressionMethod == kCompressStored) {
dierr = GenericFD::CopyFile(pOuterGFD, pWrapperGFD, wrapperLength,
&crc);
if (dierr != kDIErrNone)
goto bail;
compressedLen = wrapperLength;
} else {
assert(false);
dierr = kDIErrInternal;
goto bail;
}
/*
* Go back and take care of the local file header stuff.
*
* It's not supposed to be necessary, but some utilities (WinZip,
* Info-ZIP) get bent out of shape if these aren't set and the data
* is compressed. They seem okay with it when the file isn't
* compressed. I don't understand this behavior, but writing the
* local file header is easy enough.
*/
lfh.fCRC32 = crc;
lfh.fCompressedSize = (unsigned long) compressedLen;
lfh.fUncompressedSize = (unsigned long) wrapperLength;
di_off_t curPos;
curPos = pOuterGFD->Tell();
dierr = pOuterGFD->Seek(lfhOffset, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = lfh.Write(pOuterGFD);
if (dierr != kDIErrNone)
goto bail;
dierr = pOuterGFD->Seek(curPos, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
di_off_t cdeStart, cdeFinish;
cdeStart = pOuterGFD->Tell();
/*
* Write the central dir entry. This is largely just a copy of the
* data in the local file header (and in fact some utilities will
* get rather bent out of shape if the two don't match exactly).
*/
cde.fVersionMadeBy = kDefaultVersion;
cde.fVersionToExtract = lfh.fVersionToExtract;
cde.fGPBitFlag = lfh.fGPBitFlag;
cde.fCompressionMethod = lfh.fCompressionMethod;
cde.fLastModFileDate = lfh.fLastModFileDate;
cde.fLastModFileTime = lfh.fLastModFileTime;
cde.fCRC32 = lfh.fCRC32;
cde.fCompressedSize = lfh.fCompressedSize;
cde.fUncompressedSize = lfh.fUncompressedSize;
assert(lfh.fExtraFieldLength == 0 && cde.fExtraFieldLength == 0);
cde.fExternalAttrs = 0x81b60020; // matches what WinZip does
cde.fLocalHeaderRelOffset = (unsigned long) lfhOffset;
cde.SetFileName(fStoredFileName);
dierr = cde.Write(pOuterGFD);
if (dierr != kDIErrNone)
goto bail;
cdeFinish = pOuterGFD->Tell();
/*
* Write the end-of-central-dir stuff.
*/
eocd.fNumEntries = 1;
eocd.fTotalNumEntries = 1;
eocd.fCentralDirSize = (unsigned long) (cdeFinish - cdeStart);
eocd.fCentralDirOffset = (unsigned long) cdeStart;
assert(eocd.fCentralDirSize >= EndOfCentralDir::kEOCDLen);
dierr = eocd.Write(pOuterGFD);
if (dierr != kDIErrNone)
goto bail;
/*
* Success!
*/
assert(dierr == kDIErrNone);
bail:
return dierr;
}
/*
* Track the name of the file stored in the ZIP archive.
*/
void
OuterZip::SetStoredFileName(const char* name)
{
delete[] fStoredFileName;
fStoredFileName = StrcpyNew(name);
}
/*
* Find the central directory and read the contents.
*
* We currently only support archives with a single entry.
*
* The fun thing about ZIP archives is that they may or may not be
* readable from start to end. In some cases, notably for archives
* that were written to stdout, the only length information is in the
* central directory at the end of the file.
*
* Of course, the central directory can be followed by a variable-length
* comment field, so we have to scan through it backwards. The comment
* is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
* itself, plus apparently sometimes people throw random junk on the end
* just for the fun of it.
*
* This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that the Info-ZIP guys
* do it though, so we're in pretty good company if this fails.
*/
/*static*/ DIError
OuterZip::ReadCentralDir(GenericFD* pGFD, di_off_t outerLength,
CentralDirEntry* pDirEntry)
{
DIError dierr = kDIErrNone;
EndOfCentralDir eocd;
unsigned char* buf = nil;
di_off_t seekStart;
long readAmount;
int i;
/* too small to be a ZIP archive? */
if (outerLength < EndOfCentralDir::kEOCDLen + 4)
return kDIErrGeneric;
buf = new unsigned char[kMaxEOCDSearch];
if (buf == nil) {
dierr = kDIErrMalloc;
goto bail;
}
if (outerLength > kMaxEOCDSearch) {
seekStart = outerLength - kMaxEOCDSearch;
readAmount = kMaxEOCDSearch;
} else {
seekStart = 0;
readAmount = (long) outerLength;
}
dierr = pGFD->Seek(seekStart, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
/* read the last part of the file into the buffer */
dierr = pGFD->Read(buf, readAmount);
if (dierr != kDIErrNone)
goto bail;
/* find the end-of-central-dir magic */
for (i = readAmount - 4; i >= 0; i--) {
if (buf[i] == 0x50 &&
GetLongLE(&buf[i]) == EndOfCentralDir::kSignature)
{
WMSG1("+++ Found EOCD at buf+%d\n", i);
break;
}
}
if (i < 0) {
WMSG0("+++ EOCD not found, not ZIP\n");
dierr = kDIErrGeneric;
goto bail;
}
/* extract eocd values */
dierr = eocd.ReadBuf(buf + i, readAmount - i);
if (dierr != kDIErrNone)
goto bail;
eocd.Dump();
if (eocd.fDiskNumber != 0 || eocd.fDiskWithCentralDir != 0 ||
eocd.fNumEntries != 1 || eocd.fTotalNumEntries != 1)
{
WMSG0(" Probable ZIP archive has more than one member\n");
dierr = kDIErrFileArchive;
goto bail;
}
/*
* So far so good. "fCentralDirSize" is the size in bytes of the
* central directory, so we can just seek back that far to find it.
* We can also seek forward fCentralDirOffset bytes from the
* start of the file.
*
* We're not guaranteed to have the rest of the central dir in the
* buffer, nor are we guaranteed that the central dir will have any
* sort of convenient size. We need to skip to the start of it and
* read the header, then the other goodies.
*
* The only thing we really need right now is the file comment, which
* we're hoping to preserve.
*/
dierr = pGFD->Seek(eocd.fCentralDirOffset, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
/*
* Read the central dir entry.
*/
dierr = pDirEntry->Read(pGFD);
if (dierr != kDIErrNone)
goto bail;
pDirEntry->Dump();
{
unsigned char checkBuf[4];
dierr = pGFD->Read(checkBuf, 4);
if (dierr != kDIErrNone)
goto bail;
if (GetLongLE(checkBuf) != EndOfCentralDir::kSignature) {
WMSG0("CDE read check failed\n");
assert(false);
dierr = kDIErrGeneric;
goto bail;
}
WMSG0("+++ CDE read check passed\n");
}
bail:
delete[] buf;
return dierr;
}
/*
* The central directory tells us where to find the local header. We
* have to skip over that to get to the start of the data.s
*/
DIError
OuterZip::ExtractZipEntry(GenericFD* pOuterGFD, CentralDirEntry* pCDE,
unsigned char** pBuf, di_off_t* pLength)
{
DIError dierr = kDIErrNone;
LocalFileHeader lfh;
unsigned char* buf = nil;
/* seek to the start of the local header */
dierr = pOuterGFD->Seek(pCDE->fLocalHeaderRelOffset, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
/*
* Read the local file header, mainly as a way to get past it. There
* are legitimate reasons why the size fields and filename might be
* empty, so we really don't want to depend on any data in the LFH.
* We just need to find where the data starts.
*/
dierr = lfh.Read(pOuterGFD);
if (dierr != kDIErrNone)
goto bail;
lfh.Dump();
/* we should now be pointing at the data */
WMSG1("File offset is 0x%08lx\n", (long) pOuterGFD->Tell());
buf = new unsigned char[pCDE->fUncompressedSize];
if (buf == nil) {
/* a very real possibility */
WMSG1(" ZIP unable to allocate buffer of %lu bytes\n",
pCDE->fUncompressedSize);
dierr = kDIErrMalloc;
goto bail;
}
/* unpack or copy the data */
if (pCDE->fCompressionMethod == kCompressDeflated) {
dierr = InflateGFDToBuffer(pOuterGFD, pCDE->fCompressedSize,
pCDE->fUncompressedSize, buf);
if (dierr != kDIErrNone)
goto bail;
} else if (pCDE->fCompressionMethod == kCompressStored) {
dierr = pOuterGFD->Read(buf, pCDE->fUncompressedSize);
if (dierr != kDIErrNone)
goto bail;
} else {
assert(false);
dierr = kDIErrInternal;
goto bail;
}
/* check the CRC32 */
unsigned long crc;
crc = crc32(0L, Z_NULL, 0);
crc = crc32(crc, buf, pCDE->fUncompressedSize);
if (crc == pCDE->fCRC32) {
WMSG0("+++ ZIP CRCs match\n");
} else {
WMSG2("ZIP CRC mismatch: inflated crc32=0x%08lx, stored=0x%08lx\n",
crc, pCDE->fCRC32);
dierr = kDIErrBadChecksum;
goto bail;
}
*pBuf = buf;
*pLength = pCDE->fUncompressedSize;
buf = nil;
bail:
delete[] buf;
return dierr;
}
/*
* Uncompress data from "pOuterGFD" to "buf".
*
* "buf" must be able to hold "uncompSize" bytes.
*/
DIError
OuterZip::InflateGFDToBuffer(GenericFD* pGFD, unsigned long compSize,
unsigned long uncompSize, unsigned char* buf)
{
DIError dierr = kDIErrNone;
const unsigned long kReadBufSize = 65536;
unsigned char* readBuf = nil;
z_stream zstream;
int zerr;
unsigned long compRemaining;
readBuf = new unsigned char[kReadBufSize];
if (readBuf == nil) {
dierr = kDIErrMalloc;
goto bail;
}
compRemaining = compSize;
/*
* Initialize the zlib stream.
*/
memset(&zstream, 0, sizeof(zstream));
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zstream.next_in = nil;
zstream.avail_in = 0;
zstream.next_out = buf;
zstream.avail_out = uncompSize;
zstream.data_type = Z_UNKNOWN;
/*
* Use the undocumented "negative window bits" feature to tell zlib
* that there's no zlib header waiting for it.
*/
zerr = inflateInit2(&zstream, -MAX_WBITS);
if (zerr != Z_OK) {
dierr = kDIErrInternal;
if (zerr == Z_VERSION_ERROR) {
WMSG1("Installed zlib is not compatible with linked version (%s)\n",
ZLIB_VERSION);
} else {
WMSG1("Call to inflateInit2 failed (zerr=%d)\n", zerr);
}
goto bail;
}
/*
* Loop while we have data.
*/
do {
unsigned long getSize;
/* read as much as we can */
if (zstream.avail_in == 0) {
getSize = (compRemaining > kReadBufSize) ?
kReadBufSize : compRemaining;
WMSG2("+++ reading %ld bytes (%ld left)\n", getSize,
compRemaining);
dierr = pGFD->Read(readBuf, getSize);
if (dierr != kDIErrNone) {
WMSG0("inflate read failed\n");
goto z_bail;
}
compRemaining -= getSize;
zstream.next_in = readBuf;
zstream.avail_in = getSize;
}
/* uncompress the data */
zerr = inflate(&zstream, Z_NO_FLUSH);
if (zerr != Z_OK && zerr != Z_STREAM_END) {
dierr = kDIErrInternal;
WMSG1("zlib inflate call failed (zerr=%d)\n", zerr);
goto z_bail;
}
/* output buffer holds all, so no need to write the output */
} while (zerr == Z_OK);
assert(zerr == Z_STREAM_END); /* other errors should've been caught */
if (zstream.total_out != uncompSize) {
dierr = kDIErrBadCompressedData;
WMSG2("Size mismatch on inflated file (%ld vs %ld)\n",
zstream.total_out, uncompSize);
goto z_bail;
}
z_bail:
inflateEnd(&zstream); /* free up any allocated structures */
bail:
delete[] readBuf;
return dierr;
}
/*
* Get the current date/time, in MS-DOS format.
*/
void
OuterZip::GetMSDOSTime(unsigned short* pDate, unsigned short* pTime)
{
#if 0
/* this gets gmtime; we want localtime */
SYSTEMTIME sysTime;
FILETIME fileTime;
::GetSystemTime(&sysTime);
::SystemTimeToFileTime(&sysTime, &fileTime);
::FileTimeToDosDateTime(&fileTime, pDate, pTime);
//WMSG3("+++ Windows date: %04x %04x %d\n", *pDate, *pTime,
// (*pTime >> 11) & 0x1f);
#endif
time_t now = time(nil);
DOSTime(now, pDate, pTime);
//WMSG3("+++ Our date : %04x %04x %d\n", *pDate, *pTime,
// (*pTime >> 11) & 0x1f);
}
/*
* Convert a time_t to MS-DOS date and time values.
*/
void
OuterZip::DOSTime(time_t when, unsigned short* pDate, unsigned short* pTime)
{
time_t even;
*pDate = *pTime = 0;
struct tm* ptm;
/* round up to an even number of seconds */
even = (time_t)(((unsigned long)(when) + 1) & (~1));
/* expand */
ptm = localtime(&even);
int year;
year = ptm->tm_year;
if (year < 80)
year = 80;
*pDate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
*pTime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
}
/*
* Compress "length" bytes of data from "pSrc" to "pDst".
*/
DIError
OuterZip::DeflateGFDToGFD(GenericFD* pDst, GenericFD* pSrc, di_off_t srcLen,
di_off_t* pCompLength, unsigned long* pCRC)
{
DIError dierr = kDIErrNone;
const unsigned long kBufSize = 32768;
unsigned char* inBuf = nil;
unsigned char* outBuf = nil;
z_stream zstream;
unsigned long crc;
int zerr;
/*
* Create an input buffer and an output buffer.
*/
inBuf = new unsigned char[kBufSize];
outBuf = new unsigned char[kBufSize];
if (inBuf == nil || outBuf == nil) {
dierr = kDIErrMalloc;
goto bail;
}
/*
* Initialize the zlib stream.
*/
memset(&zstream, 0, sizeof(zstream));
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zstream.next_in = nil;
zstream.avail_in = 0;
zstream.next_out = outBuf;
zstream.avail_out = kBufSize;
zstream.data_type = Z_UNKNOWN;
zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (zerr != Z_OK) {
dierr = kDIErrInternal;
if (zerr == Z_VERSION_ERROR) {
WMSG1("Installed zlib is not compatible with linked version (%s)\n",
ZLIB_VERSION);
} else {
WMSG1("Call to deflateInit2 failed (zerr=%d)\n", zerr);
}
goto bail;
}
crc = crc32(0L, Z_NULL, 0);
/*
* Loop while we have data.
*/
do {
long getSize;
int flush;
/* only read if the input is empty */
if (zstream.avail_in == 0 && srcLen) {
getSize = (srcLen > kBufSize) ? kBufSize : (long) srcLen;
WMSG1("+++ reading %ld bytes\n", getSize);
dierr = pSrc->Read(inBuf, getSize);
if (dierr != kDIErrNone) {
WMSG0("deflate read failed\n");
goto z_bail;
}
srcLen -= getSize;
crc = crc32(crc, inBuf, getSize);
zstream.next_in = inBuf;
zstream.avail_in = getSize;
}
if (srcLen == 0)
flush = Z_FINISH; /* tell zlib that we're done */
else
flush = Z_NO_FLUSH; /* more to come! */
zerr = deflate(&zstream, flush);
if (zerr != Z_OK && zerr != Z_STREAM_END) {
WMSG1("zlib deflate call failed (zerr=%d)\n", zerr);
dierr = kDIErrInternal;
goto z_bail;
}
/* write when we're full or when we're done */
if (zstream.avail_out == 0 ||
(zerr == Z_STREAM_END && zstream.avail_out != kBufSize))
{
WMSG1("+++ writing %d bytes\n", zstream.next_out - outBuf);
dierr = pDst->Write(outBuf, zstream.next_out - outBuf);
if (dierr != kDIErrNone) {
WMSG0("write failed in deflate\n");
goto z_bail;
}
zstream.next_out = outBuf;
zstream.avail_out = kBufSize;
}
} while (zerr == Z_OK);
assert(zerr == Z_STREAM_END); /* other errors should've been caught */
*pCompLength = zstream.total_out;
*pCRC = crc;
z_bail:
deflateEnd(&zstream); /* free up any allocated structures */
bail:
delete[] inBuf;
delete[] outBuf;
return dierr;
}
/*
* Set the "fExtension" field.
*/
void
OuterZip::SetExtension(const char* ext)
{
delete[] fExtension;
fExtension = StrcpyNew(ext);
}
/*
* ===================================
* OuterZip::LocalFileHeader
* ===================================
*/
/*
* Read a local file header.
*
* On entry, "pGFD" points to the signature at the start of the header.
* On exit, "pGFD" points to the start of data.
*/
DIError
OuterZip::LocalFileHeader::Read(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
unsigned char buf[kLFHLen];
dierr = pGFD->Read(buf, kLFHLen);
if (dierr != kDIErrNone)
goto bail;
if (GetLongLE(&buf[0x00]) != kSignature) {
WMSG0(" ZIP: whoops: didn't find expected signature\n");
assert(false);
return kDIErrGeneric;
}
fVersionToExtract = GetShortLE(&buf[0x04]);
fGPBitFlag = GetShortLE(&buf[0x06]);
fCompressionMethod = GetShortLE(&buf[0x08]);
fLastModFileTime = GetShortLE(&buf[0x0a]);
fLastModFileDate = GetShortLE(&buf[0x0c]);
fCRC32 = GetLongLE(&buf[0x0e]);
fCompressedSize = GetLongLE(&buf[0x12]);
fUncompressedSize = GetLongLE(&buf[0x16]);
fFileNameLength = GetShortLE(&buf[0x1a]);
fExtraFieldLength = GetShortLE(&buf[0x1c]);
/* grab filename */
if (fFileNameLength != 0) {
assert(fFileName == nil);
fFileName = new unsigned char[fFileNameLength+1];
if (fFileName == nil) {
dierr = kDIErrMalloc;
goto bail;
} else {
dierr = pGFD->Read(fFileName, fFileNameLength);
fFileName[fFileNameLength] = '\0';
}
if (dierr != kDIErrNone)
goto bail;
}
dierr = pGFD->Seek(fExtraFieldLength, kSeekCur);
if (dierr != kDIErrNone)
goto bail;
bail:
return dierr;
}
/*
* Write a local file header.
*/
DIError
OuterZip::LocalFileHeader::Write(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
unsigned char buf[kLFHLen];
PutLongLE(&buf[0x00], kSignature);
PutShortLE(&buf[0x04], fVersionToExtract);
PutShortLE(&buf[0x06], fGPBitFlag);
PutShortLE(&buf[0x08], fCompressionMethod);
PutShortLE(&buf[0x0a], fLastModFileTime);
PutShortLE(&buf[0x0c], fLastModFileDate);
PutLongLE(&buf[0x0e], fCRC32);
PutLongLE(&buf[0x12], fCompressedSize);
PutLongLE(&buf[0x16], fUncompressedSize);
PutShortLE(&buf[0x1a], fFileNameLength);
PutShortLE(&buf[0x1c], fExtraFieldLength);
dierr = pGFD->Write(buf, kLFHLen);
if (dierr != kDIErrNone)
goto bail;
/* write filename */
if (fFileNameLength != 0) {
dierr = pGFD->Write(fFileName, fFileNameLength);
if (dierr != kDIErrNone)
goto bail;
}
assert(fExtraFieldLength == 0);
bail:
return dierr;
}
/*
* Change the filename field.
*/
void
OuterZip::LocalFileHeader::SetFileName(const char* name)
{
delete[] fFileName;
fFileName = nil;
fFileNameLength = 0;
if (name != nil) {
fFileNameLength = strlen(name);
fFileName = new unsigned char[fFileNameLength+1];
if (fFileName == nil) {
WMSG1("Malloc failure in SetFileName %u\n", fFileNameLength);
fFileName = nil;
fFileNameLength = 0;
} else {
memcpy(fFileName, name, fFileNameLength);
fFileName[fFileNameLength] = '\0';
WMSG1("+++ OuterZip LFH filename set to '%s'\n", fFileName);
}
}
}
/*
* Dump the contents of a LocalFileHeader object.
*/
void
OuterZip::LocalFileHeader::Dump(void) const
{
WMSG0(" LocalFileHeader contents:\n");
WMSG3(" versToExt=%u gpBits=0x%04x compression=%u\n",
fVersionToExtract, fGPBitFlag, fCompressionMethod);
WMSG3(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
fLastModFileTime, fLastModFileDate, fCRC32);
WMSG2(" compressedSize=%lu uncompressedSize=%lu\n",
fCompressedSize, fUncompressedSize);
WMSG2(" filenameLen=%u extraLen=%u\n",
fFileNameLength, fExtraFieldLength);
}
/*
* ===================================
* OuterZip::CentralDirEntry
* ===================================
*/
/*
* Read the central dir entry that appears next in the file.
*
* On entry, "pGFD" should be positioned on the signature bytes for the
* entry. On exit, "pGFD" will point at the signature word for the next
* entry or for the EOCD.
*/
DIError
OuterZip::CentralDirEntry::Read(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
unsigned char buf[kCDELen];
dierr = pGFD->Read(buf, kCDELen);
if (dierr != kDIErrNone)
goto bail;
if (GetLongLE(&buf[0x00]) != kSignature) {
WMSG0(" ZIP: whoops: didn't find expected signature\n");
assert(false);
return kDIErrGeneric;
}
fVersionMadeBy = GetShortLE(&buf[0x04]);
fVersionToExtract = GetShortLE(&buf[0x06]);
fGPBitFlag = GetShortLE(&buf[0x08]);
fCompressionMethod = GetShortLE(&buf[0x0a]);
fLastModFileTime = GetShortLE(&buf[0x0c]);
fLastModFileDate = GetShortLE(&buf[0x0e]);
fCRC32 = GetLongLE(&buf[0x10]);
fCompressedSize = GetLongLE(&buf[0x14]);
fUncompressedSize = GetLongLE(&buf[0x18]);
fFileNameLength = GetShortLE(&buf[0x1c]);
fExtraFieldLength = GetShortLE(&buf[0x1e]);
fFileCommentLength = GetShortLE(&buf[0x20]);
fDiskNumberStart = GetShortLE(&buf[0x22]);
fInternalAttrs = GetShortLE(&buf[0x24]);
fExternalAttrs = GetLongLE(&buf[0x26]);
fLocalHeaderRelOffset = GetLongLE(&buf[0x2a]);
/* grab filename */
if (fFileNameLength != 0) {
assert(fFileName == nil);
fFileName = new unsigned char[fFileNameLength+1];
if (fFileName == nil) {
dierr = kDIErrMalloc;
goto bail;
} else {
dierr = pGFD->Read(fFileName, fFileNameLength);
fFileName[fFileNameLength] = '\0';
}
if (dierr != kDIErrNone)
goto bail;
}
/* skip over "extra field" */
dierr = pGFD->Seek(fExtraFieldLength, kSeekCur);
if (dierr != kDIErrNone)
goto bail;
/* grab comment, if any */
if (fFileCommentLength != 0) {
assert(fFileComment == nil);
fFileComment = new unsigned char[fFileCommentLength+1];
if (fFileComment == nil) {
dierr = kDIErrMalloc;
goto bail;
} else {
dierr = pGFD->Read(fFileComment, fFileCommentLength);
fFileComment[fFileCommentLength] = '\0';
}
if (dierr != kDIErrNone)
goto bail;
}
bail:
return dierr;
}
/*
* Write a central dir entry.
*/
DIError
OuterZip::CentralDirEntry::Write(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
unsigned char buf[kCDELen];
PutLongLE(&buf[0x00], kSignature);
PutShortLE(&buf[0x04], fVersionMadeBy);
PutShortLE(&buf[0x06], fVersionToExtract);
PutShortLE(&buf[0x08], fGPBitFlag);
PutShortLE(&buf[0x0a], fCompressionMethod);
PutShortLE(&buf[0x0c], fLastModFileTime);
PutShortLE(&buf[0x0e], fLastModFileDate);
PutLongLE(&buf[0x10], fCRC32);
PutLongLE(&buf[0x14], fCompressedSize);
PutLongLE(&buf[0x18], fUncompressedSize);
PutShortLE(&buf[0x1c], fFileNameLength);
PutShortLE(&buf[0x1e], fExtraFieldLength);
PutShortLE(&buf[0x20], fFileCommentLength);
PutShortLE(&buf[0x22], fDiskNumberStart);
PutShortLE(&buf[0x24], fInternalAttrs);
PutLongLE(&buf[0x26], fExternalAttrs);
PutLongLE(&buf[0x2a], fLocalHeaderRelOffset);
dierr = pGFD->Write(buf, kCDELen);
if (dierr != kDIErrNone)
goto bail;
/* write filename */
if (fFileNameLength != 0) {
dierr = pGFD->Write(fFileName, fFileNameLength);
if (dierr != kDIErrNone)
goto bail;
}
assert(fExtraFieldLength == 0);
assert(fFileCommentLength == 0);
bail:
return dierr;
}
/*
* Change the filename field.
*/
void
OuterZip::CentralDirEntry::SetFileName(const char* name)
{
delete[] fFileName;
fFileName = nil;
fFileNameLength = 0;
if (name != nil) {
fFileNameLength = strlen(name);
fFileName = new unsigned char[fFileNameLength+1];
if (fFileName == nil) {
WMSG1("Malloc failure in SetFileName %u\n", fFileNameLength);
fFileName = nil;
fFileNameLength = 0;
} else {
memcpy(fFileName, name, fFileNameLength);
fFileName[fFileNameLength] = '\0';
WMSG1("+++ OuterZip CDE filename set to '%s'\n", fFileName);
}
}
}
/*
* Dump the contents of a CentralDirEntry object.
*/
void
OuterZip::CentralDirEntry::Dump(void) const
{
WMSG0(" CentralDirEntry contents:\n");
WMSG4(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
fVersionMadeBy, fVersionToExtract, fGPBitFlag, fCompressionMethod);
WMSG3(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
fLastModFileTime, fLastModFileDate, fCRC32);
WMSG2(" compressedSize=%lu uncompressedSize=%lu\n",
fCompressedSize, fUncompressedSize);
WMSG3(" filenameLen=%u extraLen=%u commentLen=%u\n",
fFileNameLength, fExtraFieldLength, fFileCommentLength);
WMSG4(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
fDiskNumberStart, fInternalAttrs, fExternalAttrs,
fLocalHeaderRelOffset);
if (fFileName != nil) {
WMSG1(" filename: '%s'\n", fFileName);
}
if (fFileComment != nil) {
WMSG1(" comment: '%s'\n", fFileComment);
}
}
/*
* ===================================
* OuterZip::EndOfCentralDir
* ===================================
*/
/*
* Read the end-of-central-dir fields.
*
* "buf" should be positioned at the EOCD signature.
*/
DIError
OuterZip::EndOfCentralDir::ReadBuf(const unsigned char* buf, int len)
{
if (len < kEOCDLen) {
/* looks like ZIP file got truncated */
WMSG2(" Zip EOCD: expected >= %d bytes, found %d\n",
kEOCDLen, len);
return kDIErrBadArchiveStruct;
}
if (GetLongLE(&buf[0x00]) != kSignature)
return kDIErrInternal;
fDiskNumber = GetShortLE(&buf[0x04]);
fDiskWithCentralDir = GetShortLE(&buf[0x06]);
fNumEntries = GetShortLE(&buf[0x08]);
fTotalNumEntries = GetShortLE(&buf[0x0a]);
fCentralDirSize = GetLongLE(&buf[0x0c]);
fCentralDirOffset = GetLongLE(&buf[0x10]);
fCommentLen = GetShortLE(&buf[0x14]);
return kDIErrNone;
}
/*
* Write an end-of-central-directory section.
*/
DIError
OuterZip::EndOfCentralDir::Write(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
unsigned char buf[kEOCDLen];
PutLongLE(&buf[0x00], kSignature);
PutShortLE(&buf[0x04], fDiskNumber);
PutShortLE(&buf[0x06], fDiskWithCentralDir);
PutShortLE(&buf[0x08], fNumEntries);
PutShortLE(&buf[0x0a], fTotalNumEntries);
PutLongLE(&buf[0x0c], fCentralDirSize);
PutLongLE(&buf[0x10], fCentralDirOffset);
PutShortLE(&buf[0x14], fCommentLen);
dierr = pGFD->Write(buf, kEOCDLen);
if (dierr != kDIErrNone)
goto bail;
assert(fCommentLen == 0);
bail:
return dierr;
}
/*
* Dump the contents of an EndOfCentralDir object.
*/
void
OuterZip::EndOfCentralDir::Dump(void) const
{
WMSG0(" EndOfCentralDir contents:\n");
WMSG4(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
fDiskNumber, fDiskWithCentralDir, fNumEntries, fTotalNumEntries);
WMSG3(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
fCentralDirSize, fCentralDirOffset, fCommentLen);
}