/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* * This module implements a simple archive extractor for the PKZIP format.
*/
// For placement new used for arena allocations of zip file list #include <new> #define ZIP_ARENABLOCKSIZE (1 * 1024)
#ifdef XP_UNIX # include <sys/mman.h> # include <sys/types.h> # include <sys/stat.h> # include <limits.h> # include <unistd.h> #elifdefined(XP_WIN) # include <io.h> #endif
#ifdef __SYMBIAN32__ # include <sys/syslimits.h> #endif/*__SYMBIAN32__*/
#ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */ # ifndef S_IFMT # define S_IFMT 0170000 # endif # ifndef S_IFLNK # define S_IFLNK 0120000 # endif # ifndef PATH_MAX # define PATH_MAX 1024 # endif #endif/* XP_UNIX */
#ifdef XP_WIN # include "private/pprio.h"// To get PR_ImportFile #endif
class ZipArchiveLogger { public: void Init(constchar* env) {
StaticMutexAutoLock lock(sLock);
// AddRef
MOZ_ASSERT(mRefCnt >= 0);
++mRefCnt;
if (!mFd) {
nsCOMPtr<nsIFile> logFile;
nsresult rv =
NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), getter_AddRefs(logFile)); if (NS_FAILED(rv)) return;
// Create the log file and its parent directory (in case it doesn't exist)
rv = logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); if (NS_FAILED(rv)) return;
PRFileDesc* file; #ifdef XP_WIN // PR_APPEND is racy on Windows, so open a handle ourselves with flags // that will work, and use PR_ImportFile to make it a PRFileDesc. This can // go away when bug 840435 is fixed.
nsAutoString path;
logFile->GetPath(path); if (path.IsEmpty()) return;
HANDLE handle =
CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE, nullptr,
OPEN_ALWAYS, 0, nullptr); if (handle == INVALID_HANDLE_VALUE) return;
file = PR_ImportFile((PROsfd)handle); if (!file) return; #else
rv = logFile->OpenNSPRFileDesc(
PR_WRONLY | PR_CREATE_FILE | PR_APPEND | PR_SYNC, 0644, &file); if (NS_FAILED(rv)) return; #endif
mFd = file;
}
}
//*********************************************************** // For every inflation the following allocations are done: // malloc(1 * 9520) // malloc(32768 * 1) //***********************************************************
nsresult gZlibInit(z_stream* zs) {
memset(zs, 0, sizeof(z_stream)); int zerr = inflateInit2(zs, -MAX_WBITS); if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
int64_t size = PR_Available64(fd.get()); if (size >= INT32_MAX) return NS_ERROR_FILE_TOO_BIG;
PRFileMap* map = PR_CreateFileMap(fd.get(), size, PR_PROT_READONLY); if (!map) return NS_ERROR_FAILURE;
uint8_t* buf = (uint8_t*)PR_MemMap(map, 0, (uint32_t)size); // Bug 525755: PR_MemMap fails when fd points at something other than a normal // file. if (!buf) {
PR_CloseFileMap(map); return NS_ERROR_FAILURE;
}
RefPtr<nsZipHandle> handle = new nsZipHandle(); if (!handle) {
PR_MemUnmap(buf, (uint32_t)size);
PR_CloseFileMap(map); return NS_ERROR_OUT_OF_MEMORY;
}
// This function finds the start of the ZIP data. If the file is a regular ZIP, // this is just the start of the file. If the file is a CRX file, the start of // the data is after the CRX header. // // CRX header reference, version 2: // Header requires little-endian byte ordering with 4-byte alignment. // 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|. // Equivilant to |uint32_t m = 0x34327243|. // 32 bits : version - Unsigned integer representing the CRX file // format version. Currently equal to 2. // 32 bits : pubKeyLength - Unsigned integer representing the length // of the public key in bytes. // 32 bits : sigLength - Unsigned integer representing the length // of the signature in bytes. // pubKeyLength : publicKey - Contents of the author's public key. // sigLength : signature - Signature of the ZIP content. // Signature is created using the RSA // algorithm with the SHA-1 hash function. // // CRX header reference, version 3: // Header requires little-endian byte ordering with 4-byte alignment. // 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|. // Equivilant to |uint32_t m = 0x34327243|. // 32 bits : version - Unsigned integer representing the CRX file // format version. Currently equal to 3. // 32 bits : headerLength - Unsigned integer representing the length // of the CRX header in bytes. // headerLength : header - CRXv3 header.
nsresult nsZipHandle::findDataStart() { // In the CRX header, integers are 32 bits. Our pointer to the file is of // type |uint8_t|, which is guaranteed to be 8 bits. const uint32_t CRXIntSize = 4;
if (aEntryName.Length()) // only test specified item
{
currItem = GetItem(aEntryName); if (!currItem) return NS_ERROR_FILE_NOT_FOUND; //-- don't test (synthetic) directory items if (currItem->IsDirectory()) return NS_OK; return ExtractFile(currItem, 0, 0);
}
// test all items in archive for (auto* item : mFiles) { for (currItem = item; currItem; currItem = currItem->next) { //-- don't test (synthetic) directory items if (currItem->IsDirectory()) continue;
nsresult rv = ExtractFile(currItem, 0, 0); if (rv != NS_OK) return rv;
}
}
LOG(("ZipHandle::GetItem[%p] %s", this,
PromiseFlatCString(aEntryName).get())); if (aEntryName.Length()) {
uint32_t len = aEntryName.Length(); //-- If the request is for a directory, make sure that synthetic entries //-- are created for the directories without their own entry. if (!mBuiltSynthetics) { if ((len > 0) && (aEntryName[len - 1] == '/')) { if (BuildSynthetics() != NS_OK) return 0;
}
}
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
nsZipItem* item = mFiles[HashName(aEntryName.BeginReading(), len)]; while (item) { if ((len == item->nameLength) &&
(!memcmp(aEntryName.BeginReading(), item->Name(), len))) { // Successful GetItem() is a good indicator that the file is about to be // read if (mUseZipLog && mURI.Length()) {
zipLog.Write(mURI, aEntryName.BeginReading());
} return item; //-- found it
}
item = item->next;
}
MMAP_FAULT_HANDLER_CATCH(nullptr)
} return nullptr;
}
//--------------------------------------------- // nsZipArchive::ExtractFile // This extracts the item to the filehandle provided. // If 'aFd' is null, it only tests the extraction. // On extraction error(s) it removes the file. //---------------------------------------------
nsresult nsZipArchive::ExtractFile(nsZipItem* item, nsIFile* outFile,
PRFileDesc* aFd) {
MutexAutoLock lock(mLock);
LOG(("ZipHandle::ExtractFile[%p]", this)); if (!item) return NS_ERROR_ILLEGAL_VALUE; if (!mFd) return NS_ERROR_FAILURE;
// Directory extraction is handled in nsJAR::Extract, // so the item to be extracted should never be a directory
MOZ_ASSERT(!item->IsDirectory());
MutexAutoLock lock(mArchive->mLock);
*aResult = 0;
*aNameLen = 0;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->GetFD()) // we start from last match, look for next while (mSlot < ZIP_TABSIZE) { // move to next in current chain, or move to new slot
mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
bool found = false; if (!mItem)
++mSlot; // no more in this chain, move to next slot elseif (!mPattern)
found = true; // always match elseif (mRegExp) { char buf[kMaxNameLength + 1];
memcpy(buf, mItem->Name(), mItem->nameLength);
buf[mItem->nameLength] = '\0';
found = (NS_WildCardMatch(buf, mPattern, false) == MATCH);
} else
found = ((mItem->nameLength == strlen(mPattern)) &&
(memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0)); if (found) { // Need also to return the name length, as it is NOT zero-terminatdd...
*aResult = mItem->Name();
*aNameLen = mItem->nameLength;
LOG(("ZipHandle::FindNext[%p] %s", this, *aResult)); return NS_OK;
}
}
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
LOG(("ZipHandle::FindNext[%p] not found %s", this, mPattern)); return NS_ERROR_FILE_NOT_FOUND;
}
//--------------------------------------------- // nsZipArchive::BuildFileList //---------------------------------------------
nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd)
MOZ_NO_THREAD_SAFETY_ANALYSIS { // We're only called from the constructor, but need to call // CreateZipItem(), which touches locked data, and modify mFiles. Turn // off thread-safety, which normally doesn't apply for constructors // anyways
// Get archive size using end pos const uint8_t* buf; const uint8_t* startp = mFd->mFileData; const uint8_t* endp = startp + mFd->mLen;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
uint32_t centralOffset = 4;
LOG(("ZipHandle::BuildFileList[%p]", this)); // Only perform readahead in the parent process. Children processes // don't need readahead when the file has already been readahead by // the parent process, and readahead only really happens for omni.ja, // which is used in the parent process. if (XRE_IsParentProcess() && mFd->mLen > ZIPCENTRAL_SIZE &&
xtolong(startp + centralOffset) == CENTRALSIG) { // Success means optimized jar layout from bug 559961 is in effect
uint32_t readaheadLength = xtolong(startp);
mozilla::PrefetchMemory(const_cast<uint8_t*>(startp), readaheadLength);
} else { for (buf = endp - ZIPEND_SIZE; buf > startp; buf--) { if (xtolong(buf) == ENDSIG) {
centralOffset = xtolong(((ZipEnd*)buf)->offset_central_dir); break;
}
}
}
if (!centralOffset) { return NS_ERROR_FILE_CORRUPTED;
}
buf = startp + centralOffset;
// avoid overflow of startp + centralOffset. if (buf < startp) { return NS_ERROR_FILE_CORRUPTED;
}
//-- Read the central directory headers
uint32_t sig = 0; while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
(buf + int32_t(sizeof(uint32_t)) <= endp) &&
((sig = xtolong(buf)) == CENTRALSIG)) { // Make sure there is enough data available. if ((buf > endp) || (endp - buf < ZIPCENTRAL_SIZE)) { return NS_ERROR_FILE_CORRUPTED;
}
// Read the fixed-size data.
ZipCentral* central = (ZipCentral*)buf;
if (mBuiltSynthetics) return NS_OK;
mBuiltSynthetics = true;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd) // Create synthetic entries for any missing directories. // Do this when all ziptable has scanned to prevent double entries. for (auto* item : mFiles) { for (; item != nullptr; item = item->next) { if (item->isSynthetic) continue;
//-- add entries for directories in the current item's path //-- go from end to beginning, because then we can stop trying //-- to create diritems if we find that the diritem we want to //-- create already exists //-- start just before the last char so as to not add the item //-- twice if it's a directory
uint16_t namelen = item->nameLength;
MOZ_ASSERT(namelen > 0, "Attempt to build synthetic for zero-length entry name!"); constchar* name = item->Name(); for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--) { if (name[dirlen - 1] != '/') continue;
// The character before this is '/', so if this is also '/' then we // have an empty path component. Skip it. if (name[dirlen] == '/') continue;
// Is the directory already in the file table?
uint32_t hash = HashName(item->Name(), dirlen); bool found = false; for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next) { if ((dirlen == zi->nameLength) &&
(0 == memcmp(item->Name(), zi->Name(), dirlen))) { // we've already added this dir and all its parents
found = true; break;
}
} // if the directory was found, break out of the directory // creation loop now that we know all implicit directories // are there -- otherwise, start creating the zip item if (found) break;
nsZipItem* diritem = CreateZipItem(); if (!diritem) return NS_ERROR_OUT_OF_MEMORY;
// Point to the central record of the original item for the name part.
diritem->central = item->central;
diritem->nameLength = dirlen;
diritem->isSynthetic = true;
// add diritem to the file table
diritem->next = mFiles[hash];
mFiles[hash] = diritem;
} /* end processing of dirs in item's name */
}
}
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE) return NS_OK;
}
//--------------------------------------------- // nsZipArchive::GetDataOffset // Returns 0 on an error; 0 is not a valid result for any success case //---------------------------------------------
uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
MOZ_DIAGNOSTIC_ASSERT(mFd);
uint32_t offset;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd) //-- read local header to get variable length values and calculate //-- the real data offset
uint32_t len = mFd->mLen;
MOZ_DIAGNOSTIC_ASSERT(len <= UINT32_MAX, "mLen > 2GB"); const uint8_t* data = mFd->mFileData;
offset = aItem->LocalOffset(); if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE) { return 0;
} // Check there's enough space for the signature if (offset > mFd->mLen) {
NS_WARNING("Corrupt local offset in JAR file"); return 0;
}
// -- check signature before using the structure, in case the zip file is // corrupt
ZipLocal* Local = (ZipLocal*)(data + offset); if ((xtolong(Local->signature) != LOCALSIG)) return 0;
//-- NOTE: extralen is different in central header and local header //-- for archives created using the Unix "zip" utility. To set //-- the offset accurately we need the _local_ extralen.
offset += ZIPLOCAL_SIZE + xtoint(Local->filename_len) +
xtoint(Local->extrafield_len); // Check data points inside the file. if (offset > mFd->mLen) {
NS_WARNING("Corrupt data offset in JAR file"); return 0;
}
MMAP_FAULT_HANDLER_CATCH(0) // can't be 0 return offset;
}
//-- get table of contents for archive
aRv = BuildFileList(aFd); if (NS_FAILED(aRv)) { return; // whomever created us must destroy us in this case
} if (aZipHandle->mFile && XRE_IsParentProcess()) { staticchar* env = PR_GetEnv("MOZ_JAR_LOG_FILE"); if (env) {
mUseZipLog = true;
zipLog.Init(env); // We only log accesses in jar/zip archives within the NS_GRE_DIR // and/or the APK on Android. For the former, we log the archive path // relative to NS_GRE_DIR, and for the latter, the nested-archive // path within the APK. This makes the path match the path of the // archives relative to the packaged dist/$APP_NAME directory in a // build. if (aZipHandle->mFile.IsZip()) { // Nested archive, likely omni.ja in APK.
aZipHandle->mFile.GetPath(mURI);
} elseif (nsDirectoryService::gService) { // We can reach here through the initialization of Omnijar from // XRE_InitCommandLine, which happens before the directory service // is initialized. When that happens, it means the opened archive is // the APK, and we don't care to log that one, so we just skip // when the directory service is not initialized.
nsCOMPtr<nsIFile> dir = aZipHandle->mFile.GetBaseFile();
nsCOMPtr<nsIFile> gre_dir;
nsAutoCString path; if (NS_SUCCEEDED(nsDirectoryService::gService->Get(
NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(gre_dir)))) {
nsAutoCString leaf;
nsCOMPtr<nsIFile> parent; while (NS_SUCCEEDED(dir->GetNativeLeafName(leaf)) &&
NS_SUCCEEDED(dir->GetParent(getter_AddRefs(parent)))) { if (!parent) { break;
}
dir = parent; if (path.Length()) {
path.Insert('/', 0);
}
path.Insert(leaf, 0); bool equals; if (NS_SUCCEEDED(dir->Equals(gre_dir, &equals)) && equals) {
mURI.Assign(path); break;
}
}
}
}
}
}
}
/* * HashName * * returns a hash key for the entry name
*/
MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW static uint32_t HashName(constchar* aName, uint16_t len) {
MOZ_ASSERT(aName != 0);
const uint8_t* p = (const uint8_t*)aName; const uint8_t* endp = p + len;
uint32_t val = 0; while (p != endp) {
val = val * 37 + *p++;
}
return (val % ZIP_TABSIZE);
}
/* * x t o i n t * * Converts a two byte ugly endianed integer * to our platform's integer.
*/ static uint16_t xtoint(const uint8_t* ii) { return (uint16_t)((ii[0]) | (ii[1] << 8));
}
/* * x t o l o n g * * Converts a four byte ugly endianed integer * to our platform's integer.
*/ static uint32_t xtolong(const uint8_t* ll) { return (uint32_t)((ll[0] << 0) | (ll[1] << 8) | (ll[2] << 16) |
(ll[3] << 24));
}
/* * GetModTime * * returns last modification time in microseconds
*/ static PRTime GetModTime(uint16_t aDate, uint16_t aTime) { // Note that on DST shift we can't handle correctly the hour that is valid // in both DST zones
PRExplodedTime time;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.