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 michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "Logging.h" michael@0: #include "Zip.h" michael@0: michael@0: mozilla::TemporaryRef michael@0: Zip::Create(const char *filename) michael@0: { michael@0: /* Open and map the file in memory */ michael@0: AutoCloseFD fd(open(filename, O_RDONLY)); michael@0: if (fd == -1) { michael@0: LOG("Error opening %s: %s", filename, strerror(errno)); michael@0: return nullptr; michael@0: } michael@0: struct stat st; michael@0: if (fstat(fd, &st) == -1) { michael@0: LOG("Error stating %s: %s", filename, strerror(errno)); michael@0: return nullptr; michael@0: } michael@0: size_t size = st.st_size; michael@0: if (size <= sizeof(CentralDirectoryEnd)) { michael@0: LOG("Error reading %s: too short", filename); michael@0: return nullptr; michael@0: } michael@0: void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); michael@0: if (mapped == MAP_FAILED) { michael@0: LOG("Error mmapping %s: %s", filename, strerror(errno)); michael@0: return nullptr; michael@0: } michael@0: DEBUG_LOG("Mapped %s @%p", filename, mapped); michael@0: michael@0: return Create(filename, mapped, size); michael@0: } michael@0: michael@0: mozilla::TemporaryRef michael@0: Zip::Create(const char *filename, void *mapped, size_t size) michael@0: { michael@0: mozilla::RefPtr zip = new Zip(filename, mapped, size); michael@0: michael@0: // If neither the first Local File entry nor central directory entries michael@0: // have been found, the zip was invalid. michael@0: if (!zip->nextFile && !zip->entries) { michael@0: LOG("%s - Invalid zip", filename); michael@0: return nullptr; michael@0: } michael@0: michael@0: ZipCollection::Singleton.Register(zip); michael@0: return zip; michael@0: } michael@0: michael@0: Zip::Zip(const char *filename, void *mapped, size_t size) michael@0: : name(filename ? strdup(filename) : nullptr) michael@0: , mapped(mapped) michael@0: , size(size) michael@0: , nextFile(LocalFile::validate(mapped)) // first Local File entry michael@0: , nextDir(nullptr) michael@0: , entries(nullptr) michael@0: { michael@0: // If the first local file entry couldn't be found (which can happen michael@0: // with optimized jars), check the first central directory entry. michael@0: if (!nextFile) michael@0: GetFirstEntry(); michael@0: } michael@0: michael@0: Zip::~Zip() michael@0: { michael@0: ZipCollection::Forget(this); michael@0: if (name) { michael@0: munmap(mapped, size); michael@0: DEBUG_LOG("Unmapped %s @%p", name, mapped); michael@0: free(name); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Zip::GetStream(const char *path, Zip::Stream *out) const michael@0: { michael@0: DEBUG_LOG("%s - GetFile %s", name, path); michael@0: /* Fast path: if the Local File header on store matches, we can return the michael@0: * corresponding stream right away. michael@0: * However, the Local File header may not contain enough information, in michael@0: * which case the 3rd bit on the generalFlag is set. Unfortunately, this michael@0: * bit is also set in some archives even when we do have the data (most michael@0: * notably the android packages as built by the Mozilla build system). michael@0: * So instead of testing the generalFlag bit, only use the fast path when michael@0: * we haven't read the central directory entries yet, and when the michael@0: * compressed size as defined in the header is not filled (which is a michael@0: * normal condition for the bit to be set). */ michael@0: if (nextFile && nextFile->GetName().Equals(path) && michael@0: !entries && (nextFile->compressedSize != 0)) { michael@0: DEBUG_LOG("%s - %s was next file: fast path", name, path); michael@0: /* Fill Stream info from Local File header content */ michael@0: const char *data = reinterpret_cast(nextFile->GetData()); michael@0: out->compressedBuf = data; michael@0: out->compressedSize = nextFile->compressedSize; michael@0: out->uncompressedSize = nextFile->uncompressedSize; michael@0: out->type = static_cast(uint16_t(nextFile->compression)); michael@0: michael@0: /* Find the next Local File header. It is usually simply following the michael@0: * compressed stream, but in cases where the 3rd bit of the generalFlag michael@0: * is set, there is a Data Descriptor header before. */ michael@0: data += nextFile->compressedSize; michael@0: if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) { michael@0: data += sizeof(DataDescriptor); michael@0: } michael@0: nextFile = LocalFile::validate(data); michael@0: return true; michael@0: } michael@0: michael@0: /* If the directory entry we have in store doesn't match, scan the Central michael@0: * Directory for the entry corresponding to the given path */ michael@0: if (!nextDir || !nextDir->GetName().Equals(path)) { michael@0: const DirectoryEntry *entry = GetFirstEntry(); michael@0: DEBUG_LOG("%s - Scan directory entries in search for %s", name, path); michael@0: while (entry && !entry->GetName().Equals(path)) { michael@0: entry = entry->GetNext(); michael@0: } michael@0: nextDir = entry; michael@0: } michael@0: if (!nextDir) { michael@0: DEBUG_LOG("%s - Couldn't find %s", name, path); michael@0: return false; michael@0: } michael@0: michael@0: /* Find the Local File header corresponding to the Directory entry that michael@0: * was found. */ michael@0: nextFile = LocalFile::validate(static_cast(mapped) michael@0: + nextDir->offset); michael@0: if (!nextFile) { michael@0: LOG("%s - Couldn't find the Local File header for %s", name, path); michael@0: return false; michael@0: } michael@0: michael@0: /* Fill Stream info from Directory entry content */ michael@0: const char *data = reinterpret_cast(nextFile->GetData()); michael@0: out->compressedBuf = data; michael@0: out->compressedSize = nextDir->compressedSize; michael@0: out->uncompressedSize = nextDir->uncompressedSize; michael@0: out->type = static_cast(uint16_t(nextDir->compression)); michael@0: michael@0: /* Store the next directory entry */ michael@0: nextDir = nextDir->GetNext(); michael@0: nextFile = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: const Zip::DirectoryEntry * michael@0: Zip::GetFirstEntry() const michael@0: { michael@0: if (entries) michael@0: return entries; michael@0: michael@0: const CentralDirectoryEnd *end = nullptr; michael@0: const char *_end = static_cast(mapped) + size michael@0: - sizeof(CentralDirectoryEnd); michael@0: michael@0: /* Scan for the Central Directory End */ michael@0: for (; _end > mapped && !end; _end--) michael@0: end = CentralDirectoryEnd::validate(_end); michael@0: if (!end) { michael@0: LOG("%s - Couldn't find end of central directory record", name); michael@0: return nullptr; michael@0: } michael@0: michael@0: entries = DirectoryEntry::validate(static_cast(mapped) michael@0: + end->offset); michael@0: if (!entries) { michael@0: LOG("%s - Couldn't find central directory record", name); michael@0: } michael@0: return entries; michael@0: } michael@0: michael@0: ZipCollection ZipCollection::Singleton; michael@0: michael@0: mozilla::TemporaryRef michael@0: ZipCollection::GetZip(const char *path) michael@0: { michael@0: /* Search the list of Zips we already have for a match */ michael@0: for (std::vector::iterator it = Singleton.zips.begin(); michael@0: it < Singleton.zips.end(); ++it) { michael@0: if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0)) michael@0: return *it; michael@0: } michael@0: return Zip::Create(path); michael@0: } michael@0: michael@0: void michael@0: ZipCollection::Register(Zip *zip) michael@0: { michael@0: Singleton.zips.push_back(zip); michael@0: } michael@0: michael@0: void michael@0: ZipCollection::Forget(Zip *zip) michael@0: { michael@0: DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName()); michael@0: std::vector::iterator it = std::find(Singleton.zips.begin(), michael@0: Singleton.zips.end(), zip); michael@0: if (*it == zip) { michael@0: Singleton.zips.erase(it); michael@0: } else { michael@0: DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName()); michael@0: } michael@0: }