2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 // Access to entries in a Zip archive.
24 #include <utils/Log.h>
32 using namespace android;
35 * Initialize a new ZipEntry structure from a FILE* positioned at a
36 * CentralDirectoryEntry.
38 * On exit, the file pointer will be at the start of the next CDE or
41 status_t ZipEntry::initFromCDE(FILE* fp)
47 //ALOGV("initFromCDE ---\n");
50 result = mCDE.read(fp);
51 if (result != NO_ERROR) {
52 ALOGD("mCDE.read failed\n");
58 /* using the info in the CDE, go load up the LFH */
60 if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
61 ALOGD("local header seek failed (%ld)\n",
62 mCDE.mLocalHeaderRelOffset);
66 result = mLFH.read(fp);
67 if (result != NO_ERROR) {
68 ALOGD("mLFH.read failed\n");
72 if (fseek(fp, posn, SEEK_SET) != 0)
78 * We *might* need to read the Data Descriptor at this point and
79 * integrate it into the LFH. If this bit is set, the CRC-32,
80 * compressed size, and uncompressed size will be zero. In practice
81 * these seem to be rare.
83 hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
85 // do something clever
86 //ALOGD("+++ has data descriptor\n");
90 * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
91 * flag is set, because the LFH is incomplete. (Not a problem, since we
92 * prefer the CDE values.)
94 if (!hasDD && !compareHeaders()) {
95 ALOGW("warning: header mismatch\n");
100 * If the mVersionToExtract is greater than 20, we may have an
101 * issue unpacking the record -- could be encrypted, compressed
102 * with something we don't support, or use Zip64 extensions. We
103 * can defer worrying about that to when we're extracting data.
110 * Initialize a new entry. Pass in the file name and an optional comment.
112 * Initializes the CDE and the LFH.
114 void ZipEntry::initNew(const char* fileName, const char* comment)
116 assert(fileName != NULL && *fileName != '\0'); // name required
118 /* most fields are properly initialized by constructor */
119 mCDE.mVersionMadeBy = kDefaultMadeBy;
120 mCDE.mVersionToExtract = kDefaultVersion;
121 mCDE.mCompressionMethod = kCompressStored;
122 mCDE.mFileNameLength = strlen(fileName);
124 mCDE.mFileCommentLength = strlen(comment);
125 mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
127 if (mCDE.mFileNameLength > 0) {
128 mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
129 strcpy((char*) mCDE.mFileName, fileName);
131 if (mCDE.mFileCommentLength > 0) {
132 /* TODO: stop assuming null-terminated ASCII here? */
133 mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
134 strcpy((char*) mCDE.mFileComment, comment);
141 * Initialize a new entry, starting with the ZipEntry from a different
144 * Initializes the CDE and the LFH.
146 status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
147 const ZipEntry* pEntry)
150 // Check whether we got all the memory needed.
151 if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
152 (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
153 (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
157 /* construct the LFH from the CDE */
161 * The LFH "extra" field is independent of the CDE "extra", so we
164 assert(mLFH.mExtraField == NULL);
165 mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
166 if (mLFH.mExtraFieldLength > 0) {
167 mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
168 if (mLFH.mExtraField == NULL)
170 memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
171 mLFH.mExtraFieldLength+1);
178 * Insert pad bytes in the LFH by tweaking the "extra" field. This will
179 * potentially confuse something that put "extra" data in here earlier,
180 * but I can't find an actual problem.
182 status_t ZipEntry::addPadding(int padding)
185 return INVALID_OPERATION;
187 //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
188 // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
190 if (mLFH.mExtraFieldLength > 0) {
191 /* extend existing field */
192 unsigned char* newExtra;
194 newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
195 if (newExtra == NULL)
197 memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
198 memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
200 delete[] mLFH.mExtraField;
201 mLFH.mExtraField = newExtra;
202 mLFH.mExtraFieldLength += padding;
204 /* create new field */
205 mLFH.mExtraField = new unsigned char[padding];
206 memset(mLFH.mExtraField, 0, padding);
207 mLFH.mExtraFieldLength = padding;
214 * Set the fields in the LFH equal to the corresponding fields in the CDE.
216 * This does not touch the LFH "extra" field.
218 void ZipEntry::copyCDEtoLFH(void)
220 mLFH.mVersionToExtract = mCDE.mVersionToExtract;
221 mLFH.mGPBitFlag = mCDE.mGPBitFlag;
222 mLFH.mCompressionMethod = mCDE.mCompressionMethod;
223 mLFH.mLastModFileTime = mCDE.mLastModFileTime;
224 mLFH.mLastModFileDate = mCDE.mLastModFileDate;
225 mLFH.mCRC32 = mCDE.mCRC32;
226 mLFH.mCompressedSize = mCDE.mCompressedSize;
227 mLFH.mUncompressedSize = mCDE.mUncompressedSize;
228 mLFH.mFileNameLength = mCDE.mFileNameLength;
229 // the "extra field" is independent
231 delete[] mLFH.mFileName;
232 if (mLFH.mFileNameLength > 0) {
233 mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
234 strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
236 mLFH.mFileName = NULL;
241 * Set some information about a file after we add it.
243 void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
244 int compressionMethod)
246 mCDE.mCompressionMethod = compressionMethod;
248 mCDE.mCompressedSize = compLen;
249 mCDE.mUncompressedSize = uncompLen;
250 mCDE.mCompressionMethod = compressionMethod;
251 if (compressionMethod == kCompressDeflated) {
252 mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
258 * See if the data in mCDE and mLFH match up. This is mostly useful for
259 * debugging these classes, but it can be used to identify damaged
262 * Returns "false" if they differ.
264 bool ZipEntry::compareHeaders(void) const
266 if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
267 ALOGV("cmp: VersionToExtract\n");
270 if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
271 ALOGV("cmp: GPBitFlag\n");
274 if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
275 ALOGV("cmp: CompressionMethod\n");
278 if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
279 ALOGV("cmp: LastModFileTime\n");
282 if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
283 ALOGV("cmp: LastModFileDate\n");
286 if (mCDE.mCRC32 != mLFH.mCRC32) {
287 ALOGV("cmp: CRC32\n");
290 if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
291 ALOGV("cmp: CompressedSize\n");
294 if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
295 ALOGV("cmp: UncompressedSize\n");
298 if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
299 ALOGV("cmp: FileNameLength\n");
302 #if 0 // this seems to be used for padding, not real data
303 if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
304 ALOGV("cmp: ExtraFieldLength\n");
308 if (mCDE.mFileName != NULL) {
309 if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
310 ALOGV("cmp: FileName\n");
320 * Convert the DOS date/time stamp into a UNIX time stamp.
322 time_t ZipEntry::getModWhen(void) const
326 parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
327 parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
328 parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
329 parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
330 parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
331 parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
332 parts.tm_wday = parts.tm_yday = 0;
333 parts.tm_isdst = -1; // DST info "not available"
335 return mktime(&parts);
339 * Set the CDE/LFH timestamp from UNIX time.
341 void ZipEntry::setModWhen(time_t when)
347 unsigned short zdate, ztime;
351 /* round up to an even number of seconds */
352 even = (time_t)(((unsigned long)(when) + 1) & (~1));
356 ptm = localtime_r(&even, &tmResult);
358 ptm = localtime(&even);
366 zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
367 ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
369 mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
370 mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
375 * ===========================================================================
376 * ZipEntry::LocalFileHeader
377 * ===========================================================================
381 * Read a local file header.
383 * On entry, "fp" points to the signature at the start of the header.
384 * On exit, "fp" points to the start of data.
386 status_t ZipEntry::LocalFileHeader::read(FILE* fp)
388 status_t result = NO_ERROR;
389 unsigned char buf[kLFHLen];
391 assert(mFileName == NULL);
392 assert(mExtraField == NULL);
394 if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
395 result = UNKNOWN_ERROR;
399 if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
400 ALOGD("whoops: didn't find expected signature\n");
401 result = UNKNOWN_ERROR;
405 mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
406 mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
407 mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
408 mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
409 mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
410 mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
411 mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
412 mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
413 mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
414 mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
416 // TODO: validate sizes
419 if (mFileNameLength != 0) {
420 mFileName = new unsigned char[mFileNameLength+1];
421 if (mFileName == NULL) {
425 if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
426 result = UNKNOWN_ERROR;
429 mFileName[mFileNameLength] = '\0';
432 /* grab extra field */
433 if (mExtraFieldLength != 0) {
434 mExtraField = new unsigned char[mExtraFieldLength+1];
435 if (mExtraField == NULL) {
439 if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
440 result = UNKNOWN_ERROR;
443 mExtraField[mExtraFieldLength] = '\0';
451 * Write a local file header.
453 status_t ZipEntry::LocalFileHeader::write(FILE* fp)
455 unsigned char buf[kLFHLen];
457 ZipEntry::putLongLE(&buf[0x00], kSignature);
458 ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
459 ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
460 ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
461 ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
462 ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
463 ZipEntry::putLongLE(&buf[0x0e], mCRC32);
464 ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
465 ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
466 ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
467 ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
469 if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
470 return UNKNOWN_ERROR;
473 if (mFileNameLength != 0) {
474 if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
475 return UNKNOWN_ERROR;
478 /* write "extra field" */
479 if (mExtraFieldLength != 0) {
480 if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
481 return UNKNOWN_ERROR;
489 * Dump the contents of a LocalFileHeader object.
491 void ZipEntry::LocalFileHeader::dump(void) const
493 ALOGD(" LocalFileHeader contents:\n");
494 ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
495 mVersionToExtract, mGPBitFlag, mCompressionMethod);
496 ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
497 mLastModFileTime, mLastModFileDate, mCRC32);
498 ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
499 mCompressedSize, mUncompressedSize);
500 ALOGD(" filenameLen=%u extraLen=%u\n",
501 mFileNameLength, mExtraFieldLength);
502 if (mFileName != NULL)
503 ALOGD(" filename: '%s'\n", mFileName);
508 * ===========================================================================
509 * ZipEntry::CentralDirEntry
510 * ===========================================================================
514 * Read the central dir entry that appears next in the file.
516 * On entry, "fp" should be positioned on the signature bytes for the
517 * entry. On exit, "fp" will point at the signature word for the next
518 * entry or for the EOCD.
520 status_t ZipEntry::CentralDirEntry::read(FILE* fp)
522 status_t result = NO_ERROR;
523 unsigned char buf[kCDELen];
526 assert(mFileName == NULL);
527 assert(mExtraField == NULL);
528 assert(mFileComment == NULL);
530 if (fread(buf, 1, kCDELen, fp) != kCDELen) {
531 result = UNKNOWN_ERROR;
535 if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
536 ALOGD("Whoops: didn't find expected signature\n");
537 result = UNKNOWN_ERROR;
541 mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
542 mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
543 mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
544 mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
545 mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
546 mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
547 mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
548 mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
549 mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
550 mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
551 mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
552 mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
553 mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
554 mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
555 mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
556 mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
558 // TODO: validate sizes and offsets
561 if (mFileNameLength != 0) {
562 mFileName = new unsigned char[mFileNameLength+1];
563 if (mFileName == NULL) {
567 if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
568 result = UNKNOWN_ERROR;
571 mFileName[mFileNameLength] = '\0';
574 /* read "extra field" */
575 if (mExtraFieldLength != 0) {
576 mExtraField = new unsigned char[mExtraFieldLength+1];
577 if (mExtraField == NULL) {
581 if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
582 result = UNKNOWN_ERROR;
585 mExtraField[mExtraFieldLength] = '\0';
589 /* grab comment, if any */
590 if (mFileCommentLength != 0) {
591 mFileComment = new unsigned char[mFileCommentLength+1];
592 if (mFileComment == NULL) {
596 if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
598 result = UNKNOWN_ERROR;
601 mFileComment[mFileCommentLength] = '\0';
609 * Write a central dir entry.
611 status_t ZipEntry::CentralDirEntry::write(FILE* fp)
613 unsigned char buf[kCDELen];
615 ZipEntry::putLongLE(&buf[0x00], kSignature);
616 ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
617 ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
618 ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
619 ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
620 ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
621 ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
622 ZipEntry::putLongLE(&buf[0x10], mCRC32);
623 ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
624 ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
625 ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
626 ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
627 ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
628 ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
629 ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
630 ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
631 ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
633 if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
634 return UNKNOWN_ERROR;
637 if (mFileNameLength != 0) {
638 if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
639 return UNKNOWN_ERROR;
642 /* write "extra field" */
643 if (mExtraFieldLength != 0) {
644 if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
645 return UNKNOWN_ERROR;
649 if (mFileCommentLength != 0) {
650 if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
651 return UNKNOWN_ERROR;
658 * Dump the contents of a CentralDirEntry object.
660 void ZipEntry::CentralDirEntry::dump(void) const
662 ALOGD(" CentralDirEntry contents:\n");
663 ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
664 mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
665 ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
666 mLastModFileTime, mLastModFileDate, mCRC32);
667 ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
668 mCompressedSize, mUncompressedSize);
669 ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
670 mFileNameLength, mExtraFieldLength, mFileCommentLength);
671 ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
672 mDiskNumberStart, mInternalAttrs, mExternalAttrs,
673 mLocalHeaderRelOffset);
675 if (mFileName != NULL)
676 ALOGD(" filename: '%s'\n", mFileName);
677 if (mFileComment != NULL)
678 ALOGD(" comment: '%s'\n", mFileComment);
682 * Copy-assignment operator for CentralDirEntry.
684 ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
691 delete[] mExtraField;
692 delete[] mFileComment;
695 mVersionMadeBy = src.mVersionMadeBy;
696 mVersionToExtract = src.mVersionToExtract;
697 mGPBitFlag = src.mGPBitFlag;
698 mCompressionMethod = src.mCompressionMethod;
699 mLastModFileTime = src.mLastModFileTime;
700 mLastModFileDate = src.mLastModFileDate;
702 mCompressedSize = src.mCompressedSize;
703 mUncompressedSize = src.mUncompressedSize;
704 mFileNameLength = src.mFileNameLength;
705 mExtraFieldLength = src.mExtraFieldLength;
706 mFileCommentLength = src.mFileCommentLength;
707 mDiskNumberStart = src.mDiskNumberStart;
708 mInternalAttrs = src.mInternalAttrs;
709 mExternalAttrs = src.mExternalAttrs;
710 mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
712 // Copy strings, if necessary.
713 if (mFileNameLength > 0) {
714 mFileName = new unsigned char[mFileNameLength + 1];
715 if (mFileName != NULL)
716 strcpy((char*)mFileName, (char*)src.mFileName);
720 if (mFileCommentLength > 0) {
721 mFileComment = new unsigned char[mFileCommentLength + 1];
722 if (mFileComment != NULL)
723 strcpy((char*)mFileComment, (char*)src.mFileComment);
727 if (mExtraFieldLength > 0) {
728 /* we null-terminate this, though it may not be a string */
729 mExtraField = new unsigned char[mExtraFieldLength + 1];
730 if (mExtraField != NULL)
731 memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);