michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ArchiveZipFile.h" michael@0: #include "ArchiveZipEvent.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: #include "zlib.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: USING_FILE_NAMESPACE michael@0: michael@0: #define ZIP_CHUNK 16384 michael@0: michael@0: /** michael@0: * Input stream object for zip files michael@0: */ michael@0: class ArchiveInputStream MOZ_FINAL : public nsIInputStream, michael@0: public nsISeekableStream michael@0: { michael@0: public: michael@0: ArchiveInputStream(uint64_t aParentSize, michael@0: nsIInputStream* aInputStream, michael@0: nsString& aFilename, michael@0: uint32_t aStart, michael@0: uint32_t aLength, michael@0: ZipCentral& aCentral) michael@0: : mCentral(aCentral), michael@0: mFilename(aFilename), michael@0: mStart(aStart), michael@0: mLength(aLength), michael@0: mStatus(NotStarted) michael@0: { michael@0: MOZ_COUNT_CTOR(ArchiveInputStream); michael@0: michael@0: // Reset the data: michael@0: memset(&mData, 0, sizeof(mData)); michael@0: michael@0: mData.parentSize = aParentSize; michael@0: mData.inputStream = aInputStream; michael@0: } michael@0: michael@0: virtual ~ArchiveInputStream() michael@0: { michael@0: MOZ_COUNT_DTOR(ArchiveInputStream); michael@0: Close(); michael@0: } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIINPUTSTREAM michael@0: NS_DECL_NSISEEKABLESTREAM michael@0: michael@0: private: michael@0: nsresult Init(); michael@0: michael@0: private: // data michael@0: ZipCentral mCentral; michael@0: nsString mFilename; michael@0: uint32_t mStart; michael@0: uint32_t mLength; michael@0: michael@0: z_stream mZs; michael@0: michael@0: enum { michael@0: NotStarted, michael@0: Started, michael@0: Done michael@0: } mStatus; michael@0: michael@0: struct { michael@0: uint64_t parentSize; michael@0: nsCOMPtr inputStream; michael@0: michael@0: unsigned char input[ZIP_CHUNK]; michael@0: uint32_t sizeToBeRead; michael@0: uint32_t cursor; michael@0: michael@0: bool compressed; // a zip file can contain stored or compressed files michael@0: } mData; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ArchiveInputStream, michael@0: nsIInputStream, michael@0: nsISeekableStream) michael@0: michael@0: nsresult michael@0: ArchiveInputStream::Init() michael@0: { michael@0: nsresult rv; michael@0: michael@0: memset(&mZs, 0, sizeof(z_stream)); michael@0: int zerr = inflateInit2(&mZs, -MAX_WBITS); michael@0: if (zerr != Z_OK) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: mData.sizeToBeRead = ArchiveZipItem::StrToInt32(mCentral.size); michael@0: michael@0: uint32_t offset = ArchiveZipItem::StrToInt32(mCentral.localhdr_offset); michael@0: michael@0: // The file is corrupt michael@0: if (offset + ZIPLOCAL_SIZE > mData.parentSize) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // From the input stream to a seekable stream michael@0: nsCOMPtr seekableStream; michael@0: seekableStream = do_QueryInterface(mData.inputStream); michael@0: if (!seekableStream) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Seek + read the ZipLocal struct michael@0: seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: uint8_t buffer[ZIPLOCAL_SIZE]; michael@0: uint32_t ret; michael@0: michael@0: rv = mData.inputStream->Read((char*)buffer, ZIPLOCAL_SIZE, &ret); michael@0: if (NS_FAILED(rv) || ret != ZIPLOCAL_SIZE) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Signature check: michael@0: if (ArchiveZipItem::StrToInt32(buffer) != LOCALSIG) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: ZipLocal local; michael@0: memcpy(&local, buffer, ZIPLOCAL_SIZE); michael@0: michael@0: // Seek to the real data: michael@0: offset += ZIPLOCAL_SIZE + michael@0: ArchiveZipItem::StrToInt16(local.filename_len) + michael@0: ArchiveZipItem::StrToInt16(local.extrafield_len); michael@0: michael@0: // The file is corrupt if there is not enough data michael@0: if (offset + mData.sizeToBeRead > mData.parentSize) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Data starts here: michael@0: seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset); michael@0: michael@0: // The file is compressed or not? michael@0: mData.compressed = (ArchiveZipItem::StrToInt16(mCentral.method) != 0); michael@0: michael@0: // We have to skip the first mStart bytes: michael@0: if (mStart != 0) { michael@0: rv = Seek(NS_SEEK_SET, mStart); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::Close() michael@0: { michael@0: if (mStatus != NotStarted) { michael@0: inflateEnd(&mZs); michael@0: mStatus = NotStarted; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::Available(uint64_t* _retval) michael@0: { michael@0: *_retval = mLength - mData.cursor - mStart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::Read(char* aBuffer, michael@0: uint32_t aCount, michael@0: uint32_t* _retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aBuffer); michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: michael@0: nsresult rv; michael@0: michael@0: // This is the first time: michael@0: if (mStatus == NotStarted) { michael@0: mStatus = Started; michael@0: michael@0: rv = Init(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Let's set avail_out to -1 so we read something from the stream. michael@0: mZs.avail_out = (uInt)-1; michael@0: } michael@0: michael@0: // Nothing more can be read michael@0: if (mStatus == Done) { michael@0: *_retval = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Stored file: michael@0: if (!mData.compressed) { michael@0: rv = mData.inputStream->Read(aBuffer, michael@0: (mData.sizeToBeRead > aCount ? michael@0: aCount : mData.sizeToBeRead), michael@0: _retval); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mData.sizeToBeRead -= *_retval; michael@0: mData.cursor += *_retval; michael@0: michael@0: if (mData.sizeToBeRead == 0) { michael@0: mStatus = Done; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // We have nothing ready to be processed: michael@0: if (mZs.avail_out != 0 && mData.sizeToBeRead != 0) { michael@0: uint32_t ret; michael@0: rv = mData.inputStream->Read((char*)mData.input, michael@0: (mData.sizeToBeRead > sizeof(mData.input) ? michael@0: sizeof(mData.input) : mData.sizeToBeRead), michael@0: &ret); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Terminator: michael@0: if (ret == 0) { michael@0: *_retval = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: mData.sizeToBeRead -= ret; michael@0: mZs.avail_in = ret; michael@0: mZs.next_in = mData.input; michael@0: } michael@0: michael@0: mZs.avail_out = aCount; michael@0: mZs.next_out = (unsigned char*)aBuffer; michael@0: michael@0: int ret = inflate(&mZs, mData.sizeToBeRead ? Z_NO_FLUSH : Z_FINISH); michael@0: if (ret != Z_BUF_ERROR && ret != Z_OK && ret != Z_STREAM_END) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (ret == Z_STREAM_END) { michael@0: mStatus = Done; michael@0: } michael@0: michael@0: *_retval = aCount - mZs.avail_out; michael@0: mData.cursor += *_retval; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::ReadSegments(nsWriteSegmentFun aWriter, michael@0: void* aClosure, michael@0: uint32_t aCount, michael@0: uint32_t* _retval) michael@0: { michael@0: // don't have a buffer to read from, so this better not be called! michael@0: NS_NOTREACHED("Consumers should be using Read()!"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::IsNonBlocking(bool* _retval) michael@0: { michael@0: // We are blocking michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::Seek(int32_t aWhence, int64_t aOffset) michael@0: { michael@0: int64_t pos = aOffset; michael@0: michael@0: switch (aWhence) { michael@0: case NS_SEEK_SET: michael@0: break; michael@0: michael@0: case NS_SEEK_CUR: michael@0: pos += mData.cursor; michael@0: break; michael@0: michael@0: case NS_SEEK_END: michael@0: pos += mLength; michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("unexpected whence value"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (pos == int64_t(mData.cursor)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (pos < 0 || pos >= mLength) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // We have to terminate the previous operation: michael@0: nsresult rv; michael@0: if (mStatus != NotStarted) { michael@0: rv = Close(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Reset the cursor: michael@0: mData.cursor = 0; michael@0: michael@0: // Note: This code is heavy but inflate does not have any seek() support: michael@0: uint32_t ret; michael@0: char buffer[1024]; michael@0: while (pos > 0) { michael@0: rv = Read(buffer, pos > int64_t(sizeof(buffer)) ? sizeof(buffer) : pos, &ret); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (ret == 0) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: pos -= ret; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::Tell(int64_t *aResult) michael@0: { michael@0: *aResult = mData.cursor; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveInputStream::SetEOF() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // ArchiveZipFile michael@0: michael@0: NS_IMETHODIMP michael@0: ArchiveZipFile::GetInternalStream(nsIInputStream** aStream) michael@0: { michael@0: if (mLength > INT32_MAX) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint64_t size; michael@0: nsresult rv = mArchiveReader->GetSize(&size); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr inputStream; michael@0: rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream)); michael@0: if (NS_FAILED(rv) || !inputStream) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsRefPtr stream = new ArchiveInputStream(size, michael@0: inputStream, michael@0: mFilename, michael@0: mStart, michael@0: mLength, michael@0: mCentral); michael@0: NS_ADDREF(stream); michael@0: michael@0: *aStream = stream; michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: ArchiveZipFile::CreateSlice(uint64_t aStart, michael@0: uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: { michael@0: nsCOMPtr t = new ArchiveZipFile(mFilename, michael@0: mContentType, michael@0: aStart, michael@0: mLength, michael@0: mCentral, michael@0: mArchiveReader); michael@0: return t.forget(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(ArchiveZipFile, michael@0: mArchiveReader) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ArchiveZipFile) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFile) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMBlob) michael@0: NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMFile, mIsFile) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXHRSendable) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutable) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsDOMFileCC) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(ArchiveZipFile, nsDOMFileCC) michael@0: NS_IMPL_RELEASE_INHERITED(ArchiveZipFile, nsDOMFileCC)