diff options
Diffstat (limited to 'modules/libjar/zipwriter')
47 files changed, 4047 insertions, 0 deletions
diff --git a/modules/libjar/zipwriter/StreamFunctions.cpp b/modules/libjar/zipwriter/StreamFunctions.cpp new file mode 100644 index 0000000000..a16db28844 --- /dev/null +++ b/modules/libjar/zipwriter/StreamFunctions.cpp @@ -0,0 +1,44 @@ +/* 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 "nscore.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" + +/* + * Fully reads the required amount of data. Keeps reading until all the + * data is retrieved or an error is hit. + */ +nsresult ZW_ReadData(nsIInputStream* aStream, char* aBuffer, uint32_t aCount) { + while (aCount > 0) { + uint32_t read; + nsresult rv = aStream->Read(aBuffer, aCount, &read); + NS_ENSURE_SUCCESS(rv, rv); + aCount -= read; + aBuffer += read; + // If we hit EOF before reading the data we need then throw. + if (read == 0 && aCount > 0) return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +/* + * Fully writes the required amount of data. Keeps writing until all the + * data is written or an error is hit. + */ +nsresult ZW_WriteData(nsIOutputStream* aStream, const char* aBuffer, + uint32_t aCount) { + while (aCount > 0) { + uint32_t written; + nsresult rv = aStream->Write(aBuffer, aCount, &written); + NS_ENSURE_SUCCESS(rv, rv); + if (written <= 0) return NS_ERROR_FAILURE; + aCount -= written; + aBuffer += written; + } + + return NS_OK; +} diff --git a/modules/libjar/zipwriter/StreamFunctions.h b/modules/libjar/zipwriter/StreamFunctions.h new file mode 100644 index 0000000000..b3fc20d04a --- /dev/null +++ b/modules/libjar/zipwriter/StreamFunctions.h @@ -0,0 +1,58 @@ +/* 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/. + */ + +#ifndef _nsStreamFunctions_h_ +#define _nsStreamFunctions_h_ + +#include "nscore.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" + +/* + * ZIP file data is stored little-endian. These are helper functions to read and + * write little endian data to/from a char buffer. + * The off argument, where present, is incremented according to the number of + * bytes consumed from the buffer. + */ +inline void WRITE8(uint8_t* buf, uint32_t* off, uint8_t val) { + buf[(*off)++] = val; +} + +inline void WRITE16(uint8_t* buf, uint32_t* off, uint16_t val) { + WRITE8(buf, off, val & 0xff); + WRITE8(buf, off, (val >> 8) & 0xff); +} + +inline void WRITE32(uint8_t* buf, uint32_t* off, uint32_t val) { + WRITE16(buf, off, val & 0xffff); + WRITE16(buf, off, (val >> 16) & 0xffff); +} + +inline uint8_t READ8(const uint8_t* buf, uint32_t* off) { + return buf[(*off)++]; +} + +inline uint16_t READ16(const uint8_t* buf, uint32_t* off) { + uint16_t val = READ8(buf, off); + val |= READ8(buf, off) << 8; + return val; +} + +inline uint32_t READ32(const uint8_t* buf, uint32_t* off) { + uint32_t val = READ16(buf, off); + val |= READ16(buf, off) << 16; + return val; +} + +inline uint32_t PEEK32(const uint8_t* buf) { + return (uint32_t)((buf[0]) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24)); +} + +nsresult ZW_ReadData(nsIInputStream* aStream, char* aBuffer, uint32_t aCount); + +nsresult ZW_WriteData(nsIOutputStream* aStream, const char* aBuffer, + uint32_t aCount); + +#endif diff --git a/modules/libjar/zipwriter/components.conf b/modules/libjar/zipwriter/components.conf new file mode 100644 index 0000000000..14ea6a7e4a --- /dev/null +++ b/modules/libjar/zipwriter/components.conf @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{461cd5dd-73c6-47a4-8cc3-603b37d84a61}', + 'contract_ids': [ + '@mozilla.org/streamconv;1?from=uncompressed&to=deflate', + '@mozilla.org/streamconv;1?from=uncompressed&to=gzip', + '@mozilla.org/streamconv;1?from=uncompressed&to=rawdeflate', + '@mozilla.org/streamconv;1?from=uncompressed&to=x-gzip', + ], + 'type': 'nsDeflateConverter', + 'headers': ['/modules/libjar/zipwriter/nsDeflateConverter.h'], + }, + { + 'cid': '{430d416c-a722-4ad1-be98-d9a445f85e3f}', + 'contract_ids': ['@mozilla.org/zipwriter;1'], + 'type': 'nsZipWriter', + 'headers': ['/modules/libjar/zipwriter/nsZipWriter.h'], + }, +] diff --git a/modules/libjar/zipwriter/moz.build b/modules/libjar/zipwriter/moz.build new file mode 100644 index 0000000000..4d94522536 --- /dev/null +++ b/modules/libjar/zipwriter/moz.build @@ -0,0 +1,27 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"] + +XPIDL_SOURCES += [ + "nsIZipWriter.idl", +] + +XPIDL_MODULE = "zipwriter" + +UNIFIED_SOURCES += [ + "nsDeflateConverter.cpp", + "nsZipDataStream.cpp", + "nsZipHeader.cpp", + "nsZipWriter.cpp", + "StreamFunctions.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "xul" diff --git a/modules/libjar/zipwriter/nsDeflateConverter.cpp b/modules/libjar/zipwriter/nsDeflateConverter.cpp new file mode 100644 index 0000000000..32dc3c7ebf --- /dev/null +++ b/modules/libjar/zipwriter/nsDeflateConverter.cpp @@ -0,0 +1,178 @@ +/* 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 "StreamFunctions.h" +#include "nsDeflateConverter.h" +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "plstr.h" +#include "mozilla/UniquePtr.h" + +#define ZLIB_TYPE "deflate" +#define GZIP_TYPE "gzip" +#define X_GZIP_TYPE "x-gzip" + +using namespace mozilla; + +/** + * nsDeflateConverter is a stream converter applies the deflate compression + * method to the data. + */ +NS_IMPL_ISUPPORTS(nsDeflateConverter, nsIStreamConverter, nsIStreamListener, + nsIRequestObserver) + +nsresult nsDeflateConverter::Init() { + int zerr; + + mOffset = 0; + + mZstream.zalloc = Z_NULL; + mZstream.zfree = Z_NULL; + mZstream.opaque = Z_NULL; + + int32_t window = MAX_WBITS; + switch (mWrapMode) { + case WRAP_NONE: + window = -window; + break; + case WRAP_GZIP: + window += 16; + break; + default: + break; + } + + zerr = deflateInit2(&mZstream, mLevel, Z_DEFLATED, window, 8, + Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY; + + mZstream.next_out = mWriteBuffer; + mZstream.avail_out = sizeof(mWriteBuffer); + + // mark the input buffer as empty. + mZstream.avail_in = 0; + mZstream.next_in = Z_NULL; + + return NS_OK; +} + +NS_IMETHODIMP nsDeflateConverter::Convert(nsIInputStream* aFromStream, + const char* aFromType, + const char* aToType, + nsISupports* aCtxt, + nsIInputStream** _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsDeflateConverter::AsyncConvertData(const char* aFromType, + const char* aToType, + nsIStreamListener* aListener, + nsISupports* aCtxt) { + if (mListener) return NS_ERROR_ALREADY_INITIALIZED; + + NS_ENSURE_ARG_POINTER(aListener); + + if (!PL_strncasecmp(aToType, ZLIB_TYPE, sizeof(ZLIB_TYPE) - 1)) + mWrapMode = WRAP_ZLIB; + else if (!PL_strcasecmp(aToType, GZIP_TYPE) || + !PL_strcasecmp(aToType, X_GZIP_TYPE)) + mWrapMode = WRAP_GZIP; + else + mWrapMode = WRAP_NONE; + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mListener = aListener; + mContext = aCtxt; + return rv; +} + +NS_IMETHODIMP +nsDeflateConverter::GetConvertedType(const nsACString& aFromType, + nsIChannel* aChannel, + nsACString& aToType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsDeflateConverter::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) { + if (!mListener) return NS_ERROR_NOT_INITIALIZED; + + auto buffer = MakeUnique<char[]>(aCount); + NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount); + NS_ENSURE_SUCCESS(rv, rv); + + // make sure we aren't reading too much + mZstream.avail_in = aCount; + mZstream.next_in = (unsigned char*)buffer.get(); + + int zerr = Z_OK; + // deflate loop + while (mZstream.avail_in > 0 && zerr == Z_OK) { + zerr = deflate(&mZstream, Z_NO_FLUSH); + + while (mZstream.avail_out == 0) { + // buffer is full, push the data out to the listener + rv = PushAvailableData(aRequest, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + zerr = deflate(&mZstream, Z_NO_FLUSH); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsDeflateConverter::OnStartRequest(nsIRequest* aRequest) { + if (!mListener) return NS_ERROR_NOT_INITIALIZED; + + return mListener->OnStartRequest(aRequest); +} + +NS_IMETHODIMP nsDeflateConverter::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + if (!mListener) return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + int zerr; + do { + zerr = deflate(&mZstream, Z_FINISH); + rv = PushAvailableData(aRequest, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } while (zerr == Z_OK); + + deflateEnd(&mZstream); + + return mListener->OnStopRequest(aRequest, aStatusCode); +} + +nsresult nsDeflateConverter::PushAvailableData(nsIRequest* aRequest, + nsISupports* aContext) { + uint32_t bytesToWrite = sizeof(mWriteBuffer) - mZstream.avail_out; + // We don't need to do anything if there isn't any data + if (bytesToWrite == 0) return NS_OK; + + MOZ_ASSERT(bytesToWrite <= INT32_MAX); + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), + Span((char*)mWriteBuffer, bytesToWrite), + NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mListener->OnDataAvailable(aRequest, stream, mOffset, bytesToWrite); + + // now set the state for 'deflate' + mZstream.next_out = mWriteBuffer; + mZstream.avail_out = sizeof(mWriteBuffer); + + mOffset += bytesToWrite; + return rv; +} diff --git a/modules/libjar/zipwriter/nsDeflateConverter.h b/modules/libjar/zipwriter/nsDeflateConverter.h new file mode 100644 index 0000000000..239a11c74e --- /dev/null +++ b/modules/libjar/zipwriter/nsDeflateConverter.h @@ -0,0 +1,57 @@ +/* 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/. + */ + +#ifndef _nsDeflateConverter_h_ +#define _nsDeflateConverter_h_ + +#include "nsIStreamConverter.h" +#include "nsCOMPtr.h" +#include "zlib.h" +#include "mozilla/Attributes.h" + +#define DEFLATECONVERTER_CID \ + { \ + 0x461cd5dd, 0x73c6, 0x47a4, { \ + 0x8c, 0xc3, 0x60, 0x3b, 0x37, 0xd8, 0x4a, 0x61 \ + } \ + } + +class nsDeflateConverter final : public nsIStreamConverter { + public: + static constexpr size_t kZipBufLen = (4 * 1024 - 1); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSISTREAMCONVERTER + + nsDeflateConverter() : mWrapMode(WRAP_NONE), mOffset(0), mZstream() { + // 6 is Z_DEFAULT_COMPRESSION but we need the actual value + mLevel = 6; + } + + explicit nsDeflateConverter(int32_t level) + : mWrapMode(WRAP_NONE), mOffset(0), mZstream() { + mLevel = level; + } + + private: + ~nsDeflateConverter() {} + + enum WrapMode { WRAP_ZLIB, WRAP_GZIP, WRAP_NONE }; + + WrapMode mWrapMode; + uint64_t mOffset; + int32_t mLevel; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsISupports> mContext; + z_stream mZstream; + unsigned char mWriteBuffer[kZipBufLen]; + + nsresult Init(); + nsresult PushAvailableData(nsIRequest* aRequest, nsISupports* aContext); +}; + +#endif diff --git a/modules/libjar/zipwriter/nsIZipWriter.idl b/modules/libjar/zipwriter/nsIZipWriter.idl new file mode 100644 index 0000000000..9cf32fd1e7 --- /dev/null +++ b/modules/libjar/zipwriter/nsIZipWriter.idl @@ -0,0 +1,223 @@ +/* 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 "nsISupports.idl" +interface nsIChannel; +interface nsIInputStream; +interface nsIRequestObserver; +interface nsIFile; +interface nsIZipEntry; + +/** + * nsIZipWriter + * + * An interface for a zip archiver that can be used from script. + * + * The interface supports both a synchronous method of archiving data and a + * queueing system to allow operations to be prepared then run in sequence + * with notification after completion. + * + * Operations added to the queue do not get performed until performQueue is + * called at which point they will be performed in the order that they were + * added to the queue. + * + * Operations performed on the queue will throw any errors out to the + * observer. + * + * An attempt to perform a synchronous operation while the background queue + * is in progress will throw NS_ERROR_IN_PROGRESS. + * + * Entry names should use /'s as path separators and should not start with + * a /. + * + * It is not generally necessary to add directory entries in order to add file + * entries within them, however it is possible that some zip programs may + * experience problems what that. + */ +[scriptable, uuid(3ca10750-797e-4a22-bcfe-66170b5e96dd)] +interface nsIZipWriter : nsISupports +{ + /** + * Some predefined compression levels + */ + const uint32_t COMPRESSION_NONE = 0; + const uint32_t COMPRESSION_FASTEST = 1; + const uint32_t COMPRESSION_DEFAULT = 6; + const uint32_t COMPRESSION_BEST = 9; + + /** + * Gets or sets the comment associated with the open zip file. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + */ + attribute ACString comment; + + /** + * Indicates that operations on the background queue are being performed. + */ + readonly attribute boolean inQueue; + + /** + * The file that the zipwriter is writing to. + */ + readonly attribute nsIFile file; + + /** + * Opens a zip file. + * + * @param aFile the zip file to open + * @param aIoFlags the open flags for the zip file from prio.h + * + * @throws NS_ERROR_ALREADY_INITIALIZED if a zip file is already open + * @throws NS_ERROR_INVALID_ARG if aFile is null + * @throws NS_ERROR_FILE_NOT_FOUND if aFile does not exist and flags did + * not allow for creation + * @throws NS_ERROR_FILE_CORRUPTED if the file does not contain zip markers + * @throws <other-error> on failure to open zip file (most likely corrupt + * or unsupported form) + */ + void open(in nsIFile aFile, in int32_t aIoFlags); + + /** + * Returns a nsIZipEntry describing a specified zip entry or null if there + * is no such entry in the zip file + * + * @param aZipEntry the path of the entry + */ + nsIZipEntry getEntry(in AUTF8String aZipEntry); + + /** + * Checks whether the zipfile contains an entry specified by zipEntry. + * + * @param aZipEntry the path of the entry + */ + boolean hasEntry(in AUTF8String aZipEntry); + + /** + * Adds a new directory entry to the zip file. If aZipEntry does not end with + * "/" then it will be added. + * + * @param aZipEntry the path of the directory entry + * @param aModTime the modification time of the entry in microseconds + * @param aQueue adds the operation to the background queue. Will be + * performed when processQueue is called. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the + * file + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws NS_ERROR_INVALID_ARG if aModTime is older than 1980-1-1 + */ + void addEntryDirectory(in AUTF8String aZipEntry, in PRTime aModTime, + in boolean aQueue); + + /** + * Adds a new file or directory to the zip file. If the specified file is + * a directory then this will be equivalent to a call to + * addEntryDirectory(aZipEntry, aFile.lastModifiedTime, aQueue) + * + * @param aZipEntry the path of the file entry + * @param aCompression the compression level, 0 is no compression, 9 is best + * @param aFile the file to get the data and modification time from + * @param aQueue adds the operation to the background queue. Will be + * performed when processQueue is called. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws NS_ERROR_FILE_NOT_FOUND if file does not exist + */ + void addEntryFile(in AUTF8String aZipEntry, + in int32_t aCompression, in nsIFile aFile, + in boolean aQueue); + + /** + * Adds data from a channel to the zip file. If the operation is performed + * on the queue then the channel will be opened asynchronously, otherwise + * the channel must support being opened synchronously. + * + * @param aZipEntry the path of the file entry + * @param aModTime the modification time of the entry in microseconds + * @param aCompression the compression level, 0 is no compression, 9 is best + * @param aChannel the channel to get the data from + * @param aQueue adds the operation to the background queue. Will be + * performed when processQueue is called. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws NS_ERROR_INVALID_ARG if aModTime is older than 1980-1-1 + */ + void addEntryChannel(in AUTF8String aZipEntry, in PRTime aModTime, + in int32_t aCompression, in nsIChannel aChannel, + in boolean aQueue); + + /** + * Adds data from an input stream to the zip file. + * + * @param aZipEntry the path of the file entry + * @param aModTime the modification time of the entry in microseconds + * @param aCompression the compression level, 0 is no compression, 9 is best + * @param aStream the input stream to get the data from + * @param aQueue adds the operation to the background queue. Will be + * performed when processQueue is called. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws NS_ERROR_INVALID_ARG if aModTime is older than 1980-1-1 + */ + void addEntryStream(in AUTF8String aZipEntry, in PRTime aModTime, + in int32_t aCompression, in nsIInputStream aStream, + in boolean aQueue); + + /** + * Removes an existing entry from the zip file. + * + * @param aZipEntry the path of the entry to be removed + * @param aQueue adds the operation to the background queue. Will be + * performed when processQueue is called. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws NS_ERROR_FILE_NOT_FOUND if no entry with the given path exists + * @throws <other-error> on failure to update the zip file + */ + void removeEntry(in AUTF8String aZipEntry, in boolean aQueue); + + /** + * Processes all queued items until complete or some error occurs. The + * observer will be notified when the first operation starts and when the + * last operation completes. Any failures will be passed to the observer. + * The zip writer will be busy until the queue is complete or some error + * halted processing of the queue early. In the event of an early failure, + * remaining items will stay in the queue and calling processQueue will + * continue. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_IN_PROGRESS if the queue is already in progress + */ + void processQueue(in nsIRequestObserver aObserver, in nsISupports aContext); + + /** + * Closes the zip file. + * + * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened + * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress + * @throws <other-error> on failure to complete the zip file + */ + void close(); + + /** + * Make all stored(uncompressed) files align to given alignment size. + * + * @param aAlignSize is the alignment size, valid values from 2 to 32768, and + must be power of 2. + * + * @throws NS_ERROR_INVALID_ARG if aAlignSize is invalid + * @throws <other-error> on failure to update the zip file + */ + void alignStoredFiles(in uint16_t aAlignSize); +}; diff --git a/modules/libjar/zipwriter/nsZipDataStream.cpp b/modules/libjar/zipwriter/nsZipDataStream.cpp new file mode 100644 index 0000000000..e71e12eefe --- /dev/null +++ b/modules/libjar/zipwriter/nsZipDataStream.cpp @@ -0,0 +1,159 @@ +/* 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 "StreamFunctions.h" +#include "nsZipDataStream.h" +#include "nsStringStream.h" +#include "nsISeekableStream.h" +#include "nsDeflateConverter.h" +#include "nsNetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" + +#define ZIP_METHOD_STORE 0 +#define ZIP_METHOD_DEFLATE 8 + +using namespace mozilla; + +/** + * nsZipDataStream handles the writing an entry's into the zip file. + * It is set up to wither write the data as is, or in the event that compression + * has been requested to pass it through a stream converter. + * Currently only the deflate compression method is supported. + * The CRC checksum for the entry's data is also generated here. + */ +NS_IMPL_ISUPPORTS(nsZipDataStream, nsIStreamListener, nsIRequestObserver) + +nsresult nsZipDataStream::Init(nsZipWriter* aWriter, nsIOutputStream* aStream, + nsZipHeader* aHeader, int32_t aCompression) { + mWriter = aWriter; + mHeader = aHeader; + mStream = aStream; + mHeader->mCRC = crc32(0L, Z_NULL, 0); + + nsresult rv = + NS_NewSimpleStreamListener(getter_AddRefs(mOutput), aStream, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (aCompression > 0) { + mHeader->mMethod = ZIP_METHOD_DEFLATE; + nsCOMPtr<nsIStreamConverter> converter = + new nsDeflateConverter(aCompression); + NS_ENSURE_TRUE(converter, NS_ERROR_OUT_OF_MEMORY); + + rv = converter->AsyncConvertData("uncompressed", "rawdeflate", mOutput, + nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + mOutput = converter; + } else { + mHeader->mMethod = ZIP_METHOD_STORE; + } + + return NS_OK; +} + +NS_IMETHODIMP nsZipDataStream::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) { + if (!mOutput) return NS_ERROR_NOT_INITIALIZED; + + auto buffer = MakeUnique<char[]>(aCount); + NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount); + NS_ENSURE_SUCCESS(rv, rv); + + return ProcessData(aRequest, nullptr, buffer.get(), aOffset, aCount); +} + +NS_IMETHODIMP nsZipDataStream::OnStartRequest(nsIRequest* aRequest) { + if (!mOutput) return NS_ERROR_NOT_INITIALIZED; + + return mOutput->OnStartRequest(aRequest); +} + +NS_IMETHODIMP nsZipDataStream::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + if (!mOutput) return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = mOutput->OnStopRequest(aRequest, aStatusCode); + mOutput = nullptr; + if (NS_FAILED(rv)) { + mWriter->EntryCompleteCallback(mHeader, rv); + } else { + rv = CompleteEntry(); + rv = mWriter->EntryCompleteCallback(mHeader, rv); + } + + mStream = nullptr; + mWriter = nullptr; + mHeader = nullptr; + + return rv; +} + +inline nsresult nsZipDataStream::CompleteEntry() { + nsresult rv; + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + int64_t pos; + rv = seekable->Tell(&pos); + NS_ENSURE_SUCCESS(rv, rv); + + mHeader->mCSize = pos - mHeader->mOffset - mHeader->GetFileHeaderLength(); + mHeader->mWriteOnClose = true; + return NS_OK; +} + +nsresult nsZipDataStream::ProcessData(nsIRequest* aRequest, + nsISupports* aContext, char* aBuffer, + uint64_t aOffset, uint32_t aCount) { + mHeader->mCRC = crc32( + mHeader->mCRC, reinterpret_cast<const unsigned char*>(aBuffer), aCount); + + MOZ_ASSERT(aCount <= INT32_MAX); + nsCOMPtr<nsIInputStream> stream; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(stream), Span(aBuffer, aCount), NS_ASSIGNMENT_DEPEND); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOutput->OnDataAvailable(aRequest, stream, aOffset, aCount); + mHeader->mUSize += aCount; + + return rv; +} + +nsresult nsZipDataStream::ReadStream(nsIInputStream* aStream) { + if (!mOutput) return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = OnStartRequest(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + auto buffer = MakeUnique<char[]>(4096); + NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); + + uint32_t read = 0; + uint32_t offset = 0; + do { + rv = aStream->Read(buffer.get(), 4096, &read); + if (NS_FAILED(rv)) { + OnStopRequest(nullptr, rv); + return rv; + } + + if (read > 0) { + rv = ProcessData(nullptr, nullptr, buffer.get(), offset, read); + if (NS_FAILED(rv)) { + OnStopRequest(nullptr, rv); + return rv; + } + offset += read; + } + } while (read > 0); + + return OnStopRequest(nullptr, NS_OK); +} diff --git a/modules/libjar/zipwriter/nsZipDataStream.h b/modules/libjar/zipwriter/nsZipDataStream.h new file mode 100644 index 0000000000..5f0d27de45 --- /dev/null +++ b/modules/libjar/zipwriter/nsZipDataStream.h @@ -0,0 +1,40 @@ +/* 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/. + */ + +#ifndef _nsZipDataStream_h_ +#define _nsZipDataStream_h_ + +#include "nsZipWriter.h" +#include "nsIOutputStream.h" +#include "nsIStreamListener.h" +#include "mozilla/Attributes.h" + +class nsZipDataStream final : public nsIStreamListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsZipDataStream() {} + + nsresult Init(nsZipWriter* aWriter, nsIOutputStream* aStream, + nsZipHeader* aHeader, int32_t aCompression); + + nsresult ReadStream(nsIInputStream* aStream); + + private: + ~nsZipDataStream() {} + + nsCOMPtr<nsIStreamListener> mOutput; + nsCOMPtr<nsIOutputStream> mStream; + RefPtr<nsZipWriter> mWriter; + RefPtr<nsZipHeader> mHeader; + + nsresult CompleteEntry(); + nsresult ProcessData(nsIRequest* aRequest, nsISupports* aContext, + char* aBuffer, uint64_t aOffset, uint32_t aCount); +}; + +#endif diff --git a/modules/libjar/zipwriter/nsZipHeader.cpp b/modules/libjar/zipwriter/nsZipHeader.cpp new file mode 100644 index 0000000000..8b78ea091d --- /dev/null +++ b/modules/libjar/zipwriter/nsZipHeader.cpp @@ -0,0 +1,377 @@ +/* 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 "StreamFunctions.h" +#include "nsZipHeader.h" +#include "nsMemory.h" +#include "prtime.h" + +#define ZIP_FILE_HEADER_SIGNATURE 0x04034b50 +#define ZIP_FILE_HEADER_SIZE 30 +#define ZIP_CDS_HEADER_SIGNATURE 0x02014b50 +#define ZIP_CDS_HEADER_SIZE 46 + +#define FLAGS_IS_UTF8 0x800 + +#define ZIP_EXTENDED_TIMESTAMP_FIELD 0x5455 +#define ZIP_EXTENDED_TIMESTAMP_MODTIME 0x01 + +using namespace mozilla; + +/** + * nsZipHeader represents an entry from a zip file. + */ +NS_IMPL_ISUPPORTS(nsZipHeader, nsIZipEntry) + +NS_IMETHODIMP nsZipHeader::GetCompression(uint16_t* aCompression) { + NS_ASSERTION(mInited, "Not initalised"); + + *aCompression = mMethod; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetSize(uint32_t* aSize) { + NS_ASSERTION(mInited, "Not initalised"); + + *aSize = mCSize; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetRealSize(uint32_t* aRealSize) { + NS_ASSERTION(mInited, "Not initalised"); + + *aRealSize = mUSize; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetCRC32(uint32_t* aCRC32) { + NS_ASSERTION(mInited, "Not initalised"); + + *aCRC32 = mCRC; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetIsDirectory(bool* aIsDirectory) { + NS_ASSERTION(mInited, "Not initalised"); + + if (mName.Last() == '/') + *aIsDirectory = true; + else + *aIsDirectory = false; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetLastModifiedTime(PRTime* aLastModifiedTime) { + NS_ASSERTION(mInited, "Not initalised"); + + // Try to read timestamp from extra field + uint16_t blocksize; + const uint8_t* tsField = + GetExtraField(ZIP_EXTENDED_TIMESTAMP_FIELD, false, &blocksize); + if (tsField && blocksize >= 5) { + uint32_t pos = 4; + uint8_t flags; + flags = READ8(tsField, &pos); + if (flags & ZIP_EXTENDED_TIMESTAMP_MODTIME) { + *aLastModifiedTime = (PRTime)(READ32(tsField, &pos)) * PR_USEC_PER_SEC; + return NS_OK; + } + } + + // Use DOS date/time fields + // Note that on DST shift we can't handle correctly the hour that is valid + // in both DST zones + PRExplodedTime time; + + time.tm_usec = 0; + + time.tm_hour = (mTime >> 11) & 0x1F; + time.tm_min = (mTime >> 5) & 0x3F; + time.tm_sec = (mTime & 0x1F) * 2; + + time.tm_year = (mDate >> 9) + 1980; + time.tm_month = ((mDate >> 5) & 0x0F) - 1; + time.tm_mday = mDate & 0x1F; + + time.tm_params.tp_gmt_offset = 0; + time.tm_params.tp_dst_offset = 0; + + PR_NormalizeTime(&time, PR_GMTParameters); + time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset; + PR_NormalizeTime(&time, PR_GMTParameters); + time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset; + + *aLastModifiedTime = PR_ImplodeTime(&time); + + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetIsSynthetic(bool* aIsSynthetic) { + NS_ASSERTION(mInited, "Not initalised"); + + *aIsSynthetic = false; + return NS_OK; +} + +NS_IMETHODIMP nsZipHeader::GetPermissions(uint32_t* aPermissions) { + NS_ASSERTION(mInited, "Not initalised"); + + // Always give user read access at least, this matches nsIZipReader's + // behaviour + *aPermissions = ((mEAttr >> 16) & 0xfff) | 0x100; + return NS_OK; +} + +nsresult nsZipHeader::Init(const nsACString& aPath, PRTime aDate, + uint32_t aAttr, uint32_t aOffset) { + NS_ASSERTION(!mInited, "Already initalised"); + + PRExplodedTime time; + PR_ExplodeTime(aDate, PR_LocalTimeParameters, &time); + if (time.tm_year < 1980) { + return NS_ERROR_INVALID_ARG; + } + + mTime = time.tm_sec / 2 + (time.tm_min << 5) + (time.tm_hour << 11); + mDate = + time.tm_mday + ((time.tm_month + 1) << 5) + ((time.tm_year - 1980) << 9); + + // Store modification timestamp as extra field + // First fill CDS extra field + mFieldLength = 9; + mExtraField = MakeUnique<uint8_t[]>(mFieldLength); + if (!mExtraField) { + mFieldLength = 0; + } else { + uint32_t pos = 0; + WRITE16(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_FIELD); + WRITE16(mExtraField.get(), &pos, 5); + WRITE8(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_MODTIME); + WRITE32(mExtraField.get(), &pos, aDate / PR_USEC_PER_SEC); + + // Fill local extra field + mLocalExtraField = MakeUnique<uint8_t[]>(mFieldLength); + if (mLocalExtraField) { + mLocalFieldLength = mFieldLength; + memcpy(mLocalExtraField.get(), mExtraField.get(), mLocalFieldLength); + } + } + + mEAttr = aAttr; + mOffset = aOffset; + mName = aPath; + mComment.Truncate(); + // Claim a UTF-8 path in case it needs it. + mFlags |= FLAGS_IS_UTF8; + mInited = true; + + return NS_OK; +} + +uint32_t nsZipHeader::GetFileHeaderLength() { + return ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength; +} + +nsresult nsZipHeader::WriteFileHeader(nsIOutputStream* aStream) { + NS_ASSERTION(mInited, "Not initalised"); + + uint8_t buf[ZIP_FILE_HEADER_SIZE]; + uint32_t pos = 0; + WRITE32(buf, &pos, ZIP_FILE_HEADER_SIGNATURE); + WRITE16(buf, &pos, mVersionNeeded); + WRITE16(buf, &pos, mFlags); + WRITE16(buf, &pos, mMethod); + WRITE16(buf, &pos, mTime); + WRITE16(buf, &pos, mDate); + WRITE32(buf, &pos, mCRC); + WRITE32(buf, &pos, mCSize); + WRITE32(buf, &pos, mUSize); + WRITE16(buf, &pos, mName.Length()); + WRITE16(buf, &pos, mLocalFieldLength); + + nsresult rv = ZW_WriteData(aStream, (const char*)buf, pos); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ZW_WriteData(aStream, mName.get(), mName.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + if (mLocalFieldLength) { + rv = ZW_WriteData(aStream, (const char*)mLocalExtraField.get(), + mLocalFieldLength); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +uint32_t nsZipHeader::GetCDSHeaderLength() { + return ZIP_CDS_HEADER_SIZE + mName.Length() + mComment.Length() + + mFieldLength; +} + +nsresult nsZipHeader::WriteCDSHeader(nsIOutputStream* aStream) { + NS_ASSERTION(mInited, "Not initalised"); + + uint8_t buf[ZIP_CDS_HEADER_SIZE]; + uint32_t pos = 0; + WRITE32(buf, &pos, ZIP_CDS_HEADER_SIGNATURE); + WRITE16(buf, &pos, mVersionMade); + WRITE16(buf, &pos, mVersionNeeded); + WRITE16(buf, &pos, mFlags); + WRITE16(buf, &pos, mMethod); + WRITE16(buf, &pos, mTime); + WRITE16(buf, &pos, mDate); + WRITE32(buf, &pos, mCRC); + WRITE32(buf, &pos, mCSize); + WRITE32(buf, &pos, mUSize); + WRITE16(buf, &pos, mName.Length()); + WRITE16(buf, &pos, mFieldLength); + WRITE16(buf, &pos, mComment.Length()); + WRITE16(buf, &pos, mDisk); + WRITE16(buf, &pos, mIAttr); + WRITE32(buf, &pos, mEAttr); + WRITE32(buf, &pos, mOffset); + + nsresult rv = ZW_WriteData(aStream, (const char*)buf, pos); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ZW_WriteData(aStream, mName.get(), mName.Length()); + NS_ENSURE_SUCCESS(rv, rv); + if (mExtraField) { + rv = ZW_WriteData(aStream, (const char*)mExtraField.get(), mFieldLength); + NS_ENSURE_SUCCESS(rv, rv); + } + return ZW_WriteData(aStream, mComment.get(), mComment.Length()); +} + +nsresult nsZipHeader::ReadCDSHeader(nsIInputStream* stream) { + NS_ASSERTION(!mInited, "Already initalised"); + + uint8_t buf[ZIP_CDS_HEADER_SIZE]; + + nsresult rv = ZW_ReadData(stream, (char*)buf, ZIP_CDS_HEADER_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t pos = 0; + uint32_t signature = READ32(buf, &pos); + if (signature != ZIP_CDS_HEADER_SIGNATURE) return NS_ERROR_FILE_CORRUPTED; + + mVersionMade = READ16(buf, &pos); + mVersionNeeded = READ16(buf, &pos); + mFlags = READ16(buf, &pos); + mMethod = READ16(buf, &pos); + mTime = READ16(buf, &pos); + mDate = READ16(buf, &pos); + mCRC = READ32(buf, &pos); + mCSize = READ32(buf, &pos); + mUSize = READ32(buf, &pos); + uint16_t namelength = READ16(buf, &pos); + mFieldLength = READ16(buf, &pos); + uint16_t commentlength = READ16(buf, &pos); + mDisk = READ16(buf, &pos); + mIAttr = READ16(buf, &pos); + mEAttr = READ32(buf, &pos); + mOffset = READ32(buf, &pos); + + if (namelength > 0) { + auto field = MakeUnique<char[]>(namelength); + NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY); + rv = ZW_ReadData(stream, field.get(), namelength); + NS_ENSURE_SUCCESS(rv, rv); + mName.Assign(field.get(), namelength); + } else + mName.Truncate(); + + if (mFieldLength > 0) { + mExtraField = MakeUnique<uint8_t[]>(mFieldLength); + NS_ENSURE_TRUE(mExtraField, NS_ERROR_OUT_OF_MEMORY); + rv = ZW_ReadData(stream, (char*)mExtraField.get(), mFieldLength); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (commentlength > 0) { + auto field = MakeUnique<char[]>(commentlength); + NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY); + rv = ZW_ReadData(stream, field.get(), commentlength); + NS_ENSURE_SUCCESS(rv, rv); + mComment.Assign(field.get(), commentlength); + } else + mComment.Truncate(); + + mInited = true; + return NS_OK; +} + +const uint8_t* nsZipHeader::GetExtraField(uint16_t aTag, bool aLocal, + uint16_t* aBlockSize) { + const uint8_t* buf = aLocal ? mLocalExtraField.get() : mExtraField.get(); + uint32_t buflen = aLocal ? mLocalFieldLength : mFieldLength; + uint32_t pos = 0; + uint16_t tag, blocksize; + + while (buf && (pos + 4) <= buflen) { + tag = READ16(buf, &pos); + blocksize = READ16(buf, &pos); + + if (aTag == tag && (pos + blocksize) <= buflen) { + *aBlockSize = blocksize; + return buf + pos - 4; + } + + pos += blocksize; + } + + return nullptr; +} + +/* + * Pad extra field to align data starting position to specified size. + */ +nsresult nsZipHeader::PadExtraField(uint32_t aOffset, uint16_t aAlignSize) { + uint32_t pad_size; + uint32_t pa_offset; + uint32_t pa_end; + + // Check for range and power of 2. + if (aAlignSize < 2 || aAlignSize > 32768 || + (aAlignSize & (aAlignSize - 1)) != 0) { + return NS_ERROR_INVALID_ARG; + } + + // Point to current starting data position. + aOffset += ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength; + + // Calculate aligned offset. + pa_offset = aOffset & ~(aAlignSize - 1); + pa_end = pa_offset + aAlignSize; + pad_size = pa_end - aOffset; + if (pad_size == 0) { + return NS_OK; + } + + // Leave enough room(at least 4 bytes) for valid values in extra field. + while (pad_size < 4) { + pad_size += aAlignSize; + } + // Extra field length is 2 bytes. + if (mLocalFieldLength + pad_size > 65535) { + return NS_ERROR_FAILURE; + } + + UniquePtr<uint8_t[]> field = std::move(mLocalExtraField); + uint32_t pos = mLocalFieldLength; + + mLocalExtraField = MakeUnique<uint8_t[]>(mLocalFieldLength + pad_size); + memcpy(mLocalExtraField.get(), field.get(), mLocalFieldLength); + // Use 0xFFFF as tag ID to avoid conflict with other IDs. + // For more information, please read "Extensible data fields" section in: + // http://www.pkware.com/documents/casestudies/APPNOTE.TXT + WRITE16(mLocalExtraField.get(), &pos, 0xFFFF); + WRITE16(mLocalExtraField.get(), &pos, pad_size - 4); + memset(mLocalExtraField.get() + pos, 0, pad_size - 4); + mLocalFieldLength += pad_size; + + return NS_OK; +} diff --git a/modules/libjar/zipwriter/nsZipHeader.h b/modules/libjar/zipwriter/nsZipHeader.h new file mode 100644 index 0000000000..528730b000 --- /dev/null +++ b/modules/libjar/zipwriter/nsZipHeader.h @@ -0,0 +1,92 @@ +/* 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/. + */ + +#ifndef _nsZipHeader_h_ +#define _nsZipHeader_h_ + +#include "nsString.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIZipReader.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" + +// High word is S_IFREG, low word is DOS file attribute +#define ZIP_ATTRS_FILE 0x80000000 +// High word is S_IFDIR, low word is DOS dir attribute +#define ZIP_ATTRS_DIRECTORY 0x40000010 +#define PERMISSIONS_FILE 0644 +#define PERMISSIONS_DIR 0755 + +// Combine file type attributes with unix style permissions +#define ZIP_ATTRS(p, a) ((p & 0xfff) << 16) | a + +class nsZipHeader final : public nsIZipEntry { + ~nsZipHeader() { + mExtraField = nullptr; + mLocalExtraField = nullptr; + } + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIZIPENTRY + + nsZipHeader() + : mCRC(0), + mCSize(0), + mUSize(0), + mEAttr(0), + mOffset(0), + mFieldLength(0), + mLocalFieldLength(0), + mVersionMade(0x0300 + + 23), // Generated on Unix by v2.3 (matches infozip) + mVersionNeeded(20), // Requires v2.0 to extract + mFlags(0), + mMethod(0), + mTime(0), + mDate(0), + mDisk(0), + mIAttr(0), + mInited(false), + mWriteOnClose(false), + mExtraField(nullptr), + mLocalExtraField(nullptr) {} + + uint32_t mCRC; + uint32_t mCSize; + uint32_t mUSize; + uint32_t mEAttr; + uint32_t mOffset; + uint32_t mFieldLength; + uint32_t mLocalFieldLength; + uint16_t mVersionMade; + uint16_t mVersionNeeded; + uint16_t mFlags; + uint16_t mMethod; + uint16_t mTime; + uint16_t mDate; + uint16_t mDisk; + uint16_t mIAttr; + bool mInited; + bool mWriteOnClose; + nsCString mName; + nsCString mComment; + mozilla::UniquePtr<uint8_t[]> mExtraField; + mozilla::UniquePtr<uint8_t[]> mLocalExtraField; + + nsresult Init(const nsACString& aPath, PRTime aDate, uint32_t aAttr, + uint32_t aOffset); + uint32_t GetFileHeaderLength(); + nsresult WriteFileHeader(nsIOutputStream* aStream); + uint32_t GetCDSHeaderLength(); + nsresult WriteCDSHeader(nsIOutputStream* aStream); + nsresult ReadCDSHeader(nsIInputStream* aStream); + const uint8_t* GetExtraField(uint16_t aTag, bool aLocal, + uint16_t* aBlockSize); + nsresult PadExtraField(uint32_t aOffset, uint16_t aAlignSize); +}; + +#endif diff --git a/modules/libjar/zipwriter/nsZipWriter.cpp b/modules/libjar/zipwriter/nsZipWriter.cpp new file mode 100644 index 0000000000..fa820c2f10 --- /dev/null +++ b/modules/libjar/zipwriter/nsZipWriter.cpp @@ -0,0 +1,1061 @@ +/* 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 "nsIStreamListener.h" +#include "nsIInputStreamPump.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 + MOZ_ASSERT_UNREACHABLE("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; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mQueue.AppendElement(item); + 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); + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mQueue.AppendElement(item); + 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; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mQueue.AppendElement(item); + 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_MaybeOpenChannelUsingOpen(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; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mQueue.AppendElement(item); + 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); + nsresult rv = header->Init( + aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE), mCDSOffset); + if (NS_FAILED(rv)) { + SeekCDS(); + return rv; + } + + 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; + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mQueue.AppendElement(item); + 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); + + 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) { + return NS_OK; +} + +NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest* aRequest, + 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); + + nsresult rv = NS_OK; + if (aZipEntry.Last() != '/') { + nsCString dirPath; + dirPath.Assign(aZipEntry + "/"_ns); + rv = header->Init(dirPath, aModTime, zipAttributes, mCDSOffset); + } else { + rv = header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + Cleanup(); + return rv; + } + + if (mEntryHash.Get(header->mName, nullptr)) + return NS_ERROR_FILE_ALREADY_EXISTS; + + 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); + + nsresult rv = header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes, + mCDSOffset); + NS_ENSURE_SUCCESS(rv, rv); + + 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; + nsCOMPtr<nsIInputStream> tmpStream = aItem->mStream; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), tmpStream.forget(), 0, 0, + true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pump->AsyncRead(stream); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = NS_MaybeOpenChannelUsingAsyncOpen(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; + nsCOMPtr<nsIInputStream> tmpStream = inputStream; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), tmpStream.forget(), 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); + 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; + // Clean up everything first in case the observer decides to queue more + // things + mProcessObserver = nullptr; + mProcessContext = nullptr; + mInQueue = false; + + if (observer) observer->OnStopRequest(nullptr, aStatus); +} diff --git a/modules/libjar/zipwriter/nsZipWriter.h b/modules/libjar/zipwriter/nsZipWriter.h new file mode 100644 index 0000000000..c458142639 --- /dev/null +++ b/modules/libjar/zipwriter/nsZipWriter.h @@ -0,0 +1,79 @@ +/* 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/. + */ + +#ifndef _nsZipWriter_h_ +#define _nsZipWriter_h_ + +#include "nsIZipWriter.h" +#include "nsIRequestObserver.h" +#include "nsZipHeader.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsTArray.h" +#include "nsDataHashtable.h" +#include "mozilla/Attributes.h" + +#define ZIPWRITER_CONTRACTID "@mozilla.org/zipwriter;1" +#define ZIPWRITER_CID \ + { \ + 0x430d416c, 0xa722, 0x4ad1, { \ + 0xbe, 0x98, 0xd9, 0xa4, 0x45, 0xf8, 0x5e, 0x3f \ + } \ + } + +#define OPERATION_ADD 0 +#define OPERATION_REMOVE 1 +struct nsZipQueueItem { + public: + uint32_t mOperation; + nsCString mZipEntry; + nsCOMPtr<nsIFile> mFile; + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIInputStream> mStream; + PRTime mModTime; + int32_t mCompression; + uint32_t mPermissions; +}; + +class nsZipWriter final : public nsIZipWriter, public nsIRequestObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIZIPWRITER + NS_DECL_NSIREQUESTOBSERVER + + nsZipWriter(); + nsresult EntryCompleteCallback(nsZipHeader* aHeader, nsresult aStatus); + + private: + ~nsZipWriter(); + + uint32_t mCDSOffset; + bool mCDSDirty; + bool mInQueue; + + nsCOMPtr<nsIFile> mFile; + nsCOMPtr<nsIRequestObserver> mProcessObserver; + nsCOMPtr<nsISupports> mProcessContext; + nsCOMPtr<nsIOutputStream> mStream; + nsCOMArray<nsZipHeader> mHeaders; + nsTArray<nsZipQueueItem> mQueue; + nsDataHashtable<nsCStringHashKey, int32_t> mEntryHash; + nsCString mComment; + + nsresult SeekCDS(); + void Cleanup(); + nsresult ReadFile(nsIFile* aFile); + nsresult InternalAddEntryDirectory(const nsACString& aZipEntry, + PRTime aModTime, uint32_t aPermissions); + nsresult BeginProcessingAddition(nsZipQueueItem* aItem, bool* complete); + nsresult BeginProcessingRemoval(int32_t aPos); + nsresult AddEntryStream(const nsACString& aZipEntry, PRTime aModTime, + int32_t aCompression, nsIInputStream* aStream, + bool aQueue, uint32_t aPermissions); + void BeginProcessingNextItem(); + void FinishQueue(nsresult aStatus); +}; + +#endif diff --git a/modules/libjar/zipwriter/test/unit/data/emptyfile.txt b/modules/libjar/zipwriter/test/unit/data/emptyfile.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/emptyfile.txt diff --git a/modules/libjar/zipwriter/test/unit/data/smallfile.txt b/modules/libjar/zipwriter/test/unit/data/smallfile.txt new file mode 100644 index 0000000000..e068fcf81d --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/smallfile.txt @@ -0,0 +1 @@ +Small (16 bytes)
\ No newline at end of file diff --git a/modules/libjar/zipwriter/test/unit/data/test.png b/modules/libjar/zipwriter/test/unit/data/test.png Binary files differnew file mode 100644 index 0000000000..c648f7299d --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test.png diff --git a/modules/libjar/zipwriter/test/unit/data/test.txt b/modules/libjar/zipwriter/test/unit/data/test.txt new file mode 100644 index 0000000000..1040981a30 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test.txt @@ -0,0 +1,5 @@ +This is a test text file for the zipwriter component. +It will be made available in the unit test directory. +It will also be compressed into a testcase zip file +made by a 3rd party zip tool to test the opening of +existing zip files. diff --git a/modules/libjar/zipwriter/test/unit/data/test.zip b/modules/libjar/zipwriter/test/unit/data/test.zip Binary files differnew file mode 100644 index 0000000000..96581fe8b5 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test.zip diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug399727.html b/modules/libjar/zipwriter/test/unit/data/test_bug399727.html new file mode 100644 index 0000000000..a6556274f1 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test_bug399727.html @@ -0,0 +1,160 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="Content-Language" content="en-gb"><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"><meta name="generator" content="Fog Creek CityDesk 2.0.19"><meta name="citydesk" content="BB40F561/251"><title>TK's Sleater-Kinney pages</title><style type="text/css">
+<!--
+body {
+ font-family: verdana, arial;
+ font-size: 10pt;
+ color: black;
+ background-color: #D0E8E8;
+}
+td {
+ font-family: verdana, arial;
+ font-size: 10pt;
+}
+hr {
+ color: #808080;
+ height: 1px;
+}
+caption {
+ font-family: verdana, arial;
+ font-size: 8pt;
+ color: #808080;
+ background-color: white;
+}
+.bigText {
+ font-family: verdana, arial;
+ font-size: 18pt;
+ font-weight: bold;
+ color: black;
+ padding-bottom: 10px;
+}
+.smallText {
+ font-family: verdana, arial;
+ font-size: 7pt;
+ color: #808080;
+}
+.tabText {
+ font-family: Courier, "Courier New", monospace;
+ font-size: 7pt;
+ color: #808080;
+}
+.emphasis
+{
+ font-weight: bold;
+ color: #000000;
+}
+.articleHeader {
+ padding-bottom: 4px;
+ border-bottom: #808080 dotted 1px;
+}
+.headerCell {
+ background-color: #689090;
+ color: white;
+ padding: 4px;
+}
+.navbarText {
+ color: white;
+ font-weight: bold;
+}
+.articleTable {
+ border: #808080 solid 1px;
+ background-color: white;
+ padding: 15px;
+}
+
+
+
+-->
+</style></head><body>
+
+
+
+
+
+
+
+
+
+
+<div align="center">
+ <center>
+ <table border="0" cellpadding="0" cellspacing="0" width="700">
+ <tbody><tr>
+ <td><p class="bigText">TK's Sleater-Kinney pages</p>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <table class="articleTable" border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+ <td class="headerCell" align="right"><p class="navbarText">
+ : <a class="navbarText" href="http://tk-jk.net/S-K/index.html"> <span class="1home">Home</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Archive.html"> <span class="2archive">Archive</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Tabs.html"> <span class="3tabs">Tabs</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/Drumtabs.html"> <span class="4drumtabs">Drum tabs</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/SetLists.html"> <span class="5setlists">Set Lists</span></a>
+ : <a class="navbarText" href="http://tk-jk.net/S-K/blog/general/About.html"> <span class="6about">About</span></a> :
+</p>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <div align="left">
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tbody><tr>
+ <td width="100%">
+<p class="bigText"><strong>All Hands on the Bad One</strong>
+</p>
+<p></p><p><strong>All Hands on the Bad One, <em>"All Hands on the Bad One"</em></strong></p>
+<p dir="ltr">This, followed by <a href="http://tk-jk.net/S-K/Tabs/YouthDecay.html">Youth Decay</a> - wow - most bands would be happy just to have just one of these songs. S-K has a boatload.</p>
+<p dir="ltr">Tabbing this was very frustrating. Riff 3 is a
+masterpiece of guitar and vocal harmony, rhythm and guitar
+interplay. It has a bass intro and stacks guitars and vocals on
+top. This is the definition of Sleater-Kinney magic. You can't get
+it all done on two guitars. You can get close. I
+haven't done it justice.</p>
+<p dir="ltr">As usual the guitar 2's rhythms are vital to the S-K sound. It's still good if you don't get it just right.</p>
+<p dir="ltr">I sure hope I've done this well enough for you to figure out.</p>
+<ul dir="ltr">
+<li>
+<div style="margin-right: 0px;">Quarter note = 153 or so.</div>
+</li><li>
+<div style="margin-right: 0px;">Standard tuning. But tuning down 3 half steps makes Riff 3 easier and more authentic.</div>
+</li><li>
+<div style="margin-right: 0px;">Tabbed where it felt good on the fretboard but there are alternatives.</div>
+</li><li>
+<div style="margin-right: 0px;">Tuning down is required for authenticity but it sounds great in standard tuning</div></li></ul><strong>
+</strong><p dir="ltr"><strong><br>1:</strong></p>
+<p></p><span><img alt="All Hands on the Bad One Riff 1" src="AllHandsontheBadOne_files/AHOTBO1.jpg" cd:pos="2" align="left" border="0" height="500" hspace="4" width="580"><br clear="all"></span>
+<p><strong></strong> </p>
+<p><strong>2:</strong></p><span><img alt="All Hands on the Bad One Riff 2" src="AllHandsontheBadOne_files/AHOTBO2.jpg" cd:pos="2" align="left" border="0" height="257" hspace="4" width="580"><br clear="all"></span>
+<p><strong></strong> </p>
+<p><strong>Riff 3 (3a and 3b) is a little masterpeice:</strong></p><span><img alt="All Hands on the Bad One Riff 3a" src="AllHandsontheBadOne_files/AHOTBO3a.jpg" cd:pos="2" align="left" border="0" height="446" hspace="4" width="580"><br clear="all"></span>
+<p><span><img alt="All Hands on the Bad One Riff 3b" src="AllHandsontheBadOne_files/AHOTBO3b.jpg" cd:pos="2" align="left" border="0" height="179" hspace="4" width="580"><br clear="all"></span></p>
+<p> </p>
+<p>The Outro</p>
+<p><span><img alt="All Hands on the Bad One Riff 4" src="AllHandsontheBadOne_files/AHOTBO4.jpg" cd:pos="2" align="left" border="0" height="216" hspace="4" width="580"><br clear="all"></span></p><p></p>
+ </td>
+ </tr>
+ </tbody></table>
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td valign="top">
+ <hr>
+ <center>
+ <div class="smallText">
+ The views expressed within this site pretty much represent those of the author.<br>
+ Copyright (c) 2002 <a href="http://tk-jk.net/">Terry Kearns</a>. All rights reserved.
+ </div>
+ </center>
+ </td>
+ </tr>
+ </tbody></table>
+ </td>
+ </tr>
+ </tbody></table>
+ </center>
+</div>
+</body></html>
\ No newline at end of file diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib b/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib Binary files differnew file mode 100644 index 0000000000..080b63c520 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test_bug399727.zlib diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff b/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff Binary files differnew file mode 100644 index 0000000000..6b04657580 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test_bug446708/thumbs/st14-1.tiff diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz b/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz Binary files differnew file mode 100644 index 0000000000..f990c6e519 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test_bug717061.gz diff --git a/modules/libjar/zipwriter/test/unit/data/test_bug717061.html b/modules/libjar/zipwriter/test/unit/data/test_bug717061.html new file mode 100644 index 0000000000..80ce0319b4 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/data/test_bug717061.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta http-equiv="Content-Language" content="en-us"><title>Lorem Ipsum</title><style type="text/css"> +body { + font-family: verdana, arial; + font-size: 10pt; + color: black; + background-color: #D0E8E8; +} +</style></head><body> +<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> + +<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p> + +<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.</p> + +</body></html> diff --git a/modules/libjar/zipwriter/test/unit/head_zipwriter.js b/modules/libjar/zipwriter/test/unit/head_zipwriter.js new file mode 100644 index 0000000000..f12933d664 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/head_zipwriter.js @@ -0,0 +1,61 @@ +/* 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/. + */ + +const NS_ERROR_IN_PROGRESS = 2152398863; + +const PR_RDONLY = 0x01; +const PR_WRONLY = 0x02; +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_APPEND = 0x10; +const PR_TRUNCATE = 0x20; +const PR_SYNC = 0x40; +const PR_EXCL = 0x80; + +const ZIP_EOCDR_HEADER_SIZE = 22; +const ZIP_FILE_HEADER_SIZE = 30; +const ZIP_CDS_HEADER_SIZE = 46; +const ZIP_METHOD_STORE = 0; +const ZIP_METHOD_DEFLATE = 8; +const ZIP_EXTENDED_TIMESTAMP_SIZE = 9; + +const PR_USEC_PER_MSEC = 1000; +const PR_USEC_PER_SEC = 1000000; +const PR_MSEC_PER_SEC = 1000; + +const DATA_DIR = "data/"; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var ioSvc = Services.io; + +var ZipWriter = Components.Constructor( + "@mozilla.org/zipwriter;1", + "nsIZipWriter" +); +var ZipReader = Components.Constructor( + "@mozilla.org/libjar/zip-reader;1", + "nsIZipReader", + "open" +); + +var tmpDir = do_get_profile(); +var tmpFile = tmpDir.clone(); +tmpFile.append("zipwriter-test.zip"); +if (tmpFile.exists()) { + tmpFile.remove(true); +} + +var zipW = new ZipWriter(); + +registerCleanupFunction(function() { + try { + zipW.close(); + } catch (e) { + // Just ignore a failure here and attempt to delete the file anyway. + } + if (tmpFile.exists()) { + tmpFile.remove(true); + } +}); diff --git a/modules/libjar/zipwriter/test/unit/test_alignment.js b/modules/libjar/zipwriter/test/unit/test_alignment.js new file mode 100644 index 0000000000..f256f13ed9 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_alignment.js @@ -0,0 +1,118 @@ +/* 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/. + */ + +const DATA = "ZIP WRITER TEST DATA"; +const FILENAME = "test_data.txt"; +const CRC = 0xe6164331; +const time = 1199145600000; // Jan 1st 2008 + +var TESTS = [ + { + name: "test.txt", + compression: Ci.nsIZipWriter.COMPRESSION_DEFAULT, + }, + { + name: "test.png", + compression: Ci.nsIZipWriter.COMPRESSION_NONE, + }, +]; + +function swap16(n) { + return (((n >> 8) & 0xff) << 0) | (((n >> 0) & 0xff) << 8); +} + +function swap32(n) { + return ( + (((n >> 24) & 0xff) << 0) | + (((n >> 16) & 0xff) << 8) | + (((n >> 8) & 0xff) << 16) | + (((n >> 0) & 0xff) << 24) + ); +} + +function move_to_data(bis, offset) { + bis.readBytes(18); // Move to compressed size + var size = swap32(bis.read32()); + bis.readBytes(4); + var file_len = swap16(bis.read16()); + var extra_len = swap16(bis.read16()); + bis.readBytes(file_len); + bis.readBytes(extra_len); + offset += ZIP_FILE_HEADER_SIZE + file_len + extra_len; + + return { offset, size }; +} + +function test_alignment(align_size) { + // Create zip for testing. + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + for (var i = 0; i < TESTS.length; i++) { + var source = do_get_file(DATA_DIR + TESTS[i].name); + zipW.addEntryFile(TESTS[i].name, TESTS[i].compression, source, false); + } + var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(DATA, DATA.length); + zipW.addEntryStream( + FILENAME, + time * PR_USEC_PER_MSEC, + Ci.nsIZipWriter.COMPRESSION_NONE, + stream, + false + ); + zipW.alignStoredFiles(align_size); + zipW.close(); + + // Check data can be decompressed. + var zipR = new ZipReader(tmpFile); + stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(zipR.getInputStream(FILENAME)); + var result = stream.read(DATA.length); + Assert.equal(result, DATA); + stream.close(); + zipR.close(); + + // Check data is correct and aligned. + var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fis.init(tmpFile, -1, -1, null); + let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + bis.setInputStream(fis); + var offset = 0; + + var ret = move_to_data(bis, offset); // "test.txt" + offset = ret.offset; + bis.readBytes(ret.size); + offset += ret.size; + + ret = move_to_data(bis, offset); // "test.png" + offset = ret.offset; + Assert.equal(offset % align_size, 0); + bis.readBytes(ret.size); + offset += ret.size; + + ret = move_to_data(bis, offset); // "test_data.txt" + offset = ret.offset; + result = bis.readBytes(DATA.length); + Assert.equal(result, DATA); + Assert.equal(offset % align_size, 0); + + fis.close(); + bis.close(); +} + +function run_test() { + test_alignment(2); + test_alignment(4); + test_alignment(16); + test_alignment(4096); + test_alignment(32768); +} diff --git a/modules/libjar/zipwriter/test/unit/test_asyncadd.js b/modules/libjar/zipwriter/test/unit/test_asyncadd.js new file mode 100644 index 0000000000..ef70d292d7 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_asyncadd.js @@ -0,0 +1,111 @@ +/* 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/. + */ + +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +// Values taken from using zipinfo to list the test.zip contents +var TESTS = [ + { + name: "test.txt", + size: 232, + crc: 0x0373ac26, + }, + { + name: "test.png", + size: 3402, + crc: 0x504a5c30, + }, +]; + +var size = 0; + +var observer = { + onStartRequest(request) {}, + + onStopRequest(request, status) { + Assert.equal(status, Cr.NS_OK); + + zipW.close(); + size += ZIP_EOCDR_HEADER_SIZE; + + Assert.equal(size, tmpFile.fileSize); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + + for (var i = 0; i < TESTS.length; i++) { + var source = do_get_file(DATA_DIR + TESTS[i].name); + for (let method in methods) { + var entryName = method + "/" + TESTS[i].name; + Assert.ok(zipR.hasEntry(entryName)); + + var entry = zipR.getEntry(entryName); + Assert.equal(entry.realSize, TESTS[i].size); + Assert.equal(entry.size, TESTS[i].size); + Assert.equal(entry.CRC32, TESTS[i].crc); + Assert.equal( + Math.floor(entry.lastModifiedTime / PR_USEC_PER_SEC), + Math.floor(source.lastModifiedTime / PR_MSEC_PER_SEC) + ); + + zipR.test(entryName); + } + } + + zipR.close(); + do_test_finished(); + }, +}; + +var methods = { + file: function method_file(entry, source) { + zipW.addEntryFile(entry, Ci.nsIZipWriter.COMPRESSION_NONE, source, true); + }, + channel: function method_channel(entry, source) { + zipW.addEntryChannel( + entry, + source.lastModifiedTime * PR_MSEC_PER_SEC, + Ci.nsIZipWriter.COMPRESSION_NONE, + NetUtil.newChannel({ + uri: ioSvc.newFileURI(source), + loadUsingSystemPrincipal: true, + }), + true + ); + }, + stream: function method_stream(entry, source) { + zipW.addEntryStream( + entry, + source.lastModifiedTime * PR_MSEC_PER_SEC, + Ci.nsIZipWriter.COMPRESSION_NONE, + NetUtil.newChannel({ + uri: ioSvc.newFileURI(source), + loadUsingSystemPrincipal: true, + }).open(), + true + ); + }, +}; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + for (var i = 0; i < TESTS.length; i++) { + var source = do_get_file(DATA_DIR + TESTS[i].name); + for (let method in methods) { + var entry = method + "/" + TESTS[i].name; + methods[method](entry, source); + size += + ZIP_FILE_HEADER_SIZE + + ZIP_CDS_HEADER_SIZE + + ZIP_EXTENDED_TIMESTAMP_SIZE * 2 + + entry.length * 2 + + TESTS[i].size; + } + } + do_test_pending(); + zipW.processQueue(observer, null); + Assert.ok(zipW.inQueue); +} diff --git a/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js b/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js new file mode 100644 index 0000000000..7d1c4ec610 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_asyncbadadd.js @@ -0,0 +1,31 @@ +/* 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/. + */ + +const FILENAME = "missing.txt"; + +var observer = { + onStartRequest(request) {}, + + onStopRequest(request, status) { + Assert.equal(status, Cr.NS_ERROR_FILE_NOT_FOUND); + zipW.close(); + Assert.equal(ZIP_EOCDR_HEADER_SIZE, tmpFile.fileSize); + do_test_finished(); + }, +}; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + var source = tmpDir.clone(); + source.append(FILENAME); + zipW.addEntryFile(FILENAME, Ci.nsIZipWriter.COMPRESSION_NONE, source, true); + + do_test_pending(); + zipW.processQueue(observer, null); + + // With nothing to actually do the queue would have completed immediately + Assert.ok(!zipW.inQueue); +} diff --git a/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js b/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js new file mode 100644 index 0000000000..623bc46c59 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_asyncbadremove.js @@ -0,0 +1,27 @@ +/* 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/. + */ + +const FILENAME = "missing.txt"; + +var observer = { + onStartRequest(request) {}, + + onStopRequest(request, status) { + Assert.equal(status, Cr.NS_ERROR_FILE_NOT_FOUND); + zipW.close(); + Assert.equal(ZIP_EOCDR_HEADER_SIZE, tmpFile.fileSize); + do_test_finished(); + }, +}; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + zipW.removeEntry(FILENAME, true); + do_test_pending(); + zipW.processQueue(observer, null); + + // With nothing to actually do the queue would have completed immediately + Assert.ok(!zipW.inQueue); +} diff --git a/modules/libjar/zipwriter/test/unit/test_asyncremove.js b/modules/libjar/zipwriter/test/unit/test_asyncremove.js new file mode 100644 index 0000000000..7342e18e4f --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_asyncremove.js @@ -0,0 +1,40 @@ +/* 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/. + */ + +var TESTS = ["test.txt", "test.png"]; + +var observer = { + onStartRequest(request) {}, + + onStopRequest(request, status) { + Assert.equal(status, Cr.NS_OK); + + zipW.close(); + + // Empty zip file should just be the end of central directory marker + var newTmpFile = tmpFile.clone(); + Assert.equal(newTmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE); + do_test_finished(); + }, +}; + +function run_test() { + // Copy our test zip to the tmp dir so we can modify it + var testzip = do_get_file(DATA_DIR + "test.zip"); + testzip.copyTo(tmpDir, tmpFile.leafName); + + Assert.ok(tmpFile.exists()); + + zipW.open(tmpFile, PR_RDWR); + + for (var i = 0; i < TESTS.length; i++) { + Assert.ok(zipW.hasEntry(TESTS[i])); + zipW.removeEntry(TESTS[i], true); + } + + do_test_pending(); + zipW.processQueue(observer, null); + Assert.ok(zipW.inQueue); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug399727.js b/modules/libjar/zipwriter/test/unit/test_bug399727.js new file mode 100644 index 0000000000..2967f69737 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug399727.js @@ -0,0 +1,107 @@ +/* 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/. + */ + +function BinaryComparer(file, callback) { + var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + this.length = file.fileSize; + this.fileStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + this.fileStream.setInputStream(fstream); + this.offset = 0; + this.callback = callback; +} + +BinaryComparer.prototype = { + fileStream: null, + offset: null, + length: null, + callback: null, + + onStartRequest(aRequest) {}, + + onStopRequest(aRequest, aStatusCode) { + this.fileStream.close(); + Assert.equal(aStatusCode, Cr.NS_OK); + Assert.equal(this.offset, this.length); + this.callback(); + }, + + onDataAvailable(aRequest, aInputStream, aOffset, aCount) { + var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + stream.setInputStream(aInputStream); + var source, actual; + for (var i = 0; i < aCount; i++) { + try { + source = this.fileStream.read8(); + } catch (e) { + do_throw("Unable to read from file at offset " + this.offset + " " + e); + } + try { + actual = stream.read8(); + } catch (e) { + do_throw( + "Unable to read from converted stream at offset " + + this.offset + + " " + + e + ); + } + if (source != actual) { + do_throw( + "Invalid value " + + actual + + " at offset " + + this.offset + + ", should have been " + + source + ); + } + this.offset++; + } + }, +}; + +function comparer_callback() { + do_test_finished(); +} + +function run_test() { + var source = do_get_file(DATA_DIR + "test_bug399727.html"); + var comparer = new BinaryComparer( + do_get_file(DATA_DIR + "test_bug399727.zlib"), + comparer_callback + ); + + // Prepare the stream converter + var scs = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + var converter = scs.asyncConvertData( + "uncompressed", + "deflate", + comparer, + null + ); + + // Open the expected output file + var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(source, -1, 0, 0); + + // Set up a pump to push data from the file to the stream converter + var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance( + Ci.nsIInputStreamPump + ); + pump.init(fstream, 0, 0, true); + pump.asyncRead(converter); + do_test_pending(); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug419769_1.js b/modules/libjar/zipwriter/test/unit/test_bug419769_1.js new file mode 100644 index 0000000000..0e4e70fe01 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug419769_1.js @@ -0,0 +1,75 @@ +/* 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/. + */ + +const DATA = ""; +const FILENAME = "test.txt"; +const CRC = 0x00000000; +// XXX Must use a constant time here away from DST changes. See bug 402434. +const time = 1199145600000; // Jan 1st 2008 + +function testpass(source) { + // Should exist. + Assert.ok(source.hasEntry(FILENAME)); + + var entry = source.getEntry(FILENAME); + Assert.notEqual(entry, null); + + Assert.ok(!entry.isDirectory); + + // Should be stored + Assert.equal(entry.compression, ZIP_METHOD_DEFLATE); + + Assert.equal(entry.lastModifiedTime / PR_USEC_PER_MSEC, time); + + // File size should match our data size. + Assert.equal(entry.realSize, DATA.length); + + // Check that the CRC is accurate + Assert.equal(entry.CRC32, CRC); +} + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + // Shouldn't be there to start with. + Assert.ok(!zipW.hasEntry(FILENAME)); + + Assert.ok(!zipW.inQueue); + + var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(DATA, DATA.length); + zipW.addEntryStream( + FILENAME, + time * PR_USEC_PER_MSEC, + Ci.nsIZipWriter.COMPRESSION_BEST, + stream, + false + ); + + // Check that zip state is right at this stage. + testpass(zipW); + zipW.close(); + + // Check to see if we get the same results loading afresh. + zipW.open(tmpFile, PR_RDWR); + testpass(zipW); + zipW.close(); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + testpass(zipR); + zipR.test(FILENAME); + stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(zipR.getInputStream(FILENAME)); + var result = stream.read(DATA.length); + stream.close(); + zipR.close(); + + Assert.equal(result, DATA); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug419769_2.js b/modules/libjar/zipwriter/test/unit/test_bug419769_2.js new file mode 100644 index 0000000000..a773188d75 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug419769_2.js @@ -0,0 +1,63 @@ +/* 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/. + */ + +const DATA = ""; +const FILENAME = "test.txt"; +const CRC = 0x00000000; +const time = Date.now(); + +function testpass(source) { + // Should exist. + Assert.ok(source.hasEntry(FILENAME)); + + var entry = source.getEntry(FILENAME); + Assert.notEqual(entry, null); + + Assert.ok(!entry.isDirectory); + + // Should be stored + Assert.equal(entry.compression, ZIP_METHOD_DEFLATE); + + // File size should match our data size. + Assert.equal(entry.realSize, DATA.length); + + // Check that the CRC is accurate + Assert.equal(entry.CRC32, CRC); +} + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + // Shouldn't be there to start with. + Assert.ok(!zipW.hasEntry(FILENAME)); + + Assert.ok(!zipW.inQueue); + + var file = do_get_file(DATA_DIR + "emptyfile.txt"); + zipW.addEntryFile(FILENAME, Ci.nsIZipWriter.COMPRESSION_BEST, file, false); + + // Check that zip state is right at this stage. + testpass(zipW); + zipW.close(); + + // Check to see if we get the same results loading afresh. + zipW.open(tmpFile, PR_RDWR); + testpass(zipW); + zipW.close(); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + testpass(zipR); + zipR.test(FILENAME); + var stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(zipR.getInputStream(FILENAME)); + var result = stream.read(DATA.length); + stream.close(); + zipR.close(); + + Assert.equal(result, DATA); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug425768.js b/modules/libjar/zipwriter/test/unit/test_bug425768.js new file mode 100644 index 0000000000..d3a9b30ba6 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug425768.js @@ -0,0 +1,33 @@ +/* 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/. + */ + +const DIRNAME = "test/"; +const time = Date.now(); + +function run_test() { + // Copy in the test file. + var source = do_get_file("data/test.zip"); + source.copyTo(tmpFile.parent, tmpFile.leafName); + + // Open it and add something so the CDS is rewritten. + zipW.open(tmpFile, PR_RDWR | PR_APPEND); + zipW.addEntryDirectory(DIRNAME, time * PR_USEC_PER_MSEC, false); + Assert.ok(zipW.hasEntry(DIRNAME)); + zipW.close(); + + var zipR = new ZipReader(tmpFile); + Assert.ok(zipR.hasEntry(DIRNAME)); + zipR.close(); + + // Adding the directory would have added a fixed amount to the file size. + // Any difference suggests the CDS was written out incorrectly. + var extra = + ZIP_FILE_HEADER_SIZE + + ZIP_CDS_HEADER_SIZE + + DIRNAME.length * 2 + + ZIP_EXTENDED_TIMESTAMP_SIZE * 2; + + Assert.equal(source.fileSize + extra, tmpFile.fileSize); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug433248.js b/modules/libjar/zipwriter/test/unit/test_bug433248.js new file mode 100644 index 0000000000..7b8c0525b8 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug433248.js @@ -0,0 +1,68 @@ +/* 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/. + */ + +function run_test() { + // zipW is an uninitialised zipwriter at this point. + try { + zipW.file; + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.comment; + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.comment = "test"; + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.addEntryDirectory("test", 0, false); + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.addEntryFile( + "test", + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + tmpDir, + false + ); + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.removeEntry("test", false); + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.processQueue(null, null); + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + try { + zipW.close(); + do_throw("Should have thrown uninitialized error."); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug446708.js b/modules/libjar/zipwriter/test/unit/test_bug446708.js new file mode 100644 index 0000000000..cff67d848a --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug446708.js @@ -0,0 +1,39 @@ +function run_test() { + var testBundle = do_get_file("data/test_bug446708"); + + RecursivelyZipDirectory(testBundle); +} + +// Add |file| to the zip. |path| is the current path for the file. +function AddToZip(zipWriter, path, file) { + var currentPath = path + file.leafName; + + if (file.isDirectory()) { + currentPath += "/"; + } + + // THIS IS WHERE THE ERROR OCCURS, FOR THE FILE "st14-1.tiff" IN "test_bug446708" + zipWriter.addEntryFile( + currentPath, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + file, + false + ); + + // if it's a dir, continue adding its contents recursively... + if (file.isDirectory()) { + var entries = file.QueryInterface(Ci.nsIFile).directoryEntries; + while (entries.hasMoreElements()) { + var entry = entries.nextFile; + AddToZip(zipWriter, currentPath, entry); + } + } + + // ...otherwise, we're done +} + +function RecursivelyZipDirectory(bundle) { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + AddToZip(zipW, "", bundle); + zipW.close(); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug467740.js b/modules/libjar/zipwriter/test/unit/test_bug467740.js new file mode 100644 index 0000000000..b0dc2bf01b --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug467740.js @@ -0,0 +1,35 @@ +/* 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/. + */ + +function run_test() { + // In this test we try to open some files that aren't archives: + // - An empty file, that is certainly not an archive. + // - A file that couldn't be mistaken for archive, since it is too small. + // - A file that could be mistaken for archive, if we checked only the file + // size, but is invalid since it contains no ZIP signature. + var invalidArchives = ["emptyfile.txt", "smallfile.txt", "test.png"]; + + invalidArchives.forEach(function(invalidArchive) { + // Get a reference to the invalid file + var invalidFile = do_get_file(DATA_DIR + invalidArchive); + + // Opening the invalid file should fail (but not crash) + try { + zipW.open(invalidFile, PR_RDWR); + do_throw( + "Should have thrown NS_ERROR_FILE_CORRUPTED on " + invalidArchive + " !" + ); + } catch (e) { + if ( + !( + e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FILE_CORRUPTED + ) + ) { + throw e; + } + // do nothing + } + }); +} diff --git a/modules/libjar/zipwriter/test/unit/test_bug717061.js b/modules/libjar/zipwriter/test/unit/test_bug717061.js new file mode 100644 index 0000000000..cb3faa3aa7 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_bug717061.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0 + */ + +function BinaryComparer(file, callback) { + var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + this.length = file.fileSize; + this.fileStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + this.fileStream.setInputStream(fstream); + this.offset = 0; + this.callback = callback; +} + +BinaryComparer.prototype = { + fileStream: null, + offset: null, + length: null, + callback: null, + + onStartRequest(aRequest) {}, + + onStopRequest(aRequest, aStatusCode) { + this.fileStream.close(); + Assert.equal(aStatusCode, Cr.NS_OK); + Assert.equal(this.offset, this.length); + this.callback(); + }, + + onDataAvailable(aRequest, aInputStream, aOffset, aCount) { + var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + stream.setInputStream(aInputStream); + var source, actual; + for (var i = 0; i < aCount; i++) { + try { + source = this.fileStream.read8(); + } catch (e) { + do_throw("Unable to read from file at offset " + this.offset + " " + e); + } + try { + actual = stream.read8(); + } catch (e) { + do_throw( + "Unable to read from converted stream at offset " + + this.offset + + " " + + e + ); + } + // The byte at offset 9 is the OS byte (see RFC 1952, section 2.3), which + // can legitimately differ when the source is compressed on different + // operating systems. The actual .gz for this test was created on a Unix + // system, but we want the test to work correctly everywhere. So ignore + // the byte at offset 9. + if (this.offset != 9 && source != actual) { + do_throw( + "Invalid value " + + actual + + " at offset " + + this.offset + + ", should have been " + + source + ); + } + this.offset++; + } + }, +}; + +function comparer_callback() { + do_test_finished(); +} + +function run_test() { + var source = do_get_file(DATA_DIR + "test_bug717061.html"); + var comparer = new BinaryComparer( + do_get_file(DATA_DIR + "test_bug717061.gz"), + comparer_callback + ); + + // Prepare the stream converter + var scs = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + var converter = scs.asyncConvertData("uncompressed", "gzip", comparer, null); + + // Open the expected output file + var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(source, -1, 0, 0); + + // Set up a pump to push data from the file to the stream converter + var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance( + Ci.nsIInputStreamPump + ); + pump.init(fstream, 0, 0, true); + pump.asyncRead(converter); + do_test_pending(); +} diff --git a/modules/libjar/zipwriter/test/unit/test_createempty.js b/modules/libjar/zipwriter/test/unit/test_createempty.js new file mode 100644 index 0000000000..464c3b243f --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_createempty.js @@ -0,0 +1,15 @@ +/* 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/. + */ + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + zipW.close(); + + // Should have created a zip file + Assert.ok(tmpFile.exists()); + + // Empty zip file should just be the end of central directory marker + Assert.equal(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE); +} diff --git a/modules/libjar/zipwriter/test/unit/test_deflatedata.js b/modules/libjar/zipwriter/test/unit/test_deflatedata.js new file mode 100644 index 0000000000..a8a5a0c536 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_deflatedata.js @@ -0,0 +1,59 @@ +/* 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/. + */ + +const DATA = "ZIP WRITER TEST DATA"; +const FILENAME = "test.txt"; +const CRC = 0xe6164331; +// XXX Must use a constant time here away from DST changes. See bug 402434. +const time = 1199145600000; // Jan 1st 2008 + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + // Shouldn't be there to start with. + Assert.ok(!zipW.hasEntry(FILENAME)); + + Assert.ok(!zipW.inQueue); + + var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(DATA, DATA.length); + zipW.addEntryStream( + FILENAME, + time * PR_USEC_PER_MSEC, + Ci.nsIZipWriter.COMPRESSION_BEST, + stream, + false + ); + + var entry = zipW.getEntry(FILENAME); + + Assert.ok(entry != null); + + // Check entry seems right. + Assert.equal(entry.compression, ZIP_METHOD_DEFLATE); + Assert.equal(entry.CRC32, CRC); + Assert.equal(entry.realSize, DATA.length); + Assert.equal(entry.lastModifiedTime / PR_USEC_PER_MSEC, time); + + zipW.close(); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + Assert.ok(zipR.hasEntry(FILENAME)); + + zipR.test(FILENAME); + + stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(zipR.getInputStream(FILENAME)); + var result = stream.read(DATA.length); + stream.close(); + zipR.close(); + + Assert.equal(result, DATA); +} diff --git a/modules/libjar/zipwriter/test/unit/test_directory.js b/modules/libjar/zipwriter/test/unit/test_directory.js new file mode 100644 index 0000000000..70e93bbf08 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_directory.js @@ -0,0 +1,26 @@ +/* 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/. + */ + +const DIRNAME1 = "test"; +const DIRNAME1_CORRECT = "test/"; +const DIRNAME2 = "test2/"; +const time = Date.now(); + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + zipW.addEntryDirectory(DIRNAME1, time * PR_USEC_PER_MSEC, false); + Assert.ok(!zipW.hasEntry(DIRNAME1)); + Assert.ok(zipW.hasEntry(DIRNAME1_CORRECT)); + var entry = zipW.getEntry(DIRNAME1_CORRECT); + Assert.ok(entry.isDirectory); + + zipW.addEntryDirectory(DIRNAME2, time * PR_USEC_PER_MSEC, false); + Assert.ok(zipW.hasEntry(DIRNAME2)); + entry = zipW.getEntry(DIRNAME2); + Assert.ok(entry.isDirectory); + + zipW.close(); +} diff --git a/modules/libjar/zipwriter/test/unit/test_editexisting.js b/modules/libjar/zipwriter/test/unit/test_editexisting.js new file mode 100644 index 0000000000..c58447260e --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_editexisting.js @@ -0,0 +1,60 @@ +/* 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/. + */ + +// Values taken from using zipinfo to list the test.zip contents +var TESTS = [ + { + name: "test.txt", + size: 232, + crc: 0x0373ac26, + time: Date.UTC(2007, 4, 1, 20, 44, 55), + }, + { + name: "test.png", + size: 3402, + crc: 0x504a5c30, + time: Date.UTC(2007, 4, 1, 20, 49, 39), + }, +]; +var BADENTRY = "unknown.txt"; + +function run_test() { + // Copy our test zip to the tmp dir so we can modify it + var testzip = do_get_file(DATA_DIR + "test.zip"); + testzip.copyTo(tmpDir, tmpFile.leafName); + + Assert.ok(tmpFile.exists()); + + zipW.open(tmpFile, PR_RDWR); + + for (let i = 0; i < TESTS.length; i++) { + Assert.ok(zipW.hasEntry(TESTS[i].name)); + var entry = zipW.getEntry(TESTS[i].name); + Assert.ok(entry != null); + + Assert.equal(entry.realSize, TESTS[i].size); + Assert.equal(entry.CRC32, TESTS[i].crc); + Assert.equal(entry.lastModifiedTime / PR_USEC_PER_MSEC, TESTS[i].time); + } + + try { + zipW.removeEntry(BADENTRY, false); + do_throw("shouldn't be able to remove an entry that doesn't exist"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_FILE_NOT_FOUND); + } + + for (let i = 0; i < TESTS.length; i++) { + zipW.removeEntry(TESTS[i].name, false); + } + + zipW.close(); + + // Certain platforms cache the file size so get a fresh file to check. + tmpFile = tmpFile.clone(); + + // Empty zip file should just be the end of central directory marker + Assert.equal(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE); +} diff --git a/modules/libjar/zipwriter/test/unit/test_storedata.js b/modules/libjar/zipwriter/test/unit/test_storedata.js new file mode 100644 index 0000000000..bc71caabd5 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_storedata.js @@ -0,0 +1,88 @@ +/* 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/. + */ + +const DATA = "ZIP WRITER TEST DATA"; +const FILENAME = "test.txt"; +const FILENAME2 = "test2.txt"; +const CRC = 0xe6164331; +// XXX Must use a constant time here away from DST changes. See bug 402434. +const time = 1199145600000; // Jan 1st 2008 + +function testpass(source) { + // Should exist. + Assert.ok(source.hasEntry(FILENAME)); + + var entry = source.getEntry(FILENAME); + Assert.notEqual(entry, null); + + Assert.ok(!entry.isDirectory); + + // Should be stored + Assert.equal(entry.compression, ZIP_METHOD_STORE); + + Assert.equal(entry.lastModifiedTime / PR_USEC_PER_MSEC, time); + + // File size should match our data size. + Assert.equal(entry.realSize, DATA.length); + // When stored sizes should match. + Assert.equal(entry.size, entry.realSize); + + // Check that the CRC is accurate + Assert.equal(entry.CRC32, CRC); +} + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + // Shouldn't be there to start with. + Assert.ok(!zipW.hasEntry(FILENAME)); + + Assert.ok(!zipW.inQueue); + + var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(DATA, DATA.length); + zipW.addEntryStream( + FILENAME, + time * PR_USEC_PER_MSEC, + Ci.nsIZipWriter.COMPRESSION_NONE, + stream, + false + ); + + // Check that zip state is right at this stage. + testpass(zipW); + zipW.close(); + + Assert.equal( + tmpFile.fileSize, + DATA.length + + ZIP_FILE_HEADER_SIZE + + ZIP_CDS_HEADER_SIZE + + ZIP_EXTENDED_TIMESTAMP_SIZE * 2 + + FILENAME.length * 2 + + ZIP_EOCDR_HEADER_SIZE + ); + + // Check to see if we get the same results loading afresh. + zipW.open(tmpFile, PR_RDWR); + testpass(zipW); + zipW.close(); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + testpass(zipR); + zipR.test(FILENAME); + stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + stream.init(zipR.getInputStream(FILENAME)); + var result = stream.read(DATA.length); + stream.close(); + zipR.close(); + + Assert.equal(result, DATA); +} diff --git a/modules/libjar/zipwriter/test/unit/test_sync.js b/modules/libjar/zipwriter/test/unit/test_sync.js new file mode 100644 index 0000000000..39a27db548 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_sync.js @@ -0,0 +1,65 @@ +/* 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/. + */ + +// Values taken from using zipinfo to list the test.zip contents +var TESTS = [ + { + name: "test.txt", + size: 232, + crc: 0x0373ac26, + }, + { + name: "test.png", + size: 3402, + crc: 0x504a5c30, + }, +]; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + var size = 0; + for (let i = 0; i < TESTS.length; i++) { + let source = do_get_file(DATA_DIR + TESTS[i].name); + zipW.addEntryFile( + TESTS[i].name, + Ci.nsIZipWriter.COMPRESSION_NONE, + source, + false + ); + size += + ZIP_FILE_HEADER_SIZE + + ZIP_CDS_HEADER_SIZE + + ZIP_EXTENDED_TIMESTAMP_SIZE * 2 + + TESTS[i].name.length * 2 + + TESTS[i].size; + } + + zipW.close(); + size += ZIP_EOCDR_HEADER_SIZE; + + Assert.equal(size, tmpFile.fileSize); + + // Test the stored data with the zipreader + var zipR = new ZipReader(tmpFile); + + for (let i = 0; i < TESTS.length; i++) { + let source = do_get_file(DATA_DIR + TESTS[i].name); + Assert.ok(zipR.hasEntry(TESTS[i].name)); + + var entry = zipR.getEntry(TESTS[i].name); + Assert.equal(entry.realSize, TESTS[i].size); + Assert.equal(entry.size, TESTS[i].size); + Assert.equal(entry.CRC32, TESTS[i].crc); + Assert.equal( + Math.floor(entry.lastModifiedTime / PR_USEC_PER_SEC), + Math.floor(source.lastModifiedTime / PR_MSEC_PER_SEC) + ); + + zipR.test(TESTS[i].name); + } + + zipR.close(); +} diff --git a/modules/libjar/zipwriter/test/unit/test_undochange.js b/modules/libjar/zipwriter/test/unit/test_undochange.js new file mode 100644 index 0000000000..bb1de36b1d --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_undochange.js @@ -0,0 +1,45 @@ +/* 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/. + */ + +// Values taken from using zipinfo to list the test.zip contents +var TESTS = ["test.txt", "test.png"]; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + + for (let i = 0; i < TESTS.length; i++) { + let source = do_get_file(DATA_DIR + TESTS[i]); + zipW.addEntryFile( + TESTS[i], + Ci.nsIZipWriter.COMPRESSION_NONE, + source, + false + ); + } + + try { + let source = do_get_file(DATA_DIR + TESTS[0]); + zipW.addEntryFile( + TESTS[0], + Ci.nsIZipWriter.COMPRESSION_NONE, + source, + false + ); + do_throw("Should not be able to add the same file twice"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_FILE_ALREADY_EXISTS); + } + + // Remove all the tests and see if we are left with an empty zip + for (let i = 0; i < TESTS.length; i++) { + zipW.removeEntry(TESTS[i], false); + } + + zipW.close(); + + // Empty zip file should just be the end of central directory marker + var newTmpFile = tmpFile.clone(); + Assert.equal(newTmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE); +} diff --git a/modules/libjar/zipwriter/test/unit/test_zipcomment.js b/modules/libjar/zipwriter/test/unit/test_zipcomment.js new file mode 100644 index 0000000000..afe0fd7886 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_zipcomment.js @@ -0,0 +1,33 @@ +/* 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/. + */ + +const DATA = "ZIP WRITER TEST COMMENT"; +const DATA2 = "ANOTHER ONE"; + +function run_test() { + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + zipW.comment = DATA; + zipW.close(); + + // Should have created a zip file + Assert.ok(tmpFile.exists()); + + // Empty zip file should just be the end of central directory marker + // and comment + Assert.equal(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE + DATA.length); + + zipW.open(tmpFile, PR_RDWR); + // Should have the set comment + Assert.equal(zipW.comment, DATA); + zipW.comment = DATA2; + zipW.close(); + + // Certain platforms cache the file size so get a fresh file to check. + tmpFile = tmpFile.clone(); + + // Empty zip file should just be the end of central directory marker + // and comment. This should now be shorter + Assert.equal(tmpFile.fileSize, ZIP_EOCDR_HEADER_SIZE + DATA2.length); +} diff --git a/modules/libjar/zipwriter/test/unit/test_zippermissions.js b/modules/libjar/zipwriter/test/unit/test_zippermissions.js new file mode 100644 index 0000000000..4bc65407ef --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/test_zippermissions.js @@ -0,0 +1,104 @@ +/* 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/. + */ + +const DATA = "ZIP WRITER TEST DATA"; + +var TESTS = []; + +function build_tests() { + var id = 0; + + // Minimum mode is 0o400 + for (let u = 4; u <= 7; u++) { + for (let g = 0; g <= 7; g++) { + for (let o = 0; o <= 7; o++) { + TESTS[id] = { + name: "test" + u + g + o, + permission: (u << 6) + (g << 3) + o, + }; + id++; + } + } + } +} + +function run_test() { + build_tests(); + + var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + + var tmp = tmpDir.clone(); + tmp.append("temp-permissions"); + tmp.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + + var file = tmp.clone(); + file.append("tempfile"); + + zipW.open(tmpFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + for (let i = 0; i < TESTS.length; i++) { + // Open the file with the permissions to match how the zipreader extracts + // This obeys the umask + foStream.init(file, 0x02 | 0x08 | 0x20, TESTS[i].permission, 0); + foStream.close(); + + // umask may have altered the permissions so test against what they really were. + // This reduces the coverage of the test but there isn't much we can do + var perm = file.permissions & 0xfff; + if (TESTS[i].permission != perm) { + dump( + "File permissions for " + + TESTS[i].name + + " were " + + perm.toString(8) + + "\n" + ); + TESTS[i].permission = perm; + } + + zipW.addEntryFile( + TESTS[i].name, + Ci.nsIZipWriter.COMPRESSION_NONE, + file, + false + ); + Assert.equal( + zipW.getEntry(TESTS[i].name).permissions, + TESTS[i].permission | 0o400 + ); + file.permissions = 0o600; + file.remove(true); + } + zipW.close(); + + zipW.open(tmpFile, PR_RDWR); + for (let i = 0; i < TESTS.length; i++) { + dump("Testing zipwriter file permissions for " + TESTS[i].name + "\n"); + Assert.equal( + zipW.getEntry(TESTS[i].name).permissions, + TESTS[i].permission | 0o400 + ); + } + zipW.close(); + + var zipR = new ZipReader(tmpFile); + for (let i = 0; i < TESTS.length; i++) { + dump("Testing zipreader file permissions for " + TESTS[i].name + "\n"); + Assert.equal( + zipR.getEntry(TESTS[i].name).permissions, + TESTS[i].permission | 0o400 + ); + dump("Testing extracted file permissions for " + TESTS[i].name + "\n"); + zipR.extract(TESTS[i].name, file); + Assert.equal(file.permissions & 0xfff, TESTS[i].permission); + Assert.ok(!file.isDirectory()); + file.permissions = 0o600; + file.remove(true); + } + zipR.close(); + + tmp.remove(true); +} diff --git a/modules/libjar/zipwriter/test/unit/xpcshell.ini b/modules/libjar/zipwriter/test/unit/xpcshell.ini new file mode 100644 index 0000000000..4873a92c17 --- /dev/null +++ b/modules/libjar/zipwriter/test/unit/xpcshell.ini @@ -0,0 +1,36 @@ +[DEFAULT] +head = head_zipwriter.js +support-files = + data/test_bug446708/thumbs/st14-1.tiff + data/emptyfile.txt + data/smallfile.txt + data/test.png + data/test.txt + data/test.zip + data/test_bug399727.html + data/test_bug399727.zlib + data/test_bug717061.gz + data/test_bug717061.html + +[test_asyncadd.js] +[test_asyncbadadd.js] +[test_asyncbadremove.js] +[test_asyncremove.js] +[test_bug399727.js] +[test_bug419769_1.js] +[test_bug419769_2.js] +[test_bug425768.js] +[test_bug433248.js] +[test_bug446708.js] +[test_bug467740.js] +[test_createempty.js] +[test_deflatedata.js] +[test_directory.js] +[test_editexisting.js] +[test_storedata.js] +[test_sync.js] +[test_undochange.js] +[test_zipcomment.js] +[test_zippermissions.js] +[test_bug717061.js] +[test_alignment.js] |