1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17//
18// Access to entries in a Zip archive.
19//
20
21#define LOG_TAG "zip"
22
23#include "ZipEntry.h"
24#include <utils/Log.h>
25
26#include <assert.h>
27#include <inttypes.h>
28#include <stdio.h>
29#include <string.h>
30#include <time.h>
31
32using namespace android;
33
34/*
35 * Initialize a new ZipEntry structure from a FILE* positioned at a
36 * CentralDirectoryEntry.
37 *
38 * On exit, the file pointer will be at the start of the next CDE or
39 * at the EOCD.
40 */
41status_t ZipEntry::initFromCDE(FILE* fp)
42{
43    status_t result;
44    long posn;
45    bool hasDD;
46
47    //ALOGV("initFromCDE ---\n");
48
49    /* read the CDE */
50    result = mCDE.read(fp);
51    if (result != NO_ERROR) {
52        ALOGD("mCDE.read failed\n");
53        return result;
54    }
55
56    //mCDE.dump();
57
58    /* using the info in the CDE, go load up the LFH */
59    posn = ftell(fp);
60    if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
61        ALOGD("local header seek failed (%" PRIu32 ")\n",
62            mCDE.mLocalHeaderRelOffset);
63        return UNKNOWN_ERROR;
64    }
65
66    result = mLFH.read(fp);
67    if (result != NO_ERROR) {
68        ALOGD("mLFH.read failed\n");
69        return result;
70    }
71
72    if (fseek(fp, posn, SEEK_SET) != 0)
73        return UNKNOWN_ERROR;
74
75    //mLFH.dump();
76
77    /*
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.
82     */
83    hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
84    if (hasDD) {
85        // do something clever
86        //ALOGD("+++ has data descriptor\n");
87    }
88
89    /*
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.)
93     */
94    if (!hasDD && !compareHeaders()) {
95        ALOGW("WARNING: header mismatch\n");
96        // keep going?
97    }
98
99    /*
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.
104     */
105
106    return NO_ERROR;
107}
108
109/*
110 * Initialize a new entry.  Pass in the file name and an optional comment.
111 *
112 * Initializes the CDE and the LFH.
113 */
114void ZipEntry::initNew(const char* fileName, const char* comment)
115{
116    assert(fileName != NULL && *fileName != '\0');  // name required
117
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);
123    if (comment != NULL)
124        mCDE.mFileCommentLength = strlen(comment);
125    mCDE.mExternalAttrs = 0x81b60020;   // matches what WinZip does
126
127    if (mCDE.mFileNameLength > 0) {
128        mCDE.mFileName = new uint8_t[mCDE.mFileNameLength+1];
129        strcpy((char*) mCDE.mFileName, fileName);
130    }
131    if (mCDE.mFileCommentLength > 0) {
132        /* TODO: stop assuming null-terminated ASCII here? */
133        mCDE.mFileComment = new uint8_t[mCDE.mFileCommentLength+1];
134        assert(comment != NULL);
135        strcpy((char*) mCDE.mFileComment, comment);
136    }
137
138    copyCDEtoLFH();
139}
140
141/*
142 * Initialize a new entry, starting with the ZipEntry from a different
143 * archive.
144 *
145 * Initializes the CDE and the LFH.
146 */
147status_t ZipEntry::initFromExternal(const ZipEntry* pEntry)
148{
149    /*
150     * Copy everything in the CDE over, then fix up the hairy bits.
151     */
152    memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE));
153
154    if (mCDE.mFileNameLength > 0) {
155        mCDE.mFileName = new uint8_t[mCDE.mFileNameLength+1];
156        if (mCDE.mFileName == NULL)
157            return NO_MEMORY;
158        strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName);
159    }
160    if (mCDE.mFileCommentLength > 0) {
161        mCDE.mFileComment = new uint8_t[mCDE.mFileCommentLength+1];
162        if (mCDE.mFileComment == NULL)
163            return NO_MEMORY;
164        strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment);
165    }
166    if (mCDE.mExtraFieldLength > 0) {
167        /* we null-terminate this, though it may not be a string */
168        mCDE.mExtraField = new uint8_t[mCDE.mExtraFieldLength+1];
169        if (mCDE.mExtraField == NULL)
170            return NO_MEMORY;
171        memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField,
172            mCDE.mExtraFieldLength+1);
173    }
174
175    /* construct the LFH from the CDE */
176    copyCDEtoLFH();
177
178    /*
179     * The LFH "extra" field is independent of the CDE "extra", so we
180     * handle it here.
181     */
182    assert(mLFH.mExtraField == NULL);
183    mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
184    if (mLFH.mExtraFieldLength > 0) {
185        mLFH.mExtraField = new uint8_t[mLFH.mExtraFieldLength+1];
186        if (mLFH.mExtraField == NULL)
187            return NO_MEMORY;
188        memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
189            mLFH.mExtraFieldLength+1);
190    }
191
192    return NO_ERROR;
193}
194
195/*
196 * Insert pad bytes in the LFH by tweaking the "extra" field.  This will
197 * potentially confuse something that put "extra" data in here earlier,
198 * but I can't find an actual problem.
199 */
200status_t ZipEntry::addPadding(int padding)
201{
202    if (padding <= 0)
203        return INVALID_OPERATION;
204
205    //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
206    //    padding, mLFH.mExtraFieldLength, mCDE.mFileName);
207
208    if (mLFH.mExtraFieldLength > 0) {
209        /* extend existing field */
210        uint8_t* newExtra;
211
212        newExtra = new uint8_t[mLFH.mExtraFieldLength + padding];
213        if (newExtra == NULL)
214            return NO_MEMORY;
215        memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
216        memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
217
218        delete[] mLFH.mExtraField;
219        mLFH.mExtraField = newExtra;
220        mLFH.mExtraFieldLength += padding;
221    } else {
222        /* create new field */
223        mLFH.mExtraField = new uint8_t[padding];
224        memset(mLFH.mExtraField, 0, padding);
225        mLFH.mExtraFieldLength = padding;
226    }
227
228    return NO_ERROR;
229}
230
231/*
232 * Set the fields in the LFH equal to the corresponding fields in the CDE.
233 *
234 * This does not touch the LFH "extra" field.
235 */
236void ZipEntry::copyCDEtoLFH(void)
237{
238    mLFH.mVersionToExtract  = mCDE.mVersionToExtract;
239    mLFH.mGPBitFlag         = mCDE.mGPBitFlag;
240    mLFH.mCompressionMethod = mCDE.mCompressionMethod;
241    mLFH.mLastModFileTime   = mCDE.mLastModFileTime;
242    mLFH.mLastModFileDate   = mCDE.mLastModFileDate;
243    mLFH.mCRC32             = mCDE.mCRC32;
244    mLFH.mCompressedSize    = mCDE.mCompressedSize;
245    mLFH.mUncompressedSize  = mCDE.mUncompressedSize;
246    mLFH.mFileNameLength    = mCDE.mFileNameLength;
247    // the "extra field" is independent
248
249    delete[] mLFH.mFileName;
250    if (mLFH.mFileNameLength > 0) {
251        mLFH.mFileName = new uint8_t[mLFH.mFileNameLength+1];
252        strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
253    } else {
254        mLFH.mFileName = NULL;
255    }
256}
257
258/*
259 * Set some information about a file after we add it.
260 */
261void ZipEntry::setDataInfo(long uncompLen, long compLen, uint32_t crc32,
262    int compressionMethod)
263{
264    mCDE.mCompressionMethod = compressionMethod;
265    mCDE.mCRC32 = crc32;
266    mCDE.mCompressedSize = compLen;
267    mCDE.mUncompressedSize = uncompLen;
268    mCDE.mCompressionMethod = compressionMethod;
269    if (compressionMethod == kCompressDeflated) {
270        mCDE.mGPBitFlag |= 0x0002;      // indicates maximum compression used
271    }
272    copyCDEtoLFH();
273}
274
275/*
276 * See if the data in mCDE and mLFH match up.  This is mostly useful for
277 * debugging these classes, but it can be used to identify damaged
278 * archives.
279 *
280 * Returns "false" if they differ.
281 */
282bool ZipEntry::compareHeaders(void) const
283{
284    if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
285        ALOGV("cmp: VersionToExtract\n");
286        return false;
287    }
288    if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
289        ALOGV("cmp: GPBitFlag\n");
290        return false;
291    }
292    if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
293        ALOGV("cmp: CompressionMethod\n");
294        return false;
295    }
296    if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
297        ALOGV("cmp: LastModFileTime\n");
298        return false;
299    }
300    if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
301        ALOGV("cmp: LastModFileDate\n");
302        return false;
303    }
304    if (mCDE.mCRC32 != mLFH.mCRC32) {
305        ALOGV("cmp: CRC32\n");
306        return false;
307    }
308    if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
309        ALOGV("cmp: CompressedSize\n");
310        return false;
311    }
312    if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
313        ALOGV("cmp: UncompressedSize\n");
314        return false;
315    }
316    if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
317        ALOGV("cmp: FileNameLength\n");
318        return false;
319    }
320#if 0       // this seems to be used for padding, not real data
321    if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
322        ALOGV("cmp: ExtraFieldLength\n");
323        return false;
324    }
325#endif
326    if (mCDE.mFileName != NULL) {
327        if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
328            ALOGV("cmp: FileName\n");
329            return false;
330        }
331    }
332
333    return true;
334}
335
336
337/*
338 * Convert the DOS date/time stamp into a UNIX time stamp.
339 */
340time_t ZipEntry::getModWhen(void) const
341{
342    struct tm parts;
343
344    parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
345    parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
346    parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
347    parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
348    parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
349    parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
350    parts.tm_wday = parts.tm_yday = 0;
351    parts.tm_isdst = -1;        // DST info "not available"
352
353    return mktime(&parts);
354}
355
356/*
357 * Set the CDE/LFH timestamp from UNIX time.
358 */
359void ZipEntry::setModWhen(time_t when)
360{
361#if !defined(_WIN32)
362    struct tm tmResult;
363#endif
364    time_t even;
365    uint16_t zdate, ztime;
366
367    struct tm* ptm;
368
369    /* round up to an even number of seconds */
370    even = (time_t)(((unsigned long)(when) + 1) & (~1));
371
372    /* expand */
373#if !defined(_WIN32)
374    ptm = localtime_r(&even, &tmResult);
375#else
376    ptm = localtime(&even);
377#endif
378
379    int year;
380    year = ptm->tm_year;
381    if (year < 80)
382        year = 80;
383
384    zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
385    ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
386
387    mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
388    mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
389}
390
391
392/*
393 * ===========================================================================
394 *      ZipEntry::LocalFileHeader
395 * ===========================================================================
396 */
397
398/*
399 * Read a local file header.
400 *
401 * On entry, "fp" points to the signature at the start of the header.
402 * On exit, "fp" points to the start of data.
403 */
404status_t ZipEntry::LocalFileHeader::read(FILE* fp)
405{
406    status_t result = NO_ERROR;
407    uint8_t buf[kLFHLen];
408
409    assert(mFileName == NULL);
410    assert(mExtraField == NULL);
411
412    if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
413        result = UNKNOWN_ERROR;
414        goto bail;
415    }
416
417    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
418        ALOGD("whoops: didn't find expected signature\n");
419        result = UNKNOWN_ERROR;
420        goto bail;
421    }
422
423    mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
424    mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
425    mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
426    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
427    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
428    mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
429    mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
430    mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
431    mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
432    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
433
434    // TODO: validate sizes
435
436    /* grab filename */
437    if (mFileNameLength != 0) {
438        mFileName = new uint8_t[mFileNameLength+1];
439        if (mFileName == NULL) {
440            result = NO_MEMORY;
441            goto bail;
442        }
443        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
444            result = UNKNOWN_ERROR;
445            goto bail;
446        }
447        mFileName[mFileNameLength] = '\0';
448    }
449
450    /* grab extra field */
451    if (mExtraFieldLength != 0) {
452        mExtraField = new uint8_t[mExtraFieldLength+1];
453        if (mExtraField == NULL) {
454            result = NO_MEMORY;
455            goto bail;
456        }
457        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
458            result = UNKNOWN_ERROR;
459            goto bail;
460        }
461        mExtraField[mExtraFieldLength] = '\0';
462    }
463
464bail:
465    return result;
466}
467
468/*
469 * Write a local file header.
470 */
471status_t ZipEntry::LocalFileHeader::write(FILE* fp)
472{
473    uint8_t buf[kLFHLen];
474
475    ZipEntry::putLongLE(&buf[0x00], kSignature);
476    ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
477    ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
478    ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
479    ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
480    ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
481    ZipEntry::putLongLE(&buf[0x0e], mCRC32);
482    ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
483    ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
484    ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
485    ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
486
487    if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
488        return UNKNOWN_ERROR;
489
490    /* write filename */
491    if (mFileNameLength != 0) {
492        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
493            return UNKNOWN_ERROR;
494    }
495
496    /* write "extra field" */
497    if (mExtraFieldLength != 0) {
498        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
499            return UNKNOWN_ERROR;
500    }
501
502    return NO_ERROR;
503}
504
505
506/*
507 * Dump the contents of a LocalFileHeader object.
508 */
509void ZipEntry::LocalFileHeader::dump(void) const
510{
511    ALOGD(" LocalFileHeader contents:\n");
512    ALOGD("  versToExt=%" PRIu16 " gpBits=0x%04" PRIx16 " compression=%" PRIu16 "\n",
513        mVersionToExtract, mGPBitFlag, mCompressionMethod);
514    ALOGD("  modTime=0x%04" PRIx16 " modDate=0x%04" PRIx16 " crc32=0x%08" PRIx32 "\n",
515        mLastModFileTime, mLastModFileDate, mCRC32);
516    ALOGD("  compressedSize=%" PRIu32 " uncompressedSize=%" PRIu32 "\n",
517        mCompressedSize, mUncompressedSize);
518    ALOGD("  filenameLen=%" PRIu16 " extraLen=%" PRIu16 "\n",
519        mFileNameLength, mExtraFieldLength);
520    if (mFileName != NULL)
521        ALOGD("  filename: '%s'\n", mFileName);
522}
523
524
525/*
526 * ===========================================================================
527 *      ZipEntry::CentralDirEntry
528 * ===========================================================================
529 */
530
531/*
532 * Read the central dir entry that appears next in the file.
533 *
534 * On entry, "fp" should be positioned on the signature bytes for the
535 * entry.  On exit, "fp" will point at the signature word for the next
536 * entry or for the EOCD.
537 */
538status_t ZipEntry::CentralDirEntry::read(FILE* fp)
539{
540    status_t result = NO_ERROR;
541    uint8_t buf[kCDELen];
542
543    /* no re-use */
544    assert(mFileName == NULL);
545    assert(mExtraField == NULL);
546    assert(mFileComment == NULL);
547
548    if (fread(buf, 1, kCDELen, fp) != kCDELen) {
549        result = UNKNOWN_ERROR;
550        goto bail;
551    }
552
553    if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
554        ALOGD("Whoops: didn't find expected signature\n");
555        result = UNKNOWN_ERROR;
556        goto bail;
557    }
558
559    mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
560    mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
561    mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
562    mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
563    mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
564    mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
565    mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
566    mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
567    mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
568    mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
569    mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
570    mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
571    mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
572    mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
573    mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
574    mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
575
576    // TODO: validate sizes and offsets
577
578    /* grab filename */
579    if (mFileNameLength != 0) {
580        mFileName = new uint8_t[mFileNameLength+1];
581        if (mFileName == NULL) {
582            result = NO_MEMORY;
583            goto bail;
584        }
585        if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
586            result = UNKNOWN_ERROR;
587            goto bail;
588        }
589        mFileName[mFileNameLength] = '\0';
590    }
591
592    /* read "extra field" */
593    if (mExtraFieldLength != 0) {
594        mExtraField = new uint8_t[mExtraFieldLength+1];
595        if (mExtraField == NULL) {
596            result = NO_MEMORY;
597            goto bail;
598        }
599        if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
600            result = UNKNOWN_ERROR;
601            goto bail;
602        }
603        mExtraField[mExtraFieldLength] = '\0';
604    }
605
606
607    /* grab comment, if any */
608    if (mFileCommentLength != 0) {
609        mFileComment = new uint8_t[mFileCommentLength+1];
610        if (mFileComment == NULL) {
611            result = NO_MEMORY;
612            goto bail;
613        }
614        if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
615        {
616            result = UNKNOWN_ERROR;
617            goto bail;
618        }
619        mFileComment[mFileCommentLength] = '\0';
620    }
621
622bail:
623    return result;
624}
625
626/*
627 * Write a central dir entry.
628 */
629status_t ZipEntry::CentralDirEntry::write(FILE* fp)
630{
631    uint8_t buf[kCDELen];
632
633    ZipEntry::putLongLE(&buf[0x00], kSignature);
634    ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
635    ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
636    ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
637    ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
638    ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
639    ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
640    ZipEntry::putLongLE(&buf[0x10], mCRC32);
641    ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
642    ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
643    ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
644    ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
645    ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
646    ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
647    ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
648    ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
649    ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
650
651    if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
652        return UNKNOWN_ERROR;
653
654    /* write filename */
655    if (mFileNameLength != 0) {
656        if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
657            return UNKNOWN_ERROR;
658    }
659
660    /* write "extra field" */
661    if (mExtraFieldLength != 0) {
662        if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
663            return UNKNOWN_ERROR;
664    }
665
666    /* write comment */
667    if (mFileCommentLength != 0) {
668        if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
669            return UNKNOWN_ERROR;
670    }
671
672    return NO_ERROR;
673}
674
675/*
676 * Dump the contents of a CentralDirEntry object.
677 */
678void ZipEntry::CentralDirEntry::dump(void) const
679{
680    ALOGD(" CentralDirEntry contents:\n");
681    ALOGD("  versMadeBy=%" PRIu16 " versToExt=%" PRIu16 " gpBits=0x%04" PRIx16 " compression=%" PRIu16 "\n",
682        mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
683    ALOGD("  modTime=0x%04" PRIx16 " modDate=0x%04" PRIx16 " crc32=0x%08" PRIx32 "\n",
684        mLastModFileTime, mLastModFileDate, mCRC32);
685    ALOGD("  compressedSize=%" PRIu32 " uncompressedSize=%" PRIu32 "\n",
686        mCompressedSize, mUncompressedSize);
687    ALOGD("  filenameLen=%" PRIu16 " extraLen=%" PRIu16 " commentLen=%" PRIu16 "\n",
688        mFileNameLength, mExtraFieldLength, mFileCommentLength);
689    ALOGD("  diskNumStart=%" PRIu16 " intAttr=0x%04" PRIx16 " extAttr=0x%08" PRIx32 " relOffset=%" PRIu32 "\n",
690        mDiskNumberStart, mInternalAttrs, mExternalAttrs,
691        mLocalHeaderRelOffset);
692
693    if (mFileName != NULL)
694        ALOGD("  filename: '%s'\n", mFileName);
695    if (mFileComment != NULL)
696        ALOGD("  comment: '%s'\n", mFileComment);
697}
698
699