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