diff options
Diffstat (limited to '')
-rw-r--r-- | modules/libjar/nsZipArchive.cpp | 1189 |
1 files changed, 1189 insertions, 0 deletions
diff --git a/modules/libjar/nsZipArchive.cpp b/modules/libjar/nsZipArchive.cpp new file mode 100644 index 0000000000..1f43a0d625 --- /dev/null +++ b/modules/libjar/nsZipArchive.cpp @@ -0,0 +1,1189 @@ +/* -*- 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. + */ + +#define READTYPE int32_t +#include "zlib.h" +#include "nsISupportsUtils.h" +#include "mozilla/MmapFaultHandler.h" +#include "prio.h" +#include "mozilla/Attributes.h" +#include "mozilla/Logging.h" +#include "mozilla/MemUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/StaticMutex.h" +#include "stdlib.h" +#include "nsDirectoryService.h" +#include "nsWildCard.h" +#include "nsXULAppAPI.h" +#include "nsZipArchive.h" +#include "nsString.h" +#include "prenv.h" +#if defined(XP_WIN) +# include <windows.h> +#endif + +// 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> +#elif defined(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 + +using namespace mozilla; + +static LazyLogModule gZipLog("nsZipArchive"); + +#ifdef LOG +# undef LOG +#endif +#ifdef LOG_ENABLED +# undef LOG_ENABLED +#endif + +#define LOG(args) MOZ_LOG(gZipLog, mozilla::LogLevel::Debug, args) +#define LOG_ENABLED() MOZ_LOG_TEST(gZipLog, mozilla::LogLevel::Debug) + +static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */ +// For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00. +static const uint16_t kSyntheticTime = 0; +static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9)); + +static uint16_t xtoint(const uint8_t* ii); +static uint32_t xtolong(const uint8_t* ll); +static uint32_t HashName(const char* aName, uint16_t nameLen); + +class ZipArchiveLogger { + public: + void Init(const char* env) { + StaticMutexAutoLock lock(sLock); + + // AddRef + MOZ_ASSERT(mRefCnt >= 0); + ++mRefCnt; + + if (!mFd) { + nsCOMPtr<nsIFile> logFile; + nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, + 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; + } + } + + void Write(const nsACString& zip, const char* entry) { + StaticMutexAutoLock lock(sLock); + + if (mFd) { + nsCString buf(zip); + buf.Append(' '); + buf.Append(entry); + buf.Append('\n'); + PR_Write(mFd, buf.get(), buf.Length()); + } + } + + void Release() { + StaticMutexAutoLock lock(sLock); + + MOZ_ASSERT(mRefCnt > 0); + if ((0 == --mRefCnt) && mFd) { + PR_Close(mFd); + mFd = nullptr; + } + } + + private: + static StaticMutex sLock; + int mRefCnt MOZ_GUARDED_BY(sLock); + PRFileDesc* mFd MOZ_GUARDED_BY(sLock); +}; + +StaticMutex ZipArchiveLogger::sLock; +static ZipArchiveLogger zipLog; + +//*********************************************************** +// 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; + + return NS_OK; +} + +nsZipHandle::nsZipHandle() + : mFileData(nullptr), + mLen(0), + mMap(nullptr), + mRefCnt(0), + mFileStart(nullptr), + mTotalLen(0) {} + +NS_IMPL_ADDREF(nsZipHandle) +NS_IMPL_RELEASE(nsZipHandle) + +nsresult nsZipHandle::Init(nsIFile* file, nsZipHandle** ret, PRFileDesc** aFd) { + mozilla::AutoFDClose fd; + int32_t flags = PR_RDONLY; +#if defined(XP_WIN) + flags |= nsIFile::OS_READAHEAD; +#endif + LOG(("ZipHandle::Init %s", file->HumanReadablePath().get())); + nsresult rv = file->OpenNSPRFileDesc(flags, 0000, &fd.rwget()); + if (NS_FAILED(rv)) return rv; + + int64_t size = PR_Available64(fd); + if (size >= INT32_MAX) return NS_ERROR_FILE_TOO_BIG; + + PRFileMap* map = PR_CreateFileMap(fd, 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; + } + +#if defined(XP_WIN) + if (aFd) { + *aFd = fd.forget(); + } +#else + handle->mNSPRFileDesc = fd.forget(); +#endif + handle->mFile.Init(file); + handle->mTotalLen = (uint32_t)size; + handle->mFileStart = buf; + rv = handle->findDataStart(); + if (NS_FAILED(rv)) { + PR_MemUnmap(buf, (uint32_t)size); + handle->mFileStart = nullptr; + PR_CloseFileMap(map); + return rv; + } + handle->mMap = map; + handle.forget(ret); + return NS_OK; +} + +nsresult nsZipHandle::Init(nsZipArchive* zip, const char* entry, + nsZipHandle** ret) { + RefPtr<nsZipHandle> handle = new nsZipHandle(); + if (!handle) return NS_ERROR_OUT_OF_MEMORY; + + LOG(("ZipHandle::Init entry %s", entry)); + handle->mBuf = MakeUnique<nsZipItemPtr<uint8_t>>(zip, entry); + if (!handle->mBuf) return NS_ERROR_OUT_OF_MEMORY; + + if (!handle->mBuf->Buffer()) return NS_ERROR_UNEXPECTED; + + handle->mMap = nullptr; + handle->mFile.Init(zip, entry); + handle->mTotalLen = handle->mBuf->Length(); + handle->mFileStart = handle->mBuf->Buffer(); + nsresult rv = handle->findDataStart(); + if (NS_FAILED(rv)) { + return rv; + } + handle.forget(ret); + return NS_OK; +} + +// 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: (CRX 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. +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; + + MMAP_FAULT_HANDLER_BEGIN_HANDLE(this) + if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) { + const uint8_t* headerData = mFileStart; + headerData += CRXIntSize * 2; // Skip magic number and version number + uint32_t pubKeyLength = xtolong(headerData); + headerData += CRXIntSize; + uint32_t sigLength = xtolong(headerData); + uint32_t headerSize = CRXIntSize * 4 + pubKeyLength + sigLength; + if (mTotalLen > headerSize) { + mLen = mTotalLen - headerSize; + mFileData = mFileStart + headerSize; + return NS_OK; + } + return NS_ERROR_FILE_CORRUPTED; + } + mLen = mTotalLen; + mFileData = mFileStart; + MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE) + return NS_OK; +} + +int64_t nsZipHandle::SizeOfMapping() { return mTotalLen; } + +nsresult nsZipHandle::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) { + if (!aNSPRFileDesc) { + return NS_ERROR_ILLEGAL_VALUE; + } + + *aNSPRFileDesc = mNSPRFileDesc; + if (!mNSPRFileDesc) { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +nsZipHandle::~nsZipHandle() { + if (mMap) { + PR_MemUnmap((void*)mFileStart, mTotalLen); + PR_CloseFileMap(mMap); + } + mFileStart = nullptr; + mFileData = nullptr; + mMap = nullptr; + mBuf = nullptr; +} + +//*********************************************************** +// nsZipArchive -- public methods +//*********************************************************** + +//--------------------------------------------- +// nsZipArchive::OpenArchive +//--------------------------------------------- +/* static */ +already_AddRefed<nsZipArchive> nsZipArchive::OpenArchive( + nsZipHandle* aZipHandle, PRFileDesc* aFd) { + nsresult rv; + RefPtr<nsZipArchive> self(new nsZipArchive(aZipHandle, aFd, rv)); + LOG(("ZipHandle::OpenArchive[%p]", self.get())); + if (NS_FAILED(rv)) { + self = nullptr; + } + return self.forget(); +} + +/* static */ +already_AddRefed<nsZipArchive> nsZipArchive::OpenArchive(nsIFile* aFile) { + RefPtr<nsZipHandle> handle; +#if defined(XP_WIN) + mozilla::AutoFDClose fd; + nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle), &fd.rwget()); +#else + nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle)); +#endif + if (NS_FAILED(rv)) return nullptr; + +#if defined(XP_WIN) + return OpenArchive(handle, fd.get()); +#else + return OpenArchive(handle); +#endif +} + +//--------------------------------------------- +// nsZipArchive::Test +//--------------------------------------------- +nsresult nsZipArchive::Test(const char* aEntryName) { + nsZipItem* currItem; + + if (aEntryName) // 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; + } + } + + return NS_OK; +} + +//--------------------------------------------- +// nsZipArchive::GetItem +//--------------------------------------------- +nsZipItem* nsZipArchive::GetItem(const char* aEntryName) { + MutexAutoLock lock(mLock); + + LOG(("ZipHandle::GetItem[%p] %s", this, aEntryName)); + if (aEntryName) { + uint32_t len = strlen(aEntryName); + //-- 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, len)]; + while (item) { + if ((len == item->nameLength) && + (!memcmp(aEntryName, 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); + } + 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()); + + Bytef outbuf[ZIP_BUFLEN]; + + nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true); + + nsresult rv = NS_OK; + + while (true) { + uint32_t count = 0; + uint8_t* buf = cursor.Read(&count); + if (!buf) { + rv = NS_ERROR_FILE_CORRUPTED; + break; + } + if (count == 0) { + break; + } + + if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) { + rv = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + } + } + + //-- delete the file on errors + if (aFd) { + PR_Close(aFd); + if (NS_FAILED(rv) && outFile) { + outFile->Remove(false); + } + } + + return rv; +} + +//--------------------------------------------- +// nsZipArchive::FindInit +//--------------------------------------------- +nsresult nsZipArchive::FindInit(const char* aPattern, nsZipFind** aFind) { + if (!aFind) return NS_ERROR_ILLEGAL_VALUE; + + MutexAutoLock lock(mLock); + + LOG(("ZipHandle::FindInit[%p]", this)); + // null out param in case an error happens + *aFind = nullptr; + + bool regExp = false; + char* pattern = 0; + + // Create synthetic directory entries on demand + nsresult rv = BuildSynthetics(); + if (rv != NS_OK) return rv; + + // validate the pattern + if (aPattern) { + switch (NS_WildCardValid((char*)aPattern)) { + case INVALID_SXP: + return NS_ERROR_ILLEGAL_VALUE; + + case NON_SXP: + regExp = false; + break; + + case VALID_SXP: + regExp = true; + break; + + default: + // undocumented return value from RegExpValid! + MOZ_ASSERT(false); + return NS_ERROR_ILLEGAL_VALUE; + } + + pattern = strdup(aPattern); + if (!pattern) return NS_ERROR_OUT_OF_MEMORY; + } + + *aFind = new nsZipFind(this, pattern, regExp); + if (!*aFind) { + free(pattern); + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +//--------------------------------------------- +// nsZipFind::FindNext +//--------------------------------------------- +nsresult nsZipFind::FindNext(const char** aResult, uint16_t* aNameLen) { + if (!mArchive || !aResult || !aNameLen) return NS_ERROR_ILLEGAL_VALUE; + + 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 + else if (!mPattern) + found = true; // always match + else if (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 -- private implementation +//*********************************************************** + +//--------------------------------------------- +// nsZipArchive::CreateZipItem +//--------------------------------------------- +nsZipItem* nsZipArchive::CreateZipItem() { + // Arena allocate the nsZipItem + return (nsZipItem*)mArena.Allocate(sizeof(nsZipItem), mozilla::fallible); +} + +//--------------------------------------------- +// 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; + + uint16_t namelen = xtoint(central->filename_len); + uint16_t extralen = xtoint(central->extrafield_len); + uint16_t commentlen = xtoint(central->commentfield_len); + uint32_t diff = ZIPCENTRAL_SIZE + namelen + extralen + commentlen; + + // Sanity check variable sizes and refuse to deal with + // anything too big: it's likely a corrupt archive. + if (namelen < 1 || namelen > kMaxNameLength) { + return NS_ERROR_FILE_CORRUPTED; + } + if (buf >= buf + diff || // No overflow + buf >= endp - diff) { + return NS_ERROR_FILE_CORRUPTED; + } + + // Point to the next item at the top of loop + buf += diff; + + nsZipItem* item = CreateZipItem(); + if (!item) return NS_ERROR_OUT_OF_MEMORY; + + item->central = central; + item->nameLength = namelen; + item->isSynthetic = false; + + // Add item to file table +#ifdef DEBUG + nsDependentCSubstring name(item->Name(), namelen); + LOG((" %s", PromiseFlatCString(name).get())); +#endif + uint32_t hash = HashName(item->Name(), namelen); + item->next = mFiles[hash]; + mFiles[hash] = item; + + sig = 0; + } /* while reading central directory records */ + + if (sig != ENDSIG) { + return NS_ERROR_FILE_CORRUPTED; + } + + MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE) + return NS_OK; +} + +//--------------------------------------------- +// nsZipArchive::BuildSynthetics +//--------------------------------------------- +nsresult nsZipArchive::BuildSynthetics() { + mLock.AssertCurrentThreadOwns(); + + 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!"); + const char* 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::GetFD +//--------------------------------------------- +nsZipHandle* nsZipArchive::GetFD() const { return mFd.get(); } + +//--------------------------------------------- +// 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; +} + +//--------------------------------------------- +// nsZipArchive::GetData +//--------------------------------------------- +const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) { + MOZ_DIAGNOSTIC_ASSERT(aItem); + if (!aItem) { + return nullptr; + } + uint32_t offset = GetDataOffset(aItem); + + MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd) + // -- check if there is enough source data in the file + if (!offset || mFd->mLen < aItem->Size() || + offset > mFd->mLen - aItem->Size() || + (aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) { + return nullptr; + } + MMAP_FAULT_HANDLER_CATCH(nullptr) + + return mFd->mFileData + offset; +} + +//--------------------------------------------- +// nsZipArchive::SizeOfMapping +//--------------------------------------------- +int64_t nsZipArchive::SizeOfMapping() { return mFd ? mFd->SizeOfMapping() : 0; } + +//------------------------------------------ +// nsZipArchive constructor and destructor +//------------------------------------------ + +nsZipArchive::nsZipArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd, + nsresult& aRv) + : mRefCnt(0), mFd(aZipHandle), mUseZipLog(false), mBuiltSynthetics(false) { + // initialize the table to nullptr + memset(mFiles, 0, sizeof(mFiles)); + MOZ_DIAGNOSTIC_ASSERT(aZipHandle); + + //-- 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()) { + static char* 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); + } else if (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; + } + } + } + } + } + } +} + +NS_IMPL_ADDREF(nsZipArchive) +NS_IMPL_RELEASE(nsZipArchive) + +nsZipArchive::~nsZipArchive() { + LOG(("Closing nsZipArchive[%p]", this)); + if (mUseZipLog) { + zipLog.Release(); + } +} + +//------------------------------------------ +// nsZipFind constructor and destructor +//------------------------------------------ + +nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp) + : mArchive(aZip), + mPattern(aPattern), + mItem(nullptr), + mSlot(0), + mRegExp(aRegExp) { + MOZ_COUNT_CTOR(nsZipFind); +} + +nsZipFind::~nsZipFind() { + free(mPattern); + + MOZ_COUNT_DTOR(nsZipFind); +} + +//------------------------------------------ +// helper functions +//------------------------------------------ + +/* + * HashName + * + * returns a hash key for the entry name + */ +MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW +static uint32_t HashName(const char* 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; + + time.tm_usec = 0; + + time.tm_hour = (aTime >> 11) & 0x1F; + time.tm_min = (aTime >> 5) & 0x3F; + time.tm_sec = (aTime & 0x1F) * 2; + + time.tm_year = (aDate >> 9) + 1980; + time.tm_month = ((aDate >> 5) & 0x0F) - 1; + time.tm_mday = aDate & 0x1F; + + time.tm_params.tp_gmt_offset = 0; + time.tm_params.tp_dst_offset = 0; + + PR_NormalizeTime(&time, PR_GMTParameters); + time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset; + PR_NormalizeTime(&time, PR_GMTParameters); + time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset; + + return PR_ImplodeTime(&time); +} + +nsZipItem::nsZipItem() + : next(nullptr), central(nullptr), nameLength(0), isSynthetic(false) {} + +uint32_t nsZipItem::LocalOffset() { return xtolong(central->localhdr_offset); } + +uint32_t nsZipItem::Size() { return isSynthetic ? 0 : xtolong(central->size); } + +uint32_t nsZipItem::RealSize() { + return isSynthetic ? 0 : xtolong(central->orglen); +} + +uint32_t nsZipItem::CRC32() { + return isSynthetic ? 0 : xtolong(central->crc32); +} + +uint16_t nsZipItem::Date() { + return isSynthetic ? kSyntheticDate : xtoint(central->date); +} + +uint16_t nsZipItem::Time() { + return isSynthetic ? kSyntheticTime : xtoint(central->time); +} + +uint16_t nsZipItem::Compression() { + return isSynthetic ? STORED : xtoint(central->method); +} + +bool nsZipItem::IsDirectory() { + return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1])); +} + +uint16_t nsZipItem::Mode() { + if (isSynthetic) return 0755; + return ((uint16_t)(central->external_attributes[2]) | 0x100); +} + +const uint8_t* nsZipItem::GetExtraField(uint16_t aTag, uint16_t* aBlockSize) { + if (isSynthetic) return nullptr; + + const unsigned char* buf = + ((const unsigned char*)central) + ZIPCENTRAL_SIZE + nameLength; + uint32_t buflen; + + MMAP_FAULT_HANDLER_BEGIN_BUFFER(central, ZIPCENTRAL_SIZE + nameLength) + buflen = (uint32_t)xtoint(central->extrafield_len); + MMAP_FAULT_HANDLER_CATCH(nullptr) + + uint32_t pos = 0; + uint16_t tag, blocksize; + + MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, buflen) + while (buf && (pos + 4) <= buflen) { + tag = xtoint(buf + pos); + blocksize = xtoint(buf + pos + 2); + + if (aTag == tag && (pos + 4 + blocksize) <= buflen) { + *aBlockSize = blocksize; + return buf + pos; + } + + pos += blocksize + 4; + } + MMAP_FAULT_HANDLER_CATCH(nullptr) + + return nullptr; +} + +PRTime nsZipItem::LastModTime() { + if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime); + + // Try to read timestamp from extra field + uint16_t blocksize; + const uint8_t* tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize); + if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) { + return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC; + } + + return GetModTime(Date(), Time()); +} + +nsZipCursor::nsZipCursor(nsZipItem* item, nsZipArchive* aZip, uint8_t* aBuf, + uint32_t aBufSize, bool doCRC) + : mItem(item), + mBuf(aBuf), + mBufSize(aBufSize), + mZs(), + mCRC(0), + mDoCRC(doCRC) { + if (mItem->Compression() == DEFLATED) { +#ifdef DEBUG + nsresult status = +#endif + gZlibInit(&mZs); + NS_ASSERTION(status == NS_OK, "Zlib failed to initialize"); + NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem"); + } + + mZs.avail_in = item->Size(); + mZs.next_in = (Bytef*)aZip->GetData(item); + + if (doCRC) mCRC = crc32(0L, Z_NULL, 0); +} + +nsZipCursor::~nsZipCursor() { + if (mItem->Compression() == DEFLATED) { + inflateEnd(&mZs); + } +} + +uint8_t* nsZipCursor::ReadOrCopy(uint32_t* aBytesRead, bool aCopy) { + int zerr; + uint8_t* buf = nullptr; + bool verifyCRC = true; + + if (!mZs.next_in) return nullptr; + MMAP_FAULT_HANDLER_BEGIN_BUFFER(mZs.next_in, mZs.avail_in) + switch (mItem->Compression()) { + case STORED: + if (!aCopy) { + *aBytesRead = mZs.avail_in; + buf = mZs.next_in; + mZs.next_in += mZs.avail_in; + mZs.avail_in = 0; + } else { + *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in; + memcpy(mBuf, mZs.next_in, *aBytesRead); + mZs.avail_in -= *aBytesRead; + mZs.next_in += *aBytesRead; + } + break; + case DEFLATED: + buf = mBuf; + mZs.next_out = buf; + mZs.avail_out = mBufSize; + + zerr = inflate(&mZs, Z_PARTIAL_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) return nullptr; + + *aBytesRead = mZs.next_out - buf; + verifyCRC = (zerr == Z_STREAM_END); + break; + default: + return nullptr; + } + + if (mDoCRC) { + mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead); + if (verifyCRC && mCRC != mItem->CRC32()) return nullptr; + } + MMAP_FAULT_HANDLER_CATCH(nullptr) + return buf; +} + +nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive* aZip, const char* aEntryName, + bool doCRC) + : mReturnBuf(nullptr), mReadlen(0) { + // make sure the ziparchive hangs around + mZipHandle = aZip->GetFD(); + + nsZipItem* item = aZip->GetItem(aEntryName); + if (!item) return; + + uint32_t size = 0; + bool compressed = (item->Compression() == DEFLATED); + if (compressed) { + size = item->RealSize(); + mAutoBuf = MakeUniqueFallible<uint8_t[]>(size); + if (!mAutoBuf) { + return; + } + } + + nsZipCursor cursor(item, aZip, mAutoBuf.get(), size, doCRC); + mReturnBuf = cursor.Read(&mReadlen); + if (!mReturnBuf) { + return; + } + + if (mReadlen != item->RealSize()) { + NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow"); + mReturnBuf = nullptr; + return; + } +} |