michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * This module implements a simple archive extractor for the PKZIP format. michael@0: * michael@0: * The underlying nsZipArchive is NOT thread-safe. Do not pass references michael@0: * or pointers to it across thread boundaries. michael@0: */ michael@0: michael@0: // This must be the first include in the file in order for the michael@0: // PL_ARENA_CONST_ALIGN_MASK macro to be effective. michael@0: #define PL_ARENA_CONST_ALIGN_MASK (sizeof(void*)-1) michael@0: #include "plarena.h" michael@0: michael@0: #define READTYPE int32_t michael@0: #include "zlib.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "prio.h" michael@0: #include "plstr.h" michael@0: #include "prlog.h" michael@0: #include "stdlib.h" michael@0: #include "nsWildCard.h" michael@0: #include "nsZipArchive.h" michael@0: #include "nsString.h" michael@0: #include "prenv.h" michael@0: #if defined(XP_WIN) michael@0: #include michael@0: #endif michael@0: michael@0: // For placement new used for arena allocations of zip file list michael@0: #include michael@0: #define ZIP_ARENABLOCKSIZE (1*1024) michael@0: michael@0: #ifdef XP_UNIX michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #elif defined(XP_WIN) michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef __SYMBIAN32__ michael@0: #include michael@0: #endif /*__SYMBIAN32__*/ michael@0: michael@0: michael@0: #ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */ michael@0: # ifndef S_IFMT michael@0: # define S_IFMT 0170000 michael@0: # endif michael@0: # ifndef S_IFLNK michael@0: # define S_IFLNK 0120000 michael@0: # endif michael@0: # ifndef PATH_MAX michael@0: # define PATH_MAX 1024 michael@0: # endif michael@0: #endif /* XP_UNIX */ michael@0: michael@0: #ifdef XP_WIN michael@0: #include "private/pprio.h" // To get PR_ImportFile michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */ michael@0: // For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00. michael@0: static const uint16_t kSyntheticTime = 0; michael@0: static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9)); michael@0: michael@0: static uint16_t xtoint(const uint8_t *ii); michael@0: static uint32_t xtolong(const uint8_t *ll); michael@0: static uint32_t HashName(const char* aName, uint16_t nameLen); michael@0: #ifdef XP_UNIX michael@0: static nsresult ResolveSymlink(const char *path); michael@0: #endif michael@0: michael@0: class ZipArchiveLogger { michael@0: public: michael@0: void Write(const nsACString &zip, const char *entry) const { michael@0: if (!fd) { michael@0: char *env = PR_GetEnv("MOZ_JAR_LOG_FILE"); michael@0: if (!env) michael@0: return; michael@0: michael@0: nsCOMPtr logFile; michael@0: nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(logFile)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: // Create the log file and its parent directory (in case it doesn't exist) michael@0: logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); michael@0: michael@0: PRFileDesc* file; michael@0: #ifdef XP_WIN michael@0: // PR_APPEND is racy on Windows, so open a handle ourselves with flags that michael@0: // will work, and use PR_ImportFile to make it a PRFileDesc. michael@0: // This can go away when bug 840435 is fixed. michael@0: nsAutoString path; michael@0: logFile->GetPath(path); michael@0: if (path.IsEmpty()) michael@0: return; michael@0: HANDLE handle = CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE, michael@0: nullptr, OPEN_ALWAYS, 0, nullptr); michael@0: if (handle == INVALID_HANDLE_VALUE) michael@0: return; michael@0: file = PR_ImportFile((PROsfd)handle); michael@0: if (!file) michael@0: return; michael@0: #else michael@0: rv = logFile->OpenNSPRFileDesc(PR_WRONLY|PR_CREATE_FILE|PR_APPEND, 0644, &file); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: #endif michael@0: fd = file; michael@0: } michael@0: nsCString buf(zip); michael@0: buf.Append(" "); michael@0: buf.Append(entry); michael@0: buf.Append('\n'); michael@0: PR_Write(fd, buf.get(), buf.Length()); michael@0: } michael@0: michael@0: void AddRef() { michael@0: MOZ_ASSERT(refCnt >= 0); michael@0: ++refCnt; michael@0: } michael@0: michael@0: void Release() { michael@0: MOZ_ASSERT(refCnt > 0); michael@0: if ((0 == --refCnt) && fd) { michael@0: PR_Close(fd); michael@0: fd = nullptr; michael@0: } michael@0: } michael@0: private: michael@0: int refCnt; michael@0: mutable PRFileDesc *fd; michael@0: }; michael@0: michael@0: static ZipArchiveLogger zipLog; michael@0: michael@0: //*********************************************************** michael@0: // For every inflation the following allocations are done: michael@0: // malloc(1 * 9520) michael@0: // malloc(32768 * 1) michael@0: //*********************************************************** michael@0: michael@0: nsresult gZlibInit(z_stream *zs) michael@0: { michael@0: memset(zs, 0, sizeof(z_stream)); michael@0: int zerr = inflateInit2(zs, -MAX_WBITS); michael@0: if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsZipHandle::nsZipHandle() michael@0: : mFileData(nullptr) michael@0: , mLen(0) michael@0: , mMap(nullptr) michael@0: , mRefCnt(0) michael@0: { michael@0: MOZ_COUNT_CTOR(nsZipHandle); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(nsZipHandle) michael@0: NS_IMPL_RELEASE(nsZipHandle) michael@0: michael@0: nsresult nsZipHandle::Init(nsIFile *file, nsZipHandle **ret, PRFileDesc **aFd) michael@0: { michael@0: mozilla::AutoFDClose fd; michael@0: int32_t flags = PR_RDONLY; michael@0: #if defined(XP_WIN) michael@0: flags |= nsIFile::OS_READAHEAD; michael@0: #endif michael@0: nsresult rv = file->OpenNSPRFileDesc(flags, 0000, &fd.rwget()); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: int64_t size = PR_Available64(fd); michael@0: if (size >= INT32_MAX) michael@0: return NS_ERROR_FILE_TOO_BIG; michael@0: michael@0: PRFileMap *map = PR_CreateFileMap(fd, size, PR_PROT_READONLY); michael@0: if (!map) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: uint8_t *buf = (uint8_t*) PR_MemMap(map, 0, (uint32_t) size); michael@0: // Bug 525755: PR_MemMap fails when fd points at something other than a normal file. michael@0: if (!buf) { michael@0: PR_CloseFileMap(map); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr handle = new nsZipHandle(); michael@0: if (!handle) { michael@0: PR_MemUnmap(buf, (uint32_t) size); michael@0: PR_CloseFileMap(map); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: if (aFd) { michael@0: *aFd = fd.forget(); michael@0: } michael@0: #endif michael@0: handle->mMap = map; michael@0: handle->mFile.Init(file); michael@0: handle->mLen = (uint32_t) size; michael@0: handle->mFileData = buf; michael@0: handle.forget(ret); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsZipHandle::Init(nsZipArchive *zip, const char *entry, michael@0: nsZipHandle **ret) michael@0: { michael@0: nsRefPtr handle = new nsZipHandle(); michael@0: if (!handle) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: handle->mBuf = new nsZipItemPtr(zip, entry); michael@0: if (!handle->mBuf) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (!handle->mBuf->Buffer()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: handle->mMap = nullptr; michael@0: handle->mFile.Init(zip, entry); michael@0: handle->mLen = handle->mBuf->Length(); michael@0: handle->mFileData = handle->mBuf->Buffer(); michael@0: handle.forget(ret); michael@0: return NS_OK; michael@0: } michael@0: michael@0: int64_t nsZipHandle::SizeOfMapping() michael@0: { michael@0: return mLen; michael@0: } michael@0: michael@0: nsZipHandle::~nsZipHandle() michael@0: { michael@0: if (mMap) { michael@0: PR_MemUnmap((void *)mFileData, mLen); michael@0: PR_CloseFileMap(mMap); michael@0: } michael@0: mFileData = nullptr; michael@0: mMap = nullptr; michael@0: mBuf = nullptr; michael@0: MOZ_COUNT_DTOR(nsZipHandle); michael@0: } michael@0: michael@0: //*********************************************************** michael@0: // nsZipArchive -- public methods michael@0: //*********************************************************** michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::OpenArchive michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd) michael@0: { michael@0: mFd = aZipHandle; michael@0: michael@0: // Initialize our arena michael@0: PL_INIT_ARENA_POOL(&mArena, "ZipArena", ZIP_ARENABLOCKSIZE); michael@0: michael@0: //-- get table of contents for archive michael@0: nsresult rv = BuildFileList(aFd); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (aZipHandle->mFile) michael@0: aZipHandle->mFile.GetURIString(mURI); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsZipArchive::OpenArchive(nsIFile *aFile) michael@0: { michael@0: nsRefPtr handle; michael@0: #if defined(XP_WIN) michael@0: mozilla::AutoFDClose fd; michael@0: nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle), &fd.rwget()); michael@0: #else michael@0: nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle)); michael@0: #endif michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: #if defined(XP_WIN) michael@0: return OpenArchive(handle, fd.get()); michael@0: #else michael@0: return OpenArchive(handle); michael@0: #endif michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::Test michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::Test(const char *aEntryName) michael@0: { michael@0: nsZipItem* currItem; michael@0: michael@0: if (aEntryName) // only test specified item michael@0: { michael@0: currItem = GetItem(aEntryName); michael@0: if (!currItem) michael@0: return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; michael@0: //-- don't test (synthetic) directory items michael@0: if (currItem->IsDirectory()) michael@0: return NS_OK; michael@0: return ExtractFile(currItem, 0, 0); michael@0: } michael@0: michael@0: // test all items in archive michael@0: for (int i = 0; i < ZIP_TABSIZE; i++) { michael@0: for (currItem = mFiles[i]; currItem; currItem = currItem->next) { michael@0: //-- don't test (synthetic) directory items michael@0: if (currItem->IsDirectory()) michael@0: continue; michael@0: nsresult rv = ExtractFile(currItem, 0, 0); michael@0: if (rv != NS_OK) michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::CloseArchive michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::CloseArchive() michael@0: { michael@0: if (mFd) { michael@0: PL_FinishArenaPool(&mArena); michael@0: mFd = nullptr; michael@0: } michael@0: michael@0: // CAUTION: michael@0: // We don't need to delete each of the nsZipItem as the memory for michael@0: // the zip item and the filename it holds are both allocated from the Arena. michael@0: // Hence, destroying the Arena is like destroying all the memory michael@0: // for all the nsZipItem in one shot. But if the ~nsZipItem is doing michael@0: // anything more than cleaning up memory, we should start calling it. michael@0: // Let us also cleanup the mFiles table for re-use on the next 'open' call michael@0: memset(mFiles, 0, sizeof(mFiles)); michael@0: mBuiltSynthetics = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::GetItem michael@0: //--------------------------------------------- michael@0: nsZipItem* nsZipArchive::GetItem(const char * aEntryName) michael@0: { michael@0: if (aEntryName) { michael@0: uint32_t len = strlen(aEntryName); michael@0: //-- If the request is for a directory, make sure that synthetic entries michael@0: //-- are created for the directories without their own entry. michael@0: if (!mBuiltSynthetics) { michael@0: if ((len > 0) && (aEntryName[len-1] == '/')) { michael@0: if (BuildSynthetics() != NS_OK) michael@0: return 0; michael@0: } michael@0: } michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: nsZipItem* item = mFiles[ HashName(aEntryName, len) ]; michael@0: while (item) { michael@0: if ((len == item->nameLength) && michael@0: (!memcmp(aEntryName, item->Name(), len))) { michael@0: michael@0: // Successful GetItem() is a good indicator that the file is about to be read michael@0: zipLog.Write(mURI, aEntryName); michael@0: return item; //-- found it michael@0: } michael@0: item = item->next; michael@0: } michael@0: MOZ_WIN_MEM_TRY_CATCH(return nullptr) michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::ExtractFile michael@0: // This extracts the item to the filehandle provided. michael@0: // If 'aFd' is null, it only tests the extraction. michael@0: // On extraction error(s) it removes the file. michael@0: // When needed, it also resolves the symlink. michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::ExtractFile(nsZipItem *item, const char *outname, michael@0: PRFileDesc* aFd) michael@0: { michael@0: if (!item) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: if (!mFd) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Directory extraction is handled in nsJAR::Extract, michael@0: // so the item to be extracted should never be a directory michael@0: PR_ASSERT(!item->IsDirectory()); michael@0: michael@0: Bytef outbuf[ZIP_BUFLEN]; michael@0: michael@0: nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: while (true) { michael@0: uint32_t count = 0; michael@0: uint8_t* buf = cursor.Read(&count); michael@0: if (!buf) { michael@0: rv = NS_ERROR_FILE_CORRUPTED; michael@0: break; michael@0: } else if (count == 0) { michael@0: break; michael@0: } michael@0: michael@0: if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) { michael@0: rv = NS_ERROR_FILE_DISK_FULL; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: //-- delete the file on errors, or resolve symlink if needed michael@0: if (aFd) { michael@0: PR_Close(aFd); michael@0: if (rv != NS_OK) michael@0: PR_Delete(outname); michael@0: #ifdef XP_UNIX michael@0: else if (item->IsSymlink()) michael@0: rv = ResolveSymlink(outname); michael@0: #endif michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::FindInit michael@0: //--------------------------------------------- michael@0: nsresult michael@0: nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind) michael@0: { michael@0: if (!aFind) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: // null out param in case an error happens michael@0: *aFind = nullptr; michael@0: michael@0: bool regExp = false; michael@0: char* pattern = 0; michael@0: michael@0: // Create synthetic directory entries on demand michael@0: nsresult rv = BuildSynthetics(); michael@0: if (rv != NS_OK) michael@0: return rv; michael@0: michael@0: // validate the pattern michael@0: if (aPattern) michael@0: { michael@0: switch (NS_WildCardValid((char*)aPattern)) michael@0: { michael@0: case INVALID_SXP: michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: case NON_SXP: michael@0: regExp = false; michael@0: break; michael@0: michael@0: case VALID_SXP: michael@0: regExp = true; michael@0: break; michael@0: michael@0: default: michael@0: // undocumented return value from RegExpValid! michael@0: PR_ASSERT(false); michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: pattern = PL_strdup(aPattern); michael@0: if (!pattern) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: *aFind = new nsZipFind(this, pattern, regExp); michael@0: if (!*aFind) { michael@0: PL_strfree(pattern); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipFind::FindNext michael@0: //--------------------------------------------- michael@0: nsresult nsZipFind::FindNext(const char ** aResult, uint16_t *aNameLen) michael@0: { michael@0: if (!mArchive || !aResult || !aNameLen) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: *aResult = 0; michael@0: *aNameLen = 0; michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: // we start from last match, look for next michael@0: while (mSlot < ZIP_TABSIZE) michael@0: { michael@0: // move to next in current chain, or move to new slot michael@0: mItem = mItem ? mItem->next : mArchive->mFiles[mSlot]; michael@0: michael@0: bool found = false; michael@0: if (!mItem) michael@0: ++mSlot; // no more in this chain, move to next slot michael@0: else if (!mPattern) michael@0: found = true; // always match michael@0: else if (mRegExp) michael@0: { michael@0: char buf[kMaxNameLength+1]; michael@0: memcpy(buf, mItem->Name(), mItem->nameLength); michael@0: buf[mItem->nameLength]='\0'; michael@0: found = (NS_WildCardMatch(buf, mPattern, false) == MATCH); michael@0: } michael@0: else michael@0: found = ((mItem->nameLength == strlen(mPattern)) && michael@0: (memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0)); michael@0: if (found) { michael@0: // Need also to return the name length, as it is NOT zero-terminatdd... michael@0: *aResult = mItem->Name(); michael@0: *aNameLen = mItem->nameLength; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE) michael@0: return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; michael@0: } michael@0: michael@0: #ifdef XP_UNIX michael@0: //--------------------------------------------- michael@0: // ResolveSymlink michael@0: //--------------------------------------------- michael@0: static nsresult ResolveSymlink(const char *path) michael@0: { michael@0: PRFileDesc * fIn = PR_Open(path, PR_RDONLY, 0000); michael@0: if (!fIn) michael@0: return NS_ERROR_FILE_DISK_FULL; michael@0: michael@0: char buf[PATH_MAX+1]; michael@0: int32_t length = PR_Read(fIn, (void*)buf, PATH_MAX); michael@0: PR_Close(fIn); michael@0: michael@0: if ( (length <= 0) michael@0: || ((buf[length] = 0, PR_Delete(path)) != 0) michael@0: || (symlink(buf, path) != 0)) michael@0: { michael@0: return NS_ERROR_FILE_DISK_FULL; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: //*********************************************************** michael@0: // nsZipArchive -- private implementation michael@0: //*********************************************************** michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::CreateZipItem michael@0: //--------------------------------------------- michael@0: nsZipItem* nsZipArchive::CreateZipItem() michael@0: { michael@0: // Arena allocate the nsZipItem michael@0: void *mem; michael@0: PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem)); michael@0: return (nsZipItem*)mem; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::BuildFileList michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::BuildFileList(PRFileDesc *aFd) michael@0: { michael@0: // Get archive size using end pos michael@0: const uint8_t* buf; michael@0: const uint8_t* startp = mFd->mFileData; michael@0: const uint8_t* endp = startp + mFd->mLen; michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: uint32_t centralOffset = 4; michael@0: if (mFd->mLen > ZIPCENTRAL_SIZE && xtolong(startp + centralOffset) == CENTRALSIG) { michael@0: // Success means optimized jar layout from bug 559961 is in effect michael@0: uint32_t readaheadLength = xtolong(startp); michael@0: if (readaheadLength) { michael@0: #if defined(XP_UNIX) michael@0: madvise(const_cast(startp), readaheadLength, MADV_WILLNEED); michael@0: #elif defined(XP_WIN) michael@0: if (aFd) { michael@0: HANDLE hFile = (HANDLE) PR_FileDesc2NativeHandle(aFd); michael@0: mozilla::ReadAhead(hFile, 0, readaheadLength); michael@0: } michael@0: #endif michael@0: } michael@0: } else { michael@0: for (buf = endp - ZIPEND_SIZE; buf > startp; buf--) michael@0: { michael@0: if (xtolong(buf) == ENDSIG) { michael@0: centralOffset = xtolong(((ZipEnd *)buf)->offset_central_dir); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!centralOffset) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: michael@0: //-- Read the central directory headers michael@0: buf = startp + centralOffset; michael@0: uint32_t sig = 0; michael@0: while (buf + int32_t(sizeof(uint32_t)) <= endp && michael@0: (sig = xtolong(buf)) == CENTRALSIG) { michael@0: // Make sure there is enough data available. michael@0: if (endp - buf < ZIPCENTRAL_SIZE) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: michael@0: // Read the fixed-size data. michael@0: ZipCentral* central = (ZipCentral*)buf; michael@0: michael@0: uint16_t namelen = xtoint(central->filename_len); michael@0: uint16_t extralen = xtoint(central->extrafield_len); michael@0: uint16_t commentlen = xtoint(central->commentfield_len); michael@0: michael@0: // Point to the next item at the top of loop michael@0: buf += ZIPCENTRAL_SIZE + namelen + extralen + commentlen; michael@0: michael@0: // Sanity check variable sizes and refuse to deal with michael@0: // anything too big: it's likely a corrupt archive. michael@0: if (namelen < 1 || michael@0: namelen > kMaxNameLength || michael@0: buf >= endp) { michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: nsZipItem* item = CreateZipItem(); michael@0: if (!item) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: item->central = central; michael@0: item->nameLength = namelen; michael@0: item->isSynthetic = false; michael@0: michael@0: // Add item to file table michael@0: uint32_t hash = HashName(item->Name(), namelen); michael@0: item->next = mFiles[hash]; michael@0: mFiles[hash] = item; michael@0: michael@0: sig = 0; michael@0: } /* while reading central directory records */ michael@0: michael@0: if (sig != ENDSIG) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: michael@0: // Make the comment available for consumers. michael@0: if (endp - buf >= ZIPEND_SIZE) { michael@0: ZipEnd *zipend = (ZipEnd *)buf; michael@0: michael@0: buf += ZIPEND_SIZE; michael@0: uint16_t commentlen = xtoint(zipend->commentfield_len); michael@0: if (endp - buf >= commentlen) { michael@0: mCommentPtr = (const char *)buf; michael@0: mCommentLen = commentlen; michael@0: } michael@0: } michael@0: michael@0: MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE) michael@0: return NS_OK; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::BuildSynthetics michael@0: //--------------------------------------------- michael@0: nsresult nsZipArchive::BuildSynthetics() michael@0: { michael@0: if (mBuiltSynthetics) michael@0: return NS_OK; michael@0: mBuiltSynthetics = true; michael@0: michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: // Create synthetic entries for any missing directories. michael@0: // Do this when all ziptable has scanned to prevent double entries. michael@0: for (int i = 0; i < ZIP_TABSIZE; ++i) michael@0: { michael@0: for (nsZipItem* item = mFiles[i]; item != nullptr; item = item->next) michael@0: { michael@0: if (item->isSynthetic) michael@0: continue; michael@0: michael@0: //-- add entries for directories in the current item's path michael@0: //-- go from end to beginning, because then we can stop trying michael@0: //-- to create diritems if we find that the diritem we want to michael@0: //-- create already exists michael@0: //-- start just before the last char so as to not add the item michael@0: //-- twice if it's a directory michael@0: uint16_t namelen = item->nameLength; michael@0: MOZ_ASSERT(namelen > 0, "Attempt to build synthetic for zero-length entry name!"); michael@0: const char *name = item->Name(); michael@0: for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--) michael@0: { michael@0: if (name[dirlen-1] != '/') michael@0: continue; michael@0: michael@0: // The character before this is '/', so if this is also '/' then we michael@0: // have an empty path component. Skip it. michael@0: if (name[dirlen] == '/') michael@0: continue; michael@0: michael@0: // Is the directory already in the file table? michael@0: uint32_t hash = HashName(item->Name(), dirlen); michael@0: bool found = false; michael@0: for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next) michael@0: { michael@0: if ((dirlen == zi->nameLength) && michael@0: (0 == memcmp(item->Name(), zi->Name(), dirlen))) michael@0: { michael@0: // we've already added this dir and all its parents michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: // if the directory was found, break out of the directory michael@0: // creation loop now that we know all implicit directories michael@0: // are there -- otherwise, start creating the zip item michael@0: if (found) michael@0: break; michael@0: michael@0: nsZipItem* diritem = CreateZipItem(); michael@0: if (!diritem) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // Point to the central record of the original item for the name part. michael@0: diritem->central = item->central; michael@0: diritem->nameLength = dirlen; michael@0: diritem->isSynthetic = true; michael@0: michael@0: // add diritem to the file table michael@0: diritem->next = mFiles[hash]; michael@0: mFiles[hash] = diritem; michael@0: } /* end processing of dirs in item's name */ michael@0: } michael@0: } michael@0: MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE) michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsZipHandle* nsZipArchive::GetFD() michael@0: { michael@0: if (!mFd) michael@0: return nullptr; michael@0: return mFd.get(); michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::GetData michael@0: //--------------------------------------------- michael@0: const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) michael@0: { michael@0: PR_ASSERT (aItem); michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: //-- read local header to get variable length values and calculate michael@0: //-- the real data offset michael@0: uint32_t len = mFd->mLen; michael@0: const uint8_t* data = mFd->mFileData; michael@0: uint32_t offset = aItem->LocalOffset(); michael@0: if (offset + ZIPLOCAL_SIZE > len) michael@0: return nullptr; michael@0: michael@0: // -- check signature before using the structure, in case the zip file is corrupt michael@0: ZipLocal* Local = (ZipLocal*)(data + offset); michael@0: if ((xtolong(Local->signature) != LOCALSIG)) michael@0: return nullptr; michael@0: michael@0: //-- NOTE: extralen is different in central header and local header michael@0: //-- for archives created using the Unix "zip" utility. To set michael@0: //-- the offset accurately we need the _local_ extralen. michael@0: offset += ZIPLOCAL_SIZE + michael@0: xtoint(Local->filename_len) + michael@0: xtoint(Local->extrafield_len); michael@0: michael@0: // -- check if there is enough source data in the file michael@0: if (offset + aItem->Size() > len) michael@0: return nullptr; michael@0: michael@0: return data + offset; michael@0: MOZ_WIN_MEM_TRY_CATCH(return nullptr) michael@0: } michael@0: michael@0: // nsZipArchive::GetComment michael@0: bool nsZipArchive::GetComment(nsACString &aComment) michael@0: { michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: aComment.Assign(mCommentPtr, mCommentLen); michael@0: MOZ_WIN_MEM_TRY_CATCH(return false) michael@0: return true; michael@0: } michael@0: michael@0: //--------------------------------------------- michael@0: // nsZipArchive::SizeOfMapping michael@0: //--------------------------------------------- michael@0: int64_t nsZipArchive::SizeOfMapping() michael@0: { michael@0: return mFd ? mFd->SizeOfMapping() : 0; michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // nsZipArchive constructor and destructor michael@0: //------------------------------------------ michael@0: michael@0: nsZipArchive::nsZipArchive() michael@0: : mRefCnt(0) michael@0: , mBuiltSynthetics(false) michael@0: { michael@0: zipLog.AddRef(); michael@0: michael@0: MOZ_COUNT_CTOR(nsZipArchive); michael@0: michael@0: // initialize the table to nullptr michael@0: memset(mFiles, 0, sizeof(mFiles)); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(nsZipArchive) michael@0: NS_IMPL_RELEASE(nsZipArchive) michael@0: michael@0: nsZipArchive::~nsZipArchive() michael@0: { michael@0: CloseArchive(); michael@0: michael@0: MOZ_COUNT_DTOR(nsZipArchive); michael@0: michael@0: zipLog.Release(); michael@0: } michael@0: michael@0: michael@0: //------------------------------------------ michael@0: // nsZipFind constructor and destructor michael@0: //------------------------------------------ michael@0: michael@0: nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp) : michael@0: mArchive(aZip), michael@0: mPattern(aPattern), michael@0: mItem(0), michael@0: mSlot(0), michael@0: mRegExp(aRegExp) michael@0: { michael@0: MOZ_COUNT_CTOR(nsZipFind); michael@0: } michael@0: michael@0: nsZipFind::~nsZipFind() michael@0: { michael@0: PL_strfree(mPattern); michael@0: michael@0: MOZ_COUNT_DTOR(nsZipFind); michael@0: } michael@0: michael@0: //------------------------------------------ michael@0: // helper functions michael@0: //------------------------------------------ michael@0: michael@0: /* michael@0: * HashName michael@0: * michael@0: * returns a hash key for the entry name michael@0: */ michael@0: static uint32_t HashName(const char* aName, uint16_t len) michael@0: { michael@0: PR_ASSERT(aName != 0); michael@0: michael@0: const uint8_t* p = (const uint8_t*)aName; michael@0: const uint8_t* endp = p + len; michael@0: uint32_t val = 0; michael@0: while (p != endp) { michael@0: val = val*37 + *p++; michael@0: } michael@0: michael@0: return (val % ZIP_TABSIZE); michael@0: } michael@0: michael@0: /* michael@0: * x t o i n t michael@0: * michael@0: * Converts a two byte ugly endianed integer michael@0: * to our platform's integer. michael@0: */ michael@0: static uint16_t xtoint (const uint8_t *ii) michael@0: { michael@0: return (uint16_t) ((ii [0]) | (ii [1] << 8)); michael@0: } michael@0: michael@0: /* michael@0: * x t o l o n g michael@0: * michael@0: * Converts a four byte ugly endianed integer michael@0: * to our platform's integer. michael@0: */ michael@0: static uint32_t xtolong (const uint8_t *ll) michael@0: { michael@0: return (uint32_t)( (ll [0] << 0) | michael@0: (ll [1] << 8) | michael@0: (ll [2] << 16) | michael@0: (ll [3] << 24) ); michael@0: } michael@0: michael@0: /* michael@0: * GetModTime michael@0: * michael@0: * returns last modification time in microseconds michael@0: */ michael@0: static PRTime GetModTime(uint16_t aDate, uint16_t aTime) michael@0: { michael@0: // Note that on DST shift we can't handle correctly the hour that is valid michael@0: // in both DST zones michael@0: PRExplodedTime time; michael@0: michael@0: time.tm_usec = 0; michael@0: michael@0: time.tm_hour = (aTime >> 11) & 0x1F; michael@0: time.tm_min = (aTime >> 5) & 0x3F; michael@0: time.tm_sec = (aTime & 0x1F) * 2; michael@0: michael@0: time.tm_year = (aDate >> 9) + 1980; michael@0: time.tm_month = ((aDate >> 5) & 0x0F) - 1; michael@0: time.tm_mday = aDate & 0x1F; michael@0: michael@0: time.tm_params.tp_gmt_offset = 0; michael@0: time.tm_params.tp_dst_offset = 0; michael@0: michael@0: PR_NormalizeTime(&time, PR_GMTParameters); michael@0: time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset; michael@0: PR_NormalizeTime(&time, PR_GMTParameters); michael@0: time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset; michael@0: michael@0: return PR_ImplodeTime(&time); michael@0: } michael@0: michael@0: uint32_t nsZipItem::LocalOffset() michael@0: { michael@0: return xtolong(central->localhdr_offset); michael@0: } michael@0: michael@0: uint32_t nsZipItem::Size() michael@0: { michael@0: return isSynthetic ? 0 : xtolong(central->size); michael@0: } michael@0: michael@0: uint32_t nsZipItem::RealSize() michael@0: { michael@0: return isSynthetic ? 0 : xtolong(central->orglen); michael@0: } michael@0: michael@0: uint32_t nsZipItem::CRC32() michael@0: { michael@0: return isSynthetic ? 0 : xtolong(central->crc32); michael@0: } michael@0: michael@0: uint16_t nsZipItem::Date() michael@0: { michael@0: return isSynthetic ? kSyntheticDate : xtoint(central->date); michael@0: } michael@0: michael@0: uint16_t nsZipItem::Time() michael@0: { michael@0: return isSynthetic ? kSyntheticTime : xtoint(central->time); michael@0: } michael@0: michael@0: uint16_t nsZipItem::Compression() michael@0: { michael@0: return isSynthetic ? STORED : xtoint(central->method); michael@0: } michael@0: michael@0: bool nsZipItem::IsDirectory() michael@0: { michael@0: return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1])); michael@0: } michael@0: michael@0: uint16_t nsZipItem::Mode() michael@0: { michael@0: if (isSynthetic) return 0755; michael@0: return ((uint16_t)(central->external_attributes[2]) | 0x100); michael@0: } michael@0: michael@0: const uint8_t * nsZipItem::GetExtraField(uint16_t aTag, uint16_t *aBlockSize) michael@0: { michael@0: if (isSynthetic) return nullptr; michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: const unsigned char *buf = ((const unsigned char*)central) + ZIPCENTRAL_SIZE + michael@0: nameLength; michael@0: uint32_t buflen = (uint32_t)xtoint(central->extrafield_len); michael@0: uint32_t pos = 0; michael@0: uint16_t tag, blocksize; michael@0: michael@0: while (buf && (pos + 4) <= buflen) { michael@0: tag = xtoint(buf + pos); michael@0: blocksize = xtoint(buf + pos + 2); michael@0: michael@0: if (aTag == tag && (pos + 4 + blocksize) <= buflen) { michael@0: *aBlockSize = blocksize; michael@0: return buf + pos; michael@0: } michael@0: michael@0: pos += blocksize + 4; michael@0: } michael@0: michael@0: MOZ_WIN_MEM_TRY_CATCH(return nullptr) michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: PRTime nsZipItem::LastModTime() michael@0: { michael@0: if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime); michael@0: michael@0: // Try to read timestamp from extra field michael@0: uint16_t blocksize; michael@0: const uint8_t *tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize); michael@0: if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) { michael@0: return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC; michael@0: } michael@0: michael@0: return GetModTime(Date(), Time()); michael@0: } michael@0: michael@0: #ifdef XP_UNIX michael@0: bool nsZipItem::IsSymlink() michael@0: { michael@0: if (isSynthetic) return false; michael@0: return (xtoint(central->external_attributes+2) & S_IFMT) == S_IFLNK; michael@0: } michael@0: #endif michael@0: michael@0: nsZipCursor::nsZipCursor(nsZipItem *item, nsZipArchive *aZip, uint8_t* aBuf, uint32_t aBufSize, bool doCRC) : michael@0: mItem(item), michael@0: mBuf(aBuf), michael@0: mBufSize(aBufSize), michael@0: mDoCRC(doCRC) michael@0: { michael@0: if (mItem->Compression() == DEFLATED) { michael@0: #ifdef DEBUG michael@0: nsresult status = michael@0: #endif michael@0: gZlibInit(&mZs); michael@0: NS_ASSERTION(status == NS_OK, "Zlib failed to initialize"); michael@0: NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem"); michael@0: } michael@0: michael@0: mZs.avail_in = item->Size(); michael@0: mZs.next_in = (Bytef*)aZip->GetData(item); michael@0: michael@0: if (doCRC) michael@0: mCRC = crc32(0L, Z_NULL, 0); michael@0: } michael@0: michael@0: nsZipCursor::~nsZipCursor() michael@0: { michael@0: if (mItem->Compression() == DEFLATED) { michael@0: inflateEnd(&mZs); michael@0: } michael@0: } michael@0: michael@0: uint8_t* nsZipCursor::ReadOrCopy(uint32_t *aBytesRead, bool aCopy) { michael@0: int zerr; michael@0: uint8_t *buf = nullptr; michael@0: bool verifyCRC = true; michael@0: michael@0: if (!mZs.next_in) michael@0: return nullptr; michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: switch (mItem->Compression()) { michael@0: case STORED: michael@0: if (!aCopy) { michael@0: *aBytesRead = mZs.avail_in; michael@0: buf = mZs.next_in; michael@0: mZs.next_in += mZs.avail_in; michael@0: mZs.avail_in = 0; michael@0: } else { michael@0: *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in; michael@0: memcpy(mBuf, mZs.next_in, *aBytesRead); michael@0: mZs.avail_in -= *aBytesRead; michael@0: mZs.next_in += *aBytesRead; michael@0: } michael@0: break; michael@0: case DEFLATED: michael@0: buf = mBuf; michael@0: mZs.next_out = buf; michael@0: mZs.avail_out = mBufSize; michael@0: michael@0: zerr = inflate(&mZs, Z_PARTIAL_FLUSH); michael@0: if (zerr != Z_OK && zerr != Z_STREAM_END) michael@0: return nullptr; michael@0: michael@0: *aBytesRead = mZs.next_out - buf; michael@0: verifyCRC = (zerr == Z_STREAM_END); michael@0: break; michael@0: default: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (mDoCRC) { michael@0: mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead); michael@0: if (verifyCRC && mCRC != mItem->CRC32()) michael@0: return nullptr; michael@0: } michael@0: MOZ_WIN_MEM_TRY_CATCH(return nullptr) michael@0: return buf; michael@0: } michael@0: michael@0: nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive *aZip, const char * aEntryName, bool doCRC) : michael@0: mReturnBuf(nullptr) michael@0: { michael@0: // make sure the ziparchive hangs around michael@0: mZipHandle = aZip->GetFD(); michael@0: michael@0: nsZipItem* item = aZip->GetItem(aEntryName); michael@0: if (!item) michael@0: return; michael@0: michael@0: uint32_t size = 0; michael@0: if (item->Compression() == DEFLATED) { michael@0: size = item->RealSize(); michael@0: mAutoBuf = new ((fallible_t())) uint8_t[size]; michael@0: if (!mAutoBuf) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsZipCursor cursor(item, aZip, mAutoBuf, size, doCRC); michael@0: mReturnBuf = cursor.Read(&mReadlen); michael@0: if (!mReturnBuf) { michael@0: return; michael@0: } michael@0: michael@0: if (mReadlen != item->RealSize()) { michael@0: NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow"); michael@0: mReturnBuf = nullptr; michael@0: return; michael@0: } michael@0: }