| /* | |
| * 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 Zip archives. | |
| // | |
| #include "ZipFile.h" | |
| #include <memory.h> | |
| #include <sys/stat.h> | |
| #include <errno.h> | |
| #include <assert.h> | |
| #include <inttypes.h> | |
| using namespace android; | |
| #define LOG(...) fprintf(stderr, __VA_ARGS__) | |
| /* | |
| * Open a file and rewrite the headers | |
| */ | |
| status_t ZipFile::rewrite(const char* zipFileName) | |
| { | |
| assert(mZipFp == NULL); // no reopen | |
| /* open the file */ | |
| mZipFp = fopen(zipFileName, "r+b"); | |
| if (mZipFp == NULL) { | |
| int err = errno; | |
| LOG("fopen failed: %d\n", err); | |
| return -1; | |
| } | |
| /* | |
| * Load the central directory. If that fails, then this probably | |
| * isn't a Zip archive. | |
| */ | |
| return rewriteCentralDir(); | |
| } | |
| /* | |
| * Find the central directory, read and rewrite the contents. | |
| * | |
| * The fun thing about ZIP archives is that they may or may not be | |
| * readable from start to end. In some cases, notably for archives | |
| * that were written to stdout, the only length information is in the | |
| * central directory at the end of the file. | |
| * | |
| * Of course, the central directory can be followed by a variable-length | |
| * comment field, so we have to scan through it backwards. The comment | |
| * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff | |
| * itself, plus apparently sometimes people throw random junk on the end | |
| * just for the fun of it. | |
| * | |
| * This is all a little wobbly. If the wrong value ends up in the EOCD | |
| * area, we're hosed. This appears to be the way that everbody handles | |
| * it though, so we're in pretty good company if this fails. | |
| */ | |
| status_t ZipFile::rewriteCentralDir(void) | |
| { | |
| status_t result = 0; | |
| uint8_t* buf = NULL; | |
| off_t fileLength, seekStart; | |
| long readAmount; | |
| int i; | |
| fseek(mZipFp, 0, SEEK_END); | |
| fileLength = ftell(mZipFp); | |
| rewind(mZipFp); | |
| /* too small to be a ZIP archive? */ | |
| if (fileLength < EndOfCentralDir::kEOCDLen) { | |
| LOG("Length is %ld -- too small\n", (long)fileLength); | |
| result = -1; | |
| goto bail; | |
| } | |
| buf = new uint8_t[EndOfCentralDir::kMaxEOCDSearch]; | |
| if (buf == NULL) { | |
| LOG("Failure allocating %d bytes for EOCD search", | |
| EndOfCentralDir::kMaxEOCDSearch); | |
| result = -1; | |
| goto bail; | |
| } | |
| if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { | |
| seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; | |
| readAmount = EndOfCentralDir::kMaxEOCDSearch; | |
| } else { | |
| seekStart = 0; | |
| readAmount = (long) fileLength; | |
| } | |
| if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { | |
| LOG("Failure seeking to end of zip at %ld", (long) seekStart); | |
| result = -1; | |
| goto bail; | |
| } | |
| /* read the last part of the file into the buffer */ | |
| if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { | |
| LOG("short file? wanted %ld\n", readAmount); | |
| result = -1; | |
| goto bail; | |
| } | |
| /* find the end-of-central-dir magic */ | |
| for (i = readAmount - 4; i >= 0; i--) { | |
| if (buf[i] == 0x50 && | |
| ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) | |
| { | |
| break; | |
| } | |
| } | |
| if (i < 0) { | |
| LOG("EOCD not found, not Zip\n"); | |
| result = -1; | |
| goto bail; | |
| } | |
| /* extract eocd values */ | |
| result = mEOCD.readBuf(buf + i, readAmount - i); | |
| if (result != 0) { | |
| LOG("Failure reading %ld bytes of EOCD values", readAmount - i); | |
| goto bail; | |
| } | |
| /* | |
| * So far so good. "mCentralDirSize" is the size in bytes of the | |
| * central directory, so we can just seek back that far to find it. | |
| * We can also seek forward mCentralDirOffset bytes from the | |
| * start of the file. | |
| * | |
| * We're not guaranteed to have the rest of the central dir in the | |
| * buffer, nor are we guaranteed that the central dir will have any | |
| * sort of convenient size. We need to skip to the start of it and | |
| * read the header, then the other goodies. | |
| * | |
| * The only thing we really need right now is the file comment, which | |
| * we're hoping to preserve. | |
| */ | |
| if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { | |
| LOG("Failure seeking to central dir offset %" PRIu32 "\n", | |
| mEOCD.mCentralDirOffset); | |
| result = -1; | |
| goto bail; | |
| } | |
| /* | |
| * Loop through and read the central dir entries. | |
| */ | |
| int entry; | |
| for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { | |
| ZipEntry* pEntry = new ZipEntry; | |
| result = pEntry->initAndRewriteFromCDE(mZipFp); | |
| if (result != 0) { | |
| LOG("initFromCDE failed\n"); | |
| delete pEntry; | |
| goto bail; | |
| } | |
| delete pEntry; | |
| } | |
| /* | |
| * If all went well, we should now be back at the EOCD. | |
| */ | |
| uint8_t checkBuf[4]; | |
| if (fread(checkBuf, 1, 4, mZipFp) != 4) { | |
| LOG("EOCD check read failed\n"); | |
| result = -1; | |
| goto bail; | |
| } | |
| if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { | |
| LOG("EOCD read check failed\n"); | |
| result = -1; | |
| goto bail; | |
| } | |
| bail: | |
| delete[] buf; | |
| return result; | |
| } | |
| /* | |
| * =========================================================================== | |
| * ZipFile::EndOfCentralDir | |
| * =========================================================================== | |
| */ | |
| /* | |
| * Read the end-of-central-dir fields. | |
| * | |
| * "buf" should be positioned at the EOCD signature, and should contain | |
| * the entire EOCD area including the comment. | |
| */ | |
| status_t ZipFile::EndOfCentralDir::readBuf(const uint8_t* buf, int len) | |
| { | |
| uint16_t diskNumber, diskWithCentralDir, numEntries; | |
| if (len < kEOCDLen) { | |
| /* looks like ZIP file got truncated */ | |
| LOG(" Zip EOCD: expected >= %d bytes, found %d\n", | |
| kEOCDLen, len); | |
| return -1; | |
| } | |
| /* this should probably be an assert() */ | |
| if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) | |
| return -1; | |
| diskNumber = ZipEntry::getShortLE(&buf[0x04]); | |
| diskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); | |
| numEntries = ZipEntry::getShortLE(&buf[0x08]); | |
| mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); | |
| mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); | |
| if (diskNumber != 0 || diskWithCentralDir != 0 || | |
| numEntries != mTotalNumEntries) | |
| { | |
| LOG("Archive spanning not supported\n"); | |
| return -1; | |
| } | |
| return 0; | |
| } |