mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 22:55:23 +00:00
1266 lines
36 KiB
C++
1266 lines
36 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code,
|
|
* released March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape Communications
|
|
* Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Daniel Veditz <dveditz@netscape.com>
|
|
* Samir Gehani <sgehani@netscape.com>
|
|
* Mitch Stoltz <mstoltz@netsape.com>
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
*/
|
|
#include <string.h>
|
|
#include "nsILocalFile.h"
|
|
#include "nsJARInputStream.h"
|
|
#include "nsJAR.h"
|
|
#include "nsXPIDLString.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "plbase64.h"
|
|
#include "nsIConsoleService.h"
|
|
|
|
#ifdef XP_UNIX
|
|
#include <sys/stat.h>
|
|
#elif defined (XP_PC)
|
|
#include <io.h>
|
|
#endif
|
|
|
|
//----------------------------------------------
|
|
// Errors and other utility definitions
|
|
//----------------------------------------------
|
|
#ifndef __gen_nsIFile_h__
|
|
#define NS_ERROR_FILE_UNRECOGNIZED_PATH NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 1)
|
|
#define NS_ERROR_FILE_UNRESOLVABLE_SYMLINK NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 2)
|
|
#define NS_ERROR_FILE_EXECUTION_FAILED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 3)
|
|
#define NS_ERROR_FILE_UNKNOWN_TYPE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 4)
|
|
#define NS_ERROR_FILE_DESTINATION_NOT_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 5)
|
|
#define NS_ERROR_FILE_TARGET_DOES_NOT_EXIST NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 6)
|
|
#define NS_ERROR_FILE_COPY_OR_MOVE_FAILED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 7)
|
|
#define NS_ERROR_FILE_ALREADY_EXISTS NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 8)
|
|
#define NS_ERROR_FILE_INVALID_PATH NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 9)
|
|
#define NS_ERROR_FILE_DISK_FULL NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 10)
|
|
#define NS_ERROR_FILE_CORRUPTED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 11)
|
|
#endif
|
|
|
|
static nsresult
|
|
ziperr2nsresult(PRInt32 ziperr)
|
|
{
|
|
switch (ziperr) {
|
|
case ZIP_OK: return NS_OK;
|
|
case ZIP_ERR_MEMORY: return NS_ERROR_OUT_OF_MEMORY;
|
|
case ZIP_ERR_DISK: return NS_ERROR_FILE_DISK_FULL;
|
|
case ZIP_ERR_CORRUPT: return NS_ERROR_FILE_CORRUPTED;
|
|
case ZIP_ERR_PARAM: return NS_ERROR_ILLEGAL_VALUE;
|
|
case ZIP_ERR_FNF: return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
|
|
case ZIP_ERR_UNSUPPORTED: return NS_ERROR_NOT_IMPLEMENTED;
|
|
default: return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
//-- PR_Free doesn't null the pointer.
|
|
// This macro takes care of that.
|
|
#define JAR_NULLFREE(_ptr) \
|
|
{ \
|
|
PR_FREEIF(_ptr); \
|
|
_ptr = nsnull; \
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// nsJARManifestItem declaration
|
|
//----------------------------------------------
|
|
/*
|
|
* nsJARManifestItem contains meta-information pertaining
|
|
* to an individual JAR entry, taken from the
|
|
* META-INF/MANIFEST.MF and META-INF/ *.SF files.
|
|
* This is security-critical information, defined here so it is not
|
|
* accessible from anywhere else.
|
|
*/
|
|
typedef enum
|
|
{
|
|
JAR_INVALID = 1,
|
|
JAR_INTERNAL = 2,
|
|
JAR_EXTERNAL = 3
|
|
} JARManifestItemType;
|
|
|
|
class nsJARManifestItem
|
|
{
|
|
public:
|
|
JARManifestItemType mType;
|
|
|
|
// True if the second step of verification (VerifyEntry)
|
|
// has taken place:
|
|
PRBool step2Complete;
|
|
|
|
// Not signed, valid, or failure code
|
|
PRInt16 status;
|
|
|
|
// Internal storage of digests
|
|
char* calculatedSectionDigest;
|
|
char* storedEntryDigest;
|
|
|
|
nsJARManifestItem();
|
|
virtual ~nsJARManifestItem();
|
|
};
|
|
|
|
//-------------------------------------------------
|
|
// nsJARManifestItem constructors and destructor
|
|
//-------------------------------------------------
|
|
nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
|
|
step2Complete(PR_FALSE),
|
|
status(nsIZipReader::NOT_SIGNED),
|
|
calculatedSectionDigest(nsnull),
|
|
storedEntryDigest(nsnull)
|
|
{
|
|
}
|
|
|
|
nsJARManifestItem::~nsJARManifestItem()
|
|
{
|
|
// Delete digests if necessary
|
|
PR_FREEIF(calculatedSectionDigest);
|
|
PR_FREEIF(storedEntryDigest);
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// nsJAR constructor/destructor
|
|
//----------------------------------------------
|
|
PR_STATIC_CALLBACK(PRBool)
|
|
DeleteManifestEntry(nsHashKey* aKey, void* aData, void* closure)
|
|
{
|
|
//-- deletes an entry in mManifestData.
|
|
PR_FREEIF(aData);
|
|
return PR_TRUE;
|
|
}
|
|
|
|
// The following initialization makes a guess of 10 entries per jarfile.
|
|
nsJAR::nsJAR(): mManifestData(nsnull, nsnull, DeleteManifestEntry, nsnull, 10),
|
|
mParsedManifest(PR_FALSE), mGlobalStatus(nsIZipReader::NOT_SIGNED)
|
|
{
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
nsJAR::~nsJAR()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsJAR, nsIZipReader);
|
|
|
|
//----------------------------------------------
|
|
// nsJAR public implementation
|
|
//----------------------------------------------
|
|
|
|
NS_METHOD
|
|
nsJAR::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
|
|
{
|
|
if (aOuter)
|
|
return NS_ERROR_NO_AGGREGATION;
|
|
|
|
nsJAR* jar = new nsJAR();
|
|
if (jar == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(jar);
|
|
nsresult rv = jar->QueryInterface(aIID, aResult);
|
|
NS_RELEASE(jar);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::Init(nsIFile* zipFile)
|
|
{
|
|
mZipFile = zipFile;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::GetFile(nsIFile* *result)
|
|
{
|
|
*result = mZipFile;
|
|
NS_ADDREF(*result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::Open()
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(mZipFile, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRFileDesc* fd;
|
|
rv = localFile->OpenNSPRFileDesc(PR_RDONLY, 0664, &fd);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRInt32 err = mZip.OpenArchiveWithFileDesc(fd);
|
|
|
|
return ziperr2nsresult(err);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::Close()
|
|
{
|
|
PRInt32 err = mZip.CloseArchive();
|
|
return ziperr2nsresult(err);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::Extract(const char *zipEntry, nsIFile* outFile)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(outFile, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRFileDesc* fd;
|
|
rv = localFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 0664, &fd);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsZipItem *item = 0;
|
|
PRInt32 err = mZip.ExtractFileToFileDesc(zipEntry, fd, &item);
|
|
PR_Close(fd);
|
|
|
|
if (err != ZIP_OK)
|
|
outFile->Delete(PR_FALSE);
|
|
else
|
|
{
|
|
#if defined(XP_UNIX)
|
|
char *path;
|
|
|
|
rv = outFile->GetPath(&path);
|
|
if (NS_SUCCEEDED(rv))
|
|
chmod(path, item->mode);
|
|
#endif
|
|
|
|
RestoreModTime(item, outFile); // non-fatal if this fails, ignore errors
|
|
}
|
|
|
|
return ziperr2nsresult(err);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::GetEntry(const char *zipEntry, nsIZipEntry* *result)
|
|
{
|
|
nsZipItem* zipItem;
|
|
PRInt32 err = mZip.GetItem(zipEntry, &zipItem);
|
|
if (err != ZIP_OK) return ziperr2nsresult(err);
|
|
|
|
nsJARItem* jarItem = new nsJARItem();
|
|
if (jarItem == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(jarItem);
|
|
jarItem->Init(zipItem);
|
|
*result = jarItem;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::FindEntries(const char *aPattern, nsISimpleEnumerator **result)
|
|
{
|
|
if (!result)
|
|
return NS_ERROR_INVALID_POINTER;
|
|
|
|
nsZipFind *find = mZip.FindInit(aPattern);
|
|
if (!find)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
nsISimpleEnumerator *zipEnum = new nsJAREnumerator(find);
|
|
if (!zipEnum)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF( zipEnum );
|
|
|
|
*result = zipEnum;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::GetInputStream(const char *aFilename, nsIInputStream **result)
|
|
{
|
|
if (!result)
|
|
return NS_OK;
|
|
return CreateInputStream(aFilename, PR_TRUE, result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::GetCertificatePrincipal(const char* aFilename, nsIPrincipal** aPrincipal)
|
|
{
|
|
//-- Parameter check
|
|
if (!aPrincipal)
|
|
return NS_ERROR_NULL_POINTER;
|
|
*aPrincipal = nsnull;
|
|
|
|
PRInt16 requestedStatus;
|
|
if (aFilename)
|
|
{
|
|
//-- Find the item
|
|
nsStringKey key(aFilename);
|
|
nsJARManifestItem* manItem = (nsJARManifestItem*)mManifestData.Get(&key);
|
|
if (!manItem)
|
|
{
|
|
ReportError(aFilename, nsIZipReader::NOT_SIGNED);
|
|
return NS_OK;
|
|
}
|
|
if (!manItem->step2Complete)
|
|
{
|
|
NS_ASSERTION(manItem->step2Complete,
|
|
"nsJAR: Attempt to get principal before verification.");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
requestedStatus = manItem->status;
|
|
}
|
|
else // User wants identity of signer w/o verifying any entries
|
|
{
|
|
if (!mParsedManifest)
|
|
{
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsISignatureVerifier, verifier, SIGNATURE_VERIFIER_PROGID, &rv);
|
|
if (NS_FAILED(rv)) // No signature verifier available
|
|
return NS_ERROR_FAILURE;
|
|
ParseManifest(verifier);
|
|
}
|
|
requestedStatus = mGlobalStatus;
|
|
}
|
|
|
|
if (requestedStatus != nsIZipReader::VALID)
|
|
ReportError(aFilename, requestedStatus);
|
|
else // Valid signature
|
|
{
|
|
*aPrincipal = mPrincipal;
|
|
NS_IF_ADDREF(*aPrincipal);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsJAR::VerifyExternalData(const char* aFilename, const char* aData, PRUint32 aLen,
|
|
nsIPrincipal** result)
|
|
{
|
|
if (NS_FAILED(VerifyEntry(aFilename, aData, aLen)))
|
|
return NS_ERROR_FAILURE;
|
|
return GetCertificatePrincipal(aFilename, result);
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// nsJAR private implementation
|
|
//----------------------------------------------
|
|
nsresult nsJAR::CreateInputStream(const char* aFilename, PRBool verify,
|
|
nsIInputStream** result)
|
|
{
|
|
nsresult rv;
|
|
nsJARInputStream* jis = nsnull;
|
|
rv = nsJARInputStream::Create(nsnull, NS_GET_IID(nsIInputStream), (void**)&jis);
|
|
if (!jis) return NS_ERROR_FAILURE;
|
|
|
|
rv = jis->Init(this, aFilename, verify);
|
|
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
|
|
|
|
*result = (nsIInputStream*)jis;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsJAR::LoadEntry(const char* aFilename, char** aBuf, PRUint32* aBufLen)
|
|
{
|
|
//-- Get a stream for reading the manifest file
|
|
nsresult rv;
|
|
nsCOMPtr<nsIInputStream> manifestStream;
|
|
rv = CreateInputStream(aFilename, PR_FALSE, getter_AddRefs(manifestStream));
|
|
if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
|
|
|
|
//-- Read the manifest file into memory
|
|
char* buf;
|
|
PRUint32 len;
|
|
rv = manifestStream->Available(&len);
|
|
if (NS_FAILED(rv)) return rv;
|
|
buf = (char*)PR_MALLOC(len+1);
|
|
if (!buf) return NS_ERROR_OUT_OF_MEMORY;
|
|
PRUint32 bytesRead;
|
|
rv = manifestStream->Read(buf, len, &bytesRead);
|
|
if (bytesRead != len)
|
|
rv = NS_ERROR_FILE_CORRUPTED;
|
|
if (NS_FAILED(rv)) return rv;
|
|
buf[len] = '\0'; //Null-terminate the buffer
|
|
*aBuf = buf;
|
|
if (aBufLen)
|
|
*aBufLen = len;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
PRInt32
|
|
nsJAR::ReadLine(const char** src)
|
|
{
|
|
//--Moves pointer to beginning of next line and returns line length
|
|
// not including CR/LF.
|
|
PRInt32 length;
|
|
char* eol = PL_strpbrk(*src, "\r\n");
|
|
|
|
if (eol == nsnull) // Probably reached end of file before newline
|
|
{
|
|
length = PL_strlen(*src);
|
|
if (length == 0) // immediate end-of-file
|
|
*src = nsnull;
|
|
else // some data left on this line
|
|
*src += length;
|
|
}
|
|
else
|
|
{
|
|
length = eol - *src;
|
|
if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
|
|
*src = eol+2;
|
|
else // Either CR or LF, so skip 1
|
|
*src = eol+1;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
//-- The following #defines are used by ParseManifest()
|
|
// and ParseOneFile(). The header strings are defined in the JAR specification.
|
|
#define JAR_MF 1
|
|
#define JAR_SF 2
|
|
#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
|
|
#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
|
|
#define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
|
|
#define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
|
|
|
|
nsresult
|
|
nsJAR::ParseManifest(nsISignatureVerifier* verifier)
|
|
{
|
|
//-- Verification Step 1
|
|
if (mParsedManifest)
|
|
return NS_OK;
|
|
mParsedManifest = PR_TRUE;
|
|
|
|
//-- (1)Manifest (MF) file
|
|
nsresult rv;
|
|
nsCOMPtr<nsISimpleEnumerator> files;
|
|
rv = FindEntries(JAR_MF_SEARCH_STRING, getter_AddRefs(files));
|
|
if (!files) rv = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
//-- Load the file into memory
|
|
nsCOMPtr<nsJARItem> file;
|
|
rv = files->GetNext(getter_AddRefs(file));
|
|
if (NS_FAILED(rv) || !file) return rv;
|
|
PRBool more;
|
|
rv = files->HasMoreElements(&more);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (more) return NS_ERROR_FILE_CORRUPTED; // More than one MF file
|
|
nsXPIDLCString manifestFilename;
|
|
rv = file->GetName(getter_Copies(manifestFilename));
|
|
if (!manifestFilename || NS_FAILED(rv)) return rv;
|
|
nsXPIDLCString manifestBuffer;
|
|
rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
//-- Parse it
|
|
rv = ParseOneFile(verifier, manifestBuffer, JAR_MF);
|
|
if (NS_FAILED(rv)) return rv;
|
|
DumpMetadata("PM Pass 1 End");
|
|
|
|
//-- (2)Signature (SF) file
|
|
// If there are multiple signatures, we select one.
|
|
rv = FindEntries(JAR_SF_SEARCH_STRING, getter_AddRefs(files));
|
|
if (!files) rv = NS_ERROR_FAILURE;
|
|
if (NS_FAILED(rv)) return rv;
|
|
//-- Get an SF file
|
|
rv = files->GetNext(getter_AddRefs(file));
|
|
if (NS_FAILED(rv) || !file) return rv;
|
|
rv = file->GetName(getter_Copies(manifestFilename));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 manifestLen;
|
|
rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
//-- Get its corresponding signature file
|
|
nsCAutoString sigFilename( NS_STATIC_CAST(const char*, manifestFilename) );
|
|
PRInt32 extension = sigFilename.RFindChar('.') + 1;
|
|
NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
|
|
(void)sigFilename.Cut(extension, 2);
|
|
nsXPIDLCString sigBuffer;
|
|
PRUint32 sigLen;
|
|
{
|
|
nsCAutoString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
|
|
rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
|
|
}
|
|
if (NS_FAILED(rv))
|
|
{
|
|
nsCAutoString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
|
|
rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
|
|
}
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
//-- Verify that the signature file is a valid signature of the SF file
|
|
PRInt32 verifyError;
|
|
rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
|
|
&verifyError, getter_AddRefs(mPrincipal));
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (mPrincipal)
|
|
mGlobalStatus = nsIZipReader::VALID;
|
|
else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA)
|
|
mGlobalStatus = nsIZipReader::INVALID_UNKNOWN_CA;
|
|
else
|
|
mGlobalStatus = nsIZipReader::INVALID_SIG;
|
|
|
|
//-- Parse the SF file. If the verification above failed, principal
|
|
// is null, and ParseOneFile will mark the relevant entries as invalid.
|
|
// if ParseOneFile fails, then it has no effect, and we can safely
|
|
// continue to the next SF file, or return.
|
|
ParseOneFile(verifier, manifestBuffer, JAR_SF);
|
|
DumpMetadata("PM Pass 2 End");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsJAR::ParseOneFile(nsISignatureVerifier* verifier,
|
|
const char* filebuf, PRInt16 aFileType)
|
|
{
|
|
//-- Check file header
|
|
const char* nextLineStart = filebuf;
|
|
nsCAutoString curLine;
|
|
PRInt32 linelen;
|
|
linelen = ReadLine(&nextLineStart);
|
|
curLine.Assign(filebuf, linelen);
|
|
if ( ((aFileType == JAR_MF) && (curLine != JAR_MF_HEADER) ) ||
|
|
((aFileType == JAR_SF) && (curLine != JAR_SF_HEADER) ) )
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
|
|
//-- Skip header section
|
|
do {
|
|
linelen = ReadLine(&nextLineStart);
|
|
} while (linelen > 0);
|
|
|
|
//-- Set up parsing variables
|
|
const char* curPos;
|
|
const char* sectionStart = nextLineStart;
|
|
|
|
nsJARManifestItem* curItemMF;
|
|
PRBool foundName = PR_FALSE;
|
|
if (aFileType == JAR_MF)
|
|
curItemMF = new nsJARManifestItem();
|
|
nsCAutoString curItemName;
|
|
nsCAutoString storedSectionDigest;
|
|
|
|
for(;;)
|
|
{
|
|
curPos = nextLineStart;
|
|
linelen = ReadLine(&nextLineStart);
|
|
curLine.Assign(curPos, linelen);
|
|
if (linelen == 0)
|
|
// end of section (blank line or end-of-file)
|
|
{
|
|
if (aFileType == JAR_MF)
|
|
{
|
|
if (curItemMF->mType != JAR_INVALID)
|
|
{
|
|
//-- Did this section have a name: line?
|
|
if(!foundName)
|
|
curItemMF->mType = JAR_INVALID;
|
|
else
|
|
{
|
|
if (curItemMF->mType == JAR_INTERNAL)
|
|
{
|
|
//-- If it's an internal item, it must correspond
|
|
// to a valid jar entry
|
|
nsIZipEntry* entry;
|
|
PRInt32 result = GetEntry(curItemName, &entry);
|
|
if (result != ZIP_OK || !entry)
|
|
curItemMF->mType = JAR_INVALID;
|
|
}
|
|
//-- Check for duplicates
|
|
nsStringKey key(curItemName);
|
|
if (mManifestData.Exists(&key))
|
|
curItemMF->mType = JAR_INVALID;
|
|
}
|
|
}
|
|
|
|
if (curItemMF->mType == JAR_INVALID)
|
|
delete curItemMF;
|
|
else //-- calculate section digest
|
|
{
|
|
PRUint32 sectionLength = curPos - sectionStart;
|
|
CalculateDigest(verifier, sectionStart, sectionLength,
|
|
&(curItemMF->calculatedSectionDigest));
|
|
//-- Save item in the hashtable
|
|
nsStringKey itemKey(curItemName);
|
|
mManifestData.Put(&itemKey, (void*)curItemMF);
|
|
}
|
|
if (nextLineStart == nsnull) // end-of-file
|
|
break;
|
|
|
|
sectionStart = nextLineStart;
|
|
curItemMF = new nsJARManifestItem();
|
|
} // (aFileType == JAR_MF)
|
|
else
|
|
//-- file type is SF, compare digest with calculated
|
|
// section digests from MF file.
|
|
{
|
|
if (foundName)
|
|
{
|
|
nsJARManifestItem* curItemSF;
|
|
nsStringKey key(curItemName);
|
|
curItemSF = (nsJARManifestItem*)mManifestData.Get(&key);
|
|
if(curItemSF)
|
|
{
|
|
NS_ASSERTION(curItemSF->status == nsJAR::NOT_SIGNED,
|
|
"SECURITY ERROR: nsJARManifestItem not correctly initialized");
|
|
curItemSF->status = mGlobalStatus;
|
|
if (curItemSF->status == nsIZipReader::VALID)
|
|
{ // Compare digests
|
|
if (storedSectionDigest.Length() == 0)
|
|
curItemSF->status = nsIZipReader::NOT_SIGNED;
|
|
else
|
|
{
|
|
if (storedSectionDigest !=
|
|
(const char*)curItemSF->calculatedSectionDigest)
|
|
curItemSF->status = nsIZipReader::INVALID_MANIFEST;
|
|
JAR_NULLFREE(curItemSF->calculatedSectionDigest)
|
|
storedSectionDigest = "";
|
|
}
|
|
} // (aPrincipal != nsnull)
|
|
} // if(curItemSF)
|
|
} // if(foundName)
|
|
|
|
if(nextLineStart == nsnull) // end-of-file
|
|
break;
|
|
} // aFileType == JAR_SF
|
|
foundName = PR_FALSE;
|
|
continue;
|
|
} // if(linelen == 0)
|
|
|
|
//-- Look for continuations (beginning with a space) on subsequent lines
|
|
// and append them to the current line.
|
|
while(*nextLineStart == ' ')
|
|
{
|
|
curPos = nextLineStart;
|
|
PRInt32 continuationLen = ReadLine(&nextLineStart) - 1;
|
|
nsCAutoString continuation(curPos+1, continuationLen);
|
|
curLine += continuation;
|
|
linelen += continuationLen;
|
|
}
|
|
|
|
//-- Find colon in current line, this separates name from value
|
|
PRInt32 colonPos = curLine.FindChar(':');
|
|
if (colonPos == -1) // No colon on line, ignore line
|
|
continue;
|
|
//-- Break down the line
|
|
nsCAutoString lineName;
|
|
curLine.Left(lineName, colonPos);
|
|
nsCAutoString lineData;
|
|
curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
|
|
|
|
//-- Lines to look for:
|
|
// (1) Digest:
|
|
if (lineName.CompareWithConversion("SHA1-Digest",PR_TRUE) == 0)
|
|
//-- This is a digest line, save the data in the appropriate place
|
|
{
|
|
if(aFileType == JAR_MF)
|
|
{
|
|
curItemMF->storedEntryDigest = (char*)PR_MALLOC(lineData.Length()+1);
|
|
if (!(curItemMF->storedEntryDigest))
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
PL_strcpy(curItemMF->storedEntryDigest, lineData);
|
|
}
|
|
else
|
|
storedSectionDigest = lineData;
|
|
continue;
|
|
}
|
|
|
|
// (2) Name: associates this manifest section with a file in the jar.
|
|
if (!foundName && lineName.CompareWithConversion("Name", PR_TRUE) == 0)
|
|
{
|
|
curItemName = lineData;
|
|
foundName = PR_TRUE;
|
|
continue;
|
|
}
|
|
|
|
// (3) Magic: this may be an inline Javascript.
|
|
// We can't do any other kind of magic.
|
|
if ( aFileType == JAR_MF &&
|
|
lineName.CompareWithConversion("Magic", PR_TRUE) == 0)
|
|
{
|
|
if(lineData.CompareWithConversion("javascript", PR_TRUE) == 0)
|
|
curItemMF->mType = JAR_EXTERNAL;
|
|
else
|
|
curItemMF->mType = JAR_INVALID;
|
|
continue;
|
|
}
|
|
|
|
} // for (;;)
|
|
return NS_OK;
|
|
} //ParseOneFile()
|
|
|
|
nsresult
|
|
nsJAR::VerifyEntry(const char* aEntryName, const char* aEntryData,
|
|
PRUint32 aLen)
|
|
{
|
|
nsresult rv;
|
|
NS_WITH_SERVICE(nsISignatureVerifier, verifier, SIGNATURE_VERIFIER_PROGID, &rv);
|
|
if (NS_FAILED(rv)) return NS_OK; // No verifier available; just continue.
|
|
|
|
//-- Verification Step 2
|
|
// Check that verification is supported and step 1 has been done
|
|
if (!mParsedManifest)
|
|
ParseManifest(verifier);
|
|
NS_ASSERTION(mParsedManifest,
|
|
"Verification step 2 called before step 1 complete");
|
|
if (!mParsedManifest) return NS_ERROR_FAILURE;
|
|
|
|
//-- Get the manifest item
|
|
nsStringKey key(aEntryName);
|
|
nsJARManifestItem* manItem = (nsJARManifestItem*)mManifestData.Get(&key);
|
|
if (!manItem)
|
|
return NS_OK;
|
|
if (manItem->status == nsIZipReader::VALID)
|
|
{
|
|
if(!manItem->storedEntryDigest)
|
|
// No entry digests in manifest file. Entry is unsigned.
|
|
manItem->status = nsIZipReader::NOT_SIGNED;
|
|
else
|
|
{ //-- Calculate and compare digests
|
|
char* calculatedEntryDigest;
|
|
rv = CalculateDigest(verifier, aEntryData, aLen, &calculatedEntryDigest);
|
|
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
|
|
if (PL_strcmp(manItem->storedEntryDigest, calculatedEntryDigest) != 0)
|
|
manItem->status = nsIZipReader::INVALID_ENTRY;
|
|
JAR_NULLFREE(calculatedEntryDigest)
|
|
JAR_NULLFREE(manItem->storedEntryDigest)
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(rv))
|
|
manItem->step2Complete = PR_TRUE;
|
|
DumpMetadata("VerifyEntry end");
|
|
return rv;
|
|
}
|
|
|
|
void nsJAR::ReportError(const char* aFilename, PRInt16 errorCode)
|
|
{
|
|
//-- Generate error message
|
|
nsAutoString message;
|
|
message.AssignWithConversion("Signature Verification Error: the signature on ");
|
|
if (aFilename)
|
|
message.AppendWithConversion(aFilename);
|
|
else
|
|
message.AppendWithConversion("this .jar archive");
|
|
message.AppendWithConversion(" is invalid because ");
|
|
switch(errorCode)
|
|
{
|
|
case nsIZipReader::NOT_SIGNED:
|
|
message.AppendWithConversion("the archive did not contain a valid PKCS7 signature.");
|
|
break;
|
|
case nsIZipReader::INVALID_SIG:
|
|
message.AppendWithConversion("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
|
|
break;
|
|
case nsIZipReader::INVALID_UNKNOWN_CA:
|
|
message.AppendWithConversion("the certificate used to sign this file has an unrecognized issuer.");
|
|
break;
|
|
case nsIZipReader::INVALID_MANIFEST:
|
|
message.AppendWithConversion("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
|
|
break;
|
|
case nsIZipReader::INVALID_ENTRY:
|
|
message.AppendWithConversion("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
|
|
break;
|
|
default:
|
|
message.AppendWithConversion("of an unknown problem.");
|
|
}
|
|
|
|
// Report error in JS console
|
|
nsCOMPtr<nsIConsoleService> console(do_GetService("mozilla.consoleservice.1"));
|
|
if (console)
|
|
{
|
|
PRUnichar* messageUni = message.ToNewUnicode();
|
|
if (!messageUni) return;
|
|
console->LogStringMessage(messageUni);
|
|
nsAllocator::Free(messageUni);
|
|
}
|
|
#ifndef DEBUG
|
|
else // If JS console reporting failed, print to stderr.
|
|
#endif
|
|
{
|
|
char* messageCstr = message.ToNewCString();
|
|
if (!messageCstr) return;
|
|
fprintf(stderr, "%s\n", messageCstr);
|
|
nsAllocator::Free(messageCstr);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsJAR::RestoreModTime(nsZipItem *aItem, nsIFile *aExtractedFile)
|
|
{
|
|
if (!aItem || !aExtractedFile)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
char *timestr;
|
|
PRTime prtime;
|
|
nsresult rv = NS_OK;
|
|
|
|
timestr = aItem->GetModTime();
|
|
if (timestr)
|
|
{
|
|
if (PR_SUCCESS == PR_ParseTimeString(timestr, PR_FALSE, &prtime))
|
|
{
|
|
PRTime conversion = LL_ZERO;
|
|
PRTime newTime = LL_ZERO;
|
|
LL_I2L(conversion, PR_USEC_PER_MSEC);
|
|
LL_DIV(newTime, prtime, conversion);
|
|
// nsIFile needs usecs.
|
|
rv = aExtractedFile->SetLastModificationDate(newTime);
|
|
}
|
|
|
|
JAR_NULLFREE(timestr);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsJAR::CalculateDigest(nsISignatureVerifier* verifier,
|
|
const char* aInBuf, PRUint32 aLen,
|
|
char** digest)
|
|
{
|
|
*digest = nsnull;
|
|
nsresult rv;
|
|
|
|
//-- Calculate the digest
|
|
PRUint32 id;
|
|
rv = verifier->HashBegin(nsISignatureVerifier::SHA1, &id);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = verifier->HashUpdate(id, aInBuf, aLen);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 len;
|
|
unsigned char* rawDigest = (unsigned char*)PR_MALLOC(nsISignatureVerifier::SHA1_LENGTH);
|
|
if (rawDigest == nsnull) return NS_ERROR_OUT_OF_MEMORY;
|
|
rv = verifier->HashEnd(id, &rawDigest, &len, nsISignatureVerifier::SHA1_LENGTH);
|
|
if (NS_FAILED(rv)) { PR_FREEIF(rawDigest); return rv; }
|
|
|
|
//-- Encode the digest in base64
|
|
*digest = PL_Base64Encode((char*)rawDigest, len, *digest);
|
|
if (!(*digest)) { PR_FREEIF(rawDigest); return NS_ERROR_OUT_OF_MEMORY; }
|
|
|
|
PR_FREEIF(rawDigest);
|
|
return NS_OK;
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// Debugging functions
|
|
//----------------------------------------------
|
|
#if 0
|
|
PR_STATIC_CALLBACK(PRBool)
|
|
PrintManItem(nsHashKey* aKey, void* aData, void* closure)
|
|
{
|
|
nsJARManifestItem* manItem = (nsJARManifestItem*)aData;
|
|
if (manItem)
|
|
{
|
|
nsStringKey* key2 = (nsStringKey*)aKey;
|
|
char* name = key2->GetString().ToNewCString();
|
|
if (PL_strcmp(name, "") != 0)
|
|
{
|
|
printf("------------\nName:%s.\n",name);
|
|
if (manItem->mPrincipal)
|
|
{
|
|
char* toStr;
|
|
char* caps;
|
|
manItem->mPrincipal->ToString(&toStr);
|
|
manItem->mPrincipal->CapabilitiesToString(&caps);
|
|
printf("Principal: %s.\n Caps: %s.\n", toStr, caps);
|
|
}
|
|
else
|
|
printf("No Principal.\n");
|
|
printf("step2Complete:%i.\n",manItem->step2Complete);
|
|
printf("valid:%i.\n",manItem->valid);
|
|
/*
|
|
for (PRInt32 x=0; x<JAR_DIGEST_COUNT; x++)
|
|
printf("calculated section digest:%s.\n",
|
|
manItem->calculatedSectionDigests[x]);
|
|
for (PRInt32 y=0; y<JAR_DIGEST_COUNT; y++)
|
|
printf("stored entry digest:%s.\n",
|
|
manItem->storedEntryDigests[y]);
|
|
*/
|
|
}
|
|
}
|
|
return PR_TRUE;
|
|
}
|
|
#endif
|
|
|
|
void nsJAR::DumpMetadata(const char* aMessage)
|
|
{
|
|
#if 0
|
|
printf("### nsJAR::DumpMetadata at %s ###\n", aMessage);
|
|
mManifestData.Enumerate(PrintManItem);
|
|
printf("######## nsJAR::DumpMetadata End ############\n");
|
|
#endif
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// nsJAREnumerator constructor and destructor
|
|
//----------------------------------------------
|
|
nsJAREnumerator::nsJAREnumerator(nsZipFind *aFind)
|
|
: mFind(aFind),
|
|
mCurr(nsnull),
|
|
mIsCurrStale(PR_TRUE)
|
|
{
|
|
mArchive = mFind->GetArchive();
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
nsJAREnumerator::~nsJAREnumerator()
|
|
{
|
|
mArchive->FindFree(mFind);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(nsJAREnumerator, NS_GET_IID(nsISimpleEnumerator));
|
|
|
|
//----------------------------------------------
|
|
// nsJAREnumerator::HasMoreElements
|
|
//----------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJAREnumerator::HasMoreElements(PRBool* aResult)
|
|
{
|
|
PRInt32 err;
|
|
|
|
if (!mFind)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// try to get the next element
|
|
if (mIsCurrStale)
|
|
{
|
|
err = mArchive->FindNext( mFind, &mCurr );
|
|
if (err == ZIP_ERR_FNF)
|
|
{
|
|
*aResult = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
if (err != ZIP_OK)
|
|
return NS_ERROR_FAILURE; // no error translation
|
|
|
|
mIsCurrStale = PR_FALSE;
|
|
}
|
|
|
|
*aResult = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// nsJAREnumerator::GetNext
|
|
//----------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJAREnumerator::GetNext(nsISupports** aResult)
|
|
{
|
|
nsresult rv;
|
|
PRBool bMore;
|
|
|
|
// check if the current item is "stale"
|
|
if (mIsCurrStale)
|
|
{
|
|
rv = HasMoreElements( &bMore );
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
if (bMore == PR_FALSE)
|
|
{
|
|
*aResult = nsnull; // null return value indicates no more elements
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// pack into an nsIJARItem
|
|
nsJARItem* jarItem = new nsJARItem();
|
|
if(jarItem)
|
|
{
|
|
NS_ADDREF(jarItem);
|
|
jarItem->Init(mCurr);
|
|
*aResult = jarItem;
|
|
mIsCurrStale = PR_TRUE; // we just gave this one away
|
|
return NS_OK;
|
|
}
|
|
else
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// nsJARItem constructors and destructor
|
|
//-------------------------------------------------
|
|
nsJARItem::nsJARItem()
|
|
{
|
|
NS_INIT_ISUPPORTS();
|
|
}
|
|
|
|
nsJARItem::~nsJARItem()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(nsJARItem, nsIZipEntry);
|
|
|
|
void nsJARItem::Init(nsZipItem* aZipItem)
|
|
{
|
|
mZipItem = aZipItem;
|
|
}
|
|
|
|
//------------------------------------------
|
|
// nsJARItem::GetName
|
|
//------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJARItem::GetName(char * *aName)
|
|
{
|
|
char *namedup;
|
|
|
|
if ( !aName )
|
|
return NS_ERROR_NULL_POINTER;
|
|
if ( !mZipItem->name )
|
|
return NS_ERROR_FAILURE;
|
|
|
|
namedup = PL_strndup( mZipItem->name, mZipItem->namelen );
|
|
if ( !namedup )
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
*aName = namedup;
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------
|
|
// nsJARItem::GetCompression
|
|
//------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJARItem::GetCompression(PRUint16 *aCompression)
|
|
{
|
|
if (!aCompression)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (!mZipItem->compression)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aCompression = mZipItem->compression;
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------
|
|
// nsJARItem::GetSize
|
|
//------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJARItem::GetSize(PRUint32 *aSize)
|
|
{
|
|
if (!aSize)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (!mZipItem->size)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aSize = mZipItem->size;
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------
|
|
// nsJARItem::GetRealSize
|
|
//------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJARItem::GetRealSize(PRUint32 *aRealsize)
|
|
{
|
|
if (!aRealsize)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (!mZipItem->realsize)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aRealsize = mZipItem->realsize;
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------
|
|
// nsJARItem::GetCrc32
|
|
//------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsJARItem::GetCRC32(PRUint32 *aCrc32)
|
|
{
|
|
if (!aCrc32)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (!mZipItem->crc32)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aCrc32 = mZipItem->crc32;
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// nsIZipReaderCache
|
|
|
|
class nsZipCacheEntry
|
|
{
|
|
public:
|
|
nsZipCacheEntry(nsIZipReader* zip)
|
|
: mZip(zip), mUseCount(0), mNextOlder(nsnull) {}
|
|
~nsZipCacheEntry() {}
|
|
|
|
static void* PR_CALLBACK
|
|
Clone(nsHashKey *aKey, void *aData, void* closure) {
|
|
NS_NOTREACHED("nsZipCacheEntry::Clone"); // should never be called
|
|
return nsnull;
|
|
}
|
|
|
|
static PRBool PR_CALLBACK
|
|
Delete(nsHashKey *aKey, void *aData, void* closure) {
|
|
nsZipCacheEntry* entry = (nsZipCacheEntry*)aData;
|
|
delete entry;
|
|
return PR_TRUE;
|
|
}
|
|
|
|
nsCOMPtr<nsIZipReader> mZip;
|
|
nsrefcnt mUseCount;
|
|
nsZipCacheEntry* mNextOlder;
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS1(nsZipReaderCache, nsIZipReaderCache)
|
|
|
|
nsZipReaderCache::nsZipReaderCache()
|
|
: mLock(nsnull),
|
|
mZips(nsZipCacheEntry::Clone, nsnull, nsZipCacheEntry::Delete, nsnull),
|
|
mFreeCount(0),
|
|
mFreeList(nsnull)
|
|
{
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsZipReaderCache::Init(PRUint32 cacheSize)
|
|
{
|
|
#ifdef DEBUG_warren
|
|
mCacheSize = 1;//cacheSize; // XXX hack
|
|
#else
|
|
mCacheSize = cacheSize;
|
|
#endif
|
|
mLock = PR_NewLock();
|
|
return mLock ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
nsZipReaderCache::~nsZipReaderCache()
|
|
{
|
|
if (mLock)
|
|
PR_DestroyLock(mLock);
|
|
}
|
|
|
|
NS_METHOD
|
|
nsZipReaderCache::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
|
|
{
|
|
if (aOuter)
|
|
return NS_ERROR_NO_AGGREGATION;
|
|
|
|
nsZipReaderCache* cache = new nsZipReaderCache();
|
|
if (cache == nsnull)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(cache);
|
|
nsresult rv = cache->QueryInterface(aIID, aResult);
|
|
NS_RELEASE(cache);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
|
|
{
|
|
nsresult rv;
|
|
nsAutoLock lock(mLock);
|
|
|
|
nsXPIDLCString path;
|
|
rv = zipFile->GetPath(getter_Copies(path));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsStringKey key(path);
|
|
nsZipCacheEntry* entry = (nsZipCacheEntry*)mZips.Get(&key);
|
|
if (entry) {
|
|
*result = entry->mZip;
|
|
NS_ADDREF(*result);
|
|
if (entry->mUseCount++ == 0) {
|
|
// remove from free list
|
|
nsZipCacheEntry** entryPtr = &mFreeList;
|
|
NS_ASSERTION(*entryPtr, "null free list");
|
|
while ((*entryPtr)->mNextOlder != nsnull) {
|
|
if ((*entryPtr)->mNextOlder == entry) {
|
|
(*entryPtr)->mNextOlder = entry->mNextOlder;
|
|
entry->mNextOlder = nsnull;
|
|
--mFreeCount;
|
|
return NS_OK;
|
|
}
|
|
entryPtr = &(*entryPtr)->mNextOlder;
|
|
}
|
|
NS_NOTREACHED("couldn't find entry in free list");
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// not found -- create a new one and cache it
|
|
nsCOMPtr<nsIZipReader> zip;
|
|
rv = nsJAR::Create(nsnull, NS_GET_IID(nsIZipReader), getter_AddRefs(zip));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = zip->Init(zipFile);
|
|
if (NS_FAILED(rv)) return rv;
|
|
rv = zip->Open();
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
entry = new nsZipCacheEntry(zip);
|
|
if (entry == nsnull) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
entry->mUseCount++;
|
|
(void)mZips.Put(&key, entry);
|
|
*result = zip;
|
|
NS_ADDREF(*result);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsZipReaderCache::ReleaseZip(nsIZipReader* zip)
|
|
{
|
|
nsresult rv;
|
|
nsAutoLock lock(mLock);
|
|
|
|
nsCOMPtr<nsIFile> zipFile;
|
|
rv = zip->GetFile(getter_AddRefs(zipFile));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsXPIDLCString path;
|
|
rv = zipFile->GetPath(getter_Copies(path));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsStringKey key(path);
|
|
nsZipCacheEntry* entry = (nsZipCacheEntry*)mZips.Get(&key);
|
|
if (entry == nsnull)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
if (--entry->mUseCount == 0) {
|
|
// The first step in releasing a zip is to throw it on the LRU free list.
|
|
// That way it can be quickly re-opened if necessary. But if the free
|
|
// list grows too long, some need to be thrown out.
|
|
|
|
entry->mNextOlder = mFreeList;
|
|
mFreeList = entry;
|
|
|
|
if (++mFreeCount > mCacheSize) {
|
|
// throw out the oldest one:
|
|
nsZipCacheEntry** oldestPtr = &mFreeList;
|
|
NS_ASSERTION(*oldestPtr, "null free list");
|
|
while ((*oldestPtr)->mNextOlder != nsnull) {
|
|
oldestPtr = &(*oldestPtr)->mNextOlder;
|
|
}
|
|
nsZipCacheEntry* oldest = *oldestPtr;
|
|
*oldestPtr = nsnull;
|
|
|
|
nsZipCacheEntry* elt = (nsZipCacheEntry*)mZips.Remove(&key);
|
|
NS_ASSERTION(elt == entry, "Remove failed");
|
|
--mFreeCount;
|
|
}
|
|
}
|
|
|
|
NS_RELEASE(zip);
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|