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 mozilla::TemporaryRef
<Zip
>
16 Zip::Create(const char *filename
)
18 /* Open and map the file in memory */
19 AutoCloseFD
fd(open(filename
, O_RDONLY
));
21 ERROR("Error opening %s: %s", filename
, strerror(errno
));
25 if (fstat(fd
, &st
) == -1) {
26 ERROR("Error stating %s: %s", filename
, strerror(errno
));
29 size_t size
= st
.st_size
;
30 if (size
<= sizeof(CentralDirectoryEnd
)) {
31 ERROR("Error reading %s: too short", filename
);
34 void *mapped
= mmap(nullptr, size
, PROT_READ
, MAP_SHARED
, fd
, 0);
35 if (mapped
== MAP_FAILED
) {
36 ERROR("Error mmapping %s: %s", filename
, strerror(errno
));
39 DEBUG_LOG("Mapped %s @%p", filename
, mapped
);
41 return Create(filename
, mapped
, size
);
44 mozilla::TemporaryRef
<Zip
>
45 Zip::Create(const char *filename
, void *mapped
, size_t size
)
47 mozilla::RefPtr
<Zip
> zip
= new Zip(filename
, mapped
, size
);
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 ERROR("%s - Invalid zip", filename
);
56 ZipCollection::Singleton
.Register(zip
);
60 Zip::Zip(const char *filename
, void *mapped
, size_t size
)
61 : name(filename
? strdup(filename
) : nullptr)
64 , nextFile(LocalFile::validate(mapped
)) // first Local File entry
68 // If the first local file entry couldn't be found (which can happen
69 // with optimized jars), check the first central directory entry.
76 ZipCollection::Forget(this);
79 DEBUG_LOG("Unmapped %s @%p", name
, mapped
);
85 Zip::GetStream(const char *path
, Zip::Stream
*out
) const
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
));
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
);
115 nextFile
= LocalFile::validate(data
);
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();
130 DEBUG_LOG("%s - Couldn't find %s", name
, path
);
134 /* Find the Local File header corresponding to the Directory entry that
136 nextFile
= LocalFile::validate(static_cast<const char *>(mapped
)
139 ERROR("%s - Couldn't find the Local File header for %s", name
, path
);
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
));
150 /* Store the next directory entry */
151 nextDir
= nextDir
->GetNext();
156 const Zip::DirectoryEntry
*
157 Zip::GetFirstEntry() const
162 const CentralDirectoryEnd
*end
= nullptr;
163 const char *_end
= static_cast<const char *>(mapped
) + size
164 - sizeof(CentralDirectoryEnd
);
166 /* Scan for the Central Directory End */
167 for (; _end
> mapped
&& !end
; _end
--)
168 end
= CentralDirectoryEnd::validate(_end
);
170 ERROR("%s - Couldn't find end of central directory record", name
);
174 entries
= DirectoryEntry::validate(static_cast<const char *>(mapped
)
177 ERROR("%s - Couldn't find central directory record", name
);
182 ZipCollection
ZipCollection::Singleton
;
184 mozilla::TemporaryRef
<Zip
>
185 ZipCollection::GetZip(const char *path
)
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))
193 return Zip::Create(path
);
197 ZipCollection::Register(Zip
*zip
)
199 Singleton
.zips
.push_back(zip
);
203 ZipCollection::Forget(Zip
*zip
)
205 DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip
->GetName());
206 std::vector
<Zip
*>::iterator it
= std::find(Singleton
.zips
.begin(),
207 Singleton
.zips
.end(), zip
);
209 Singleton
.zips
.erase(it
);
211 DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip
->GetName());