gecko-dev/modules/libjar/zipwriter/nsZipWriter.cpp

1133 строки
32 KiB
C++

/* 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/.
*/
#include "nsZipWriter.h"
#include <algorithm>
#include "StreamFunctions.h"
#include "nsZipDataStream.h"
#include "nsISeekableStream.h"
#include "nsIAsyncStreamCopier.h"
#include "nsIStreamListener.h"
#include "nsIInputStreamPump.h"
#include "nsILoadInfo.h"
#include "nsComponentManagerUtils.h"
#include "nsMemory.h"
#include "nsError.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "nsIChannel.h"
#include "nsIFile.h"
#include "prio.h"
#define ZIP_EOCDR_HEADER_SIZE 22
#define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
using namespace mozilla;
/**
* nsZipWriter is used to create and add to zip files.
* It is based on the spec available at
* http://www.pkware.com/documents/casestudies/APPNOTE.TXT.
*
* The basic structure of a zip file created is slightly simpler than that
* illustrated in the spec because certain features of the zip format are
* unsupported:
*
* [local file header 1]
* [file data 1]
* .
* .
* .
* [local file header n]
* [file data n]
* [central directory]
* [end of central directory record]
*/
NS_IMPL_ISUPPORTS(nsZipWriter, nsIZipWriter,
nsIRequestObserver)
nsZipWriter::nsZipWriter()
: mCDSOffset(0)
, mCDSDirty(false)
, mInQueue(false)
{}
nsZipWriter::~nsZipWriter()
{
if (mStream && !mInQueue)
Close();
}
NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
aComment = mComment;
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
mComment = aComment;
mCDSDirty = true;
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
{
*aInQueue = mInQueue;
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
{
if (!mFile)
return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIFile> file;
nsresult rv = mFile->Clone(getter_AddRefs(file));
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*aFile = file);
return NS_OK;
}
/*
* Reads file entries out of an existing zip file.
*/
nsresult nsZipWriter::ReadFile(nsIFile *aFile)
{
int64_t size;
nsresult rv = aFile->GetFileSize(&size);
NS_ENSURE_SUCCESS(rv, rv);
// If the file is too short, it cannot be a valid archive, thus we fail
// without even attempting to open it
NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
NS_ENSURE_SUCCESS(rv, rv);
uint8_t buf[1024];
int64_t seek = size - 1024;
uint32_t length = 1024;
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
while (true) {
if (seek < 0) {
length += (int32_t)seek;
seek = 0;
}
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
rv = ZW_ReadData(inputStream, (char *)buf, length);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
/*
* We have to backtrack from the end of the file until we find the
* CDS signature
*/
// We know it's at least this far from the end
for (uint32_t pos = length - ZIP_EOCDR_HEADER_SIZE;
(int32_t)pos >= 0; pos--) {
uint32_t sig = PEEK32(buf + pos);
if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
// Skip down to entry count
pos += 10;
uint32_t entries = READ16(buf, &pos);
// Skip past CDS size
pos += 4;
mCDSOffset = READ32(buf, &pos);
uint32_t commentlen = READ16(buf, &pos);
if (commentlen == 0)
mComment.Truncate();
else if (pos + commentlen <= length)
mComment.Assign((const char *)buf + pos, commentlen);
else {
if ((seek + pos + commentlen) > size) {
inputStream->Close();
return NS_ERROR_FILE_CORRUPTED;
}
auto field = MakeUnique<char[]>(commentlen);
NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
seek + pos);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
rv = ZW_ReadData(inputStream, field.get(), length);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
mComment.Assign(field.get(), commentlen);
}
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
mCDSOffset);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
for (uint32_t entry = 0; entry < entries; entry++) {
nsZipHeader* header = new nsZipHeader();
if (!header) {
inputStream->Close();
mEntryHash.Clear();
mHeaders.Clear();
return NS_ERROR_OUT_OF_MEMORY;
}
rv = header->ReadCDSHeader(inputStream);
if (NS_FAILED(rv)) {
inputStream->Close();
mEntryHash.Clear();
mHeaders.Clear();
return rv;
}
mEntryHash.Put(header->mName, mHeaders.Count());
if (!mHeaders.AppendObject(header))
return NS_ERROR_OUT_OF_MEMORY;
}
return inputStream->Close();
}
}
if (seek == 0) {
// We've reached the start with no signature found. Corrupt.
inputStream->Close();
return NS_ERROR_FILE_CORRUPTED;
}
// Overlap by the size of the end of cdr
seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
}
// Will never reach here in reality
NS_NOTREACHED("Loop should never complete");
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, int32_t aIoFlags)
{
if (mStream)
return NS_ERROR_ALREADY_INITIALIZED;
NS_ENSURE_ARG_POINTER(aFile);
// Need to be able to write to the file
if (aIoFlags & PR_RDONLY)
return NS_ERROR_FAILURE;
nsresult rv = aFile->Clone(getter_AddRefs(mFile));
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = mFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists && !(aIoFlags & PR_CREATE_FILE))
return NS_ERROR_FILE_NOT_FOUND;
if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
rv = ReadFile(mFile);
NS_ENSURE_SUCCESS(rv, rv);
mCDSDirty = false;
}
else {
mCDSOffset = 0;
mCDSDirty = true;
mComment.Truncate();
}
// Silently drop PR_APPEND
aIoFlags &= 0xef;
nsCOMPtr<nsIOutputStream> stream;
rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
if (NS_FAILED(rv)) {
mHeaders.Clear();
mEntryHash.Clear();
return rv;
}
rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream.forget(),
64 * 1024);
if (NS_FAILED(rv)) {
mHeaders.Clear();
mEntryHash.Clear();
return rv;
}
if (mCDSOffset > 0) {
rv = SeekCDS();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
nsIZipEntry **_retval)
{
int32_t pos;
if (mEntryHash.Get(aZipEntry, &pos))
NS_ADDREF(*_retval = mHeaders[pos]);
else
*_retval = nullptr;
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
bool *_retval)
{
*_retval = mEntryHash.Get(aZipEntry, nullptr);
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
PRTime aModTime, bool aQueue)
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (aQueue) {
nsZipQueueItem item;
item.mOperation = OPERATION_ADD;
item.mZipEntry = aZipEntry;
item.mModTime = aModTime;
item.mPermissions = PERMISSIONS_DIR;
if (!mQueue.AppendElement(item))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
}
NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
int32_t aCompression, nsIFile *aFile,
bool aQueue)
{
NS_ENSURE_ARG_POINTER(aFile);
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
nsresult rv;
if (aQueue) {
nsZipQueueItem item;
item.mOperation = OPERATION_ADD;
item.mZipEntry = aZipEntry;
item.mCompression = aCompression;
rv = aFile->Clone(getter_AddRefs(item.mFile));
NS_ENSURE_SUCCESS(rv, rv);
if (!mQueue.AppendElement(item))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
bool exists;
rv = aFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists)
return NS_ERROR_FILE_NOT_FOUND;
bool isdir;
rv = aFile->IsDirectory(&isdir);
NS_ENSURE_SUCCESS(rv, rv);
PRTime modtime;
rv = aFile->GetLastModifiedTime(&modtime);
NS_ENSURE_SUCCESS(rv, rv);
modtime *= PR_USEC_PER_MSEC;
uint32_t permissions;
rv = aFile->GetPermissions(&permissions);
NS_ENSURE_SUCCESS(rv, rv);
if (isdir)
return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
if (mEntryHash.Get(aZipEntry, nullptr))
return NS_ERROR_FILE_ALREADY_EXISTS;
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
aFile);
NS_ENSURE_SUCCESS(rv, rv);
rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
false, permissions);
NS_ENSURE_SUCCESS(rv, rv);
return inputStream->Close();
}
NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
PRTime aModTime,
int32_t aCompression,
nsIChannel *aChannel,
bool aQueue)
{
NS_ENSURE_ARG_POINTER(aChannel);
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (aQueue) {
nsZipQueueItem item;
item.mOperation = OPERATION_ADD;
item.mZipEntry = aZipEntry;
item.mModTime = aModTime;
item.mCompression = aCompression;
item.mPermissions = PERMISSIONS_FILE;
item.mChannel = aChannel;
if (!mQueue.AppendElement(item))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
if (mEntryHash.Get(aZipEntry, nullptr))
return NS_ERROR_FILE_ALREADY_EXISTS;
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel,
getter_AddRefs(inputStream));
NS_ENSURE_SUCCESS(rv, rv);
rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
false, PERMISSIONS_FILE);
NS_ENSURE_SUCCESS(rv, rv);
return inputStream->Close();
}
NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
PRTime aModTime,
int32_t aCompression,
nsIInputStream *aStream,
bool aQueue)
{
return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
PERMISSIONS_FILE);
}
nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
PRTime aModTime,
int32_t aCompression,
nsIInputStream *aStream,
bool aQueue,
uint32_t aPermissions)
{
NS_ENSURE_ARG_POINTER(aStream);
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (aQueue) {
nsZipQueueItem item;
item.mOperation = OPERATION_ADD;
item.mZipEntry = aZipEntry;
item.mModTime = aModTime;
item.mCompression = aCompression;
item.mPermissions = aPermissions;
item.mStream = aStream;
if (!mQueue.AppendElement(item))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
if (mEntryHash.Get(aZipEntry, nullptr))
return NS_ERROR_FILE_ALREADY_EXISTS;
RefPtr<nsZipHeader> header = new nsZipHeader();
NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
mCDSOffset);
nsresult rv = header->WriteFileHeader(mStream);
if (NS_FAILED(rv)) {
SeekCDS();
return rv;
}
RefPtr<nsZipDataStream> stream = new nsZipDataStream();
if (!stream) {
SeekCDS();
return NS_ERROR_OUT_OF_MEMORY;
}
rv = stream->Init(this, mStream, header, aCompression);
if (NS_FAILED(rv)) {
SeekCDS();
return rv;
}
rv = stream->ReadStream(aStream);
if (NS_FAILED(rv))
SeekCDS();
return rv;
}
NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
bool aQueue)
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (aQueue) {
nsZipQueueItem item;
item.mOperation = OPERATION_REMOVE;
item.mZipEntry = aZipEntry;
if (!mQueue.AppendElement(item))
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
int32_t pos;
if (mEntryHash.Get(aZipEntry, &pos)) {
// Flush any remaining data before we seek.
nsresult rv = mStream->Flush();
NS_ENSURE_SUCCESS(rv, rv);
if (pos < mHeaders.Count() - 1) {
// This is not the last entry, pull back the data.
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
mHeaders[pos]->mOffset);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
mFile);
NS_ENSURE_SUCCESS(rv, rv);
seekable = do_QueryInterface(inputStream);
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
mHeaders[pos + 1]->mOffset);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
uint32_t count = mCDSOffset - mHeaders[pos + 1]->mOffset;
uint32_t read = 0;
char buf[4096];
while (count > 0) {
read = std::min(count, (uint32_t) sizeof(buf));
rv = inputStream->Read(buf, read, &read);
if (NS_FAILED(rv)) {
inputStream->Close();
Cleanup();
return rv;
}
rv = ZW_WriteData(mStream, buf, read);
if (NS_FAILED(rv)) {
inputStream->Close();
Cleanup();
return rv;
}
count -= read;
}
inputStream->Close();
// Rewrite header offsets and update hash
uint32_t shift = (mHeaders[pos + 1]->mOffset -
mHeaders[pos]->mOffset);
mCDSOffset -= shift;
int32_t pos2 = pos + 1;
while (pos2 < mHeaders.Count()) {
mEntryHash.Put(mHeaders[pos2]->mName, pos2-1);
mHeaders[pos2]->mOffset -= shift;
pos2++;
}
}
else {
// Remove the last entry is just a case of moving the CDS
mCDSOffset = mHeaders[pos]->mOffset;
rv = SeekCDS();
NS_ENSURE_SUCCESS(rv, rv);
}
mEntryHash.Remove(mHeaders[pos]->mName);
mHeaders.RemoveObjectAt(pos);
mCDSDirty = true;
return NS_OK;
}
return NS_ERROR_FILE_NOT_FOUND;
}
NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
nsISupports *aContext)
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
mProcessObserver = aObserver;
mProcessContext = aContext;
mInQueue = true;
if (mProcessObserver)
mProcessObserver->OnStartRequest(nullptr, mProcessContext);
BeginProcessingNextItem();
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::Close()
{
if (!mStream)
return NS_ERROR_NOT_INITIALIZED;
if (mInQueue)
return NS_ERROR_IN_PROGRESS;
if (mCDSDirty) {
uint32_t size = 0;
for (int32_t i = 0; i < mHeaders.Count(); i++) {
nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
size += mHeaders[i]->GetCDSHeaderLength();
}
uint8_t buf[ZIP_EOCDR_HEADER_SIZE];
uint32_t pos = 0;
WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
WRITE16(buf, &pos, 0);
WRITE16(buf, &pos, 0);
WRITE16(buf, &pos, mHeaders.Count());
WRITE16(buf, &pos, mHeaders.Count());
WRITE32(buf, &pos, size);
WRITE32(buf, &pos, mCDSOffset);
WRITE16(buf, &pos, mComment.Length());
nsresult rv = ZW_WriteData(mStream, (const char *)buf, pos);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
rv = seekable->SetEOF();
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
// Go back and rewrite the file headers
for (int32_t i = 0; i < mHeaders.Count(); i++) {
nsZipHeader *header = mHeaders[i];
if (!header->mWriteOnClose)
continue;
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, header->mOffset);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
rv = header->WriteFileHeader(mStream);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
}
}
nsresult rv = mStream->Close();
mStream = nullptr;
mHeaders.Clear();
mEntryHash.Clear();
mQueue.Clear();
return rv;
}
// Our nsIRequestObserver monitors removal operations performed on the queue
NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
nsISupports *aContext)
{
return NS_OK;
}
NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatusCode)
{
if (NS_FAILED(aStatusCode)) {
FinishQueue(aStatusCode);
Cleanup();
}
nsresult rv = mStream->Flush();
if (NS_FAILED(rv)) {
FinishQueue(rv);
Cleanup();
return rv;
}
rv = SeekCDS();
if (NS_FAILED(rv)) {
FinishQueue(rv);
return rv;
}
BeginProcessingNextItem();
return NS_OK;
}
/*
* Make all stored(uncompressed) files align to given alignment size.
*/
NS_IMETHODIMP nsZipWriter::AlignStoredFiles(uint16_t aAlignSize)
{
nsresult rv;
// Check for range and power of 2.
if (aAlignSize < 2 || aAlignSize > 32768 ||
(aAlignSize & (aAlignSize - 1)) != 0) {
return NS_ERROR_INVALID_ARG;
}
for (int i = 0; i < mHeaders.Count(); i++) {
nsZipHeader *header = mHeaders[i];
// Check whether this entry is file and compression method is stored.
bool isdir;
rv = header->GetIsDirectory(&isdir);
if (NS_FAILED(rv)) {
return rv;
}
if (isdir || header->mMethod != 0) {
continue;
}
// Pad extra field to align data starting position to specified size.
uint32_t old_len = header->mLocalFieldLength;
rv = header->PadExtraField(header->mOffset, aAlignSize);
if (NS_FAILED(rv)) {
continue;
}
// No padding means data already aligned.
uint32_t shift = header->mLocalFieldLength - old_len;
if (shift == 0) {
continue;
}
// Flush any remaining data before we start.
rv = mStream->Flush();
if (NS_FAILED(rv)) {
return rv;
}
// Open zip file for reading.
nsCOMPtr<nsIInputStream> inputStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mFile);
if (NS_FAILED(rv)) {
return rv;
}
nsCOMPtr<nsISeekableStream> in_seekable = do_QueryInterface(inputStream);
nsCOMPtr<nsISeekableStream> out_seekable = do_QueryInterface(mStream);
uint32_t data_offset = header->mOffset + header->GetFileHeaderLength() - shift;
uint32_t count = mCDSOffset - data_offset;
uint32_t read;
char buf[4096];
// Shift data to aligned postion.
while (count > 0) {
read = std::min(count, (uint32_t) sizeof(buf));
rv = in_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
data_offset + count - read);
if (NS_FAILED(rv)) {
break;
}
rv = inputStream->Read(buf, read, &read);
if (NS_FAILED(rv)) {
break;
}
rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
data_offset + count - read + shift);
if (NS_FAILED(rv)) {
break;
}
rv = ZW_WriteData(mStream, buf, read);
if (NS_FAILED(rv)) {
break;
}
count -= read;
}
inputStream->Close();
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
// Update current header
rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
header->mOffset);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
rv = header->WriteFileHeader(mStream);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
// Update offset of all other headers
int pos = i + 1;
while (pos < mHeaders.Count()) {
mHeaders[pos]->mOffset += shift;
pos++;
}
mCDSOffset += shift;
rv = SeekCDS();
if (NS_FAILED(rv)) {
return rv;
}
mCDSDirty = true;
}
return NS_OK;
}
nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
PRTime aModTime,
uint32_t aPermissions)
{
RefPtr<nsZipHeader> header = new nsZipHeader();
NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
uint32_t zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
if (aZipEntry.Last() != '/') {
nsCString dirPath;
dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
}
else
header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
if (mEntryHash.Get(header->mName, nullptr))
return NS_ERROR_FILE_ALREADY_EXISTS;
nsresult rv = header->WriteFileHeader(mStream);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
mCDSDirty = true;
mCDSOffset += header->GetFileHeaderLength();
mEntryHash.Put(header->mName, mHeaders.Count());
if (!mHeaders.AppendObject(header)) {
Cleanup();
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
/*
* Recovering from an error while adding a new entry is simply a case of
* seeking back to the CDS. If we fail trying to do that though then cleanup
* and bail out.
*/
nsresult nsZipWriter::SeekCDS()
{
nsresult rv;
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
if (NS_FAILED(rv))
Cleanup();
return rv;
}
/*
* In a bad error condition this essentially closes down the component as best
* it can.
*/
void nsZipWriter::Cleanup()
{
mHeaders.Clear();
mEntryHash.Clear();
if (mStream)
mStream->Close();
mStream = nullptr;
mFile = nullptr;
}
/*
* Called when writing a file to the zip is complete.
*/
nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
nsresult aStatus)
{
if (NS_SUCCEEDED(aStatus)) {
mEntryHash.Put(aHeader->mName, mHeaders.Count());
if (!mHeaders.AppendObject(aHeader)) {
mEntryHash.Remove(aHeader->mName);
SeekCDS();
return NS_ERROR_OUT_OF_MEMORY;
}
mCDSDirty = true;
mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
if (mInQueue)
BeginProcessingNextItem();
return NS_OK;
}
nsresult rv = SeekCDS();
if (mInQueue)
FinishQueue(aStatus);
return rv;
}
inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
bool* complete)
{
if (aItem->mFile) {
bool exists;
nsresult rv = aItem->mFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) return NS_ERROR_FILE_NOT_FOUND;
bool isdir;
rv = aItem->mFile->IsDirectory(&isdir);
NS_ENSURE_SUCCESS(rv, rv);
rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
NS_ENSURE_SUCCESS(rv, rv);
aItem->mModTime *= PR_USEC_PER_MSEC;
rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
NS_ENSURE_SUCCESS(rv, rv);
if (!isdir) {
// Set up for fall through to stream reader
rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
aItem->mFile);
NS_ENSURE_SUCCESS(rv, rv);
}
// If a dir then this will fall through to the plain dir addition
}
uint32_t zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
if (aItem->mStream || aItem->mChannel) {
RefPtr<nsZipHeader> header = new nsZipHeader();
NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
mCDSOffset);
nsresult rv = header->WriteFileHeader(mStream);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsZipDataStream> stream = new nsZipDataStream();
NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
rv = stream->Init(this, mStream, header, aItem->mCompression);
NS_ENSURE_SUCCESS(rv, rv);
if (aItem->mStream) {
nsCOMPtr<nsIInputStreamPump> pump;
rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream,
0, 0, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = pump->AsyncRead(stream, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
rv = NS_MaybeOpenChannelUsingAsyncOpen2(aItem->mChannel, stream);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// Must be plain directory addition
*complete = true;
return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
aItem->mPermissions);
}
inline nsresult nsZipWriter::BeginProcessingRemoval(int32_t aPos)
{
// Open the zip file for reading
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
mFile);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStreamPump> pump;
rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, 0, 0, true);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
nsCOMPtr<nsIStreamListener> listener;
rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
mHeaders[aPos]->mOffset);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
uint32_t shift = (mHeaders[aPos + 1]->mOffset -
mHeaders[aPos]->mOffset);
mCDSOffset -= shift;
int32_t pos2 = aPos + 1;
while (pos2 < mHeaders.Count()) {
mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
mHeaders[pos2]->mOffset -= shift;
pos2++;
}
mEntryHash.Remove(mHeaders[aPos]->mName);
mHeaders.RemoveObjectAt(aPos);
mCDSDirty = true;
rv = pump->AsyncRead(listener, nullptr);
if (NS_FAILED(rv)) {
inputStream->Close();
Cleanup();
return rv;
}
return NS_OK;
}
/*
* Starts processing on the next item in the queue.
*/
void nsZipWriter::BeginProcessingNextItem()
{
while (!mQueue.IsEmpty()) {
nsZipQueueItem next = mQueue[0];
mQueue.RemoveElementAt(0);
if (next.mOperation == OPERATION_REMOVE) {
int32_t pos = -1;
if (mEntryHash.Get(next.mZipEntry, &pos)) {
if (pos < mHeaders.Count() - 1) {
nsresult rv = BeginProcessingRemoval(pos);
if (NS_FAILED(rv)) FinishQueue(rv);
return;
}
mCDSOffset = mHeaders[pos]->mOffset;
nsresult rv = SeekCDS();
if (NS_FAILED(rv)) {
FinishQueue(rv);
return;
}
mEntryHash.Remove(mHeaders[pos]->mName);
mHeaders.RemoveObjectAt(pos);
}
else {
FinishQueue(NS_ERROR_FILE_NOT_FOUND);
return;
}
}
else if (next.mOperation == OPERATION_ADD) {
if (mEntryHash.Get(next.mZipEntry, nullptr)) {
FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
return;
}
bool complete = false;
nsresult rv = BeginProcessingAddition(&next, &complete);
if (NS_FAILED(rv)) {
SeekCDS();
FinishQueue(rv);
return;
}
if (!complete)
return;
}
}
FinishQueue(NS_OK);
}
/*
* Ends processing with the given status.
*/
void nsZipWriter::FinishQueue(nsresult aStatus)
{
nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
nsCOMPtr<nsISupports> context = mProcessContext;
// Clean up everything first in case the observer decides to queue more
// things
mProcessObserver = nullptr;
mProcessContext = nullptr;
mInQueue = false;
if (observer)
observer->OnStopRequest(nullptr, context, aStatus);
}