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