/* * Find an entry by name. */ ZipEntry* ZipFile::getEntryByName(const char* fileName) const { /* * Do a stupid linear string-compare search. * * There are various ways to speed this up, especially since it's rare * to intermingle changes to the archive with "get by name" calls. We * don't want to sort the mEntries vector itself, however, because * it's used to recreate the Central Directory. * * (Hash table works, parallel list of pointers in sorted order is good.) */ int idx; for (idx = mEntries.size()-1; idx >= 0; idx--) { ZipEntry* pEntry = mEntries[idx]; if (!pEntry->getDeleted() && strcmp(fileName, pEntry->getFileName()) == 0) { return pEntry; } } return NULL; }
bool ZipArchive::setEntryComment(const ZipEntry& entry, const string& comment) const { if (!isOpen()) { return false; } if (entry.zipFile!=this) { return false; } bool result = zip_file_set_comment(zipHandle, entry.getIndex(), comment.c_str(), comment.size(), ZIP_FL_ENC_GUESS); return result==0; }
s::String MountpointZip::FileReadAll(const char* fnm) { ZipEntry entry; if (!m_zip.GetEntry(fnm, entry)) { #ifndef SCRATCH_NO_EXCEPTIONS throw s::Exception("File not found."); #else return ""; #endif } size_t sz = entry.GetSize(); char* buffer = (char*)malloc(sz + 1); entry.Read(buffer, sz); buffer[sz] = '\0'; s::String ret(buffer); free(buffer); return ret; }
string ZipArchive::getEntryComment(const ZipEntry& entry, State state) const { if (!isOpen()) { return string(); } if (entry.zipFile!=this) { return string(); } int flag = ZIP_FL_ENC_GUESS; if (state==ORIGINAL) { flag = flag | ZIP_FL_UNCHANGED; } unsigned int clen; const char* com = zip_file_get_comment(zipHandle, entry.getIndex(), &clen, flag); string comment = com==NULL ? string() : string(com, clen); return comment; }
/* * Process a regular file, adding it to the archive if appropriate. * * This function is intended for use when creating a cached overlay package. * Only xml and .9.png files are processed and added to the package. * * If we're in "update" mode, and the file already exists in the archive, * delete the existing entry before adding the new one. */ bool processOverlayFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file) { const bool hasData = file->hasData(); storageName.convertToResPath(); ZipEntry* entry; bool fromGzip = false; status_t result; if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) { fromGzip = true; storageName = storageName.getBasePath(); } if (bundle->getUpdate()) { entry = zip->getEntryByName(storageName.string()); if (entry != NULL) { /* file already exists in archive; there can be only one */ if (entry->getMarked()) { fprintf(stderr, "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", file->getPrintableSource().string()); return false; } zip->remove(entry); } } if (hasData) { const char* name = storageName.string(); if (endsWith(name, ".9.png") || endsWith(name, ".xml") || endsWith(name, ".arsc")) { result = zip->add(file->getData(), file->getSize(), storageName.string(), file->getCompressionMethod(), &entry); if (result == NO_ERROR) { if (bundle->getVerbose()) { printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : ""); if (entry->getCompressionMethod() == ZipEntry::kCompressStored) { printf(" (not compressed)\n"); } else { printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(), entry->getCompressedLen())); } } entry->setMarked(true); } else { if (result == ALREADY_EXISTS) { fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", file->getPrintableSource().string()); } else { fprintf(stderr, " Unable to add '%s': Zip add failed\n", file->getPrintableSource().string()); } return false; } } } return true; }
int ZipArchive::deleteEntry(const ZipEntry& entry) const { if (!isOpen()) { return LIBZIPPP_ERROR_NOT_OPEN; } if (entry.zipFile!=this) { return LIBZIPPP_ERROR_INVALID_ENTRY; } if (mode==READ_ONLY) { return LIBZIPPP_ERROR_NOT_ALLOWED; } //deletion not allowed if (entry.isFile()) { int result = zip_delete(zipHandle, entry.getIndex()); if (result==0) { return 1; } return LIBZIPPP_ERROR_UNKNOWN; //unable to delete the entry } else { int counter = 0; vector<ZipEntry> allEntries = getEntries(); vector<ZipEntry>::const_iterator eit; for(eit=allEntries.begin() ; eit!=allEntries.end() ; ++eit) { ZipEntry ze = *eit; int startPosition = ze.getName().find(entry.getName()); if (startPosition==0) { int result = zip_delete(zipHandle, ze.getIndex()); if (result==0) { ++counter; } else { return LIBZIPPP_ERROR_UNKNOWN; } //unable to remove the current entry } } return counter; } }
ssize_t processJarFile(ZipFile* jar, ZipFile* out) { size_t N = jar->getNumEntries(); size_t count = 0; for (size_t i=0; i<N; i++) { ZipEntry* entry = jar->getEntryByIndex(i); const char* storageName = entry->getFileName(); if (endsWith(storageName, ".class")) { int compressionMethod = entry->getCompressionMethod(); size_t size = entry->getUncompressedLen(); const void* data = jar->uncompress(entry); if (data == NULL) { fprintf(stderr, "ERROR: unable to uncompress entry '%s'\n", storageName); return -1; } out->add(data, size, storageName, compressionMethod, NULL); free((void*)data); } count++; } return count; }
static void ListOne(const ZipEntry& entry, const std::string& name) { tm t = entry.GetModificationTime(); char time[32]; snprintf(time, sizeof(time), "%04d-%02d-%02d %02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min); if (flag_v) { printf("%8d %s %7d %3d%% %s %08x %s\n", entry.uncompressed_length, (entry.method == kCompressStored) ? "Stored" : "Defl:N", entry.compressed_length, CompressionRatio(entry.uncompressed_length, entry.compressed_length), time, entry.crc32, name.c_str()); } else { printf("%9d %s %s\n", entry.uncompressed_length, time, name.c_str()); } }
int ZipArchive::deleteEntry(const string& e) const { ZipEntry entry = getEntry(e); if (entry.isNull()) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } return deleteEntry(entry); }
/* * The directory hierarchy looks like this: * "outputDir" and "assetRoot" are existing directories. * * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet) { #if BENCHMARK fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); long startAPKTime = clock(); #endif /* BENCHMARK */ status_t result = NO_ERROR; ZipFile* zip = NULL; int count; //bundle->setPackageCount(0); /* * Prep the Zip archive. * * If the file already exists, fail unless "update" or "force" is set. * If "update" is set, update the contents of the existing archive. * Else, if "force" is set, remove the existing archive. */ FileType fileType = getFileType(outputFile.string()); if (fileType == kFileTypeNonexistent) { // okay, create it below } else if (fileType == kFileTypeRegular) { if (bundle->getUpdate()) { // okay, open it below } else if (bundle->getForce()) { if (unlink(outputFile.string()) != 0) { fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(), strerror(errno)); goto bail; } } else { fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n", outputFile.string()); goto bail; } } else { fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string()); goto bail; } if (bundle->getVerbose()) { printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening", outputFile.string()); } status_t status; zip = new ZipFile; status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate); if (status != NO_ERROR) { fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n", outputFile.string()); goto bail; } if (bundle->getVerbose()) { printf("Writing all files...\n"); } count = processAssets(bundle, zip, outputSet); if (count < 0) { fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", outputFile.string()); result = count; goto bail; } if (bundle->getVerbose()) { printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); } count = processJarFiles(bundle, zip); if (count < 0) { fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", outputFile.string()); result = count; goto bail; } if (bundle->getVerbose()) printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); result = NO_ERROR; /* * Check for cruft. We set the "marked" flag on all entries we created * or decided not to update. If the entry isn't already slated for * deletion, remove it now. */ { if (bundle->getVerbose()) printf("Checking for deleted files\n"); int i, removed = 0; for (i = 0; i < zip->getNumEntries(); i++) { ZipEntry* entry = zip->getEntryByIndex(i); if (!entry->getMarked() && entry->getDeleted()) { if (bundle->getVerbose()) { printf(" (removing crufty '%s')\n", entry->getFileName()); } zip->remove(entry); removed++; } } if (bundle->getVerbose() && removed > 0) printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); } /* tell Zip lib to process deletions and other pending changes */ result = zip->flush(); if (result != NO_ERROR) { fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); goto bail; } /* anything here? */ if (zip->getNumEntries() == 0) { if (bundle->getVerbose()) { printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string()); } delete zip; // close the file so we can remove it in Win32 zip = NULL; if (unlink(outputFile.string()) != 0) { fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); } } // If we've been asked to generate a dependency file for the .ap_ package, // do so here if (bundle->getGenDependencies()) { // The dependency file gets output to the same directory // as the specified output file with an additional .d extension. // e.g. bin/resources.ap_.d String8 dependencyFile = outputFile; dependencyFile.append(".d"); FILE* fp = fopen(dependencyFile.string(), "a"); // Add this file to the dependency file fprintf(fp, "%s \\\n", outputFile.string()); fclose(fp); } assert(result == NO_ERROR); bail: delete zip; // must close before remove in Win32 if (result != NO_ERROR) { if (bundle->getVerbose()) { printf("Removing %s due to earlier failures\n", outputFile.string()); } if (unlink(outputFile.string()) != 0) { fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); } } if (result == NO_ERROR && bundle->getVerbose()) printf("Done!\n"); #if BENCHMARK fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0); #endif /* BENCHMARK */ return result; }
/* * Process a regular file, adding it to the archive if appropriate. * * If we're in "update" mode, and the file already exists in the archive, * delete the existing entry before adding the new one. */ bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file) { const bool hasData = file->hasData(); ZipEntry* entry; bool fromGzip = false; status_t result; /* * See if the filename ends in ".EXCLUDE". We can't use * String8::getPathExtension() because the length of what it considers * to be an extension is capped. * * The Asset Manager doesn't check for ".EXCLUDE" in Zip archives, * so there's no value in adding them (and it makes life easier on * the AssetManager lib if we don't). * * NOTE: this restriction has been removed. If you're in this code, you * should clean this up, but I'm in here getting rid of Path Name, and I * don't want to make other potentially breaking changes --joeo */ int fileNameLen = storageName.length(); int excludeExtensionLen = strlen(kExcludeExtension); if (fileNameLen > excludeExtensionLen && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen), kExcludeExtension))) { fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string()); return true; } if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) { fromGzip = true; storageName = storageName.getBasePath(); } if (bundle->getUpdate()) { entry = zip->getEntryByName(storageName.string()); if (entry != NULL) { /* file already exists in archive; there can be only one */ if (entry->getMarked()) { fprintf(stderr, "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", file->getPrintableSource().string()); return false; } if (!hasData) { const String8& srcName = file->getSourceFile(); time_t fileModWhen; fileModWhen = getFileModDate(srcName.string()); if (fileModWhen == (time_t) -1) { // file existence tested earlier, return false; // not expecting an error here } if (fileModWhen > entry->getModWhen()) { // mark as deleted so add() will succeed if (bundle->getVerbose()) { printf(" (removing old '%s')\n", storageName.string()); } zip->remove(entry); } else { // version in archive is newer if (bundle->getVerbose()) { printf(" (not updating '%s')\n", storageName.string()); } entry->setMarked(true); return true; } } else { // Generated files are always replaced. zip->remove(entry); } } } //android_setMinPriority(NULL, ANDROID_LOG_VERBOSE); if (fromGzip) { result = zip->addGzip(file->getSourceFile().string(), storageName.string(), &entry); } else if (!hasData) { /* don't compress certain files, e.g. PNGs */ int compressionMethod = bundle->getCompressionMethod(); if (!okayToCompress(bundle, storageName)) { compressionMethod = ZipEntry::kCompressStored; } result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod, &entry); } else { result = zip->add(file->getData(), file->getSize(), storageName.string(), file->getCompressionMethod(), &entry); } if (result == NO_ERROR) { if (bundle->getVerbose()) { printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : ""); if (entry->getCompressionMethod() == ZipEntry::kCompressStored) { printf(" (not compressed)\n"); } else { printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(), entry->getCompressedLen())); } } entry->setMarked(true); } else { if (result == ALREADY_EXISTS) { fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", file->getPrintableSource().string()); } else { fprintf(stderr, " Unable to add '%s': Zip add failed (%d)\n", file->getPrintableSource().string(), result); } return false; } return true; }
ssize_t AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename) { int count = 0; SortedVector<AaptGroupEntry> entries; ZipFile* zip = new ZipFile; status_t err = zip->open(filename, ZipFile::kOpenReadOnly); if (err != NO_ERROR) { fprintf(stderr, "error opening zip file %s\n", filename); count = err; delete zip; return -1; } const int N = zip->getNumEntries(); for (int i=0; i<N; i++) { ZipEntry* entry = zip->getEntryByIndex(i); if (entry->getDeleted()) { continue; } String8 entryName(entry->getFileName()); String8 dirName = entryName.getPathDir(); sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName); String8 resType; AaptGroupEntry kind; String8 remain; if (entryName.walkPath(&remain) == kResourceDir) { // these are the resources, pull their type out of the directory name kind.initFromDirName(remain.walkPath().string(), &resType); } else { // these are untyped and don't have an AaptGroupEntry } if (entries.indexOf(kind) < 0) { entries.add(kind); mGroupEntries.add(kind); } // use the one from the zip file if they both exist. dir->removeFile(entryName.getPathLeaf()); sp<AaptFile> file = new AaptFile(entryName, kind, resType); status_t err = dir->addLeafFile(entryName.getPathLeaf(), file); if (err != NO_ERROR) { fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string()); count = err; goto bail; } file->setCompressionMethod(entry->getCompressionMethod()); #if 0 if (entryName == "AndroidManifest.xml") { printf("AndroidManifest.xml\n"); } printf("\n\nfile: %s\n", entryName.string()); #endif size_t len = entry->getUncompressedLen(); void* data = zip->uncompress(entry); void* buf = file->editData(len); memcpy(buf, data, len); #if 0 const int OFF = 0; const unsigned char* p = (unsigned char*)data; const unsigned char* end = p+len; p += OFF; for (int i=0; i<32 && p < end; i++) { printf("0x%03x ", i*0x10 + OFF); for (int j=0; j<0x10 && p < end; j++) { printf(" %02x", *p); p++; } printf("\n"); } #endif free(data); count++; } bail: delete zip; return count; }
/* * Add an entry by copying it from another zip file, recompressing with * Zopfli if already compressed. * * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. */ status_t ZipFile::addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result; long lfhPosn, startPosn, endPosn, uncompressedLen; if (mReadOnly) return INVALID_OPERATION; /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; if (pEntry == NULL) { result = NO_MEMORY; goto bail; } result = pEntry->initFromExternal(pSourceEntry); if (result != NO_ERROR) goto bail; /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH, even though it's still mostly blank. We need it * as a place-holder. In theory the LFH isn't necessary, but in * practice some utilities demand it. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); startPosn = ftell(mZipFp); /* * Copy the data over. * * If the "has data descriptor" flag is set, we want to copy the DD * fields as well. This is a fixed-size area immediately following * the data. */ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } uncompressedLen = pSourceEntry->getUncompressedLen(); if (pSourceEntry->isCompressed()) { void *buf = pSourceZip->uncompress(pSourceEntry); if (buf == NULL) { result = NO_MEMORY; goto bail; } long startPosn = ftell(mZipFp); uint32_t crc; if (compressFpToFp(mZipFp, NULL, buf, uncompressedLen, &crc) != NO_ERROR) { ALOGW("recompress of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; free(buf); goto bail; } long endPosn = ftell(mZipFp); pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, pSourceEntry->getCRC32(), ZipEntry::kCompressDeflated); free(buf); } else { off_t copyLen; copyLen = pSourceEntry->getCompressedLen(); if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) copyLen += ZipEntry::kDataDescriptorLen; if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) != NO_ERROR) { ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; goto bail; } } /* * Update file offsets. */ endPosn = ftell(mZipFp); /* * Success! Fill out new values. */ pEntry->setLFHOffset(lfhPosn); mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Go back and write the LFH. */ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry->mLFH.write(mZipFp); /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; result = NO_ERROR; bail: delete pEntry; return result; }
int ZipArchive::readEntry(const ZipEntry& zipEntry, std::ofstream& ofOutput, State state, libzippp_uint64 chunksize) const { if (!ofOutput.is_open()) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } if (!isOpen()) { return LIBZIPPP_ERROR_NOT_OPEN; } if (zipEntry.zipFile!=this) { return LIBZIPPP_ERROR_INVALID_ENTRY; } int iRes = LIBZIPPP_OK; int flag = state==ORIGINAL ? ZIP_FL_UNCHANGED : 0; struct zip_file* zipFile = zip_fopen_index(zipHandle, zipEntry.getIndex(), flag); if (zipFile) { libzippp_uint64 maxSize = zipEntry.getSize(); if (!chunksize) { chunksize = DEFAULT_CHUNK_SIZE; } // use the default chunk size (512K) if not specified by the user if (maxSize<chunksize) { char* data = new char[maxSize]; if (data!=NULL) { libzippp_int64 result = zip_fread(zipFile, data, maxSize); if (result>0) { if (result != static_cast<libzippp_int64>(maxSize)) { iRes = LIBZIPPP_ERROR_OWRITE_INDEX_FAILURE; } else { ofOutput.write(data, maxSize); if (!ofOutput) { iRes = LIBZIPPP_ERROR_OWRITE_FAILURE; } } } else { iRes = LIBZIPPP_ERROR_FREAD_FAILURE; } delete[] data; } else { iRes = LIBZIPPP_ERROR_MEMORY_ALLOCATION; } } else { libzippp_uint64 uWrittenBytes = 0; libzippp_int64 result = 0; char* data = new char[chunksize]; if (data!=NULL) { int nbChunks = maxSize/chunksize; for (int uiChunk=0 ; uiChunk<nbChunks ; ++uiChunk) { result = zip_fread(zipFile, data, chunksize); if (result>0) { if (result!=static_cast<libzippp_int64>(chunksize)) { iRes = LIBZIPPP_ERROR_OWRITE_INDEX_FAILURE; break; } else { ofOutput.write(data, chunksize); if (!ofOutput) { iRes = LIBZIPPP_ERROR_OWRITE_FAILURE; break; } uWrittenBytes += result; } } else { iRes = LIBZIPPP_ERROR_FREAD_FAILURE; break; } } delete[] data; } else { iRes = LIBZIPPP_ERROR_MEMORY_ALLOCATION; } int leftOver = maxSize%chunksize; if (iRes==0 && leftOver>0) { char* data = new char[leftOver]; if (data!=NULL) { result = zip_fread(zipFile, data, leftOver); if (result>0) { if (result!=static_cast<libzippp_int64>(leftOver)) { iRes = LIBZIPPP_ERROR_OWRITE_INDEX_FAILURE; } else { ofOutput.write(data, leftOver); if (!ofOutput) { iRes = LIBZIPPP_ERROR_OWRITE_FAILURE; } else { uWrittenBytes += result; if (uWrittenBytes!=maxSize) { iRes = LIBZIPPP_ERROR_UNKNOWN; // shouldn't occur but let's be careful } } } } else { iRes = LIBZIPPP_ERROR_FREAD_FAILURE; } } else { iRes = LIBZIPPP_ERROR_MEMORY_ALLOCATION; } delete[] data; } } zip_fclose(zipFile); } else { iRes = LIBZIPPP_ERROR_FOPEN_FAILURE; } return iRes; }
int ZipArchive::renameEntry(const string& e, const string& newName) const { ZipEntry entry = getEntry(e); if (entry.isNull()) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } return renameEntry(entry, newName); }
int ZipArchive::renameEntry(const string& e, const string& newName) const { ZipEntry entry = getEntry(e); if (entry.isNull()) { return -4; } return renameEntry(entry, newName); }
/* * Add an entry by copying it from another zip file. If "padding" is * nonzero, the specified number of bytes will be added to the "extra" * field in the header. * * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. */ status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, int padding, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result; long lfhPosn, endPosn; if (mReadOnly) return INVALID_OPERATION; /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; if (pEntry == NULL) { result = NO_MEMORY; goto bail; } result = pEntry->initFromExternal(pSourceZip, pSourceEntry); if (result != NO_ERROR) goto bail; if (padding != 0) { result = pEntry->addPadding(padding); if (result != NO_ERROR) goto bail; } /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH. Since we're not recompressing the data, we already * have all of the fields filled out. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); /* * Copy the data over. * * If the "has data descriptor" flag is set, we want to copy the DD * fields as well. This is a fixed-size area immediately following * the data. */ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } off_t copyLen; copyLen = pSourceEntry->getCompressedLen(); if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) copyLen += ZipEntry::kDataDescriptorLen; if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) != NO_ERROR) { LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); result = UNKNOWN_ERROR; goto bail; } /* * Update file offsets. */ endPosn = ftell(mZipFp); /* * Success! Fill out new values. */ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; result = NO_ERROR; bail: delete pEntry; return result; }
/* * Find the central directory and read 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::readCentralDir(void) { status_t result = NO_ERROR; unsigned char* 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) { LOGD("Length is %ld -- too small\n", (long)fileLength); result = INVALID_OPERATION; goto bail; } buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; if (buf == NULL) { LOGD("Failure allocating %d bytes for EOCD search", EndOfCentralDir::kMaxEOCDSearch); result = NO_MEMORY; 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) { LOGD("Failure seeking to end of zip at %ld", (long) seekStart); result = UNKNOWN_ERROR; goto bail; } /* read the last part of the file into the buffer */ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { LOGD("short file? wanted %ld\n", readAmount); result = UNKNOWN_ERROR; 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) { LOGV("+++ Found EOCD at buf+%d\n", i); break; } } if (i < 0) { LOGD("EOCD not found, not Zip\n"); result = INVALID_OPERATION; goto bail; } /* extract eocd values */ result = mEOCD.readBuf(buf + i, readAmount - i); if (result != NO_ERROR) { LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); goto bail; } //mEOCD.dump(); if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || mEOCD.mNumEntries != mEOCD.mTotalNumEntries) { LOGD("Archive spanning not supported\n"); result = INVALID_OPERATION; 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) { LOGD("Failure seeking to central dir offset %ld\n", mEOCD.mCentralDirOffset); result = UNKNOWN_ERROR; goto bail; } /* * Loop through and read the central dir entries. */ LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); int entry; for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { ZipEntry* pEntry = new ZipEntry; result = pEntry->initFromCDE(mZipFp); if (result != NO_ERROR) { LOGD("initFromCDE failed\n"); delete pEntry; goto bail; } mEntries.add(pEntry); } /* * If all went well, we should now be back at the EOCD. */ { unsigned char checkBuf[4]; if (fread(checkBuf, 1, 4, mZipFp) != 4) { LOGD("EOCD check read failed\n"); result = INVALID_OPERATION; goto bail; } if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { LOGD("EOCD read check failed\n"); result = UNKNOWN_ERROR; goto bail; } LOGV("+++ EOCD read check passed\n"); } bail: delete[] buf; return result; }
void* ZipArchive::readEntry(const string& zipEntry, bool asText, State state, libzippp_uint64 size) const { ZipEntry entry = getEntry(zipEntry); if (entry.isNull()) { return NULL; } return readEntry(entry, asText, state, size); }
/* * The directory hierarchy looks like this: * "outputDir" and "assetRoot" are existing directories. * * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ status_t writeAPK(Bundle* bundle, ZipFile* zip, const char* outputFileName, const sp<OutputSet>& outputSet, bool isOverlay) { status_t result = NO_ERROR; int count; if (bundle->getVerbose()) { printf("Writing all files...\n"); } count = processAssets(bundle, zip, outputSet, isOverlay); if (count < 0) { fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", outputFileName); result = count; goto bail; } if (bundle->getVerbose()) { printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); } if (!isOverlay) { count = processJarFiles(bundle, zip); if (count < 0) { fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", outputFileName); result = count; goto bail; } if (bundle->getVerbose()) printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); } result = NO_ERROR; /* * Check for cruft. We set the "marked" flag on all entries we created * or decided not to update. If the entry isn't already slated for * deletion, remove it now. */ { if (bundle->getVerbose()) printf("Checking for deleted files\n"); int i, removed = 0; for (i = 0; i < zip->getNumEntries(); i++) { ZipEntry* entry = zip->getEntryByIndex(i); if (!entry->getMarked() && entry->getDeleted()) { if (bundle->getVerbose()) { printf(" (removing crufty '%s')\n", entry->getFileName()); } zip->remove(entry); removed++; } } if (bundle->getVerbose() && removed > 0) printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); } /* tell Zip lib to process deletions and other pending changes */ result = zip->flush(); if (result != NO_ERROR) { fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); goto bail; } bail: return result; }
/* * The directory hierarchy looks like this: * "outputDir" and "assetRoot" are existing directories. * * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, const String8& outputFile) { status_t result = NO_ERROR; ZipFile* zip = NULL; int count; //bundle->setPackageCount(0); /* * Prep the Zip archive. * * If the file already exists, fail unless "update" or "force" is set. * If "update" is set, update the contents of the existing archive. * Else, if "force" is set, remove the existing archive. */ FileType fileType = getFileType(outputFile.string()); if (fileType == kFileTypeNonexistent) { // okay, create it below } else if (fileType == kFileTypeRegular) { if (bundle->getUpdate()) { // okay, open it below } else if (bundle->getForce()) { if (unlink(outputFile.string()) != 0) { fprintf(stderr, "ERROR: unable to remove '%s': %s\n", outputFile.string(), strerror(errno)); goto bail; } } else { fprintf(stderr, "ERROR: '%s' exists (use '-f' to force overwrite)\n", outputFile.string()); goto bail; } } else { fprintf(stderr, "ERROR: '%s' exists and is not a regular file\n", outputFile.string()); goto bail; } if (bundle->getVerbose()) { printf("%s '%s'\n", (fileType == kFileTypeNonexistent) ? "Creating" : "Opening", outputFile.string()); } status_t status; zip = new ZipFile; status = zip->open(outputFile.string(), ZipFile::kOpenReadWrite | ZipFile::kOpenCreate); if (status != NO_ERROR) { fprintf(stderr, "ERROR: unable to open '%s' as Zip file for writing\n", outputFile.string()); goto bail; } if (bundle->getVerbose()) { printf("Writing all files...\n"); } count = processAssets(bundle, zip, assets); if (count < 0) { fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", outputFile.string()); result = count; goto bail; } if (bundle->getVerbose()) { printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); } count = processJarFiles(bundle, zip); if (count < 0) { fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", outputFile.string()); result = count; goto bail; } if (bundle->getVerbose()) printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); result = NO_ERROR; /* * Check for cruft. We set the "marked" flag on all entries we created * or decided not to update. If the entry isn't already slated for * deletion, remove it now. */ { if (bundle->getVerbose()) printf("Checking for deleted files\n"); int i, removed = 0; for (i = 0; i < zip->getNumEntries(); i++) { ZipEntry* entry = zip->getEntryByIndex(i); if (!entry->getMarked() && entry->getDeleted()) { if (bundle->getVerbose()) { printf(" (removing crufty '%s')\n", entry->getFileName()); } zip->remove(entry); removed++; } } if (bundle->getVerbose() && removed > 0) printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); } /* tell Zip lib to process deletions and other pending changes */ result = zip->flush(); if (result != NO_ERROR) { fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); goto bail; } /* anything here? */ if (zip->getNumEntries() == 0) { if (bundle->getVerbose()) { printf("Archive is empty -- removing %s\n", outputFile.getPathLeaf().string()); } delete zip; // close the file so we can remove it in Win32 zip = NULL; if (unlink(outputFile.string()) != 0) { fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); } } assert(result == NO_ERROR); bail: delete zip; // must close before remove in Win32 if (result != NO_ERROR) { if (bundle->getVerbose()) { printf("Removing %s due to earlier failures\n", outputFile.string()); } if (unlink(outputFile.string()) != 0) { fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); } } if (result == NO_ERROR && bundle->getVerbose()) printf("Done!\n"); return result; }
/* * Add a new file to the archive. * * This requires creating and populating a ZipEntry structure, and copying * the data into the file at the appropriate position. The "appropriate * position" is the current location of the central directory, which we * casually overwrite (we can put it back later). * * If we were concerned about safety, we would want to make all changes * in a temp file and then overwrite the original after everything was * safely written. Not really a concern for us. */ status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, const char* storageName, int sourceType, int compressionMethod, ZipEntry** ppEntry) { ZipEntry* pEntry = NULL; status_t result = NO_ERROR; long lfhPosn, startPosn, endPosn, uncompressedLen; FILE* inputFp = NULL; unsigned long crc; time_t modWhen; if (mReadOnly) return INVALID_OPERATION; assert(compressionMethod == ZipEntry::kCompressDeflated || compressionMethod == ZipEntry::kCompressStored); /* make sure we're in a reasonable state */ assert(mZipFp != NULL); assert(mEntries.size() == mEOCD.mTotalNumEntries); /* make sure it doesn't already exist */ if (getEntryByName(storageName) != NULL) return ALREADY_EXISTS; if (!data) { inputFp = fopen(fileName, FILE_OPEN_RO); if (inputFp == NULL) return errnoToStatus(errno); } if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry = new ZipEntry; pEntry->initNew(storageName, NULL); /* * From here on out, failures are more interesting. */ mNeedCDRewrite = true; /* * Write the LFH, even though it's still mostly blank. We need it * as a place-holder. In theory the LFH isn't necessary, but in * practice some utilities demand it. */ lfhPosn = ftell(mZipFp); pEntry->mLFH.write(mZipFp); startPosn = ftell(mZipFp); /* * Copy the data in, possibly compressing it as we go. */ if (sourceType == ZipEntry::kCompressStored) { if (compressionMethod == ZipEntry::kCompressDeflated) { bool failed = false; result = compressFpToFp(mZipFp, inputFp, data, size, &crc); if (result != NO_ERROR) { LOGD("compression failed, storing\n"); failed = true; } else { /* * Make sure it has compressed "enough". This probably ought * to be set through an API call, but I don't expect our * criteria to change over time. */ long src = inputFp ? ftell(inputFp) : size; long dst = ftell(mZipFp) - startPosn; if (dst + (dst / 10) > src) { LOGD("insufficient compression (src=%ld dst=%ld), storing\n", src, dst); failed = true; } } if (failed) { compressionMethod = ZipEntry::kCompressStored; if (inputFp) rewind(inputFp); fseek(mZipFp, startPosn, SEEK_SET); /* fall through to kCompressStored case */ } } /* handle "no compression" request, or failed compression from above */ if (compressionMethod == ZipEntry::kCompressStored) { if (inputFp) { result = copyFpToFp(mZipFp, inputFp, &crc); } else { result = copyDataToFp(mZipFp, data, size, &crc); } if (result != NO_ERROR) { // don't need to truncate; happens in CDE rewrite LOGD("failed copying data in\n"); goto bail; } } // currently seeked to end of file uncompressedLen = inputFp ? ftell(inputFp) : size; } else if (sourceType == ZipEntry::kCompressDeflated) { /* we should support uncompressed-from-compressed, but it's not * important right now */ assert(compressionMethod == ZipEntry::kCompressDeflated); bool scanResult; int method; long compressedLen; scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, &compressedLen, &crc); if (!scanResult || method != ZipEntry::kCompressDeflated) { LOGD("this isn't a deflated gzip file?"); result = UNKNOWN_ERROR; goto bail; } result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); if (result != NO_ERROR) { LOGD("failed copying gzip data in\n"); goto bail; } } else { assert(false); result = UNKNOWN_ERROR; goto bail; } /* * We could write the "Data Descriptor", but there doesn't seem to * be any point since we're going to go back and write the LFH. * * Update file offsets. */ endPosn = ftell(mZipFp); // seeked to end of compressed data /* * Success! Fill out new values. */ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, compressionMethod); modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); pEntry->setModWhen(modWhen); pEntry->setLFHOffset(lfhPosn); mEOCD.mNumEntries++; mEOCD.mTotalNumEntries++; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() mEOCD.mCentralDirOffset = endPosn; /* * Go back and write the LFH. */ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { result = UNKNOWN_ERROR; goto bail; } pEntry->mLFH.write(mZipFp); /* * Add pEntry to the list. */ mEntries.add(pEntry); if (ppEntry != NULL) *ppEntry = pEntry; pEntry = NULL; bail: if (inputFp != NULL) fclose(inputFp); delete pEntry; return result; }
int ZipArchive::renameEntry(const ZipEntry& entry, const string& newName) const { if (!isOpen()) { return LIBZIPPP_ERROR_NOT_OPEN; } if (entry.zipFile!=this) { return LIBZIPPP_ERROR_INVALID_ENTRY; } if (mode==READ_ONLY) { return LIBZIPPP_ERROR_NOT_ALLOWED; } //renaming not allowed if (newName.length()==0) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } if (newName==entry.getName()) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } if (entry.isFile()) { if (ENTRY_IS_DIRECTORY(newName)) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } //invalid new name int lastSlash = newName.rfind(ENTRY_PATH_SEPARATOR); if (lastSlash!=1) { bool dadded = addEntry(newName.substr(0, lastSlash+1)); if (!dadded) { return LIBZIPPP_ERROR_UNKNOWN; } //the hierarchy hasn't been created } int result = zip_file_rename(zipHandle, entry.getIndex(), newName.c_str(), ZIP_FL_ENC_GUESS); if (result==0) { return 1; } return LIBZIPPP_ERROR_UNKNOWN; //renaming was not possible (entry already exists ?) } else { if (!ENTRY_IS_DIRECTORY(newName)) { return LIBZIPPP_ERROR_INVALID_PARAMETER; } //invalid new name int parentSlash = newName.rfind(ENTRY_PATH_SEPARATOR, newName.length()-2); if (parentSlash!=-1) { //updates the dir hierarchy string parent = newName.substr(0, parentSlash+1); bool dadded = addEntry(parent); if (!dadded) { return LIBZIPPP_ERROR_UNKNOWN; } } int counter = 0; string originalName = entry.getName(); vector<ZipEntry> allEntries = getEntries(); vector<ZipEntry>::const_iterator eit; for(eit=allEntries.begin() ; eit!=allEntries.end() ; ++eit) { ZipEntry ze = *eit; string currentName = ze.getName(); int startPosition = currentName.find(originalName); if (startPosition==0) { if (currentName == originalName) { int result = zip_file_rename(zipHandle, entry.getIndex(), newName.c_str(), ZIP_FL_ENC_GUESS); if (result==0) { ++counter; } else { return LIBZIPPP_ERROR_UNKNOWN; } //unable to rename the folder } else { string targetName = currentName.replace(0, originalName.length(), newName); int result = zip_file_rename(zipHandle, ze.getIndex(), targetName.c_str(), ZIP_FL_ENC_GUESS); if (result==0) { ++counter; } else { return LIBZIPPP_ERROR_UNKNOWN; } //unable to rename a sub-entry } } else { //file not affected by the renaming } } /* * Special case for moving a directory a/x to a/x/y to avoid to lose * the a/x path in the archive. */ bool newNameIsInsideCurrent = (newName.find(entry.getName())==0); if (newNameIsInsideCurrent) { bool dadded = addEntry(newName); if (!dadded) { return LIBZIPPP_ERROR_UNKNOWN; } } return counter; } }
/* * Crunch deleted files out of an archive by shifting the later files down. * * Because we're not using a temp file, we do the operation inside the * current file. */ status_t ZipFile::crunchArchive(void) { status_t result = NO_ERROR; int i, count; long delCount, adjust; #if 0 printf("CONTENTS:\n"); for (i = 0; i < (int) mEntries.size(); i++) { printf(" %d: lfhOff=%ld del=%d\n", i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); } printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); #endif /* * Roll through the set of files, shifting them as appropriate. We * could probably get a slight performance improvement by sliding * multiple files down at once (because we could use larger reads * when operating on batches of small files), but it's not that useful. */ count = mEntries.size(); delCount = adjust = 0; for (i = 0; i < count; i++) { ZipEntry* pEntry = mEntries[i]; long span; if (pEntry->getLFHOffset() != 0) { long nextOffset; /* Get the length of this entry by finding the offset * of the next entry. Directory entries don't have * file offsets, so we need to find the next non-directory * entry. */ nextOffset = 0; for (int ii = i+1; nextOffset == 0 && ii < count; ii++) nextOffset = mEntries[ii]->getLFHOffset(); if (nextOffset == 0) nextOffset = mEOCD.mCentralDirOffset; span = nextOffset - pEntry->getLFHOffset(); assert(span >= ZipEntry::LocalFileHeader::kLFHLen); } else { /* This is a directory entry. It doesn't have * any actual file contents, so there's no need to * move anything. */ span = 0; } //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); if (pEntry->getDeleted()) { adjust += span; delCount++; delete pEntry; mEntries.removeAt(i); /* adjust loop control */ count--; i--; } else if (span != 0 && adjust > 0) { /* shuffle this entry back */ //printf("+++ Shuffling '%s' back %ld\n", // pEntry->getFileName(), adjust); result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, pEntry->getLFHOffset(), span); if (result != NO_ERROR) { /* this is why you use a temp file */ LOGE("error during crunch - archive is toast\n"); return result; } pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); } } /* * Fix EOCD info. We have to wait until the end to do some of this * because we use mCentralDirOffset to determine "span" for the * last entry. */ mEOCD.mCentralDirOffset -= adjust; mEOCD.mNumEntries -= delCount; mEOCD.mTotalNumEntries -= delCount; mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); assert(mEOCD.mNumEntries == count); return result; }
int ZipArchive::deleteEntry(const string& e) const { ZipEntry entry = getEntry(e); if (entry.isNull()) { return -4; } return deleteEntry(entry); }