/* * Copyright 2006 The Android Open Source Project * * Simple Zip file support. */ #include "safe_iop.h" #include "zlib.h" #include "xz_config.h" #include #include #include #include // for uintptr_t #include #include // for S_ISLNK() #include #define LOG_TAG "minzip" #include "Zip.h" #include "Bits.h" #include "Log.h" #include "DirUtil.h" #undef NDEBUG // do this after including Log.h #include #define SORT_ENTRIES 1 /* * Offset and length constants (java.util.zip naming convention). */ enum { CENSIG = 0x02014b50, // PK12 CENHDR = 46, CENVEM = 4, CENVER = 6, CENFLG = 8, CENHOW = 10, CENTIM = 12, CENCRC = 16, CENSIZ = 20, CENLEN = 24, CENNAM = 28, CENEXT = 30, CENCOM = 32, CENDSK = 34, CENATT = 36, CENATX = 38, CENOFF = 42, ENDSIG = 0x06054b50, // PK56 ENDHDR = 22, ENDSUB = 8, ENDTOT = 10, ENDSIZ = 12, ENDOFF = 16, ENDCOM = 20, EXTSIG = 0x08074b50, // PK78 EXTHDR = 16, EXTCRC = 4, EXTSIZ = 8, EXTLEN = 12, LOCSIG = 0x04034b50, // PK34 LOCHDR = 30, LOCVER = 4, LOCFLG = 6, LOCHOW = 8, LOCTIM = 10, LOCCRC = 14, LOCSIZ = 18, LOCLEN = 22, LOCNAM = 26, LOCEXT = 28, STORED = 0, DEFLATED = 8, CENVEM_UNIX = 3 << 8, // the high byte of CENVEM }; /* * For debugging, dump the contents of a ZipEntry. */ #if 0 static void dumpEntry(const ZipEntry* pEntry) { LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName); LOGI(" off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset, pEntry->compLen, pEntry->uncompLen, pEntry->compression); } #endif /* * (This is a mzHashTableLookup callback.) * * Compare two ZipEntry structs, by name. */ static int hashcmpZipEntry(const void* ventry1, const void* ventry2) { const ZipEntry* entry1 = (const ZipEntry*) ventry1; const ZipEntry* entry2 = (const ZipEntry*) ventry2; if (entry1->fileNameLen != entry2->fileNameLen) return entry1->fileNameLen - entry2->fileNameLen; return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen); } /* * (This is a mzHashTableLookup callback.) * * find a ZipEntry struct by name. */ static int hashcmpZipName(const void* ventry, const void* vname) { const ZipEntry* entry = (const ZipEntry*) ventry; const char* name = (const char*) vname; unsigned int nameLen = strlen(name); if (entry->fileNameLen != nameLen) return entry->fileNameLen - nameLen; return memcmp(entry->fileName, name, nameLen); } /* * Compute the hash code for a ZipEntry filename. * * Not expected to be compatible with any other hash function, so we init * to 2 to ensure it doesn't happen to match. */ static unsigned int computeHash(const char* name, int nameLen) { unsigned int hash = 2; while (nameLen--) hash = hash * 31 + *name++; return hash; } static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry) { unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen); const ZipEntry* found; found = (const ZipEntry*)mzHashTableLookup(pHash, itemHash, pEntry, hashcmpZipEntry, true); if (found != pEntry) { LOGW("WARNING: duplicate entry '%.*s' in Zip\n", found->fileNameLen, found->fileName); /* keep going */ } } static int validFilename(const char *fileName, unsigned int fileNameLen) { // Forbid super long filenames. if (fileNameLen >= PATH_MAX) { LOGW("Filename too long (%d chatacters)\n", fileNameLen); return 0; } // Require all characters to be printable ASCII (no NUL, no UTF-8, etc). unsigned int i; for (i = 0; i < fileNameLen; ++i) { if (fileName[i] < 32 || fileName[i] >= 127) { LOGW("Filename contains invalid character '\%03o'\n", fileName[i]); return 0; } } return 1; } /* * Parse the contents of a Zip archive. After confirming that the file * is in fact a Zip, we scan out the contents of the central directory and * store it in a hash table. * * Returns "true" on success. */ static bool parseZipArchive(ZipArchive* pArchive) { bool result = false; const unsigned char* ptr; unsigned int i, numEntries, cdOffset; unsigned int val; /* * The first 4 bytes of the file will either be the local header * signature for the first file (LOCSIG) or, if the archive doesn't * have any files in it, the end-of-central-directory signature (ENDSIG). */ val = get4LE(pArchive->addr); if (val == ENDSIG) { LOGW("Found Zip archive, but it looks empty\n"); goto bail; } else if (val != LOCSIG) { LOGW("Not a Zip archive (found 0x%08x)\n", val); goto bail; } /* * Find the EOCD. We'll find it immediately unless they have a file * comment. */ ptr = pArchive->addr + pArchive->length - ENDHDR; while (ptr >= (const unsigned char*) pArchive->addr) { if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG) break; ptr--; } if (ptr < (const unsigned char*) pArchive->addr) { LOGW("Could not find end-of-central-directory in Zip\n"); goto bail; } /* * There are two interesting items in the EOCD block: the number of * entries in the file, and the file offset of the start of the * central directory. */ numEntries = get2LE(ptr + ENDSUB); cdOffset = get4LE(ptr + ENDOFF); LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset); if (numEntries == 0 || cdOffset >= pArchive->length) { LOGW("Invalid entries=%d offset=%d (len=%zd)\n", numEntries, cdOffset, pArchive->length); goto bail; } /* * Create data structures to hold entries. */ pArchive->numEntries = numEntries; pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry)); pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL); if (pArchive->pEntries == NULL || pArchive->pHash == NULL) goto bail; ptr = pArchive->addr + cdOffset; for (i = 0; i < numEntries; i++) { ZipEntry* pEntry; unsigned int fileNameLen, extraLen, commentLen, localHdrOffset; const unsigned char* localHdr; const char *fileName; if (ptr + CENHDR > (const unsigned char*)pArchive->addr + pArchive->length) { LOGW("Ran off the end (at %d)\n", i); goto bail; } if (get4LE(ptr) != CENSIG) { LOGW("Missed a central dir sig (at %d)\n", i); goto bail; } localHdrOffset = get4LE(ptr + CENOFF); fileNameLen = get2LE(ptr + CENNAM); extraLen = get2LE(ptr + CENEXT); commentLen = get2LE(ptr + CENCOM); fileName = (const char*)ptr + CENHDR; if (fileName + fileNameLen > (const char*)pArchive->addr + pArchive->length) { LOGW("Filename ran off the end (at %d)\n", i); goto bail; } if (!validFilename(fileName, fileNameLen)) { LOGW("Invalid filename (at %d)\n", i); goto bail; } #if SORT_ENTRIES /* Figure out where this entry should go (binary search). */ if (i > 0) { int low, high; low = 0; high = i - 1; while (low <= high) { int mid; int diff; int diffLen; mid = low + ((high - low) / 2); // avoid overflow if (pArchive->pEntries[mid].fileNameLen < fileNameLen) { diffLen = pArchive->pEntries[mid].fileNameLen; } else { diffLen = fileNameLen; } diff = strncmp(pArchive->pEntries[mid].fileName, fileName, diffLen); if (diff == 0) { diff = pArchive->pEntries[mid].fileNameLen - fileNameLen; } if (diff < 0) { low = mid + 1; } else if (diff > 0) { high = mid - 1; } else { high = mid; break; } } unsigned int target = high + 1; assert(target <= i); if (target != i) { /* It belongs somewhere other than at the end of * the list. Make some room at [target]. */ memmove(pArchive->pEntries + target + 1, pArchive->pEntries + target, (i - target) * sizeof(ZipEntry)); } pEntry = &pArchive->pEntries[target]; } else { pEntry = &pArchive->pEntries[0]; } #else pEntry = &pArchive->pEntries[i]; #endif pEntry->fileNameLen = fileNameLen; pEntry->fileName = fileName; pEntry->compLen = get4LE(ptr + CENSIZ); pEntry->uncompLen = get4LE(ptr + CENLEN); pEntry->compression = get2LE(ptr + CENHOW); pEntry->modTime = get4LE(ptr + CENTIM); pEntry->crc32 = get4LE(ptr + CENCRC); /* These two are necessary for finding the mode of the file. */ pEntry->versionMadeBy = get2LE(ptr + CENVEM); if ((pEntry->versionMadeBy & 0xff00) != 0 && (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX) { LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n", pEntry->versionMadeBy >> 8, i); goto bail; } pEntry->externalFileAttributes = get4LE(ptr + CENATX); // Perform pArchive->addr + localHdrOffset, ensuring that it won't // overflow. This is needed because localHdrOffset is untrusted. if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pArchive->addr, (uintptr_t)localHdrOffset)) { LOGW("Integer overflow adding in parseZipArchive\n"); goto bail; } if ((uintptr_t)localHdr + LOCHDR > (uintptr_t)pArchive->addr + pArchive->length) { LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i); goto bail; } if (get4LE(localHdr) != LOCSIG) { LOGW("Missed a local header sig (at %d)\n", i); goto bail; } pEntry->offset = localHdrOffset + LOCHDR + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT); if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) { LOGW("Integer overflow adding in parseZipArchive\n"); goto bail; } if ((size_t)pEntry->offset + pEntry->compLen > pArchive->length) { LOGW("Data ran off the end (at %d)\n", i); goto bail; } #if !SORT_ENTRIES /* Add to hash table; no need to lock here. * Can't do this now if we're sorting, because entries * will move around. */ addEntryToHashTable(pArchive->pHash, pEntry); #endif //dumpEntry(pEntry); ptr += CENHDR + fileNameLen + extraLen + commentLen; } #if SORT_ENTRIES /* If we're sorting, we have to wait until all entries * are in their final places, otherwise the pointers will * probably point to the wrong things. */ for (i = 0; i < numEntries; i++) { /* Add to hash table; no need to lock here. */ addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]); } #endif result = true; bail: if (!result) { mzHashTableFree(pArchive->pHash); pArchive->pHash = NULL; } return result; } /* * Open a Zip archive and scan out the contents. * * The easiest way to do this is to mmap() the whole thing and do the * traditional backward scan for central directory. Since the EOCD is * a relatively small bit at the end, we should end up only touching a * small set of pages. * * This will be called on non-Zip files, especially during startup, so * we don't want to be too noisy about failures. (Do we want a "quiet" * flag?) * * On success, we fill out the contents of "pArchive". */ int mzOpenZipArchive(unsigned char* addr, size_t length, ZipArchive* pArchive) { int err; if (length < ENDHDR) { err = -1; LOGW("Archive %p is too small to be zip (%zd)\n", pArchive, length); goto bail; } pArchive->addr = addr; pArchive->length = length; if (!parseZipArchive(pArchive)) { err = -1; LOGW("Parsing archive %p failed\n", pArchive); goto bail; } err = 0; bail: if (err != 0) mzCloseZipArchive(pArchive); return err; } /* * Close a ZipArchive, closing the file and freeing the contents. * * NOTE: the ZipArchive may not have been fully created. */ void mzCloseZipArchive(ZipArchive* pArchive) { LOGV("Closing archive %p\n", pArchive); free(pArchive->pEntries); mzHashTableFree(pArchive->pHash); pArchive->pHash = NULL; pArchive->pEntries = NULL; } /* * Find a matching entry. * * Returns NULL if no matching entry found. */ const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive, const char* entryName) { unsigned int itemHash = computeHash(entryName, strlen(entryName)); return (const ZipEntry*)mzHashTableLookup(pArchive->pHash, itemHash, (char*) entryName, hashcmpZipName, false); } /* * Return true if the entry is a symbolic link. */ static bool mzIsZipEntrySymlink(const ZipEntry* pEntry) { if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) { return S_ISLNK(pEntry->externalFileAttributes >> 16); } return false; } /* Call processFunction on the uncompressed data of a STORED entry. */ static bool processStoredEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { return processFunction(pArchive->addr + pEntry->offset, pEntry->uncompLen, cookie); } static bool processXZEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { unsigned char out[32*1024]; struct xz_buf b; struct xz_dec *s; enum xz_ret ret; printf("ok!\n"); xz_crc32_init(); xz_crc64_init(); s = xz_dec_init(XZ_DYNALLOC, 1 << 26); if (s == NULL) { LOGE("XZ decompression alloc failed\n"); goto bail; } b.in = pArchive->addr + pEntry->offset; b.in_pos = 0; b.in_size = pEntry->compLen; b.out = out; b.out_pos = 0; b.out_size = sizeof(out); do { ret = xz_dec_run(s, &b); LOGVV("+++ b.in_pos = %zu b.out_pos = %zu ret=%d\n", b.in_pos, b.out_pos, ret); if (b.out_pos == sizeof(out)) { LOGVV("+++ processing %d bytes\n", b.out_pos); bool err = processFunction(out, b.out_pos, cookie); if (!err) { LOGW("Process function elected to fail (in xz_dec)\n"); goto xz_bail; } b.out_pos = 0; } } while (ret == XZ_OK); assert(ret == XZ_STREAM_END); bool err = processFunction(out, b.out_pos, cookie); if (!err) { LOGW("Process function elected to fail (in xz_dec)\n"); goto xz_bail; } xz_bail: xz_dec_end(s); bail: if (b.in_pos != (unsigned long)pEntry->compLen) { LOGW("Size mismatch on file after xz_dec (%ld vs %zu)\n", pEntry->compLen, b.in_pos); //return false; } return true; } static bool processDeflatedEntry(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { long result = -1; unsigned char procBuf[32 * 1024]; z_stream zstream; int zerr; long compRemaining; compRemaining = pEntry->compLen; /* * Initialize the zlib stream. */ memset(&zstream, 0, sizeof(zstream)); zstream.zalloc = Z_NULL; zstream.zfree = Z_NULL; zstream.opaque = Z_NULL; zstream.next_in = pArchive->addr + pEntry->offset; zstream.avail_in = pEntry->compLen; zstream.next_out = (Bytef*) procBuf; zstream.avail_out = sizeof(procBuf); zstream.data_type = Z_UNKNOWN; /* * Use the undocumented "negative window bits" feature to tell zlib * that there's no zlib header waiting for it. */ zerr = inflateInit2(&zstream, -MAX_WBITS); if (zerr != Z_OK) { if (zerr == Z_VERSION_ERROR) { LOGE("Installed zlib is not compatible with linked version (%s)\n", ZLIB_VERSION); } else { LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); } goto bail; } /* * Loop while we have data. */ do { /* uncompress the data */ zerr = inflate(&zstream, Z_NO_FLUSH); if (zerr != Z_OK && zerr != Z_STREAM_END) { LOGW("zlib inflate call failed (zerr=%d)\n", zerr); goto z_bail; } /* write when we're full or when we're done */ if (zstream.avail_out == 0 || (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf))) { long procSize = zstream.next_out - procBuf; LOGVV("+++ processing %d bytes\n", (int) procSize); bool ret = processFunction(procBuf, procSize, cookie); if (!ret) { LOGW("Process function elected to fail (in inflate)\n"); goto z_bail; } zstream.next_out = procBuf; zstream.avail_out = sizeof(procBuf); } } while (zerr == Z_OK); assert(zerr == Z_STREAM_END); /* other errors should've been caught */ // success! result = zstream.total_out; z_bail: inflateEnd(&zstream); /* free up any allocated structures */ bail: if (result != pEntry->uncompLen) { if (result != -1) // error already shown? LOGW("Size mismatch on inflated file (%ld vs %ld)\n", result, pEntry->uncompLen); return false; } return true; } /* * Stream the uncompressed data through the supplied function, * passing cookie to it each time it gets called. processFunction * may be called more than once. * * If processFunction returns false, the operation is abandoned and * mzProcessZipEntryContents() immediately returns false. * * This is useful for calculating the hash of an entry's uncompressed contents. */ bool mzProcessZipEntryContents(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { bool ret = false; switch (pEntry->compression) { case STORED: ret = processStoredEntry(pArchive, pEntry, processFunction, cookie); break; case DEFLATED: ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie); break; default: LOGE("Unsupported compression type %d for entry '%s'\n", pEntry->compression, pEntry->fileName); break; } return ret; } typedef struct { char *buf; int bufLen; } CopyProcessArgs; static bool copyProcessFunction(const unsigned char *data, int dataLen, void *cookie) { CopyProcessArgs *args = (CopyProcessArgs *)cookie; if (dataLen <= args->bufLen) { memcpy(args->buf, data, dataLen); args->buf += dataLen; args->bufLen -= dataLen; return true; } return false; } /* * Similar to mzProcessZipEntryContents, but explicitly process the stream * using XZ/LZMA before calling processFunction. * * This is a separate function for use by the updater. LZMA provides huge * size reductions vs deflate, but isn't actually supported by the ZIP format. * We need to process it using as little memory as possible. */ bool mzProcessZipEntryContentsXZ(const ZipArchive *pArchive, const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction, void *cookie) { if (pEntry->compression == STORED) { return processXZEntry(pArchive, pEntry, processFunction, cookie); } LOGE("Explicit XZ decoding of entry '%s' unsupported for type %d", pEntry->fileName, pEntry->compression); return false; } /* * Read an entry into a buffer allocated by the caller. */ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry, char *buf, int bufLen) { CopyProcessArgs args; bool ret; args.buf = buf; args.bufLen = bufLen; ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction, (void *)&args); if (!ret) { LOGE("Can't extract entry to buffer.\n"); return false; } return true; } static bool writeProcessFunction(const unsigned char *data, int dataLen, void *cookie) { int fd = (int)(intptr_t)cookie; if (dataLen == 0) { return true; } ssize_t soFar = 0; while (true) { ssize_t n = TEMP_FAILURE_RETRY(write(fd, data+soFar, dataLen-soFar)); if (n <= 0) { LOGE("Error writing %zd bytes from zip file from %p: %s\n", dataLen-soFar, data+soFar, strerror(errno)); return false; } else if (n > 0) { soFar += n; if (soFar == dataLen) return true; if (soFar > dataLen) { LOGE("write overrun? (%zd bytes instead of %d)\n", soFar, dataLen); return false; } } } } /* * Uncompress "pEntry" in "pArchive" to "fd" at the current offset. */ bool mzExtractZipEntryToFile(const ZipArchive *pArchive, const ZipEntry *pEntry, int fd) { bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction, (void*)(intptr_t)fd); if (!ret) { LOGE("Can't extract entry to file.\n"); return false; } return true; } typedef struct { unsigned char* buffer; long len; } BufferExtractCookie; static bool bufferProcessFunction(const unsigned char *data, int dataLen, void *cookie) { BufferExtractCookie *bec = (BufferExtractCookie*)cookie; memmove(bec->buffer, data, dataLen); bec->buffer += dataLen; bec->len -= dataLen; return true; } /* * Uncompress "pEntry" in "pArchive" to buffer, which must be large * enough to hold mzGetZipEntryUncomplen(pEntry) bytes. */ bool mzExtractZipEntryToBuffer(const ZipArchive *pArchive, const ZipEntry *pEntry, unsigned char *buffer) { BufferExtractCookie bec; bec.buffer = buffer; bec.len = mzGetZipEntryUncompLen(pEntry); bool ret = mzProcessZipEntryContents(pArchive, pEntry, bufferProcessFunction, (void*)&bec); if (!ret || bec.len != 0) { LOGE("Can't extract entry to memory buffer.\n"); return false; } return true; } /* Helper state to make path translation easier and less malloc-happy. */ typedef struct { const char *targetDir; const char *zipDir; char *buf; int targetDirLen; int zipDirLen; int bufLen; } MzPathHelper; /* Given the values of targetDir and zipDir in the helper, * return the target filename of the provided entry. * The helper must be initialized first. */ static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry) { int needLen; bool firstTime = (helper->buf == NULL); /* target file <-- targetDir + / + entry[zipDirLen:] */ needLen = helper->targetDirLen + 1 + pEntry->fileNameLen - helper->zipDirLen + 1; if (needLen > helper->bufLen) { char *newBuf; needLen *= 2; newBuf = (char *)realloc(helper->buf, needLen); if (newBuf == NULL) { return NULL; } helper->buf = newBuf; helper->bufLen = needLen; } /* Every path will start with the target path and a slash. */ if (firstTime) { char *p = helper->buf; memcpy(p, helper->targetDir, helper->targetDirLen); p += helper->targetDirLen; if (p == helper->buf || p[-1] != '/') { helper->targetDirLen += 1; *p++ = '/'; } } /* Replace the custom part of the path with the appropriate * part of the entry's path. */ char *epath = helper->buf + helper->targetDirLen; memcpy(epath, pEntry->fileName + helper->zipDirLen, pEntry->fileNameLen - helper->zipDirLen); epath += pEntry->fileNameLen - helper->zipDirLen; *epath = '\0'; return helper->buf; } /* * Inflate all entries under zipDir to the directory specified by * targetDir, which must exist and be a writable directory. * * The immediate children of zipDir will become the immediate * children of targetDir; e.g., if the archive contains the entries * * a/b/c/one * a/b/c/two * a/b/c/d/three * * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting * files will be * * /tmp/one * /tmp/two * /tmp/d/three * * Returns true on success, false on failure. */ bool mzExtractRecursive(const ZipArchive *pArchive, const char *zipDir, const char *targetDir, const struct utimbuf *timestamp, void (*callback)(const char *fn, void *), void *cookie, struct selabel_handle *sehnd) { if (zipDir[0] == '/') { LOGE("mzExtractRecursive(): zipDir must be a relative path.\n"); return false; } if (targetDir[0] != '/') { LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n"); return false; } unsigned int zipDirLen; char *zpath; zipDirLen = strlen(zipDir); zpath = (char *)malloc(zipDirLen + 2); if (zpath == NULL) { LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2); return false; } /* If zipDir is empty, we'll extract the entire zip file. * Otherwise, canonicalize the path. */ if (zipDirLen > 0) { /* Make sure there's (hopefully, exactly one) slash at the * end of the path. This way we don't need to worry about * accidentally extracting "one/twothree" when a path like * "one/two" is specified. */ memcpy(zpath, zipDir, zipDirLen); if (zpath[zipDirLen-1] != '/') { zpath[zipDirLen++] = '/'; } } zpath[zipDirLen] = '\0'; /* Set up the helper structure that we'll use to assemble paths. */ MzPathHelper helper; helper.targetDir = targetDir; helper.targetDirLen = strlen(helper.targetDir); helper.zipDir = zpath; helper.zipDirLen = strlen(helper.zipDir); helper.buf = NULL; helper.bufLen = 0; /* Walk through the entries and extract anything whose path begins * with zpath. //TODO: since the entries are sorted, binary search for the first match // and stop after the first non-match. */ unsigned int i; bool seenMatch = false; int ok = true; int extractCount = 0; for (i = 0; i < pArchive->numEntries; i++) { ZipEntry *pEntry = pArchive->pEntries + i; if (pEntry->fileNameLen < zipDirLen) { //TODO: look out for a single empty directory entry that matches zpath, but // missing the trailing slash. Most zip files seem to include // the trailing slash, but I think it's legal to leave it off. // e.g., zpath "a/b/", entry "a/b", with no children of the entry. /* No chance of matching. */ #if SORT_ENTRIES if (seenMatch) { /* Since the entries are sorted, we can give up * on the first mismatch after the first match. */ break; } #endif continue; } /* If zpath is empty, this strncmp() will match everything, * which is what we want. */ if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) { #if SORT_ENTRIES if (seenMatch) { /* Since the entries are sorted, we can give up * on the first mismatch after the first match. */ break; } #endif continue; } /* This entry begins with zipDir, so we'll extract it. */ seenMatch = true; /* Find the target location of the entry. */ const char *targetFile = targetEntryPath(&helper, pEntry); if (targetFile == NULL) { LOGE("Can't assemble target path for \"%.*s\"\n", pEntry->fileNameLen, pEntry->fileName); ok = false; break; } #define UNZIP_DIRMODE 0755 #define UNZIP_FILEMODE 0644 /* * Create the file or directory. We ignore directory entries * because we recursively create paths to each file entry we encounter * in the zip archive anyway. * * NOTE: A "directory entry" in a zip archive is just a zero length * entry that ends in a "/". They're not mandatory and many tools get * rid of them. We need to process them only if we want to preserve * empty directories from the archive. */ if (pEntry->fileName[pEntry->fileNameLen-1] != '/') { /* This is not a directory. First, make sure that * the containing directory exists. */ int ret = dirCreateHierarchy( targetFile, UNZIP_DIRMODE, timestamp, true, sehnd); if (ret != 0) { LOGE("Can't create containing directory for \"%s\": %s\n", targetFile, strerror(errno)); ok = false; break; } /* * The entry is a regular file or a symlink. Open the target for writing. * * TODO: This behavior for symlinks seems rather bizarre. For a * symlink foo/bar/baz -> foo/tar/taz, we will create a file called * "foo/bar/baz" whose contents are the literal "foo/tar/taz". We * warn about this for now and preserve older behavior. */ if (mzIsZipEntrySymlink(pEntry)) { LOGE("Symlink entry \"%.*s\" will be output as a regular file.", pEntry->fileNameLen, pEntry->fileName); } char *secontext = NULL; if (sehnd) { selabel_lookup(sehnd, &secontext, targetFile, UNZIP_FILEMODE); setfscreatecon(secontext); } int fd = open(targetFile, O_CREAT|O_WRONLY|O_TRUNC|O_SYNC, UNZIP_FILEMODE); if (secontext) { freecon(secontext); setfscreatecon(NULL); } if (fd < 0) { LOGE("Can't create target file \"%s\": %s\n", targetFile, strerror(errno)); ok = false; break; } bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd); if (ok) { ok = (fsync(fd) == 0); } if (close(fd) != 0) { ok = false; } if (!ok) { LOGE("Error extracting \"%s\"\n", targetFile); ok = false; break; } if (timestamp != NULL && utime(targetFile, timestamp)) { LOGE("Error touching \"%s\"\n", targetFile); ok = false; break; } LOGV("Extracted file \"%s\"\n", targetFile); ++extractCount; } if (callback != NULL) callback(targetFile, cookie); } LOGV("Extracted %d file(s)\n", extractCount); free(helper.buf); free(zpath); return ok; }