blob: 579e5319f584fca1bb13618a0211edbf3afd6545 [file] [log] [blame]
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Simple Zip file support.
5 */
6#include "safe_iop.h"
7#include "zlib.h"
8
9#include <errno.h>
10#include <fcntl.h>
11#include <limits.h>
12#include <stdint.h> // for uintptr_t
13#include <stdlib.h>
14#include <sys/stat.h> // for S_ISLNK()
15#include <unistd.h>
16
17#define LOG_TAG "minzip"
18#include "Zip.h"
19#include "Bits.h"
20#include "Log.h"
21#include "DirUtil.h"
22
23#undef NDEBUG // do this after including Log.h
24#include <assert.h>
25
26#define SORT_ENTRIES 1
27
28/*
29 * Offset and length constants (java.util.zip naming convention).
30 */
31enum {
32 CENSIG = 0x02014b50, // PK12
33 CENHDR = 46,
34
35 CENVEM = 4,
36 CENVER = 6,
37 CENFLG = 8,
38 CENHOW = 10,
39 CENTIM = 12,
40 CENCRC = 16,
41 CENSIZ = 20,
42 CENLEN = 24,
43 CENNAM = 28,
Doug Zongker596271f2009-04-29 16:52:04 -070044 CENEXT = 30,
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080045 CENCOM = 32,
46 CENDSK = 34,
47 CENATT = 36,
48 CENATX = 38,
49 CENOFF = 42,
50
51 ENDSIG = 0x06054b50, // PK56
52 ENDHDR = 22,
53
54 ENDSUB = 8,
55 ENDTOT = 10,
56 ENDSIZ = 12,
57 ENDOFF = 16,
58 ENDCOM = 20,
59
60 EXTSIG = 0x08074b50, // PK78
61 EXTHDR = 16,
62
63 EXTCRC = 4,
64 EXTSIZ = 8,
65 EXTLEN = 12,
66
67 LOCSIG = 0x04034b50, // PK34
68 LOCHDR = 30,
Doug Zongker596271f2009-04-29 16:52:04 -070069
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080070 LOCVER = 4,
71 LOCFLG = 6,
72 LOCHOW = 8,
73 LOCTIM = 10,
74 LOCCRC = 14,
Doug Zongker596271f2009-04-29 16:52:04 -070075 LOCSIZ = 18,
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -080076 LOCLEN = 22,
77 LOCNAM = 26,
78 LOCEXT = 28,
79
80 STORED = 0,
81 DEFLATED = 8,
82
83 CENVEM_UNIX = 3 << 8, // the high byte of CENVEM
84};
85
86
87/*
88 * For debugging, dump the contents of a ZipEntry.
89 */
90#if 0
91static void dumpEntry(const ZipEntry* pEntry)
92{
93 LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
94 LOGI(" off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
95 pEntry->compLen, pEntry->uncompLen, pEntry->compression);
96}
97#endif
98
99/*
100 * (This is a mzHashTableLookup callback.)
101 *
102 * Compare two ZipEntry structs, by name.
103 */
104static int hashcmpZipEntry(const void* ventry1, const void* ventry2)
105{
106 const ZipEntry* entry1 = (const ZipEntry*) ventry1;
107 const ZipEntry* entry2 = (const ZipEntry*) ventry2;
108
109 if (entry1->fileNameLen != entry2->fileNameLen)
110 return entry1->fileNameLen - entry2->fileNameLen;
111 return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen);
112}
113
114/*
115 * (This is a mzHashTableLookup callback.)
116 *
117 * find a ZipEntry struct by name.
118 */
119static int hashcmpZipName(const void* ventry, const void* vname)
120{
121 const ZipEntry* entry = (const ZipEntry*) ventry;
122 const char* name = (const char*) vname;
123 unsigned int nameLen = strlen(name);
124
125 if (entry->fileNameLen != nameLen)
126 return entry->fileNameLen - nameLen;
127 return memcmp(entry->fileName, name, nameLen);
128}
129
130/*
131 * Compute the hash code for a ZipEntry filename.
132 *
133 * Not expected to be compatible with any other hash function, so we init
134 * to 2 to ensure it doesn't happen to match.
135 */
136static unsigned int computeHash(const char* name, int nameLen)
137{
138 unsigned int hash = 2;
139
140 while (nameLen--)
141 hash = hash * 31 + *name++;
142
143 return hash;
144}
145
146static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry)
147{
148 unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen);
149 const ZipEntry* found;
150
151 found = (const ZipEntry*)mzHashTableLookup(pHash,
152 itemHash, pEntry, hashcmpZipEntry, true);
153 if (found != pEntry) {
154 LOGW("WARNING: duplicate entry '%.*s' in Zip\n",
155 found->fileNameLen, found->fileName);
156 /* keep going */
157 }
158}
159
160static int validFilename(const char *fileName, unsigned int fileNameLen)
161{
162 // Forbid super long filenames.
163 if (fileNameLen >= PATH_MAX) {
164 LOGW("Filename too long (%d chatacters)\n", fileNameLen);
165 return 0;
166 }
167
168 // Require all characters to be printable ASCII (no NUL, no UTF-8, etc).
169 unsigned int i;
170 for (i = 0; i < fileNameLen; ++i) {
171 if (fileName[i] < 32 || fileName[i] >= 127) {
172 LOGW("Filename contains invalid character '\%03o'\n", fileName[i]);
173 return 0;
174 }
175 }
176
177 return 1;
178}
179
180/*
181 * Parse the contents of a Zip archive. After confirming that the file
182 * is in fact a Zip, we scan out the contents of the central directory and
183 * store it in a hash table.
184 *
185 * Returns "true" on success.
186 */
Doug Zongker99916f02014-01-13 14:16:58 -0800187static bool parseZipArchive(ZipArchive* pArchive)
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800188{
189 bool result = false;
190 const unsigned char* ptr;
191 unsigned int i, numEntries, cdOffset;
192 unsigned int val;
193
194 /*
195 * The first 4 bytes of the file will either be the local header
196 * signature for the first file (LOCSIG) or, if the archive doesn't
197 * have any files in it, the end-of-central-directory signature (ENDSIG).
198 */
Doug Zongker99916f02014-01-13 14:16:58 -0800199 val = get4LE(pArchive->addr);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800200 if (val == ENDSIG) {
201 LOGI("Found Zip archive, but it looks empty\n");
202 goto bail;
203 } else if (val != LOCSIG) {
204 LOGV("Not a Zip archive (found 0x%08x)\n", val);
205 goto bail;
206 }
207
208 /*
209 * Find the EOCD. We'll find it immediately unless they have a file
210 * comment.
211 */
Doug Zongker99916f02014-01-13 14:16:58 -0800212 ptr = pArchive->addr + pArchive->length - ENDHDR;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800213
Doug Zongker99916f02014-01-13 14:16:58 -0800214 while (ptr >= (const unsigned char*) pArchive->addr) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800215 if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
216 break;
217 ptr--;
218 }
Doug Zongker99916f02014-01-13 14:16:58 -0800219 if (ptr < (const unsigned char*) pArchive->addr) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800220 LOGI("Could not find end-of-central-directory in Zip\n");
221 goto bail;
222 }
223
224 /*
225 * There are two interesting items in the EOCD block: the number of
226 * entries in the file, and the file offset of the start of the
227 * central directory.
228 */
229 numEntries = get2LE(ptr + ENDSUB);
230 cdOffset = get4LE(ptr + ENDOFF);
231
232 LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
Doug Zongker99916f02014-01-13 14:16:58 -0800233 if (numEntries == 0 || cdOffset >= pArchive->length) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800234 LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
Doug Zongker99916f02014-01-13 14:16:58 -0800235 numEntries, cdOffset, pArchive->length);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800236 goto bail;
237 }
238
239 /*
240 * Create data structures to hold entries.
241 */
242 pArchive->numEntries = numEntries;
243 pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry));
244 pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL);
245 if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
246 goto bail;
247
Doug Zongker99916f02014-01-13 14:16:58 -0800248 ptr = pArchive->addr + cdOffset;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800249 for (i = 0; i < numEntries; i++) {
250 ZipEntry* pEntry;
251 unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
252 const unsigned char* localHdr;
253 const char *fileName;
254
Doug Zongker99916f02014-01-13 14:16:58 -0800255 if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800256 LOGW("Ran off the end (at %d)\n", i);
257 goto bail;
258 }
259 if (get4LE(ptr) != CENSIG) {
260 LOGW("Missed a central dir sig (at %d)\n", i);
261 goto bail;
262 }
263
264 localHdrOffset = get4LE(ptr + CENOFF);
265 fileNameLen = get2LE(ptr + CENNAM);
266 extraLen = get2LE(ptr + CENEXT);
267 commentLen = get2LE(ptr + CENCOM);
268 fileName = (const char*)ptr + CENHDR;
Doug Zongker99916f02014-01-13 14:16:58 -0800269 if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800270 LOGW("Filename ran off the end (at %d)\n", i);
271 goto bail;
272 }
273 if (!validFilename(fileName, fileNameLen)) {
274 LOGW("Invalid filename (at %d)\n", i);
275 goto bail;
276 }
277
278#if SORT_ENTRIES
279 /* Figure out where this entry should go (binary search).
280 */
281 if (i > 0) {
282 int low, high;
283
284 low = 0;
285 high = i - 1;
286 while (low <= high) {
287 int mid;
288 int diff;
289 int diffLen;
290
291 mid = low + ((high - low) / 2); // avoid overflow
292
293 if (pArchive->pEntries[mid].fileNameLen < fileNameLen) {
294 diffLen = pArchive->pEntries[mid].fileNameLen;
295 } else {
296 diffLen = fileNameLen;
297 }
298 diff = strncmp(pArchive->pEntries[mid].fileName, fileName,
299 diffLen);
300 if (diff == 0) {
301 diff = pArchive->pEntries[mid].fileNameLen - fileNameLen;
302 }
303 if (diff < 0) {
304 low = mid + 1;
305 } else if (diff > 0) {
306 high = mid - 1;
307 } else {
308 high = mid;
309 break;
310 }
311 }
312
313 unsigned int target = high + 1;
314 assert(target <= i);
315 if (target != i) {
316 /* It belongs somewhere other than at the end of
317 * the list. Make some room at [target].
318 */
319 memmove(pArchive->pEntries + target + 1,
320 pArchive->pEntries + target,
321 (i - target) * sizeof(ZipEntry));
322 }
323 pEntry = &pArchive->pEntries[target];
324 } else {
325 pEntry = &pArchive->pEntries[0];
326 }
327#else
328 pEntry = &pArchive->pEntries[i];
329#endif
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800330 pEntry->fileNameLen = fileNameLen;
331 pEntry->fileName = fileName;
332
333 pEntry->compLen = get4LE(ptr + CENSIZ);
334 pEntry->uncompLen = get4LE(ptr + CENLEN);
335 pEntry->compression = get2LE(ptr + CENHOW);
336 pEntry->modTime = get4LE(ptr + CENTIM);
337 pEntry->crc32 = get4LE(ptr + CENCRC);
338
339 /* These two are necessary for finding the mode of the file.
340 */
341 pEntry->versionMadeBy = get2LE(ptr + CENVEM);
342 if ((pEntry->versionMadeBy & 0xff00) != 0 &&
343 (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX)
344 {
345 LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n",
346 pEntry->versionMadeBy >> 8, i);
347 goto bail;
348 }
349 pEntry->externalFileAttributes = get4LE(ptr + CENATX);
350
Doug Zongker99916f02014-01-13 14:16:58 -0800351 // Perform pArchive->addr + localHdrOffset, ensuring that it won't
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800352 // overflow. This is needed because localHdrOffset is untrusted.
Doug Zongker99916f02014-01-13 14:16:58 -0800353 if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr,
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800354 (uintptr_t)localHdrOffset)) {
355 LOGW("Integer overflow adding in parseZipArchive\n");
356 goto bail;
357 }
358 if ((uintptr_t)localHdr + LOCHDR >
Doug Zongker99916f02014-01-13 14:16:58 -0800359 (uintptr_t)pArchive->addr + pArchive->length) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800360 LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
361 goto bail;
362 }
363 if (get4LE(localHdr) != LOCSIG) {
364 LOGW("Missed a local header sig (at %d)\n", i);
365 goto bail;
366 }
367 pEntry->offset = localHdrOffset + LOCHDR
368 + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT);
369 if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) {
370 LOGW("Integer overflow adding in parseZipArchive\n");
371 goto bail;
372 }
Doug Zongker99916f02014-01-13 14:16:58 -0800373 if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800374 LOGW("Data ran off the end (at %d)\n", i);
375 goto bail;
376 }
377
378#if !SORT_ENTRIES
379 /* Add to hash table; no need to lock here.
380 * Can't do this now if we're sorting, because entries
381 * will move around.
382 */
383 addEntryToHashTable(pArchive->pHash, pEntry);
384#endif
385
386 //dumpEntry(pEntry);
387 ptr += CENHDR + fileNameLen + extraLen + commentLen;
388 }
389
390#if SORT_ENTRIES
391 /* If we're sorting, we have to wait until all entries
392 * are in their final places, otherwise the pointers will
393 * probably point to the wrong things.
394 */
395 for (i = 0; i < numEntries; i++) {
396 /* Add to hash table; no need to lock here.
397 */
398 addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]);
399 }
400#endif
401
402 result = true;
403
404bail:
405 if (!result) {
406 mzHashTableFree(pArchive->pHash);
407 pArchive->pHash = NULL;
408 }
409 return result;
410}
411
412/*
413 * Open a Zip archive and scan out the contents.
414 *
415 * The easiest way to do this is to mmap() the whole thing and do the
416 * traditional backward scan for central directory. Since the EOCD is
417 * a relatively small bit at the end, we should end up only touching a
418 * small set of pages.
419 *
420 * This will be called on non-Zip files, especially during startup, so
421 * we don't want to be too noisy about failures. (Do we want a "quiet"
422 * flag?)
423 *
424 * On success, we fill out the contents of "pArchive".
425 */
Doug Zongker99916f02014-01-13 14:16:58 -0800426int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive)
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800427{
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800428 int err;
429
Doug Zongker99916f02014-01-13 14:16:58 -0800430 if (length < ENDHDR) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800431 err = -1;
432 LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length);
433 goto bail;
434 }
435
Doug Zongker99916f02014-01-13 14:16:58 -0800436 pArchive->addr = addr;
437 pArchive->length = length;
438
439 if (!parseZipArchive(pArchive)) {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800440 err = -1;
441 LOGV("Parsing '%s' failed\n", fileName);
442 goto bail;
443 }
444
445 err = 0;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800446
447bail:
448 if (err != 0)
449 mzCloseZipArchive(pArchive);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800450 return err;
451}
452
453/*
454 * Close a ZipArchive, closing the file and freeing the contents.
455 *
456 * NOTE: the ZipArchive may not have been fully created.
457 */
458void mzCloseZipArchive(ZipArchive* pArchive)
459{
460 LOGV("Closing archive %p\n", pArchive);
461
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800462 free(pArchive->pEntries);
463
464 mzHashTableFree(pArchive->pHash);
465
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800466 pArchive->pHash = NULL;
467 pArchive->pEntries = NULL;
468}
469
470/*
471 * Find a matching entry.
472 *
473 * Returns NULL if no matching entry found.
474 */
475const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
476 const char* entryName)
477{
478 unsigned int itemHash = computeHash(entryName, strlen(entryName));
479
480 return (const ZipEntry*)mzHashTableLookup(pArchive->pHash,
481 itemHash, (char*) entryName, hashcmpZipName, false);
482}
483
484/*
485 * Return true if the entry is a symbolic link.
486 */
487bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
488{
489 if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
490 return S_ISLNK(pEntry->externalFileAttributes >> 16);
491 }
492 return false;
493}
494
495/* Call processFunction on the uncompressed data of a STORED entry.
496 */
497static bool processStoredEntry(const ZipArchive *pArchive,
498 const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
499 void *cookie)
500{
Doug Zongker99916f02014-01-13 14:16:58 -0800501 return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800502}
503
504static bool processDeflatedEntry(const ZipArchive *pArchive,
505 const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
506 void *cookie)
507{
508 long result = -1;
509 unsigned char readBuf[32 * 1024];
510 unsigned char procBuf[32 * 1024];
511 z_stream zstream;
512 int zerr;
513 long compRemaining;
514
515 compRemaining = pEntry->compLen;
516
517 /*
518 * Initialize the zlib stream.
519 */
520 memset(&zstream, 0, sizeof(zstream));
521 zstream.zalloc = Z_NULL;
522 zstream.zfree = Z_NULL;
523 zstream.opaque = Z_NULL;
Doug Zongker99916f02014-01-13 14:16:58 -0800524 zstream.next_in = pArchive->addr + pEntry->offset;
525 zstream.avail_in = pEntry->compLen;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800526 zstream.next_out = (Bytef*) procBuf;
527 zstream.avail_out = sizeof(procBuf);
528 zstream.data_type = Z_UNKNOWN;
529
530 /*
531 * Use the undocumented "negative window bits" feature to tell zlib
532 * that there's no zlib header waiting for it.
533 */
534 zerr = inflateInit2(&zstream, -MAX_WBITS);
535 if (zerr != Z_OK) {
536 if (zerr == Z_VERSION_ERROR) {
537 LOGE("Installed zlib is not compatible with linked version (%s)\n",
538 ZLIB_VERSION);
539 } else {
540 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
541 }
542 goto bail;
543 }
544
545 /*
546 * Loop while we have data.
547 */
548 do {
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800549 /* uncompress the data */
550 zerr = inflate(&zstream, Z_NO_FLUSH);
551 if (zerr != Z_OK && zerr != Z_STREAM_END) {
552 LOGD("zlib inflate call failed (zerr=%d)\n", zerr);
553 goto z_bail;
554 }
555
556 /* write when we're full or when we're done */
557 if (zstream.avail_out == 0 ||
558 (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf)))
559 {
560 long procSize = zstream.next_out - procBuf;
561 LOGVV("+++ processing %d bytes\n", (int) procSize);
562 bool ret = processFunction(procBuf, procSize, cookie);
563 if (!ret) {
564 LOGW("Process function elected to fail (in inflate)\n");
565 goto z_bail;
566 }
567
568 zstream.next_out = procBuf;
569 zstream.avail_out = sizeof(procBuf);
570 }
571 } while (zerr == Z_OK);
572
573 assert(zerr == Z_STREAM_END); /* other errors should've been caught */
574
575 // success!
576 result = zstream.total_out;
577
578z_bail:
579 inflateEnd(&zstream); /* free up any allocated structures */
580
581bail:
582 if (result != pEntry->uncompLen) {
583 if (result != -1) // error already shown?
584 LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
585 result, pEntry->uncompLen);
586 return false;
587 }
588 return true;
589}
590
591/*
592 * Stream the uncompressed data through the supplied function,
593 * passing cookie to it each time it gets called. processFunction
594 * may be called more than once.
595 *
596 * If processFunction returns false, the operation is abandoned and
597 * mzProcessZipEntryContents() immediately returns false.
598 *
599 * This is useful for calculating the hash of an entry's uncompressed contents.
600 */
601bool mzProcessZipEntryContents(const ZipArchive *pArchive,
602 const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
603 void *cookie)
604{
605 bool ret = false;
606 off_t oldOff;
607
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800608 switch (pEntry->compression) {
609 case STORED:
610 ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
611 break;
612 case DEFLATED:
613 ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie);
614 break;
615 default:
616 LOGE("Unsupported compression type %d for entry '%s'\n",
617 pEntry->compression, pEntry->fileName);
618 break;
619 }
620
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800621 return ret;
622}
623
624static bool crcProcessFunction(const unsigned char *data, int dataLen,
625 void *crc)
626{
627 *(unsigned long *)crc = crc32(*(unsigned long *)crc, data, dataLen);
628 return true;
629}
630
631/*
632 * Check the CRC on this entry; return true if it is correct.
633 * May do other internal checks as well.
634 */
635bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry)
636{
637 unsigned long crc;
638 bool ret;
639
640 crc = crc32(0L, Z_NULL, 0);
641 ret = mzProcessZipEntryContents(pArchive, pEntry, crcProcessFunction,
642 (void *)&crc);
643 if (!ret) {
644 LOGE("Can't calculate CRC for entry\n");
645 return false;
646 }
647 if (crc != (unsigned long)pEntry->crc32) {
648 LOGW("CRC for entry %.*s (0x%08lx) != expected (0x%08lx)\n",
649 pEntry->fileNameLen, pEntry->fileName, crc, pEntry->crc32);
650 return false;
651 }
652 return true;
653}
654
655typedef struct {
656 char *buf;
657 int bufLen;
658} CopyProcessArgs;
659
660static bool copyProcessFunction(const unsigned char *data, int dataLen,
661 void *cookie)
662{
663 CopyProcessArgs *args = (CopyProcessArgs *)cookie;
664 if (dataLen <= args->bufLen) {
665 memcpy(args->buf, data, dataLen);
666 args->buf += dataLen;
667 args->bufLen -= dataLen;
668 return true;
669 }
670 return false;
671}
672
673/*
674 * Read an entry into a buffer allocated by the caller.
675 */
676bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
677 char *buf, int bufLen)
678{
679 CopyProcessArgs args;
680 bool ret;
Doug Zongker596271f2009-04-29 16:52:04 -0700681
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800682 args.buf = buf;
683 args.bufLen = bufLen;
684 ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
685 (void *)&args);
686 if (!ret) {
687 LOGE("Can't extract entry to buffer.\n");
688 return false;
689 }
690 return true;
691}
692
693static bool writeProcessFunction(const unsigned char *data, int dataLen,
Doug Zongker683c4622009-05-05 17:50:21 -0700694 void *cookie)
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800695{
Colin Cross92cdf9c2014-02-05 17:30:31 -0800696 int fd = (int)(intptr_t)cookie;
Michael Rungef5d9f892014-05-06 16:54:42 -0700697 if (dataLen == 0) {
698 return true;
699 }
Doug Zongker596271f2009-04-29 16:52:04 -0700700 ssize_t soFar = 0;
Doug Zongker683c4622009-05-05 17:50:21 -0700701 while (true) {
Doug Zongker1c4ceae2009-05-08 09:43:28 -0700702 ssize_t n = write(fd, data+soFar, dataLen-soFar);
Doug Zongker683c4622009-05-05 17:50:21 -0700703 if (n <= 0) {
Doug Zongkera9300302014-02-10 12:35:19 -0800704 LOGE("Error writing %zd bytes from zip file from %p: %s\n",
Doug Zongker683c4622009-05-05 17:50:21 -0700705 dataLen-soFar, data+soFar, strerror(errno));
Doug Zongker1c4ceae2009-05-08 09:43:28 -0700706 if (errno != EINTR) {
707 return false;
Doug Zongker683c4622009-05-05 17:50:21 -0700708 }
Doug Zongker596271f2009-04-29 16:52:04 -0700709 } else if (n > 0) {
710 soFar += n;
711 if (soFar == dataLen) return true;
712 if (soFar > dataLen) {
Doug Zongkera9300302014-02-10 12:35:19 -0800713 LOGE("write overrun? (%zd bytes instead of %d)\n",
Doug Zongker596271f2009-04-29 16:52:04 -0700714 soFar, dataLen);
715 return false;
716 }
Doug Zongker596271f2009-04-29 16:52:04 -0700717 }
Doug Zongker683c4622009-05-05 17:50:21 -0700718 }
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800719}
720
721/*
722 * Uncompress "pEntry" in "pArchive" to "fd" at the current offset.
723 */
724bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
Doug Zongker1c4ceae2009-05-08 09:43:28 -0700725 const ZipEntry *pEntry, int fd)
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800726{
727 bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
Colin Cross92cdf9c2014-02-05 17:30:31 -0800728 (void*)(intptr_t)fd);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800729 if (!ret) {
730 LOGE("Can't extract entry to file.\n");
731 return false;
732 }
733 return true;
734}
735
Doug Zongkera9300302014-02-10 12:35:19 -0800736/*
737 * Obtain a pointer to the in-memory representation of a stored entry.
738 */
739bool mzGetStoredEntry(const ZipArchive *pArchive,
740 const ZipEntry *pEntry, unsigned char **addr, size_t *length)
741{
742 if (pEntry->compression != STORED) {
743 LOGE("Can't getStoredEntry for '%s'; not stored\n",
744 pEntry->fileName);
745 return false;
746 }
747
748 *addr = pArchive->addr + pEntry->offset;
749 *length = pEntry->uncompLen;
750 return true;
751}
752
Doug Zongker6aece332010-02-01 14:40:12 -0800753typedef struct {
754 unsigned char* buffer;
755 long len;
756} BufferExtractCookie;
757
758static bool bufferProcessFunction(const unsigned char *data, int dataLen,
759 void *cookie) {
760 BufferExtractCookie *bec = (BufferExtractCookie*)cookie;
761
762 memmove(bec->buffer, data, dataLen);
763 bec->buffer += dataLen;
764 bec->len -= dataLen;
765
766 return true;
767}
768
769/*
770 * Uncompress "pEntry" in "pArchive" to buffer, which must be large
771 * enough to hold mzGetZipEntryUncomplen(pEntry) bytes.
772 */
773bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive,
774 const ZipEntry *pEntry, unsigned char *buffer)
775{
776 BufferExtractCookie bec;
777 bec.buffer = buffer;
778 bec.len = mzGetZipEntryUncompLen(pEntry);
779
780 bool ret = mzProcessZipEntryContents(pArchive, pEntry,
781 bufferProcessFunction, (void*)&bec);
782 if (!ret || bec.len != 0) {
783 LOGE("Can't extract entry to memory buffer.\n");
784 return false;
785 }
786 return true;
787}
788
789
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800790/* Helper state to make path translation easier and less malloc-happy.
791 */
792typedef struct {
793 const char *targetDir;
794 const char *zipDir;
795 char *buf;
796 int targetDirLen;
797 int zipDirLen;
798 int bufLen;
799} MzPathHelper;
800
801/* Given the values of targetDir and zipDir in the helper,
802 * return the target filename of the provided entry.
803 * The helper must be initialized first.
804 */
805static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
806{
807 int needLen;
808 bool firstTime = (helper->buf == NULL);
809
810 /* target file <-- targetDir + / + entry[zipDirLen:]
811 */
812 needLen = helper->targetDirLen + 1 +
813 pEntry->fileNameLen - helper->zipDirLen + 1;
814 if (needLen > helper->bufLen) {
815 char *newBuf;
816
817 needLen *= 2;
818 newBuf = (char *)realloc(helper->buf, needLen);
819 if (newBuf == NULL) {
820 return NULL;
821 }
822 helper->buf = newBuf;
823 helper->bufLen = needLen;
824 }
825
826 /* Every path will start with the target path and a slash.
827 */
828 if (firstTime) {
829 char *p = helper->buf;
830 memcpy(p, helper->targetDir, helper->targetDirLen);
831 p += helper->targetDirLen;
832 if (p == helper->buf || p[-1] != '/') {
833 helper->targetDirLen += 1;
834 *p++ = '/';
835 }
836 }
837
838 /* Replace the custom part of the path with the appropriate
839 * part of the entry's path.
840 */
841 char *epath = helper->buf + helper->targetDirLen;
842 memcpy(epath, pEntry->fileName + helper->zipDirLen,
843 pEntry->fileNameLen - helper->zipDirLen);
844 epath += pEntry->fileNameLen - helper->zipDirLen;
845 *epath = '\0';
846
847 return helper->buf;
848}
849
850/*
851 * Inflate all entries under zipDir to the directory specified by
852 * targetDir, which must exist and be a writable directory.
853 *
854 * The immediate children of zipDir will become the immediate
855 * children of targetDir; e.g., if the archive contains the entries
856 *
857 * a/b/c/one
858 * a/b/c/two
859 * a/b/c/d/three
860 *
861 * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting
862 * files will be
863 *
864 * /tmp/one
865 * /tmp/two
866 * /tmp/d/three
867 *
868 * Returns true on success, false on failure.
869 */
870bool mzExtractRecursive(const ZipArchive *pArchive,
871 const char *zipDir, const char *targetDir,
872 int flags, const struct utimbuf *timestamp,
Stephen Smalley779701d2012-02-09 14:13:23 -0500873 void (*callback)(const char *fn, void *), void *cookie,
874 struct selabel_handle *sehnd)
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800875{
876 if (zipDir[0] == '/') {
877 LOGE("mzExtractRecursive(): zipDir must be a relative path.\n");
878 return false;
879 }
880 if (targetDir[0] != '/') {
881 LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n");
882 return false;
883 }
884
885 unsigned int zipDirLen;
886 char *zpath;
887
888 zipDirLen = strlen(zipDir);
889 zpath = (char *)malloc(zipDirLen + 2);
890 if (zpath == NULL) {
891 LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2);
892 return false;
893 }
894 /* If zipDir is empty, we'll extract the entire zip file.
895 * Otherwise, canonicalize the path.
896 */
897 if (zipDirLen > 0) {
898 /* Make sure there's (hopefully, exactly one) slash at the
899 * end of the path. This way we don't need to worry about
900 * accidentally extracting "one/twothree" when a path like
901 * "one/two" is specified.
902 */
903 memcpy(zpath, zipDir, zipDirLen);
904 if (zpath[zipDirLen-1] != '/') {
905 zpath[zipDirLen++] = '/';
906 }
907 }
908 zpath[zipDirLen] = '\0';
909
910 /* Set up the helper structure that we'll use to assemble paths.
911 */
912 MzPathHelper helper;
913 helper.targetDir = targetDir;
914 helper.targetDirLen = strlen(helper.targetDir);
915 helper.zipDir = zpath;
916 helper.zipDirLen = strlen(helper.zipDir);
917 helper.buf = NULL;
918 helper.bufLen = 0;
919
920 /* Walk through the entries and extract anything whose path begins
921 * with zpath.
Nanik Tolaram4e8e93b2015-02-08 22:31:14 +1100922 //TODO: since the entries are sorted, binary search for the first match
923 // and stop after the first non-match.
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800924 */
925 unsigned int i;
926 bool seenMatch = false;
927 int ok = true;
Doug Zongkerbf80f492012-10-19 12:24:26 -0700928 int extractCount = 0;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800929 for (i = 0; i < pArchive->numEntries; i++) {
930 ZipEntry *pEntry = pArchive->pEntries + i;
931 if (pEntry->fileNameLen < zipDirLen) {
Nanik Tolaram4e8e93b2015-02-08 22:31:14 +1100932 //TODO: look out for a single empty directory entry that matches zpath, but
933 // missing the trailing slash. Most zip files seem to include
934 // the trailing slash, but I think it's legal to leave it off.
935 // e.g., zpath "a/b/", entry "a/b", with no children of the entry.
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800936 /* No chance of matching.
937 */
938#if SORT_ENTRIES
939 if (seenMatch) {
940 /* Since the entries are sorted, we can give up
941 * on the first mismatch after the first match.
942 */
943 break;
944 }
945#endif
946 continue;
947 }
948 /* If zpath is empty, this strncmp() will match everything,
949 * which is what we want.
950 */
951 if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) {
952#if SORT_ENTRIES
953 if (seenMatch) {
954 /* Since the entries are sorted, we can give up
955 * on the first mismatch after the first match.
956 */
957 break;
958 }
959#endif
960 continue;
961 }
962 /* This entry begins with zipDir, so we'll extract it.
963 */
964 seenMatch = true;
965
966 /* Find the target location of the entry.
967 */
968 const char *targetFile = targetEntryPath(&helper, pEntry);
969 if (targetFile == NULL) {
970 LOGE("Can't assemble target path for \"%.*s\"\n",
971 pEntry->fileNameLen, pEntry->fileName);
972 ok = false;
973 break;
974 }
975
976 /* With DRY_RUN set, invoke the callback but don't do anything else.
977 */
978 if (flags & MZ_EXTRACT_DRY_RUN) {
979 if (callback != NULL) callback(targetFile, cookie);
980 continue;
981 }
982
983 /* Create the file or directory.
984 */
985#define UNZIP_DIRMODE 0755
986#define UNZIP_FILEMODE 0644
987 if (pEntry->fileName[pEntry->fileNameLen-1] == '/') {
988 if (!(flags & MZ_EXTRACT_FILES_ONLY)) {
989 int ret = dirCreateHierarchy(
Stephen Smalley779701d2012-02-09 14:13:23 -0500990 targetFile, UNZIP_DIRMODE, timestamp, false, sehnd);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -0800991 if (ret != 0) {
992 LOGE("Can't create containing directory for \"%s\": %s\n",
993 targetFile, strerror(errno));
994 ok = false;
995 break;
996 }
997 LOGD("Extracted dir \"%s\"\n", targetFile);
998 }
999 } else {
1000 /* This is not a directory. First, make sure that
1001 * the containing directory exists.
1002 */
1003 int ret = dirCreateHierarchy(
Stephen Smalley779701d2012-02-09 14:13:23 -05001004 targetFile, UNZIP_DIRMODE, timestamp, true, sehnd);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001005 if (ret != 0) {
1006 LOGE("Can't create containing directory for \"%s\": %s\n",
1007 targetFile, strerror(errno));
1008 ok = false;
1009 break;
1010 }
1011
1012 /* With FILES_ONLY set, we need to ignore metadata entirely,
1013 * so treat symlinks as regular files.
1014 */
1015 if (!(flags & MZ_EXTRACT_FILES_ONLY) && mzIsZipEntrySymlink(pEntry)) {
1016 /* The entry is a symbolic link.
1017 * The relative target of the symlink is in the
1018 * data section of this entry.
1019 */
1020 if (pEntry->uncompLen == 0) {
1021 LOGE("Symlink entry \"%s\" has no target\n",
1022 targetFile);
1023 ok = false;
1024 break;
1025 }
1026 char *linkTarget = malloc(pEntry->uncompLen + 1);
1027 if (linkTarget == NULL) {
1028 ok = false;
1029 break;
1030 }
1031 ok = mzReadZipEntry(pArchive, pEntry, linkTarget,
1032 pEntry->uncompLen);
1033 if (!ok) {
1034 LOGE("Can't read symlink target for \"%s\"\n",
1035 targetFile);
1036 free(linkTarget);
1037 break;
1038 }
1039 linkTarget[pEntry->uncompLen] = '\0';
1040
1041 /* Make the link.
1042 */
1043 ret = symlink(linkTarget, targetFile);
1044 if (ret != 0) {
1045 LOGE("Can't symlink \"%s\" to \"%s\": %s\n",
1046 targetFile, linkTarget, strerror(errno));
1047 free(linkTarget);
1048 ok = false;
1049 break;
1050 }
1051 LOGD("Extracted symlink \"%s\" -> \"%s\"\n",
1052 targetFile, linkTarget);
1053 free(linkTarget);
1054 } else {
1055 /* The entry is a regular file.
1056 * Open the target for writing.
1057 */
Stephen Smalley779701d2012-02-09 14:13:23 -05001058
Stephen Smalley779701d2012-02-09 14:13:23 -05001059 char *secontext = NULL;
1060
1061 if (sehnd) {
1062 selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE);
1063 setfscreatecon(secontext);
1064 }
Stephen Smalley779701d2012-02-09 14:13:23 -05001065
Michael Runge042c3cd2014-10-28 19:49:57 -07001066 int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC
1067 , UNZIP_FILEMODE);
Stephen Smalley779701d2012-02-09 14:13:23 -05001068
Stephen Smalley779701d2012-02-09 14:13:23 -05001069 if (secontext) {
1070 freecon(secontext);
1071 setfscreatecon(NULL);
1072 }
Stephen Smalley779701d2012-02-09 14:13:23 -05001073
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001074 if (fd < 0) {
1075 LOGE("Can't create target file \"%s\": %s\n",
1076 targetFile, strerror(errno));
1077 ok = false;
1078 break;
1079 }
1080
Doug Zongker1c4ceae2009-05-08 09:43:28 -07001081 bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
Michael Runge042c3cd2014-10-28 19:49:57 -07001082 if (ok) {
1083 ok = (fsync(fd) == 0);
1084 }
1085 if (close(fd) != 0) {
1086 ok = false;
1087 }
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001088 if (!ok) {
1089 LOGE("Error extracting \"%s\"\n", targetFile);
1090 ok = false;
1091 break;
1092 }
1093
1094 if (timestamp != NULL && utime(targetFile, timestamp)) {
1095 LOGE("Error touching \"%s\"\n", targetFile);
1096 ok = false;
1097 break;
1098 }
1099
Doug Zongkerbf80f492012-10-19 12:24:26 -07001100 LOGV("Extracted file \"%s\"\n", targetFile);
1101 ++extractCount;
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001102 }
1103 }
1104
1105 if (callback != NULL) callback(targetFile, cookie);
1106 }
1107
Doug Zongkerbf80f492012-10-19 12:24:26 -07001108 LOGD("Extracted %d file(s)\n", extractCount);
1109
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001110 free(helper.buf);
1111 free(zpath);
The Android Open Source Projectc24a8e62009-03-03 19:28:42 -08001112
1113 return ok;
1114}