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 "ArchiveZipEvent.h" michael@0: #include "ArchiveZipFile.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "nsCExternalHandlerService.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: USING_FILE_NAMESPACE michael@0: michael@0: #ifndef PATH_MAX michael@0: # define PATH_MAX 65536 // The filename length is stored in 2 bytes michael@0: #endif michael@0: michael@0: ArchiveZipItem::ArchiveZipItem(const char* aFilename, michael@0: const ZipCentral& aCentralStruct, michael@0: const nsACString& aEncoding) michael@0: : mFilename(aFilename), michael@0: mCentralStruct(aCentralStruct), michael@0: mEncoding(aEncoding) michael@0: { michael@0: MOZ_COUNT_CTOR(ArchiveZipItem); michael@0: } michael@0: michael@0: ArchiveZipItem::~ArchiveZipItem() michael@0: { michael@0: MOZ_COUNT_DTOR(ArchiveZipItem); michael@0: } michael@0: michael@0: nsresult michael@0: ArchiveZipItem::ConvertFilename() michael@0: { michael@0: if (mEncoding.IsEmpty()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsString filenameU; michael@0: nsresult rv = nsContentUtils::ConvertStringFromEncoding( michael@0: mEncoding, michael@0: mFilename, filenameU); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (filenameU.IsEmpty()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mFilenameU = filenameU; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ArchiveZipItem::GetFilename(nsString& aFilename) michael@0: { michael@0: if (mFilenameU.IsEmpty()) { michael@0: // Maybe this string is UTF-8: michael@0: if (IsUTF8(mFilename, false)) { michael@0: mFilenameU = NS_ConvertUTF8toUTF16(mFilename); michael@0: } michael@0: michael@0: // Let's use the enconding value for the dictionary michael@0: else { michael@0: nsresult rv = ConvertFilename(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: aFilename = mFilenameU; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // From zipItem to DOMFile: michael@0: nsIDOMFile* michael@0: ArchiveZipItem::File(ArchiveReader* aArchiveReader) michael@0: { michael@0: nsString filename; michael@0: michael@0: if (NS_FAILED(GetFilename(filename))) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return new ArchiveZipFile(filename, michael@0: NS_ConvertUTF8toUTF16(GetType()), michael@0: StrToInt32(mCentralStruct.orglen), michael@0: mCentralStruct, michael@0: aArchiveReader); michael@0: } michael@0: michael@0: uint32_t michael@0: ArchiveZipItem::StrToInt32(const uint8_t* aStr) michael@0: { michael@0: return (uint32_t)( (aStr [0] << 0) | michael@0: (aStr [1] << 8) | michael@0: (aStr [2] << 16) | michael@0: (aStr [3] << 24) ); michael@0: } michael@0: michael@0: uint16_t michael@0: ArchiveZipItem::StrToInt16(const uint8_t* aStr) michael@0: { michael@0: return (uint16_t) ((aStr [0]) | (aStr [1] << 8)); michael@0: } michael@0: michael@0: // ArchiveReaderZipEvent michael@0: michael@0: ArchiveReaderZipEvent::ArchiveReaderZipEvent(ArchiveReader* aArchiveReader, michael@0: const nsACString& aEncoding) michael@0: : ArchiveReaderEvent(aArchiveReader), michael@0: mEncoding(aEncoding) michael@0: { michael@0: } michael@0: michael@0: // NOTE: this runs in a different thread!! michael@0: nsresult michael@0: ArchiveReaderZipEvent::Exec() michael@0: { michael@0: uint32_t centralOffset(0); michael@0: nsresult 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 RunShare(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(inputStream); michael@0: if (!seekableStream) { michael@0: return RunShare(NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: uint64_t size; michael@0: rv = mArchiveReader->GetSize(&size); michael@0: if (NS_FAILED(rv)) { michael@0: return RunShare(NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: // Reading backward.. looking for the ZipEnd signature michael@0: for (uint64_t curr = size - ZIPEND_SIZE; curr > 4; --curr) { michael@0: seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, curr); michael@0: michael@0: uint8_t buffer[ZIPEND_SIZE]; michael@0: uint32_t ret; michael@0: michael@0: rv = inputStream->Read((char*)buffer, sizeof(buffer), &ret); michael@0: if (NS_FAILED(rv) || ret != sizeof(buffer)) { michael@0: return RunShare(NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: // Here we are: michael@0: if (ArchiveZipItem::StrToInt32(buffer) == ENDSIG) { michael@0: centralOffset = ArchiveZipItem::StrToInt32(((ZipEnd*)buffer)->offset_central_dir); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // No central Offset michael@0: if (!centralOffset || centralOffset >= size - ZIPEND_SIZE) { michael@0: return RunShare(NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Seek to the first central directory: michael@0: seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, centralOffset); michael@0: michael@0: // For each central directory: michael@0: while (centralOffset <= size - ZIPCENTRAL_SIZE) { michael@0: ZipCentral centralStruct; michael@0: uint32_t ret; michael@0: michael@0: rv = inputStream->Read((char*)¢ralStruct, ZIPCENTRAL_SIZE, &ret); michael@0: if (NS_FAILED(rv) || ret != ZIPCENTRAL_SIZE) { michael@0: return RunShare(NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: uint16_t filenameLen = ArchiveZipItem::StrToInt16(centralStruct.filename_len); michael@0: uint16_t extraLen = ArchiveZipItem::StrToInt16(centralStruct.extrafield_len); michael@0: uint16_t commentLen = ArchiveZipItem::StrToInt16(centralStruct.commentfield_len); michael@0: michael@0: // Point to the next item at the top of loop michael@0: centralOffset += ZIPCENTRAL_SIZE + filenameLen + extraLen + commentLen; michael@0: if (filenameLen == 0 || filenameLen >= PATH_MAX || centralOffset >= size) { michael@0: return RunShare(NS_ERROR_FILE_CORRUPTED); michael@0: } michael@0: michael@0: // Read the name: michael@0: nsAutoArrayPtr filename(new char[filenameLen + 1]); michael@0: rv = inputStream->Read(filename, filenameLen, &ret); michael@0: if (NS_FAILED(rv) || ret != filenameLen) { michael@0: return RunShare(NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: filename[filenameLen] = 0; michael@0: michael@0: // We ignore the directories: michael@0: if (filename[filenameLen - 1] != '/') { michael@0: mFileList.AppendElement(new ArchiveZipItem(filename, centralStruct, mEncoding)); michael@0: } michael@0: michael@0: // Ignore the rest michael@0: seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, extraLen + commentLen); michael@0: } michael@0: michael@0: return RunShare(NS_OK); michael@0: }