/*
* 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 _POSIX_THREAD_SAFE_FUNCTIONS
// For mingw localtime_r().
#define LOG_TAG
"zip"
#include "ZipEntry.h"
#include <utils/Log.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
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)
{
//ALOGV("initFromCDE ---\n");
/* read the CDE */
status_t result = mCDE.read(fp);
if (result != OK) {
ALOGD(
"mCDE.read failed\n");
return result;
}
//mCDE.dump();
/* using the info in the CDE, go load up the LFH */
off_t posn = ftello(fp);
if (fseeko(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) !=
0) {
ALOGD(
"local header seek failed (%" PRIu32
")\n",
mCDE.mLocalHeaderRelOffset);
return UNKNOWN_ERROR;
}
result = mLFH.read(fp);
if (result != OK) {
ALOGD(
"mLFH.read failed\n");
return result;
}
if (fseeko(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.
*/
bool hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) !=
0;
if (hasDD) {
// do something clever
//ALOGD("+++ has data descriptor\n");
}
/*
* 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()) {
ALOGW(
"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 OK;
}
/*
* 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 uint8_t[mCDE.mFileNameLength+
1];
strcpy((
char*) mCDE.mFileName, fileName);
}
if (mCDE.mFileCommentLength >
0) {
/* TODO: stop assuming null-terminated ASCII here? */
mCDE.mFileComment =
new uint8_t[mCDE.mFileCommentLength+
1];
assert(comment != NULL);
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 ZipEntry* pEntry)
{
/*
* Copy everything in the CDE over, then fix up the hairy bits.
*/
mCDE = pEntry->mCDE;
if (mCDE.mFileNameLength >
0) {
mCDE.mFileName =
new uint8_t[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 uint8_t[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 uint8_t[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 uint8_t[mLFH.mExtraFieldLength+
1];
if (mLFH.mExtraField == NULL)
return NO_MEMORY;
memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
mLFH.mExtraFieldLength+
1);
}
return OK;
}
/*
* 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;
//ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
// padding, mLFH.mExtraFieldLength, mCDE.mFileName);
if (mLFH.mExtraFieldLength >
0) {
/* extend existing field */
uint8_t* newExtra;
newExtra =
new uint8_t[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 uint8_t[padding];
memset(mLFH.mExtraField,
0, padding);
mLFH.mExtraFieldLength = padding;
}
return OK;
}
/*
* 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 uint8_t[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(uint32_t uncompLen, uint32_t compLen, uint32_t crc32,
uint32_t 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) {
ALOGV(
"cmp: VersionToExtract\n");
return false;
}
if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
ALOGV(
"cmp: GPBitFlag\n");
return false;
}
if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
ALOGV(
"cmp: CompressionMethod\n");
return false;
}
if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
ALOGV(
"cmp: LastModFileTime\n");
return false;
}
if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
ALOGV(
"cmp: LastModFileDate\n");
return false;
}
if (mCDE.mCRC32 != mLFH.mCRC32) {
ALOGV(
"cmp: CRC32\n");
return false;
}
if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
ALOGV(
"cmp: CompressedSize\n");
return false;
}
if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
ALOGV(
"cmp: UncompressedSize\n");
return false;
}
if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
ALOGV(
"cmp: FileNameLength\n");
return false;
}
#if 0 // this seems to be used for padding, not real data
if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
ALOGV(
"cmp: ExtraFieldLength\n");
return false;
}
#endif
if (mCDE.mFileName != NULL) {
if (strcmp((
char*) mCDE.mFileName, (
char*) mLFH.mFileName) !=
0) {
ALOGV(
"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)
{
/* round up to an even number of seconds */
time_t even = (when &
1) ? (when +
1) : when;
/* expand */
struct tm tmResult;
struct tm* ptm = localtime_r(&even, &tmResult);
// The earliest valid time for ZIP file entries is 1980-01-01. See:
// https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html.
// Set any time before 1980 to 1980-01-01.
if (ptm->tm_year <
80) {
ptm->tm_year =
80;
ptm->tm_mon =
0;
ptm->tm_mday =
1;
ptm->tm_hour =
0;
ptm->tm_min =
0;
ptm->tm_sec =
0;
}
uint16_t zdate =
static_cast<uint16_t>(
(ptm->tm_year -
80) <<
9 | (ptm->tm_mon +
1) <<
5 | ptm->tm_mday);
uint16_t ztime =
static_cast<uint16_t>(
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 = OK;
uint8_t 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) {
ALOGD(
"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 uint8_t[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 uint8_t[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)
{
uint8_t 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 OK;
}
/*
* Dump the contents of a LocalFileHeader object.
*/
void ZipEntry::LocalFileHeader::dump(
void)
const
{
ALOGD(
" LocalFileHeader contents:\n");
ALOGD(
" versToExt=%" PRIu16
" gpBits=0x%04" PRIx16
" compression=%" PRIu16
"\n",
mVersionToExtract, mGPBitFlag, mCompressionMethod);
ALOGD(
" modTime=0x%04" PRIx16
" modDate=0x%04" PRIx16
" crc32=0x%08" PRIx32
"\n",
mLastModFileTime, mLastModFileDate, mCRC32);
ALOGD(
" compressedSize=%" PRIu32
" uncompressedSize=%" PRIu32
"\n",
mCompressedSize, mUncompressedSize);
ALOGD(
" filenameLen=%" PRIu16
" extraLen=%" PRIu16
"\n",
mFileNameLength, mExtraFieldLength);
if (mFileName != NULL)
ALOGD(
" 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 = OK;
uint8_t 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) {
ALOGD(
"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 uint8_t[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 uint8_t[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 uint8_t[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)
{
uint8_t 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 OK;
}
/*
* Dump the contents of a CentralDirEntry object.
*/
void ZipEntry::CentralDirEntry::dump(
void)
const
{
ALOGD(
" CentralDirEntry contents:\n");
ALOGD(
" versMadeBy=%" PRIu16
" versToExt=%" PRIu16
" gpBits=0x%04" PRIx16
" compression=%" PRIu16 "\n",
mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
ALOGD(" modTime=0x%04" PRIx16 " modDate=0x%04" PRIx16 " crc32=0x%08" PRIx32 "\n",
mLastModFileTime, mLastModFileDate, mCRC32);
ALOGD(" compressedSize=%" PRIu32 " uncompressedSize=%" PRIu32 "\n",
mCompressedSize, mUncompressedSize);
ALOGD(" filenameLen=%" PRIu16 " extraLen=%" PRIu16 " commentLen=%" PRIu16 "\n",
mFileNameLength, mExtraFieldLength, mFileCommentLength);
ALOGD(" diskNumStart=%" PRIu16 " intAttr=0x%04" PRIx16 " extAttr=0x%08" PRIx32 " relOffset=%" PRIu32 "\n",
mDiskNumberStart, mInternalAttrs, mExternalAttrs,
mLocalHeaderRelOffset);
if (mFileName != NULL)
ALOGD(" filename: '%s'\n", mFileName);
if (mFileComment != NULL)
ALOGD(" comment: '%s'\n", mFileComment);
}
} // namespace android