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