Home | History | Annotate | Download | only in ziptime
      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 Zip archives.
     19 //
     20 
     21 #include "ZipFile.h"
     22 
     23 #include <memory.h>
     24 #include <sys/stat.h>
     25 #include <errno.h>
     26 #include <assert.h>
     27 #include <inttypes.h>
     28 
     29 using namespace android;
     30 
     31 #define LOG(...) fprintf(stderr, __VA_ARGS__)
     32 
     33 /*
     34  * Open a file and rewrite the headers
     35  */
     36 status_t ZipFile::rewrite(const char* zipFileName)
     37 {
     38     assert(mZipFp == NULL);     // no reopen
     39 
     40     /* open the file */
     41     mZipFp = fopen(zipFileName, "r+b");
     42     if (mZipFp == NULL) {
     43         int err = errno;
     44         LOG("fopen failed: %d\n", err);
     45         return -1;
     46     }
     47 
     48     /*
     49      * Load the central directory.  If that fails, then this probably
     50      * isn't a Zip archive.
     51      */
     52     return rewriteCentralDir();
     53 }
     54 
     55 /*
     56  * Find the central directory, read and rewrite the contents.
     57  *
     58  * The fun thing about ZIP archives is that they may or may not be
     59  * readable from start to end.  In some cases, notably for archives
     60  * that were written to stdout, the only length information is in the
     61  * central directory at the end of the file.
     62  *
     63  * Of course, the central directory can be followed by a variable-length
     64  * comment field, so we have to scan through it backwards.  The comment
     65  * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
     66  * itself, plus apparently sometimes people throw random junk on the end
     67  * just for the fun of it.
     68  *
     69  * This is all a little wobbly.  If the wrong value ends up in the EOCD
     70  * area, we're hosed.  This appears to be the way that everbody handles
     71  * it though, so we're in pretty good company if this fails.
     72  */
     73 status_t ZipFile::rewriteCentralDir(void)
     74 {
     75     status_t result = 0;
     76     uint8_t* buf = NULL;
     77     off_t fileLength, seekStart;
     78     long readAmount;
     79     int i;
     80 
     81     fseek(mZipFp, 0, SEEK_END);
     82     fileLength = ftell(mZipFp);
     83     rewind(mZipFp);
     84 
     85     /* too small to be a ZIP archive? */
     86     if (fileLength < EndOfCentralDir::kEOCDLen) {
     87         LOG("Length is %ld -- too small\n", (long)fileLength);
     88         result = -1;
     89         goto bail;
     90     }
     91 
     92     buf = new uint8_t[EndOfCentralDir::kMaxEOCDSearch];
     93     if (buf == NULL) {
     94         LOG("Failure allocating %d bytes for EOCD search",
     95              EndOfCentralDir::kMaxEOCDSearch);
     96         result = -1;
     97         goto bail;
     98     }
     99 
    100     if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
    101         seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
    102         readAmount = EndOfCentralDir::kMaxEOCDSearch;
    103     } else {
    104         seekStart = 0;
    105         readAmount = (long) fileLength;
    106     }
    107     if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
    108         LOG("Failure seeking to end of zip at %ld", (long) seekStart);
    109         result = -1;
    110         goto bail;
    111     }
    112 
    113     /* read the last part of the file into the buffer */
    114     if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
    115         LOG("short file? wanted %ld\n", readAmount);
    116         result = -1;
    117         goto bail;
    118     }
    119 
    120     /* find the end-of-central-dir magic */
    121     for (i = readAmount - 4; i >= 0; i--) {
    122         if (buf[i] == 0x50 &&
    123             ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
    124         {
    125             break;
    126         }
    127     }
    128     if (i < 0) {
    129         LOG("EOCD not found, not Zip\n");
    130         result = -1;
    131         goto bail;
    132     }
    133 
    134     /* extract eocd values */
    135     result = mEOCD.readBuf(buf + i, readAmount - i);
    136     if (result != 0) {
    137         LOG("Failure reading %ld bytes of EOCD values", readAmount - i);
    138         goto bail;
    139     }
    140 
    141     /*
    142      * So far so good.  "mCentralDirSize" is the size in bytes of the
    143      * central directory, so we can just seek back that far to find it.
    144      * We can also seek forward mCentralDirOffset bytes from the
    145      * start of the file.
    146      *
    147      * We're not guaranteed to have the rest of the central dir in the
    148      * buffer, nor are we guaranteed that the central dir will have any
    149      * sort of convenient size.  We need to skip to the start of it and
    150      * read the header, then the other goodies.
    151      *
    152      * The only thing we really need right now is the file comment, which
    153      * we're hoping to preserve.
    154      */
    155     if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
    156         LOG("Failure seeking to central dir offset %" PRIu32 "\n",
    157              mEOCD.mCentralDirOffset);
    158         result = -1;
    159         goto bail;
    160     }
    161 
    162     /*
    163      * Loop through and read the central dir entries.
    164      */
    165     int entry;
    166     for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
    167         ZipEntry* pEntry = new ZipEntry;
    168 
    169         result = pEntry->initAndRewriteFromCDE(mZipFp);
    170         if (result != 0) {
    171             LOG("initFromCDE failed\n");
    172             delete pEntry;
    173             goto bail;
    174         }
    175 
    176         delete pEntry;
    177     }
    178 
    179 
    180     /*
    181      * If all went well, we should now be back at the EOCD.
    182      */
    183     uint8_t checkBuf[4];
    184     if (fread(checkBuf, 1, 4, mZipFp) != 4) {
    185         LOG("EOCD check read failed\n");
    186         result = -1;
    187         goto bail;
    188     }
    189     if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
    190         LOG("EOCD read check failed\n");
    191         result = -1;
    192         goto bail;
    193     }
    194 
    195 bail:
    196     delete[] buf;
    197     return result;
    198 }
    199 
    200 /*
    201  * ===========================================================================
    202  *      ZipFile::EndOfCentralDir
    203  * ===========================================================================
    204  */
    205 
    206 /*
    207  * Read the end-of-central-dir fields.
    208  *
    209  * "buf" should be positioned at the EOCD signature, and should contain
    210  * the entire EOCD area including the comment.
    211  */
    212 status_t ZipFile::EndOfCentralDir::readBuf(const uint8_t* buf, int len)
    213 {
    214     uint16_t diskNumber, diskWithCentralDir, numEntries;
    215 
    216     if (len < kEOCDLen) {
    217         /* looks like ZIP file got truncated */
    218         LOG(" Zip EOCD: expected >= %d bytes, found %d\n",
    219             kEOCDLen, len);
    220         return -1;
    221     }
    222 
    223     /* this should probably be an assert() */
    224     if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
    225         return -1;
    226 
    227     diskNumber = ZipEntry::getShortLE(&buf[0x04]);
    228     diskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
    229     numEntries = ZipEntry::getShortLE(&buf[0x08]);
    230     mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
    231     mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
    232 
    233     if (diskNumber != 0 || diskWithCentralDir != 0 ||
    234         numEntries != mTotalNumEntries)
    235     {
    236         LOG("Archive spanning not supported\n");
    237         return -1;
    238     }
    239 
    240     return 0;
    241 }
    242