mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-23 11:33:58 +00:00
1536 lines
41 KiB
C++
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);
|
|
}
|