697 lines
21 KiB
C++
697 lines
21 KiB
C++
|
/*
|
||
|
* Copyright (C) 2006 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
//
|
||
|
// Access to entries in a Zip archive.
|
||
|
//
|
||
|
|
||
|
#define LOG_TAG "zip"
|
||
|
|
||
|
#include "ZipEntry.h"
|
||
|
#include <utils/Log.h>
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
using namespace android;
|
||
|
|
||
|
/*
|
||
|
* Initialize a new ZipEntry structure from a FILE* positioned at a
|
||
|
* CentralDirectoryEntry.
|
||
|
*
|
||
|
* On exit, the file pointer will be at the start of the next CDE or
|
||
|
* at the EOCD.
|
||
|
*/
|
||
|
status_t ZipEntry::initFromCDE(FILE* fp)
|
||
|
{
|
||
|
status_t result;
|
||
|
long posn;
|
||
|
bool hasDD;
|
||
|
|
||
|
//LOGV("initFromCDE ---\n");
|
||
|
|
||
|
/* read the CDE */
|
||
|
result = mCDE.read(fp);
|
||
|
if (result != NO_ERROR) {
|
||
|
LOGD("mCDE.read failed\n");
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
//mCDE.dump();
|
||
|
|
||
|
/* using the info in the CDE, go load up the LFH */
|
||
|
posn = ftell(fp);
|
||
|
if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
|
||
|
LOGD("local header seek failed (%ld)\n",
|
||
|
mCDE.mLocalHeaderRelOffset);
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
result = mLFH.read(fp);
|
||
|
if (result != NO_ERROR) {
|
||
|
LOGD("mLFH.read failed\n");
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
if (fseek(fp, posn, SEEK_SET) != 0)
|
||
|
return UNKNOWN_ERROR;
|
||
|
|
||
|
//mLFH.dump();
|
||
|
|
||
|
/*
|
||
|
* We *might* need to read the Data Descriptor at this point and
|
||
|
* integrate it into the LFH. If this bit is set, the CRC-32,
|
||
|
* compressed size, and uncompressed size will be zero. In practice
|
||
|
* these seem to be rare.
|
||
|
*/
|
||
|
hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
|
||
|
if (hasDD) {
|
||
|
// do something clever
|
||
|
//LOGD("+++ has data descriptor\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
|
||
|
* flag is set, because the LFH is incomplete. (Not a problem, since we
|
||
|
* prefer the CDE values.)
|
||
|
*/
|
||
|
if (!hasDD && !compareHeaders()) {
|
||
|
LOGW("warning: header mismatch\n");
|
||
|
// keep going?
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If the mVersionToExtract is greater than 20, we may have an
|
||
|
* issue unpacking the record -- could be encrypted, compressed
|
||
|
* with something we don't support, or use Zip64 extensions. We
|
||
|
* can defer worrying about that to when we're extracting data.
|
||
|
*/
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize a new entry. Pass in the file name and an optional comment.
|
||
|
*
|
||
|
* Initializes the CDE and the LFH.
|
||
|
*/
|
||
|
void ZipEntry::initNew(const char* fileName, const char* comment)
|
||
|
{
|
||
|
assert(fileName != NULL && *fileName != '\0'); // name required
|
||
|
|
||
|
/* most fields are properly initialized by constructor */
|
||
|
mCDE.mVersionMadeBy = kDefaultMadeBy;
|
||
|
mCDE.mVersionToExtract = kDefaultVersion;
|
||
|
mCDE.mCompressionMethod = kCompressStored;
|
||
|
mCDE.mFileNameLength = strlen(fileName);
|
||
|
if (comment != NULL)
|
||
|
mCDE.mFileCommentLength = strlen(comment);
|
||
|
mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
|
||
|
|
||
|
if (mCDE.mFileNameLength > 0) {
|
||
|
mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
|
||
|
strcpy((char*) mCDE.mFileName, fileName);
|
||
|
}
|
||
|
if (mCDE.mFileCommentLength > 0) {
|
||
|
/* TODO: stop assuming null-terminated ASCII here? */
|
||
|
mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
|
||
|
strcpy((char*) mCDE.mFileComment, comment);
|
||
|
}
|
||
|
|
||
|
copyCDEtoLFH();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize a new entry, starting with the ZipEntry from a different
|
||
|
* archive.
|
||
|
*
|
||
|
* Initializes the CDE and the LFH.
|
||
|
*/
|
||
|
status_t ZipEntry::initFromExternal(const ZipFile* pZipFile,
|
||
|
const ZipEntry* pEntry)
|
||
|
{
|
||
|
/*
|
||
|
* Copy everything in the CDE over, then fix up the hairy bits.
|
||
|
*/
|
||
|
memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE));
|
||
|
|
||
|
if (mCDE.mFileNameLength > 0) {
|
||
|
mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
|
||
|
if (mCDE.mFileName == NULL)
|
||
|
return NO_MEMORY;
|
||
|
strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName);
|
||
|
}
|
||
|
if (mCDE.mFileCommentLength > 0) {
|
||
|
mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
|
||
|
if (mCDE.mFileComment == NULL)
|
||
|
return NO_MEMORY;
|
||
|
strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment);
|
||
|
}
|
||
|
if (mCDE.mExtraFieldLength > 0) {
|
||
|
/* we null-terminate this, though it may not be a string */
|
||
|
mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1];
|
||
|
if (mCDE.mExtraField == NULL)
|
||
|
return NO_MEMORY;
|
||
|
memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField,
|
||
|
mCDE.mExtraFieldLength+1);
|
||
|
}
|
||
|
|
||
|
/* construct the LFH from the CDE */
|
||
|
copyCDEtoLFH();
|
||
|
|
||
|
/*
|
||
|
* The LFH "extra" field is independent of the CDE "extra", so we
|
||
|
* handle it here.
|
||
|
*/
|
||
|
assert(mLFH.mExtraField == NULL);
|
||
|
mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
|
||
|
if (mLFH.mExtraFieldLength > 0) {
|
||
|
mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
|
||
|
if (mLFH.mExtraField == NULL)
|
||
|
return NO_MEMORY;
|
||
|
memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
|
||
|
mLFH.mExtraFieldLength+1);
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Insert pad bytes in the LFH by tweaking the "extra" field. This will
|
||
|
* potentially confuse something that put "extra" data in here earlier,
|
||
|
* but I can't find an actual problem.
|
||
|
*/
|
||
|
status_t ZipEntry::addPadding(int padding)
|
||
|
{
|
||
|
if (padding <= 0)
|
||
|
return INVALID_OPERATION;
|
||
|
|
||
|
//LOGI("HEY: adding %d pad bytes to existing %d in %s\n",
|
||
|
// padding, mLFH.mExtraFieldLength, mCDE.mFileName);
|
||
|
|
||
|
if (mLFH.mExtraFieldLength > 0) {
|
||
|
/* extend existing field */
|
||
|
unsigned char* newExtra;
|
||
|
|
||
|
newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
|
||
|
if (newExtra == NULL)
|
||
|
return NO_MEMORY;
|
||
|
memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
|
||
|
memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
|
||
|
|
||
|
delete[] mLFH.mExtraField;
|
||
|
mLFH.mExtraField = newExtra;
|
||
|
mLFH.mExtraFieldLength += padding;
|
||
|
} else {
|
||
|
/* create new field */
|
||
|
mLFH.mExtraField = new unsigned char[padding];
|
||
|
memset(mLFH.mExtraField, 0, padding);
|
||
|
mLFH.mExtraFieldLength = padding;
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set the fields in the LFH equal to the corresponding fields in the CDE.
|
||
|
*
|
||
|
* This does not touch the LFH "extra" field.
|
||
|
*/
|
||
|
void ZipEntry::copyCDEtoLFH(void)
|
||
|
{
|
||
|
mLFH.mVersionToExtract = mCDE.mVersionToExtract;
|
||
|
mLFH.mGPBitFlag = mCDE.mGPBitFlag;
|
||
|
mLFH.mCompressionMethod = mCDE.mCompressionMethod;
|
||
|
mLFH.mLastModFileTime = mCDE.mLastModFileTime;
|
||
|
mLFH.mLastModFileDate = mCDE.mLastModFileDate;
|
||
|
mLFH.mCRC32 = mCDE.mCRC32;
|
||
|
mLFH.mCompressedSize = mCDE.mCompressedSize;
|
||
|
mLFH.mUncompressedSize = mCDE.mUncompressedSize;
|
||
|
mLFH.mFileNameLength = mCDE.mFileNameLength;
|
||
|
// the "extra field" is independent
|
||
|
|
||
|
delete[] mLFH.mFileName;
|
||
|
if (mLFH.mFileNameLength > 0) {
|
||
|
mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
|
||
|
strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
|
||
|
} else {
|
||
|
mLFH.mFileName = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set some information about a file after we add it.
|
||
|
*/
|
||
|
void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
|
||
|
int compressionMethod)
|
||
|
{
|
||
|
mCDE.mCompressionMethod = compressionMethod;
|
||
|
mCDE.mCRC32 = crc32;
|
||
|
mCDE.mCompressedSize = compLen;
|
||
|
mCDE.mUncompressedSize = uncompLen;
|
||
|
mCDE.mCompressionMethod = compressionMethod;
|
||
|
if (compressionMethod == kCompressDeflated) {
|
||
|
mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
|
||
|
}
|
||
|
copyCDEtoLFH();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* See if the data in mCDE and mLFH match up. This is mostly useful for
|
||
|
* debugging these classes, but it can be used to identify damaged
|
||
|
* archives.
|
||
|
*
|
||
|
* Returns "false" if they differ.
|
||
|
*/
|
||
|
bool ZipEntry::compareHeaders(void) const
|
||
|
{
|
||
|
if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
|
||
|
LOGV("cmp: VersionToExtract\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
|
||
|
LOGV("cmp: GPBitFlag\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
|
||
|
LOGV("cmp: CompressionMethod\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
|
||
|
LOGV("cmp: LastModFileTime\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
|
||
|
LOGV("cmp: LastModFileDate\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mCRC32 != mLFH.mCRC32) {
|
||
|
LOGV("cmp: CRC32\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
|
||
|
LOGV("cmp: CompressedSize\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
|
||
|
LOGV("cmp: UncompressedSize\n");
|
||
|
return false;
|
||
|
}
|
||
|
if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
|
||
|
LOGV("cmp: FileNameLength\n");
|
||
|
return false;
|
||
|
}
|
||
|
#if 0 // this seems to be used for padding, not real data
|
||
|
if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
|
||
|
LOGV("cmp: ExtraFieldLength\n");
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
if (mCDE.mFileName != NULL) {
|
||
|
if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
|
||
|
LOGV("cmp: FileName\n");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Convert the DOS date/time stamp into a UNIX time stamp.
|
||
|
*/
|
||
|
time_t ZipEntry::getModWhen(void) const
|
||
|
{
|
||
|
struct tm parts;
|
||
|
|
||
|
parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
|
||
|
parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
|
||
|
parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
|
||
|
parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
|
||
|
parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
|
||
|
parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
|
||
|
parts.tm_wday = parts.tm_yday = 0;
|
||
|
parts.tm_isdst = -1; // DST info "not available"
|
||
|
|
||
|
return mktime(&parts);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set the CDE/LFH timestamp from UNIX time.
|
||
|
*/
|
||
|
void ZipEntry::setModWhen(time_t when)
|
||
|
{
|
||
|
#ifdef HAVE_LOCALTIME_R
|
||
|
struct tm tmResult;
|
||
|
#endif
|
||
|
time_t even;
|
||
|
unsigned short zdate, ztime;
|
||
|
|
||
|
struct tm* ptm;
|
||
|
|
||
|
/* round up to an even number of seconds */
|
||
|
even = (time_t)(((unsigned long)(when) + 1) & (~1));
|
||
|
|
||
|
/* expand */
|
||
|
#ifdef HAVE_LOCALTIME_R
|
||
|
ptm = localtime_r(&even, &tmResult);
|
||
|
#else
|
||
|
ptm = localtime(&even);
|
||
|
#endif
|
||
|
|
||
|
int year;
|
||
|
year = ptm->tm_year;
|
||
|
if (year < 80)
|
||
|
year = 80;
|
||
|
|
||
|
zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
|
||
|
ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
|
||
|
|
||
|
mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
|
||
|
mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* ZipEntry::LocalFileHeader
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Read a local file header.
|
||
|
*
|
||
|
* On entry, "fp" points to the signature at the start of the header.
|
||
|
* On exit, "fp" points to the start of data.
|
||
|
*/
|
||
|
status_t ZipEntry::LocalFileHeader::read(FILE* fp)
|
||
|
{
|
||
|
status_t result = NO_ERROR;
|
||
|
unsigned char buf[kLFHLen];
|
||
|
|
||
|
assert(mFileName == NULL);
|
||
|
assert(mExtraField == NULL);
|
||
|
|
||
|
if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
|
||
|
LOGD("whoops: didn't find expected signature\n");
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
|
||
|
mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
|
||
|
mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
|
||
|
mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
|
||
|
mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
|
||
|
mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
|
||
|
mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
|
||
|
mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
|
||
|
mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
|
||
|
mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
|
||
|
|
||
|
// TODO: validate sizes
|
||
|
|
||
|
/* grab filename */
|
||
|
if (mFileNameLength != 0) {
|
||
|
mFileName = new unsigned char[mFileNameLength+1];
|
||
|
if (mFileName == NULL) {
|
||
|
result = NO_MEMORY;
|
||
|
goto bail;
|
||
|
}
|
||
|
if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
mFileName[mFileNameLength] = '\0';
|
||
|
}
|
||
|
|
||
|
/* grab extra field */
|
||
|
if (mExtraFieldLength != 0) {
|
||
|
mExtraField = new unsigned char[mExtraFieldLength+1];
|
||
|
if (mExtraField == NULL) {
|
||
|
result = NO_MEMORY;
|
||
|
goto bail;
|
||
|
}
|
||
|
if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
mExtraField[mExtraFieldLength] = '\0';
|
||
|
}
|
||
|
|
||
|
bail:
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write a local file header.
|
||
|
*/
|
||
|
status_t ZipEntry::LocalFileHeader::write(FILE* fp)
|
||
|
{
|
||
|
unsigned char buf[kLFHLen];
|
||
|
|
||
|
ZipEntry::putLongLE(&buf[0x00], kSignature);
|
||
|
ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
|
||
|
ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
|
||
|
ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
|
||
|
ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
|
||
|
ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
|
||
|
ZipEntry::putLongLE(&buf[0x0e], mCRC32);
|
||
|
ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
|
||
|
ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
|
||
|
ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
|
||
|
ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
|
||
|
|
||
|
if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
|
||
|
return UNKNOWN_ERROR;
|
||
|
|
||
|
/* write filename */
|
||
|
if (mFileNameLength != 0) {
|
||
|
if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
/* write "extra field" */
|
||
|
if (mExtraFieldLength != 0) {
|
||
|
if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Dump the contents of a LocalFileHeader object.
|
||
|
*/
|
||
|
void ZipEntry::LocalFileHeader::dump(void) const
|
||
|
{
|
||
|
LOGD(" LocalFileHeader contents:\n");
|
||
|
LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
|
||
|
mVersionToExtract, mGPBitFlag, mCompressionMethod);
|
||
|
LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
|
||
|
mLastModFileTime, mLastModFileDate, mCRC32);
|
||
|
LOGD(" compressedSize=%lu uncompressedSize=%lu\n",
|
||
|
mCompressedSize, mUncompressedSize);
|
||
|
LOGD(" filenameLen=%u extraLen=%u\n",
|
||
|
mFileNameLength, mExtraFieldLength);
|
||
|
if (mFileName != NULL)
|
||
|
LOGD(" filename: '%s'\n", mFileName);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* ZipEntry::CentralDirEntry
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Read the central dir entry that appears next in the file.
|
||
|
*
|
||
|
* On entry, "fp" should be positioned on the signature bytes for the
|
||
|
* entry. On exit, "fp" will point at the signature word for the next
|
||
|
* entry or for the EOCD.
|
||
|
*/
|
||
|
status_t ZipEntry::CentralDirEntry::read(FILE* fp)
|
||
|
{
|
||
|
status_t result = NO_ERROR;
|
||
|
unsigned char buf[kCDELen];
|
||
|
|
||
|
/* no re-use */
|
||
|
assert(mFileName == NULL);
|
||
|
assert(mExtraField == NULL);
|
||
|
assert(mFileComment == NULL);
|
||
|
|
||
|
if (fread(buf, 1, kCDELen, fp) != kCDELen) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
|
||
|
LOGD("Whoops: didn't find expected signature\n");
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
|
||
|
mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
|
||
|
mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
|
||
|
mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
|
||
|
mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
|
||
|
mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
|
||
|
mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
|
||
|
mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
|
||
|
mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
|
||
|
mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
|
||
|
mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
|
||
|
mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
|
||
|
mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
|
||
|
mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
|
||
|
mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
|
||
|
mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
|
||
|
|
||
|
// TODO: validate sizes and offsets
|
||
|
|
||
|
/* grab filename */
|
||
|
if (mFileNameLength != 0) {
|
||
|
mFileName = new unsigned char[mFileNameLength+1];
|
||
|
if (mFileName == NULL) {
|
||
|
result = NO_MEMORY;
|
||
|
goto bail;
|
||
|
}
|
||
|
if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
mFileName[mFileNameLength] = '\0';
|
||
|
}
|
||
|
|
||
|
/* read "extra field" */
|
||
|
if (mExtraFieldLength != 0) {
|
||
|
mExtraField = new unsigned char[mExtraFieldLength+1];
|
||
|
if (mExtraField == NULL) {
|
||
|
result = NO_MEMORY;
|
||
|
goto bail;
|
||
|
}
|
||
|
if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
mExtraField[mExtraFieldLength] = '\0';
|
||
|
}
|
||
|
|
||
|
|
||
|
/* grab comment, if any */
|
||
|
if (mFileCommentLength != 0) {
|
||
|
mFileComment = new unsigned char[mFileCommentLength+1];
|
||
|
if (mFileComment == NULL) {
|
||
|
result = NO_MEMORY;
|
||
|
goto bail;
|
||
|
}
|
||
|
if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
|
||
|
{
|
||
|
result = UNKNOWN_ERROR;
|
||
|
goto bail;
|
||
|
}
|
||
|
mFileComment[mFileCommentLength] = '\0';
|
||
|
}
|
||
|
|
||
|
bail:
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write a central dir entry.
|
||
|
*/
|
||
|
status_t ZipEntry::CentralDirEntry::write(FILE* fp)
|
||
|
{
|
||
|
unsigned char buf[kCDELen];
|
||
|
|
||
|
ZipEntry::putLongLE(&buf[0x00], kSignature);
|
||
|
ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
|
||
|
ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
|
||
|
ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
|
||
|
ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
|
||
|
ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
|
||
|
ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
|
||
|
ZipEntry::putLongLE(&buf[0x10], mCRC32);
|
||
|
ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
|
||
|
ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
|
||
|
ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
|
||
|
ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
|
||
|
ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
|
||
|
ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
|
||
|
ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
|
||
|
ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
|
||
|
ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
|
||
|
|
||
|
if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
|
||
|
return UNKNOWN_ERROR;
|
||
|
|
||
|
/* write filename */
|
||
|
if (mFileNameLength != 0) {
|
||
|
if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
/* write "extra field" */
|
||
|
if (mExtraFieldLength != 0) {
|
||
|
if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
/* write comment */
|
||
|
if (mFileCommentLength != 0) {
|
||
|
if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
|
||
|
return UNKNOWN_ERROR;
|
||
|
}
|
||
|
|
||
|
return NO_ERROR;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Dump the contents of a CentralDirEntry object.
|
||
|
*/
|
||
|
void ZipEntry::CentralDirEntry::dump(void) const
|
||
|
{
|
||
|
LOGD(" CentralDirEntry contents:\n");
|
||
|
LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
|
||
|
mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
|
||
|
LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
|
||
|
mLastModFileTime, mLastModFileDate, mCRC32);
|
||
|
LOGD(" compressedSize=%lu uncompressedSize=%lu\n",
|
||
|
mCompressedSize, mUncompressedSize);
|
||
|
LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
|
||
|
mFileNameLength, mExtraFieldLength, mFileCommentLength);
|
||
|
LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
|
||
|
mDiskNumberStart, mInternalAttrs, mExternalAttrs,
|
||
|
mLocalHeaderRelOffset);
|
||
|
|
||
|
if (mFileName != NULL)
|
||
|
LOGD(" filename: '%s'\n", mFileName);
|
||
|
if (mFileComment != NULL)
|
||
|
LOGD(" comment: '%s'\n", mFileComment);
|
||
|
}
|
||
|
|