|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include <sys/mman.h> |
|
6 #include <sys/stat.h> |
|
7 #include <fcntl.h> |
|
8 #include <errno.h> |
|
9 #include <unistd.h> |
|
10 #include <cstdlib> |
|
11 #include <algorithm> |
|
12 #include "Logging.h" |
|
13 #include "Zip.h" |
|
14 |
|
15 mozilla::TemporaryRef<Zip> |
|
16 Zip::Create(const char *filename) |
|
17 { |
|
18 /* Open and map the file in memory */ |
|
19 AutoCloseFD fd(open(filename, O_RDONLY)); |
|
20 if (fd == -1) { |
|
21 LOG("Error opening %s: %s", filename, strerror(errno)); |
|
22 return nullptr; |
|
23 } |
|
24 struct stat st; |
|
25 if (fstat(fd, &st) == -1) { |
|
26 LOG("Error stating %s: %s", filename, strerror(errno)); |
|
27 return nullptr; |
|
28 } |
|
29 size_t size = st.st_size; |
|
30 if (size <= sizeof(CentralDirectoryEnd)) { |
|
31 LOG("Error reading %s: too short", filename); |
|
32 return nullptr; |
|
33 } |
|
34 void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); |
|
35 if (mapped == MAP_FAILED) { |
|
36 LOG("Error mmapping %s: %s", filename, strerror(errno)); |
|
37 return nullptr; |
|
38 } |
|
39 DEBUG_LOG("Mapped %s @%p", filename, mapped); |
|
40 |
|
41 return Create(filename, mapped, size); |
|
42 } |
|
43 |
|
44 mozilla::TemporaryRef<Zip> |
|
45 Zip::Create(const char *filename, void *mapped, size_t size) |
|
46 { |
|
47 mozilla::RefPtr<Zip> zip = new Zip(filename, mapped, size); |
|
48 |
|
49 // If neither the first Local File entry nor central directory entries |
|
50 // have been found, the zip was invalid. |
|
51 if (!zip->nextFile && !zip->entries) { |
|
52 LOG("%s - Invalid zip", filename); |
|
53 return nullptr; |
|
54 } |
|
55 |
|
56 ZipCollection::Singleton.Register(zip); |
|
57 return zip; |
|
58 } |
|
59 |
|
60 Zip::Zip(const char *filename, void *mapped, size_t size) |
|
61 : name(filename ? strdup(filename) : nullptr) |
|
62 , mapped(mapped) |
|
63 , size(size) |
|
64 , nextFile(LocalFile::validate(mapped)) // first Local File entry |
|
65 , nextDir(nullptr) |
|
66 , entries(nullptr) |
|
67 { |
|
68 // If the first local file entry couldn't be found (which can happen |
|
69 // with optimized jars), check the first central directory entry. |
|
70 if (!nextFile) |
|
71 GetFirstEntry(); |
|
72 } |
|
73 |
|
74 Zip::~Zip() |
|
75 { |
|
76 ZipCollection::Forget(this); |
|
77 if (name) { |
|
78 munmap(mapped, size); |
|
79 DEBUG_LOG("Unmapped %s @%p", name, mapped); |
|
80 free(name); |
|
81 } |
|
82 } |
|
83 |
|
84 bool |
|
85 Zip::GetStream(const char *path, Zip::Stream *out) const |
|
86 { |
|
87 DEBUG_LOG("%s - GetFile %s", name, path); |
|
88 /* Fast path: if the Local File header on store matches, we can return the |
|
89 * corresponding stream right away. |
|
90 * However, the Local File header may not contain enough information, in |
|
91 * which case the 3rd bit on the generalFlag is set. Unfortunately, this |
|
92 * bit is also set in some archives even when we do have the data (most |
|
93 * notably the android packages as built by the Mozilla build system). |
|
94 * So instead of testing the generalFlag bit, only use the fast path when |
|
95 * we haven't read the central directory entries yet, and when the |
|
96 * compressed size as defined in the header is not filled (which is a |
|
97 * normal condition for the bit to be set). */ |
|
98 if (nextFile && nextFile->GetName().Equals(path) && |
|
99 !entries && (nextFile->compressedSize != 0)) { |
|
100 DEBUG_LOG("%s - %s was next file: fast path", name, path); |
|
101 /* Fill Stream info from Local File header content */ |
|
102 const char *data = reinterpret_cast<const char *>(nextFile->GetData()); |
|
103 out->compressedBuf = data; |
|
104 out->compressedSize = nextFile->compressedSize; |
|
105 out->uncompressedSize = nextFile->uncompressedSize; |
|
106 out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression)); |
|
107 |
|
108 /* Find the next Local File header. It is usually simply following the |
|
109 * compressed stream, but in cases where the 3rd bit of the generalFlag |
|
110 * is set, there is a Data Descriptor header before. */ |
|
111 data += nextFile->compressedSize; |
|
112 if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) { |
|
113 data += sizeof(DataDescriptor); |
|
114 } |
|
115 nextFile = LocalFile::validate(data); |
|
116 return true; |
|
117 } |
|
118 |
|
119 /* If the directory entry we have in store doesn't match, scan the Central |
|
120 * Directory for the entry corresponding to the given path */ |
|
121 if (!nextDir || !nextDir->GetName().Equals(path)) { |
|
122 const DirectoryEntry *entry = GetFirstEntry(); |
|
123 DEBUG_LOG("%s - Scan directory entries in search for %s", name, path); |
|
124 while (entry && !entry->GetName().Equals(path)) { |
|
125 entry = entry->GetNext(); |
|
126 } |
|
127 nextDir = entry; |
|
128 } |
|
129 if (!nextDir) { |
|
130 DEBUG_LOG("%s - Couldn't find %s", name, path); |
|
131 return false; |
|
132 } |
|
133 |
|
134 /* Find the Local File header corresponding to the Directory entry that |
|
135 * was found. */ |
|
136 nextFile = LocalFile::validate(static_cast<const char *>(mapped) |
|
137 + nextDir->offset); |
|
138 if (!nextFile) { |
|
139 LOG("%s - Couldn't find the Local File header for %s", name, path); |
|
140 return false; |
|
141 } |
|
142 |
|
143 /* Fill Stream info from Directory entry content */ |
|
144 const char *data = reinterpret_cast<const char *>(nextFile->GetData()); |
|
145 out->compressedBuf = data; |
|
146 out->compressedSize = nextDir->compressedSize; |
|
147 out->uncompressedSize = nextDir->uncompressedSize; |
|
148 out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression)); |
|
149 |
|
150 /* Store the next directory entry */ |
|
151 nextDir = nextDir->GetNext(); |
|
152 nextFile = nullptr; |
|
153 return true; |
|
154 } |
|
155 |
|
156 const Zip::DirectoryEntry * |
|
157 Zip::GetFirstEntry() const |
|
158 { |
|
159 if (entries) |
|
160 return entries; |
|
161 |
|
162 const CentralDirectoryEnd *end = nullptr; |
|
163 const char *_end = static_cast<const char *>(mapped) + size |
|
164 - sizeof(CentralDirectoryEnd); |
|
165 |
|
166 /* Scan for the Central Directory End */ |
|
167 for (; _end > mapped && !end; _end--) |
|
168 end = CentralDirectoryEnd::validate(_end); |
|
169 if (!end) { |
|
170 LOG("%s - Couldn't find end of central directory record", name); |
|
171 return nullptr; |
|
172 } |
|
173 |
|
174 entries = DirectoryEntry::validate(static_cast<const char *>(mapped) |
|
175 + end->offset); |
|
176 if (!entries) { |
|
177 LOG("%s - Couldn't find central directory record", name); |
|
178 } |
|
179 return entries; |
|
180 } |
|
181 |
|
182 ZipCollection ZipCollection::Singleton; |
|
183 |
|
184 mozilla::TemporaryRef<Zip> |
|
185 ZipCollection::GetZip(const char *path) |
|
186 { |
|
187 /* Search the list of Zips we already have for a match */ |
|
188 for (std::vector<Zip *>::iterator it = Singleton.zips.begin(); |
|
189 it < Singleton.zips.end(); ++it) { |
|
190 if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0)) |
|
191 return *it; |
|
192 } |
|
193 return Zip::Create(path); |
|
194 } |
|
195 |
|
196 void |
|
197 ZipCollection::Register(Zip *zip) |
|
198 { |
|
199 Singleton.zips.push_back(zip); |
|
200 } |
|
201 |
|
202 void |
|
203 ZipCollection::Forget(Zip *zip) |
|
204 { |
|
205 DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName()); |
|
206 std::vector<Zip *>::iterator it = std::find(Singleton.zips.begin(), |
|
207 Singleton.zips.end(), zip); |
|
208 if (*it == zip) { |
|
209 Singleton.zips.erase(it); |
|
210 } else { |
|
211 DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName()); |
|
212 } |
|
213 } |