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/. */
15 already_AddRefed
<Zip
> Zip::Create(const char* filename
) {
16 /* Open and map the file in memory */
17 AutoCloseFD
fd(open(filename
, O_RDONLY
));
19 ERROR("Error opening %s: %s", filename
, strerror(errno
));
23 if (fstat(fd
, &st
) == -1) {
24 ERROR("Error stating %s: %s", filename
, strerror(errno
));
27 size_t size
= st
.st_size
;
28 if (size
<= sizeof(CentralDirectoryEnd
)) {
29 ERROR("Error reading %s: too short", filename
);
32 void* mapped
= mmap(nullptr, size
, PROT_READ
, MAP_SHARED
, fd
, 0);
33 if (mapped
== MAP_FAILED
) {
34 ERROR("Error mmapping %s: %s", filename
, strerror(errno
));
37 DEBUG_LOG("Mapped %s @%p", filename
, mapped
);
39 return Create(filename
, mapped
, size
);
42 already_AddRefed
<Zip
> Zip::Create(const char* filename
, void* mapped
,
44 RefPtr
<Zip
> zip
= new Zip(filename
, mapped
, size
);
46 // If neither the first Local File entry nor central directory entries
47 // have been found, the zip was invalid.
48 if (!zip
->nextFile
&& !zip
->entries
) {
49 ERROR("%s - Invalid zip", filename
);
53 ZipCollection::Singleton
.Register(zip
);
57 Zip::Zip(const char* filename
, void* mapped
, size_t size
)
58 : name(filename
? strdup(filename
) : nullptr),
61 nextFile(LocalFile::validate(mapped
)) // first Local File entry
65 pthread_mutex_init(&mutex
, nullptr);
66 // If the first local file entry couldn't be found (which can happen
67 // with optimized jars), check the first central directory entry.
68 if (!nextFile
) GetFirstEntry();
74 DEBUG_LOG("Unmapped %s @%p", name
, mapped
);
77 pthread_mutex_destroy(&mutex
);
80 bool Zip::GetStream(const char* path
, Zip::Stream
* out
) const {
81 AutoLock
lock(&mutex
);
83 DEBUG_LOG("%s - GetFile %s", name
, path
);
84 /* Fast path: if the Local File header on store matches, we can return the
85 * corresponding stream right away.
86 * However, the Local File header may not contain enough information, in
87 * which case the 3rd bit on the generalFlag is set. Unfortunately, this
88 * bit is also set in some archives even when we do have the data (most
89 * notably the android packages as built by the Mozilla build system).
90 * So instead of testing the generalFlag bit, only use the fast path when
91 * we haven't read the central directory entries yet, and when the
92 * compressed size as defined in the header is not filled (which is a
93 * normal condition for the bit to be set). */
94 if (nextFile
&& nextFile
->GetName().Equals(path
) && !entries
&&
95 (nextFile
->compressedSize
!= 0)) {
96 DEBUG_LOG("%s - %s was next file: fast path", name
, path
);
97 /* Fill Stream info from Local File header content */
98 const char* data
= reinterpret_cast<const char*>(nextFile
->GetData());
99 out
->compressedBuf
= data
;
100 out
->compressedSize
= nextFile
->compressedSize
;
101 out
->uncompressedSize
= nextFile
->uncompressedSize
;
102 out
->CRC32
= nextFile
->CRC32
;
103 out
->type
= static_cast<Stream::Type
>(uint16_t(nextFile
->compression
));
105 /* Find the next Local File header. It is usually simply following the
106 * compressed stream, but in cases where the 3rd bit of the generalFlag
107 * is set, there is a Data Descriptor header before. */
108 data
+= nextFile
->compressedSize
;
109 if ((nextFile
->generalFlag
& 0x8) && DataDescriptor::validate(data
)) {
110 data
+= sizeof(DataDescriptor
);
112 nextFile
= LocalFile::validate(data
);
116 /* If the directory entry we have in store doesn't match, scan the Central
117 * Directory for the entry corresponding to the given path */
118 if (!nextDir
|| !nextDir
->GetName().Equals(path
)) {
119 const DirectoryEntry
* entry
= GetFirstEntry();
120 DEBUG_LOG("%s - Scan directory entries in search for %s", name
, path
);
121 while (entry
&& !entry
->GetName().Equals(path
)) {
122 entry
= entry
->GetNext();
127 DEBUG_LOG("%s - Couldn't find %s", name
, path
);
131 /* Find the Local File header corresponding to the Directory entry that
134 LocalFile::validate(static_cast<const char*>(mapped
) + nextDir
->offset
);
136 ERROR("%s - Couldn't find the Local File header for %s", name
, path
);
140 /* Fill Stream info from Directory entry content */
141 const char* data
= reinterpret_cast<const char*>(nextFile
->GetData());
142 out
->compressedBuf
= data
;
143 out
->compressedSize
= nextDir
->compressedSize
;
144 out
->uncompressedSize
= nextDir
->uncompressedSize
;
145 out
->CRC32
= nextDir
->CRC32
;
146 out
->type
= static_cast<Stream::Type
>(uint16_t(nextDir
->compression
));
148 /* Store the next directory entry */
149 nextDir
= nextDir
->GetNext();
154 const Zip::DirectoryEntry
* Zip::GetFirstEntry() const {
155 if (entries
) return entries
;
157 const CentralDirectoryEnd
* end
= nullptr;
159 static_cast<const char*>(mapped
) + size
- sizeof(CentralDirectoryEnd
);
161 /* Scan for the Central Directory End */
162 for (; _end
> mapped
&& !end
; _end
--)
163 end
= CentralDirectoryEnd::validate(_end
);
165 ERROR("%s - Couldn't find end of central directory record", name
);
170 DirectoryEntry::validate(static_cast<const char*>(mapped
) + end
->offset
);
172 ERROR("%s - Couldn't find central directory record", name
);
177 bool Zip::VerifyCRCs() const {
178 AutoLock
lock(&mutex
);
180 for (const DirectoryEntry
* entry
= GetFirstEntry(); entry
;
181 entry
= entry
->GetNext()) {
182 const LocalFile
* file
=
183 LocalFile::validate(static_cast<const char*>(mapped
) + entry
->offset
);
184 uint32_t crc
= crc32(0, nullptr, 0);
186 DEBUG_LOG("%.*s: crc=%08x", int(entry
->filenameSize
),
187 reinterpret_cast<const char*>(entry
) + sizeof(*entry
),
188 uint32_t(entry
->CRC32
));
190 if (entry
->compression
== Stream::Type::STORE
) {
191 crc
= crc32(crc
, static_cast<const uint8_t*>(file
->GetData()),
192 entry
->compressedSize
);
193 DEBUG_LOG(" STORE size=%d crc=%08x", int(entry
->compressedSize
), crc
);
195 } else if (entry
->compression
== Stream::Type::DEFLATE
) {
198 zstream
.avail_in
= entry
->compressedSize
;
200 reinterpret_cast<Bytef
*>(const_cast<void*>(file
->GetData()));
201 zstream
.zalloc
= nullptr;
202 zstream
.zfree
= nullptr;
203 zstream
.opaque
= nullptr;
205 if (inflateInit2(&zstream
, -MAX_WBITS
) != Z_OK
) {
210 zstream
.avail_out
= sizeof(buffer
);
211 zstream
.next_out
= buffer
;
213 int ret
= inflate(&zstream
, Z_SYNC_FLUSH
);
214 crc
= crc32(crc
, buffer
, sizeof(buffer
) - zstream
.avail_out
);
216 if (ret
== Z_STREAM_END
) {
218 } else if (ret
!= Z_OK
) {
223 inflateEnd(&zstream
);
224 DEBUG_LOG(" DEFLATE size=%d crc=%08x", int(zstream
.total_out
), crc
);
227 MOZ_ASSERT_UNREACHABLE("Unexpected stream type");
231 if (entry
->CRC32
!= crc
) {
239 ZipCollection
ZipCollection::Singleton
;
241 static pthread_mutex_t sZipCollectionMutex
= PTHREAD_MUTEX_INITIALIZER
;
243 already_AddRefed
<Zip
> ZipCollection::GetZip(const char* path
) {
245 AutoLock
lock(&sZipCollectionMutex
);
246 /* Search the list of Zips we already have for a match */
247 for (const auto& zip
: Singleton
.zips
) {
248 if (zip
->GetName() && (strcmp(zip
->GetName(), path
) == 0)) {
249 return RefPtr
<Zip
>(zip
).forget();
253 return Zip::Create(path
);
256 void ZipCollection::Register(Zip
* zip
) {
257 AutoLock
lock(&sZipCollectionMutex
);
258 DEBUG_LOG("ZipCollection::Register(\"%s\")", zip
->GetName());
259 Singleton
.zips
.push_back(zip
);
262 void ZipCollection::Forget(const Zip
* zip
) {
263 AutoLock
lock(&sZipCollectionMutex
);
264 if (zip
->refCount() > 1) {
265 // Someone has acquired a reference before we had acquired the lock,
266 // ignore this request.
269 DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip
->GetName());
270 const auto it
= std::find(Singleton
.zips
.begin(), Singleton
.zips
.end(), zip
);
272 Singleton
.zips
.erase(it
);
274 DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping",