1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mozglue/linker/Zip.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,213 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include <sys/mman.h> 1.9 +#include <sys/stat.h> 1.10 +#include <fcntl.h> 1.11 +#include <errno.h> 1.12 +#include <unistd.h> 1.13 +#include <cstdlib> 1.14 +#include <algorithm> 1.15 +#include "Logging.h" 1.16 +#include "Zip.h" 1.17 + 1.18 +mozilla::TemporaryRef<Zip> 1.19 +Zip::Create(const char *filename) 1.20 +{ 1.21 + /* Open and map the file in memory */ 1.22 + AutoCloseFD fd(open(filename, O_RDONLY)); 1.23 + if (fd == -1) { 1.24 + LOG("Error opening %s: %s", filename, strerror(errno)); 1.25 + return nullptr; 1.26 + } 1.27 + struct stat st; 1.28 + if (fstat(fd, &st) == -1) { 1.29 + LOG("Error stating %s: %s", filename, strerror(errno)); 1.30 + return nullptr; 1.31 + } 1.32 + size_t size = st.st_size; 1.33 + if (size <= sizeof(CentralDirectoryEnd)) { 1.34 + LOG("Error reading %s: too short", filename); 1.35 + return nullptr; 1.36 + } 1.37 + void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); 1.38 + if (mapped == MAP_FAILED) { 1.39 + LOG("Error mmapping %s: %s", filename, strerror(errno)); 1.40 + return nullptr; 1.41 + } 1.42 + DEBUG_LOG("Mapped %s @%p", filename, mapped); 1.43 + 1.44 + return Create(filename, mapped, size); 1.45 +} 1.46 + 1.47 +mozilla::TemporaryRef<Zip> 1.48 +Zip::Create(const char *filename, void *mapped, size_t size) 1.49 +{ 1.50 + mozilla::RefPtr<Zip> zip = new Zip(filename, mapped, size); 1.51 + 1.52 + // If neither the first Local File entry nor central directory entries 1.53 + // have been found, the zip was invalid. 1.54 + if (!zip->nextFile && !zip->entries) { 1.55 + LOG("%s - Invalid zip", filename); 1.56 + return nullptr; 1.57 + } 1.58 + 1.59 + ZipCollection::Singleton.Register(zip); 1.60 + return zip; 1.61 +} 1.62 + 1.63 +Zip::Zip(const char *filename, void *mapped, size_t size) 1.64 +: name(filename ? strdup(filename) : nullptr) 1.65 +, mapped(mapped) 1.66 +, size(size) 1.67 +, nextFile(LocalFile::validate(mapped)) // first Local File entry 1.68 +, nextDir(nullptr) 1.69 +, entries(nullptr) 1.70 +{ 1.71 + // If the first local file entry couldn't be found (which can happen 1.72 + // with optimized jars), check the first central directory entry. 1.73 + if (!nextFile) 1.74 + GetFirstEntry(); 1.75 +} 1.76 + 1.77 +Zip::~Zip() 1.78 +{ 1.79 + ZipCollection::Forget(this); 1.80 + if (name) { 1.81 + munmap(mapped, size); 1.82 + DEBUG_LOG("Unmapped %s @%p", name, mapped); 1.83 + free(name); 1.84 + } 1.85 +} 1.86 + 1.87 +bool 1.88 +Zip::GetStream(const char *path, Zip::Stream *out) const 1.89 +{ 1.90 + DEBUG_LOG("%s - GetFile %s", name, path); 1.91 + /* Fast path: if the Local File header on store matches, we can return the 1.92 + * corresponding stream right away. 1.93 + * However, the Local File header may not contain enough information, in 1.94 + * which case the 3rd bit on the generalFlag is set. Unfortunately, this 1.95 + * bit is also set in some archives even when we do have the data (most 1.96 + * notably the android packages as built by the Mozilla build system). 1.97 + * So instead of testing the generalFlag bit, only use the fast path when 1.98 + * we haven't read the central directory entries yet, and when the 1.99 + * compressed size as defined in the header is not filled (which is a 1.100 + * normal condition for the bit to be set). */ 1.101 + if (nextFile && nextFile->GetName().Equals(path) && 1.102 + !entries && (nextFile->compressedSize != 0)) { 1.103 + DEBUG_LOG("%s - %s was next file: fast path", name, path); 1.104 + /* Fill Stream info from Local File header content */ 1.105 + const char *data = reinterpret_cast<const char *>(nextFile->GetData()); 1.106 + out->compressedBuf = data; 1.107 + out->compressedSize = nextFile->compressedSize; 1.108 + out->uncompressedSize = nextFile->uncompressedSize; 1.109 + out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression)); 1.110 + 1.111 + /* Find the next Local File header. It is usually simply following the 1.112 + * compressed stream, but in cases where the 3rd bit of the generalFlag 1.113 + * is set, there is a Data Descriptor header before. */ 1.114 + data += nextFile->compressedSize; 1.115 + if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) { 1.116 + data += sizeof(DataDescriptor); 1.117 + } 1.118 + nextFile = LocalFile::validate(data); 1.119 + return true; 1.120 + } 1.121 + 1.122 + /* If the directory entry we have in store doesn't match, scan the Central 1.123 + * Directory for the entry corresponding to the given path */ 1.124 + if (!nextDir || !nextDir->GetName().Equals(path)) { 1.125 + const DirectoryEntry *entry = GetFirstEntry(); 1.126 + DEBUG_LOG("%s - Scan directory entries in search for %s", name, path); 1.127 + while (entry && !entry->GetName().Equals(path)) { 1.128 + entry = entry->GetNext(); 1.129 + } 1.130 + nextDir = entry; 1.131 + } 1.132 + if (!nextDir) { 1.133 + DEBUG_LOG("%s - Couldn't find %s", name, path); 1.134 + return false; 1.135 + } 1.136 + 1.137 + /* Find the Local File header corresponding to the Directory entry that 1.138 + * was found. */ 1.139 + nextFile = LocalFile::validate(static_cast<const char *>(mapped) 1.140 + + nextDir->offset); 1.141 + if (!nextFile) { 1.142 + LOG("%s - Couldn't find the Local File header for %s", name, path); 1.143 + return false; 1.144 + } 1.145 + 1.146 + /* Fill Stream info from Directory entry content */ 1.147 + const char *data = reinterpret_cast<const char *>(nextFile->GetData()); 1.148 + out->compressedBuf = data; 1.149 + out->compressedSize = nextDir->compressedSize; 1.150 + out->uncompressedSize = nextDir->uncompressedSize; 1.151 + out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression)); 1.152 + 1.153 + /* Store the next directory entry */ 1.154 + nextDir = nextDir->GetNext(); 1.155 + nextFile = nullptr; 1.156 + return true; 1.157 +} 1.158 + 1.159 +const Zip::DirectoryEntry * 1.160 +Zip::GetFirstEntry() const 1.161 +{ 1.162 + if (entries) 1.163 + return entries; 1.164 + 1.165 + const CentralDirectoryEnd *end = nullptr; 1.166 + const char *_end = static_cast<const char *>(mapped) + size 1.167 + - sizeof(CentralDirectoryEnd); 1.168 + 1.169 + /* Scan for the Central Directory End */ 1.170 + for (; _end > mapped && !end; _end--) 1.171 + end = CentralDirectoryEnd::validate(_end); 1.172 + if (!end) { 1.173 + LOG("%s - Couldn't find end of central directory record", name); 1.174 + return nullptr; 1.175 + } 1.176 + 1.177 + entries = DirectoryEntry::validate(static_cast<const char *>(mapped) 1.178 + + end->offset); 1.179 + if (!entries) { 1.180 + LOG("%s - Couldn't find central directory record", name); 1.181 + } 1.182 + return entries; 1.183 +} 1.184 + 1.185 +ZipCollection ZipCollection::Singleton; 1.186 + 1.187 +mozilla::TemporaryRef<Zip> 1.188 +ZipCollection::GetZip(const char *path) 1.189 +{ 1.190 + /* Search the list of Zips we already have for a match */ 1.191 + for (std::vector<Zip *>::iterator it = Singleton.zips.begin(); 1.192 + it < Singleton.zips.end(); ++it) { 1.193 + if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0)) 1.194 + return *it; 1.195 + } 1.196 + return Zip::Create(path); 1.197 +} 1.198 + 1.199 +void 1.200 +ZipCollection::Register(Zip *zip) 1.201 +{ 1.202 + Singleton.zips.push_back(zip); 1.203 +} 1.204 + 1.205 +void 1.206 +ZipCollection::Forget(Zip *zip) 1.207 +{ 1.208 + DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName()); 1.209 + std::vector<Zip *>::iterator it = std::find(Singleton.zips.begin(), 1.210 + Singleton.zips.end(), zip); 1.211 + if (*it == zip) { 1.212 + Singleton.zips.erase(it); 1.213 + } else { 1.214 + DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName()); 1.215 + } 1.216 +}