gecko-dev/modules/libjar/nsZipArchive.cpp

1421 строка
41 KiB
C++

/* -*- 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.
*
* The underlying nsZipArchive is NOT thread-safe. Do not pass references
* or pointers to it across thread boundaries.
*/
#define READTYPE int32_t
#include "zlib.h"
#ifdef MOZ_JAR_BROTLI
# include "brotli/decode.h" // brotli
#endif
#include "nsISupportsUtils.h"
#include "mozilla/MmapFaultHandler.h"
#include "prio.h"
#include "plstr.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 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,
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;
PRFileDesc* mFd;
};
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
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;
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;
}
nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen,
nsZipHandle** aRet) {
RefPtr<nsZipHandle> handle = new nsZipHandle();
handle->mFileStart = aData;
handle->mTotalLen = aLen;
nsresult rv = handle->findDataStart();
if (NS_FAILED(rv)) {
return rv;
}
handle.forget(aRet);
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;
}
}
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
//---------------------------------------------
nsresult nsZipArchive::OpenArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd,
Span<const uint8_t> aCachedCentral) {
mFd = aZipHandle;
//-- get table of contents for archive
nsresult rv = NS_OK;
if (!mBuiltFileList) {
if (!aCachedCentral.IsEmpty()) {
auto* start = aCachedCentral.Elements();
auto* end = start + aCachedCentral.Length();
rv = BuildFileListFromBuffer(start, end);
} else {
rv = BuildFileList(aFd);
}
}
if (NS_SUCCEEDED(rv)) {
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;
}
}
}
}
}
}
}
return rv;
}
nsresult nsZipArchive::OpenArchive(nsIFile* aFile,
Span<const uint8_t> aCachedCentral) {
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 rv;
#if defined(XP_WIN)
return OpenArchive(handle, fd.get(), aCachedCentral);
#else
return OpenArchive(handle, nullptr, aCachedCentral);
#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_TARGET_DOES_NOT_EXIST;
//-- 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::CloseArchive
//---------------------------------------------
nsresult nsZipArchive::CloseArchive() {
if (mFd) {
mArena.Clear();
mFd = nullptr;
}
// CAUTION:
// We don't need to delete each of the nsZipItem as the memory for
// the zip item and the filename it holds are both allocated from the Arena.
// Hence, destroying the Arena is like destroying all the memory
// for all the nsZipItem in one shot. But if the ~nsZipItem is doing
// anything more than cleaning up memory, we should start calling it.
// Let us also cleanup the mFiles table for re-use on the next 'open' call
memset(mFiles, 0, sizeof(mFiles));
mBuiltSynthetics = false;
AutoWriteLock lock(mLazyOpenLock);
mLazyOpenParams = Nothing();
return NS_OK;
}
nsresult nsZipArchive::EnsureArchiveOpenedOnDisk() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
return NS_OK;
}
}
AutoWriteLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread beat us to opening the archive while we were waiting on
// the mutex.
return NS_OK;
}
nsresult rv = OpenArchive(mLazyOpenParams->mFile);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
mLazyOpenParams = Nothing();
return NS_OK;
}
nsresult nsZipArchive::EnsureFileListBuilt() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams || mBuiltFileList) {
return NS_OK;
}
}
AutoWriteLock lock(mLazyOpenLock);
if (!mLazyOpenParams || mBuiltFileList) {
// Another thread beat us to building the file list while we were waiting
// on the mutex.
return NS_OK;
}
nsresult rv;
if (!mLazyOpenParams->mCachedCentral.IsEmpty()) {
auto* start = mLazyOpenParams->mCachedCentral.Elements();
auto* end = start + mLazyOpenParams->mCachedCentral.Length();
rv = BuildFileListFromBuffer(start, end);
} else {
rv = OpenArchive(mLazyOpenParams->mFile);
mLazyOpenParams = Nothing();
}
return rv;
}
//---------------------------------------------
// nsZipArchive::GetItem
//---------------------------------------------
nsZipItem* nsZipArchive::GetItem(const char* aEntryName) {
nsresult rv = EnsureFileListBuilt();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
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) {
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_DISK_FULL;
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;
nsresult rv = EnsureFileListBuilt();
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
// null out param in case an error happens
*aFind = nullptr;
bool regExp = false;
char* pattern = 0;
// Create synthetic directory entries on demand
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 = PL_strdup(aPattern);
if (!pattern) return NS_ERROR_OUT_OF_MEMORY;
}
*aFind = new nsZipFind(this, pattern, regExp);
if (!*aFind) {
PL_strfree(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;
*aResult = 0;
*aNameLen = 0;
// NOTE: don't use GetFD here. if mFd is not null, then we need to have this
// fault handler, as we may be reading from the memory mapped file. However
// if it is null, then we can guarantee that we're reading here from a cached
// buffer, which we assume is not mapped to a file.
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->mFd)
// 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;
return NS_OK;
}
}
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
}
//***********************************************************
// 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) {
mBuiltFileList = true;
// Get archive size using end pos
const uint8_t* buf;
const uint8_t* startp = mFd->mFileData;
const uint8_t* endp = startp + mFd->mLen;
nsresult rv;
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
uint32_t centralOffset = 4;
// 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;
}
uintptr_t startpInt = reinterpret_cast<uintptr_t>(startp);
if (startpInt + centralOffset < startpInt || centralOffset > mFd->mLen) {
return NS_ERROR_FILE_CORRUPTED;
}
buf = startp + centralOffset;
mZipCentralOffset = centralOffset;
rv = BuildFileListFromBuffer(buf, endp);
MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
return rv;
}
UniquePtr<uint8_t[]> nsZipArchive::CopyCentralDirectoryBuffer(size_t* aSize) {
*aSize = 0;
// mZipCentralOffset could in theory be 0. In practice though, we likely
// won't ever see this. If the end result is that we can't cache the buffer
// in these cases, that's fine.
if (!mZipCentralOffset || !mZipCentralSize) {
return nullptr;
}
const uint8_t* buf;
const uint8_t* startp = mFd->mFileData;
buf = startp + mZipCentralOffset;
// Just a sanity check to make sure these values haven't overflowed the
// buffer mapped to our file. Technically the pointer could overflow the max
// pointer value, but that could only happen with this check succeeding if
// mFd->mLen is incorrect, which we will here assume is impossible.
if (mZipCentralOffset + mZipCentralSize > mFd->mLen) {
return nullptr;
}
auto resultBuf = MakeUnique<uint8_t[]>(mZipCentralSize);
MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
memcpy(resultBuf.get(), buf, mZipCentralSize);
MMAP_FAULT_HANDLER_CATCH(nullptr)
*aSize = mZipCentralSize;
return resultBuf;
}
nsresult nsZipArchive::BuildFileListFromBuffer(const uint8_t* aBuf,
const uint8_t* aEnd) {
mBuiltFileList = true;
const uint8_t* buf = aBuf;
//-- Read the central directory headers
uint32_t sig = 0;
while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
(buf + int32_t(sizeof(uint32_t)) <= aEnd) &&
((sig = xtolong(buf)) == CENTRALSIG)) {
// Make sure there is enough data available.
if ((buf > aEnd) || (aEnd - 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 >= aEnd - 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
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;
}
// Make the comment available for consumers.
if ((aEnd >= buf) && (aEnd - buf >= ZIPEND_SIZE)) {
ZipEnd* zipend = (ZipEnd*)buf;
buf += ZIPEND_SIZE;
uint16_t commentlen = xtoint(zipend->commentfield_len);
if (aEnd - buf >= commentlen) {
mCommentPtr = (const char*)aBuf;
mCommentLen = commentlen;
}
}
mZipCentralSize = buf - aBuf;
return NS_OK;
}
//---------------------------------------------
// nsZipArchive::BuildSynthetics
//---------------------------------------------
nsresult nsZipArchive::BuildSynthetics() {
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;
}
nsZipHandle* nsZipArchive::GetFD() {
nsresult rv = EnsureArchiveOpenedOnDisk();
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return mFd.get();
}
void nsZipArchive::GetURIString(nsACString& result) {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
mFd->mFile.GetURIString(result);
return;
}
}
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread consumed mLazyOpenParams while we were waiting.
mFd->mFile.GetURIString(result);
return;
}
// This is a bit tricky - typically, we could just
// NS_GetURLSpecFromActualFile from mLazyOpenParams->mFile or from
// mFd->mFile.GetBaseFile(), depending on which we currently have. However,
// this won't actually be correct if this zip archive is nested inside
// another archive. However, at present, we know that mLazyOpenParams can
// only be here if we were opened from a real underlying file, so we assume
// that we're safe to do this. Any future code that breaks this assumption
// will need to update things here.
NS_GetURLSpecFromActualFile(mLazyOpenParams->mFile, result);
}
already_AddRefed<nsIFile> nsZipArchive::GetBaseFile() {
{
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
return mFd->mFile.GetBaseFile();
}
}
AutoReadLock lock(mLazyOpenLock);
if (!mLazyOpenParams) {
// Another thread consumed mLazyOpenParams while we were waiting.
return mFd->mFile.GetBaseFile();
}
nsCOMPtr<nsIFile> file = mLazyOpenParams->mFile;
return file.forget();
}
//---------------------------------------------
// nsZipArchive::GetDataOffset
//---------------------------------------------
uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
nsresult rv = EnsureArchiveOpenedOnDisk();
MOZ_RELEASE_ASSERT(!NS_FAILED(rv),
"Should have been able to open the zip archive");
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;
const uint8_t* data = mFd->mFileData;
offset = aItem->LocalOffset();
if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE) 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);
MMAP_FAULT_HANDLER_CATCH(0)
return offset;
}
//---------------------------------------------
// nsZipArchive::GetData
//---------------------------------------------
const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) {
MOZ_ASSERT(aItem);
nsresult rv = EnsureArchiveOpenedOnDisk();
if (NS_WARN_IF(NS_FAILED(rv))) {
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::GetComment
bool nsZipArchive::GetComment(nsACString& aComment) {
MMAP_FAULT_HANDLER_BEGIN_BUFFER(mCommentPtr, mCommentLen)
aComment.Assign(mCommentPtr, mCommentLen);
MMAP_FAULT_HANDLER_CATCH(false)
return true;
}
//---------------------------------------------
// nsZipArchive::SizeOfMapping
//---------------------------------------------
int64_t nsZipArchive::SizeOfMapping() { return mFd ? mFd->SizeOfMapping() : 0; }
//------------------------------------------
// nsZipArchive constructor and destructor
//------------------------------------------
nsZipArchive::nsZipArchive()
: mRefCnt(0),
mCommentPtr(nullptr),
mZipCentralOffset(0),
mZipCentralSize(0),
mCommentLen(0),
mBuiltSynthetics(false),
mBuiltFileList(false),
mLazyOpenLock("nsZipArchive::mLazyOpenLock"),
mUseZipLog(false) {
// initialize the table to nullptr
memset(mFiles, 0, sizeof(mFiles));
}
NS_IMPL_ADDREF(nsZipArchive)
NS_IMPL_RELEASE(nsZipArchive)
nsZipArchive::~nsZipArchive() {
CloseArchive();
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() {
PL_strfree(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()
#ifdef MOZ_JAR_BROTLI
,
mBrotliState(nullptr)
#endif
,
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);
#ifdef MOZ_JAR_BROTLI
if (mItem->Compression() == MOZ_JAR_BROTLI) {
mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
}
#endif
if (doCRC) mCRC = crc32(0L, Z_NULL, 0);
}
nsZipCursor::~nsZipCursor() {
if (mItem->Compression() == DEFLATED) {
inflateEnd(&mZs);
}
#ifdef MOZ_JAR_BROTLI
if (mItem->Compression() == MOZ_JAR_BROTLI) {
BrotliDecoderDestroyInstance(mBrotliState);
}
#endif
}
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;
#ifdef MOZ_JAR_BROTLI
case MOZ_JAR_BROTLI: {
buf = mBuf;
mZs.next_out = buf;
/* The brotli library wants size_t, but z_stream only contains
* unsigned int for avail_*. So use temporary stack values. */
size_t avail_out = mBufSize;
size_t avail_in = mZs.avail_in;
BrotliDecoderResult result = BrotliDecoderDecompressStream(
mBrotliState, &avail_in,
const_cast<const unsigned char**>(&mZs.next_in), &avail_out,
&mZs.next_out, nullptr);
/* We don't need to update avail_out, it's not used outside this
* function. */
mZs.avail_in = avail_in;
if (result == BROTLI_DECODER_RESULT_ERROR) {
return nullptr;
}
*aBytesRead = mZs.next_out - buf;
verifyCRC = (result == BROTLI_DECODER_RESULT_SUCCESS);
break;
}
#endif
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) {
nsZipItem* item = aZip->GetItem(aEntryName);
if (!item) {
return;
}
// make sure the ziparchive hangs around
mZipHandle = aZip->GetFD();
uint32_t size = 0;
bool compressed = (item->Compression() == DEFLATED);
#ifdef MOZ_JAR_BROTLI
compressed |= (item->Compression() == MOZ_JAR_BROTLI);
#endif
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;
}
}