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