Bug 1888033 - [Menu Redesign] Add a secret setting and feature flag for the menu...
[gecko.git] / modules / libjar / nsZipArchive.cpp
blob2d845f25f0a1d4ea641eb872e33538551755a156
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * This module implements a simple archive extractor for the PKZIP format.
8 */
10 #define READTYPE int32_t
11 #include "zlib.h"
12 #include "nsISupportsUtils.h"
13 #include "mozilla/MmapFaultHandler.h"
14 #include "prio.h"
15 #include "mozilla/Attributes.h"
16 #include "mozilla/Logging.h"
17 #include "mozilla/MemUtils.h"
18 #include "mozilla/UniquePtrExtensions.h"
19 #include "mozilla/StaticMutex.h"
20 #include "mozilla/StaticPrefs_network.h"
21 #include "stdlib.h"
22 #include "nsDirectoryService.h"
23 #include "nsWildCard.h"
24 #include "nsXULAppAPI.h"
25 #include "nsZipArchive.h"
26 #include "nsString.h"
27 #include "prenv.h"
28 #if defined(XP_WIN)
29 # include <windows.h>
30 #endif
32 // For placement new used for arena allocations of zip file list
33 #include <new>
34 #define ZIP_ARENABLOCKSIZE (1 * 1024)
36 #ifdef XP_UNIX
37 # include <sys/mman.h>
38 # include <sys/types.h>
39 # include <sys/stat.h>
40 # include <limits.h>
41 # include <unistd.h>
42 #elif defined(XP_WIN)
43 # include <io.h>
44 #endif
46 #ifdef __SYMBIAN32__
47 # include <sys/syslimits.h>
48 #endif /*__SYMBIAN32__*/
50 #ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */
51 # ifndef S_IFMT
52 # define S_IFMT 0170000
53 # endif
54 # ifndef S_IFLNK
55 # define S_IFLNK 0120000
56 # endif
57 # ifndef PATH_MAX
58 # define PATH_MAX 1024
59 # endif
60 #endif /* XP_UNIX */
62 #ifdef XP_WIN
63 # include "private/pprio.h" // To get PR_ImportFile
64 #endif
66 using namespace mozilla;
68 static LazyLogModule gZipLog("nsZipArchive");
70 #ifdef LOG
71 # undef LOG
72 #endif
73 #ifdef LOG_ENABLED
74 # undef LOG_ENABLED
75 #endif
77 #define LOG(args) MOZ_LOG(gZipLog, mozilla::LogLevel::Debug, args)
78 #define LOG_ENABLED() MOZ_LOG_TEST(gZipLog, mozilla::LogLevel::Debug)
80 static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */
81 // For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00.
82 static const uint16_t kSyntheticTime = 0;
83 static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9));
85 static uint16_t xtoint(const uint8_t* ii);
86 static uint32_t xtolong(const uint8_t* ll);
87 static uint32_t HashName(const char* aName, uint16_t nameLen);
89 class ZipArchiveLogger {
90 public:
91 void Init(const char* env) {
92 StaticMutexAutoLock lock(sLock);
94 // AddRef
95 MOZ_ASSERT(mRefCnt >= 0);
96 ++mRefCnt;
98 if (!mFd) {
99 nsCOMPtr<nsIFile> logFile;
100 nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false,
101 getter_AddRefs(logFile));
102 if (NS_FAILED(rv)) return;
104 // Create the log file and its parent directory (in case it doesn't exist)
105 rv = logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
106 if (NS_FAILED(rv)) return;
108 PRFileDesc* file;
109 #ifdef XP_WIN
110 // PR_APPEND is racy on Windows, so open a handle ourselves with flags
111 // that will work, and use PR_ImportFile to make it a PRFileDesc. This can
112 // go away when bug 840435 is fixed.
113 nsAutoString path;
114 logFile->GetPath(path);
115 if (path.IsEmpty()) return;
116 HANDLE handle =
117 CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE, nullptr,
118 OPEN_ALWAYS, 0, nullptr);
119 if (handle == INVALID_HANDLE_VALUE) return;
120 file = PR_ImportFile((PROsfd)handle);
121 if (!file) return;
122 #else
123 rv = logFile->OpenNSPRFileDesc(
124 PR_WRONLY | PR_CREATE_FILE | PR_APPEND | PR_SYNC, 0644, &file);
125 if (NS_FAILED(rv)) return;
126 #endif
127 mFd = file;
131 void Write(const nsACString& zip, const char* entry) {
132 StaticMutexAutoLock lock(sLock);
134 if (mFd) {
135 nsCString buf(zip);
136 buf.Append(' ');
137 buf.Append(entry);
138 buf.Append('\n');
139 PR_Write(mFd, buf.get(), buf.Length());
143 void Release() {
144 StaticMutexAutoLock lock(sLock);
146 MOZ_ASSERT(mRefCnt > 0);
147 if ((0 == --mRefCnt) && mFd) {
148 PR_Close(mFd);
149 mFd = nullptr;
153 private:
154 static StaticMutex sLock;
155 int mRefCnt MOZ_GUARDED_BY(sLock);
156 PRFileDesc* mFd MOZ_GUARDED_BY(sLock);
159 StaticMutex ZipArchiveLogger::sLock;
160 static ZipArchiveLogger zipLog;
162 //***********************************************************
163 // For every inflation the following allocations are done:
164 // malloc(1 * 9520)
165 // malloc(32768 * 1)
166 //***********************************************************
168 nsresult gZlibInit(z_stream* zs) {
169 memset(zs, 0, sizeof(z_stream));
170 int zerr = inflateInit2(zs, -MAX_WBITS);
171 if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
173 return NS_OK;
176 nsZipHandle::nsZipHandle()
177 : mFileData(nullptr),
178 mLen(0),
179 mMap(nullptr),
180 mRefCnt(0),
181 mFileStart(nullptr),
182 mTotalLen(0) {}
184 NS_IMPL_ADDREF(nsZipHandle)
185 NS_IMPL_RELEASE(nsZipHandle)
187 nsresult nsZipHandle::Init(nsIFile* file, nsZipHandle** ret, PRFileDesc** aFd) {
188 mozilla::AutoFDClose fd;
189 int32_t flags = PR_RDONLY;
190 #if defined(XP_WIN)
191 flags |= nsIFile::OS_READAHEAD;
192 #endif
193 LOG(("ZipHandle::Init %s", file->HumanReadablePath().get()));
194 nsresult rv = file->OpenNSPRFileDesc(flags, 0000, getter_Transfers(fd));
195 if (NS_FAILED(rv)) return rv;
197 int64_t size = PR_Available64(fd.get());
198 if (size >= INT32_MAX) return NS_ERROR_FILE_TOO_BIG;
200 PRFileMap* map = PR_CreateFileMap(fd.get(), size, PR_PROT_READONLY);
201 if (!map) return NS_ERROR_FAILURE;
203 uint8_t* buf = (uint8_t*)PR_MemMap(map, 0, (uint32_t)size);
204 // Bug 525755: PR_MemMap fails when fd points at something other than a normal
205 // file.
206 if (!buf) {
207 PR_CloseFileMap(map);
208 return NS_ERROR_FAILURE;
211 RefPtr<nsZipHandle> handle = new nsZipHandle();
212 if (!handle) {
213 PR_MemUnmap(buf, (uint32_t)size);
214 PR_CloseFileMap(map);
215 return NS_ERROR_OUT_OF_MEMORY;
218 #if defined(XP_WIN)
219 if (aFd) {
220 *aFd = fd.release();
222 #else
223 handle->mNSPRFileDesc = std::move(fd);
224 #endif
225 handle->mFile.Init(file);
226 handle->mTotalLen = (uint32_t)size;
227 handle->mFileStart = buf;
228 rv = handle->findDataStart();
229 if (NS_FAILED(rv)) {
230 PR_MemUnmap(buf, (uint32_t)size);
231 handle->mFileStart = nullptr;
232 PR_CloseFileMap(map);
233 return rv;
235 handle->mMap = map;
236 handle.forget(ret);
237 return NS_OK;
240 nsresult nsZipHandle::Init(nsZipArchive* zip, const char* entry,
241 nsZipHandle** ret) {
242 RefPtr<nsZipHandle> handle = new nsZipHandle();
243 if (!handle) return NS_ERROR_OUT_OF_MEMORY;
245 LOG(("ZipHandle::Init entry %s", entry));
247 nsZipItem* item = zip->GetItem(entry);
248 if (item && item->Compression() == DEFLATED &&
249 StaticPrefs::network_jar_max_entry_size()) {
250 if (item->RealSize() > StaticPrefs::network_jar_max_entry_size()) {
251 return NS_ERROR_OUT_OF_MEMORY;
255 handle->mBuf = MakeUnique<nsZipItemPtr<uint8_t>>(zip, entry);
256 if (!handle->mBuf) return NS_ERROR_OUT_OF_MEMORY;
258 if (!handle->mBuf->Buffer()) return NS_ERROR_UNEXPECTED;
260 handle->mMap = nullptr;
261 handle->mFile.Init(zip, entry);
262 handle->mTotalLen = handle->mBuf->Length();
263 handle->mFileStart = handle->mBuf->Buffer();
264 nsresult rv = handle->findDataStart();
265 if (NS_FAILED(rv)) {
266 return rv;
268 handle.forget(ret);
269 return NS_OK;
272 nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen,
273 nsZipHandle** aRet) {
274 RefPtr<nsZipHandle> handle = new nsZipHandle();
276 handle->mFileStart = aData;
277 handle->mTotalLen = aLen;
278 nsresult rv = handle->findDataStart();
279 if (NS_FAILED(rv)) {
280 return rv;
282 handle.forget(aRet);
283 return NS_OK;
286 // This function finds the start of the ZIP data. If the file is a regular ZIP,
287 // this is just the start of the file. If the file is a CRX file, the start of
288 // the data is after the CRX header.
290 // CRX header reference, version 2:
291 // Header requires little-endian byte ordering with 4-byte alignment.
292 // 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|.
293 // Equivilant to |uint32_t m = 0x34327243|.
294 // 32 bits : version - Unsigned integer representing the CRX file
295 // format version. Currently equal to 2.
296 // 32 bits : pubKeyLength - Unsigned integer representing the length
297 // of the public key in bytes.
298 // 32 bits : sigLength - Unsigned integer representing the length
299 // of the signature in bytes.
300 // pubKeyLength : publicKey - Contents of the author's public key.
301 // sigLength : signature - Signature of the ZIP content.
302 // Signature is created using the RSA
303 // algorithm with the SHA-1 hash function.
305 // CRX header reference, version 3:
306 // Header requires little-endian byte ordering with 4-byte alignment.
307 // 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|.
308 // Equivilant to |uint32_t m = 0x34327243|.
309 // 32 bits : version - Unsigned integer representing the CRX file
310 // format version. Currently equal to 3.
311 // 32 bits : headerLength - Unsigned integer representing the length
312 // of the CRX header in bytes.
313 // headerLength : header - CRXv3 header.
314 nsresult nsZipHandle::findDataStart() {
315 // In the CRX header, integers are 32 bits. Our pointer to the file is of
316 // type |uint8_t|, which is guaranteed to be 8 bits.
317 const uint32_t CRXIntSize = 4;
319 MMAP_FAULT_HANDLER_BEGIN_HANDLE(this)
320 if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) {
321 const uint8_t* headerData = mFileStart;
322 headerData += CRXIntSize; // Skip magic number
323 uint32_t version = xtolong(headerData);
324 headerData += CRXIntSize; // Skip version
325 uint32_t headerSize = CRXIntSize * 2;
326 if (version == 3) {
327 uint32_t subHeaderSize = xtolong(headerData);
328 headerSize += CRXIntSize + subHeaderSize;
329 } else if (version < 3) {
330 uint32_t pubKeyLength = xtolong(headerData);
331 headerData += CRXIntSize;
332 uint32_t sigLength = xtolong(headerData);
333 headerSize += CRXIntSize * 2 + pubKeyLength + sigLength;
334 } else {
335 return NS_ERROR_FILE_CORRUPTED;
337 if (mTotalLen > headerSize) {
338 mLen = mTotalLen - headerSize;
339 mFileData = mFileStart + headerSize;
340 return NS_OK;
342 return NS_ERROR_FILE_CORRUPTED;
344 mLen = mTotalLen;
345 mFileData = mFileStart;
346 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
347 return NS_OK;
350 int64_t nsZipHandle::SizeOfMapping() { return mTotalLen; }
352 nsresult nsZipHandle::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc) {
353 if (!aNSPRFileDesc) {
354 return NS_ERROR_ILLEGAL_VALUE;
357 *aNSPRFileDesc = mNSPRFileDesc.get();
358 if (!mNSPRFileDesc) {
359 return NS_ERROR_NOT_AVAILABLE;
362 return NS_OK;
365 nsZipHandle::~nsZipHandle() {
366 if (mMap) {
367 PR_MemUnmap((void*)mFileStart, mTotalLen);
368 PR_CloseFileMap(mMap);
370 mFileStart = nullptr;
371 mFileData = nullptr;
372 mMap = nullptr;
373 mBuf = nullptr;
376 //***********************************************************
377 // nsZipArchive -- public methods
378 //***********************************************************
380 //---------------------------------------------
381 // nsZipArchive::OpenArchive
382 //---------------------------------------------
383 /* static */
384 already_AddRefed<nsZipArchive> nsZipArchive::OpenArchive(
385 nsZipHandle* aZipHandle, PRFileDesc* aFd) {
386 nsresult rv;
387 RefPtr<nsZipArchive> self(new nsZipArchive(aZipHandle, aFd, rv));
388 LOG(("ZipHandle::OpenArchive[%p]", self.get()));
389 if (NS_FAILED(rv)) {
390 self = nullptr;
392 return self.forget();
395 /* static */
396 already_AddRefed<nsZipArchive> nsZipArchive::OpenArchive(nsIFile* aFile) {
397 RefPtr<nsZipHandle> handle;
398 #if defined(XP_WIN)
399 mozilla::AutoFDClose fd;
400 nsresult rv =
401 nsZipHandle::Init(aFile, getter_AddRefs(handle), getter_Transfers(fd));
402 #else
403 nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle));
404 #endif
405 if (NS_FAILED(rv)) return nullptr;
407 #if defined(XP_WIN)
408 return OpenArchive(handle, fd.get());
409 #else
410 return OpenArchive(handle);
411 #endif
414 //---------------------------------------------
415 // nsZipArchive::Test
416 //---------------------------------------------
417 nsresult nsZipArchive::Test(const char* aEntryName) {
418 nsZipItem* currItem;
420 if (aEntryName) // only test specified item
422 currItem = GetItem(aEntryName);
423 if (!currItem) return NS_ERROR_FILE_NOT_FOUND;
424 //-- don't test (synthetic) directory items
425 if (currItem->IsDirectory()) return NS_OK;
426 return ExtractFile(currItem, 0, 0);
429 // test all items in archive
430 for (auto* item : mFiles) {
431 for (currItem = item; currItem; currItem = currItem->next) {
432 //-- don't test (synthetic) directory items
433 if (currItem->IsDirectory()) continue;
434 nsresult rv = ExtractFile(currItem, 0, 0);
435 if (rv != NS_OK) return rv;
439 return NS_OK;
442 //---------------------------------------------
443 // nsZipArchive::GetItem
444 //---------------------------------------------
445 nsZipItem* nsZipArchive::GetItem(const char* aEntryName) {
446 MutexAutoLock lock(mLock);
448 LOG(("ZipHandle::GetItem[%p] %s", this, aEntryName));
449 if (aEntryName) {
450 uint32_t len = strlen(aEntryName);
451 //-- If the request is for a directory, make sure that synthetic entries
452 //-- are created for the directories without their own entry.
453 if (!mBuiltSynthetics) {
454 if ((len > 0) && (aEntryName[len - 1] == '/')) {
455 if (BuildSynthetics() != NS_OK) return 0;
458 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
459 nsZipItem* item = mFiles[HashName(aEntryName, len)];
460 while (item) {
461 if ((len == item->nameLength) &&
462 (!memcmp(aEntryName, item->Name(), len))) {
463 // Successful GetItem() is a good indicator that the file is about to be
464 // read
465 if (mUseZipLog && mURI.Length()) {
466 zipLog.Write(mURI, aEntryName);
468 return item; //-- found it
470 item = item->next;
472 MMAP_FAULT_HANDLER_CATCH(nullptr)
474 return nullptr;
477 //---------------------------------------------
478 // nsZipArchive::ExtractFile
479 // This extracts the item to the filehandle provided.
480 // If 'aFd' is null, it only tests the extraction.
481 // On extraction error(s) it removes the file.
482 //---------------------------------------------
483 nsresult nsZipArchive::ExtractFile(nsZipItem* item, nsIFile* outFile,
484 PRFileDesc* aFd) {
485 MutexAutoLock lock(mLock);
486 LOG(("ZipHandle::ExtractFile[%p]", this));
487 if (!item) return NS_ERROR_ILLEGAL_VALUE;
488 if (!mFd) return NS_ERROR_FAILURE;
490 // Directory extraction is handled in nsJAR::Extract,
491 // so the item to be extracted should never be a directory
492 MOZ_ASSERT(!item->IsDirectory());
494 Bytef outbuf[ZIP_BUFLEN];
496 nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true);
498 nsresult rv = NS_OK;
500 while (true) {
501 uint32_t count = 0;
502 uint8_t* buf = cursor.Read(&count);
503 if (!buf) {
504 rv = NS_ERROR_FILE_CORRUPTED;
505 break;
507 if (count == 0) {
508 break;
511 if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) {
512 rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
513 break;
517 //-- delete the file on errors
518 if (aFd) {
519 PR_Close(aFd);
520 if (NS_FAILED(rv) && outFile) {
521 outFile->Remove(false);
525 return rv;
528 //---------------------------------------------
529 // nsZipArchive::FindInit
530 //---------------------------------------------
531 nsresult nsZipArchive::FindInit(const char* aPattern, nsZipFind** aFind) {
532 if (!aFind) return NS_ERROR_ILLEGAL_VALUE;
534 MutexAutoLock lock(mLock);
536 LOG(("ZipHandle::FindInit[%p]", this));
537 // null out param in case an error happens
538 *aFind = nullptr;
540 bool regExp = false;
541 char* pattern = 0;
543 // Create synthetic directory entries on demand
544 nsresult rv = BuildSynthetics();
545 if (rv != NS_OK) return rv;
547 // validate the pattern
548 if (aPattern) {
549 switch (NS_WildCardValid((char*)aPattern)) {
550 case INVALID_SXP:
551 return NS_ERROR_ILLEGAL_VALUE;
553 case NON_SXP:
554 regExp = false;
555 break;
557 case VALID_SXP:
558 regExp = true;
559 break;
561 default:
562 // undocumented return value from RegExpValid!
563 MOZ_ASSERT(false);
564 return NS_ERROR_ILLEGAL_VALUE;
567 pattern = strdup(aPattern);
568 if (!pattern) return NS_ERROR_OUT_OF_MEMORY;
571 *aFind = new nsZipFind(this, pattern, regExp);
572 if (!*aFind) {
573 free(pattern);
574 return NS_ERROR_OUT_OF_MEMORY;
577 return NS_OK;
580 //---------------------------------------------
581 // nsZipFind::FindNext
582 //---------------------------------------------
583 nsresult nsZipFind::FindNext(const char** aResult, uint16_t* aNameLen) {
584 if (!mArchive || !aResult || !aNameLen) return NS_ERROR_ILLEGAL_VALUE;
586 MutexAutoLock lock(mArchive->mLock);
587 *aResult = 0;
588 *aNameLen = 0;
589 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mArchive->GetFD())
590 // we start from last match, look for next
591 while (mSlot < ZIP_TABSIZE) {
592 // move to next in current chain, or move to new slot
593 mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
595 bool found = false;
596 if (!mItem)
597 ++mSlot; // no more in this chain, move to next slot
598 else if (!mPattern)
599 found = true; // always match
600 else if (mRegExp) {
601 char buf[kMaxNameLength + 1];
602 memcpy(buf, mItem->Name(), mItem->nameLength);
603 buf[mItem->nameLength] = '\0';
604 found = (NS_WildCardMatch(buf, mPattern, false) == MATCH);
605 } else
606 found = ((mItem->nameLength == strlen(mPattern)) &&
607 (memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0));
608 if (found) {
609 // Need also to return the name length, as it is NOT zero-terminatdd...
610 *aResult = mItem->Name();
611 *aNameLen = mItem->nameLength;
612 LOG(("ZipHandle::FindNext[%p] %s", this, *aResult));
613 return NS_OK;
616 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
617 LOG(("ZipHandle::FindNext[%p] not found %s", this, mPattern));
618 return NS_ERROR_FILE_NOT_FOUND;
621 //***********************************************************
622 // nsZipArchive -- private implementation
623 //***********************************************************
625 //---------------------------------------------
626 // nsZipArchive::CreateZipItem
627 //---------------------------------------------
628 nsZipItem* nsZipArchive::CreateZipItem() {
629 // Arena allocate the nsZipItem
630 return (nsZipItem*)mArena.Allocate(sizeof(nsZipItem), mozilla::fallible);
633 //---------------------------------------------
634 // nsZipArchive::BuildFileList
635 //---------------------------------------------
636 nsresult nsZipArchive::BuildFileList(PRFileDesc* aFd)
637 MOZ_NO_THREAD_SAFETY_ANALYSIS {
638 // We're only called from the constructor, but need to call
639 // CreateZipItem(), which touches locked data, and modify mFiles. Turn
640 // off thread-safety, which normally doesn't apply for constructors
641 // anyways
643 // Get archive size using end pos
644 const uint8_t* buf;
645 const uint8_t* startp = mFd->mFileData;
646 const uint8_t* endp = startp + mFd->mLen;
647 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
648 uint32_t centralOffset = 4;
649 LOG(("ZipHandle::BuildFileList[%p]", this));
650 // Only perform readahead in the parent process. Children processes
651 // don't need readahead when the file has already been readahead by
652 // the parent process, and readahead only really happens for omni.ja,
653 // which is used in the parent process.
654 if (XRE_IsParentProcess() && mFd->mLen > ZIPCENTRAL_SIZE &&
655 xtolong(startp + centralOffset) == CENTRALSIG) {
656 // Success means optimized jar layout from bug 559961 is in effect
657 uint32_t readaheadLength = xtolong(startp);
658 mozilla::PrefetchMemory(const_cast<uint8_t*>(startp), readaheadLength);
659 } else {
660 for (buf = endp - ZIPEND_SIZE; buf > startp; buf--) {
661 if (xtolong(buf) == ENDSIG) {
662 centralOffset = xtolong(((ZipEnd*)buf)->offset_central_dir);
663 break;
668 if (!centralOffset) {
669 return NS_ERROR_FILE_CORRUPTED;
672 buf = startp + centralOffset;
674 // avoid overflow of startp + centralOffset.
675 if (buf < startp) {
676 return NS_ERROR_FILE_CORRUPTED;
679 //-- Read the central directory headers
680 uint32_t sig = 0;
681 while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
682 (buf + int32_t(sizeof(uint32_t)) <= endp) &&
683 ((sig = xtolong(buf)) == CENTRALSIG)) {
684 // Make sure there is enough data available.
685 if ((buf > endp) || (endp - buf < ZIPCENTRAL_SIZE)) {
686 return NS_ERROR_FILE_CORRUPTED;
689 // Read the fixed-size data.
690 ZipCentral* central = (ZipCentral*)buf;
692 uint16_t namelen = xtoint(central->filename_len);
693 uint16_t extralen = xtoint(central->extrafield_len);
694 uint16_t commentlen = xtoint(central->commentfield_len);
695 uint32_t diff = ZIPCENTRAL_SIZE + namelen + extralen + commentlen;
697 // Sanity check variable sizes and refuse to deal with
698 // anything too big: it's likely a corrupt archive.
699 if (namelen < 1 || namelen > kMaxNameLength) {
700 return NS_ERROR_FILE_CORRUPTED;
702 if (buf >= buf + diff || // No overflow
703 buf >= endp - diff) {
704 return NS_ERROR_FILE_CORRUPTED;
707 // Point to the next item at the top of loop
708 buf += diff;
710 nsZipItem* item = CreateZipItem();
711 if (!item) return NS_ERROR_OUT_OF_MEMORY;
713 item->central = central;
714 item->nameLength = namelen;
715 item->isSynthetic = false;
717 // Add item to file table
718 #ifdef DEBUG
719 nsDependentCSubstring name(item->Name(), namelen);
720 LOG((" %s", PromiseFlatCString(name).get()));
721 #endif
722 uint32_t hash = HashName(item->Name(), namelen);
723 item->next = mFiles[hash];
724 mFiles[hash] = item;
726 sig = 0;
727 } /* while reading central directory records */
729 if (sig != ENDSIG && sig != ENDSIG64) {
730 return NS_ERROR_FILE_CORRUPTED;
733 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
734 return NS_OK;
737 //---------------------------------------------
738 // nsZipArchive::BuildSynthetics
739 //---------------------------------------------
740 nsresult nsZipArchive::BuildSynthetics() {
741 mLock.AssertCurrentThreadOwns();
743 if (mBuiltSynthetics) return NS_OK;
744 mBuiltSynthetics = true;
746 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
747 // Create synthetic entries for any missing directories.
748 // Do this when all ziptable has scanned to prevent double entries.
749 for (auto* item : mFiles) {
750 for (; item != nullptr; item = item->next) {
751 if (item->isSynthetic) continue;
753 //-- add entries for directories in the current item's path
754 //-- go from end to beginning, because then we can stop trying
755 //-- to create diritems if we find that the diritem we want to
756 //-- create already exists
757 //-- start just before the last char so as to not add the item
758 //-- twice if it's a directory
759 uint16_t namelen = item->nameLength;
760 MOZ_ASSERT(namelen > 0,
761 "Attempt to build synthetic for zero-length entry name!");
762 const char* name = item->Name();
763 for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--) {
764 if (name[dirlen - 1] != '/') continue;
766 // The character before this is '/', so if this is also '/' then we
767 // have an empty path component. Skip it.
768 if (name[dirlen] == '/') continue;
770 // Is the directory already in the file table?
771 uint32_t hash = HashName(item->Name(), dirlen);
772 bool found = false;
773 for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next) {
774 if ((dirlen == zi->nameLength) &&
775 (0 == memcmp(item->Name(), zi->Name(), dirlen))) {
776 // we've already added this dir and all its parents
777 found = true;
778 break;
781 // if the directory was found, break out of the directory
782 // creation loop now that we know all implicit directories
783 // are there -- otherwise, start creating the zip item
784 if (found) break;
786 nsZipItem* diritem = CreateZipItem();
787 if (!diritem) return NS_ERROR_OUT_OF_MEMORY;
789 // Point to the central record of the original item for the name part.
790 diritem->central = item->central;
791 diritem->nameLength = dirlen;
792 diritem->isSynthetic = true;
794 // add diritem to the file table
795 diritem->next = mFiles[hash];
796 mFiles[hash] = diritem;
797 } /* end processing of dirs in item's name */
800 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
801 return NS_OK;
804 //---------------------------------------------
805 // nsZipArchive::GetFD
806 //---------------------------------------------
807 nsZipHandle* nsZipArchive::GetFD() const { return mFd.get(); }
809 //---------------------------------------------
810 // nsZipArchive::GetDataOffset
811 // Returns 0 on an error; 0 is not a valid result for any success case
812 //---------------------------------------------
813 uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem) {
814 MOZ_ASSERT(aItem);
815 MOZ_DIAGNOSTIC_ASSERT(mFd);
817 uint32_t offset;
818 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
819 //-- read local header to get variable length values and calculate
820 //-- the real data offset
821 uint32_t len = mFd->mLen;
822 MOZ_DIAGNOSTIC_ASSERT(len <= UINT32_MAX, "mLen > 2GB");
823 const uint8_t* data = mFd->mFileData;
824 offset = aItem->LocalOffset();
825 if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE) {
826 return 0;
828 // Check there's enough space for the signature
829 if (offset > mFd->mLen) {
830 NS_WARNING("Corrupt local offset in JAR file");
831 return 0;
834 // -- check signature before using the structure, in case the zip file is
835 // corrupt
836 ZipLocal* Local = (ZipLocal*)(data + offset);
837 if ((xtolong(Local->signature) != LOCALSIG)) return 0;
839 //-- NOTE: extralen is different in central header and local header
840 //-- for archives created using the Unix "zip" utility. To set
841 //-- the offset accurately we need the _local_ extralen.
842 offset += ZIPLOCAL_SIZE + xtoint(Local->filename_len) +
843 xtoint(Local->extrafield_len);
844 // Check data points inside the file.
845 if (offset > mFd->mLen) {
846 NS_WARNING("Corrupt data offset in JAR file");
847 return 0;
850 MMAP_FAULT_HANDLER_CATCH(0)
851 // can't be 0
852 return offset;
855 //---------------------------------------------
856 // nsZipArchive::GetData
857 //---------------------------------------------
858 const uint8_t* nsZipArchive::GetData(nsZipItem* aItem) {
859 MOZ_DIAGNOSTIC_ASSERT(aItem);
860 if (!aItem) {
861 return nullptr;
863 uint32_t offset = GetDataOffset(aItem);
865 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
866 // -- check if there is enough source data in the file
867 if (!offset || mFd->mLen < aItem->Size() ||
868 offset > mFd->mLen - aItem->Size() ||
869 (aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) {
870 return nullptr;
872 MMAP_FAULT_HANDLER_CATCH(nullptr)
874 return mFd->mFileData + offset;
877 //---------------------------------------------
878 // nsZipArchive::SizeOfMapping
879 //---------------------------------------------
880 int64_t nsZipArchive::SizeOfMapping() { return mFd ? mFd->SizeOfMapping() : 0; }
882 //------------------------------------------
883 // nsZipArchive constructor and destructor
884 //------------------------------------------
886 nsZipArchive::nsZipArchive(nsZipHandle* aZipHandle, PRFileDesc* aFd,
887 nsresult& aRv)
888 : mRefCnt(0), mFd(aZipHandle), mUseZipLog(false), mBuiltSynthetics(false) {
889 // initialize the table to nullptr
890 memset(mFiles, 0, sizeof(mFiles));
891 MOZ_DIAGNOSTIC_ASSERT(aZipHandle);
893 //-- get table of contents for archive
894 aRv = BuildFileList(aFd);
895 if (NS_FAILED(aRv)) {
896 return; // whomever created us must destroy us in this case
898 if (aZipHandle->mFile && XRE_IsParentProcess()) {
899 static char* env = PR_GetEnv("MOZ_JAR_LOG_FILE");
900 if (env) {
901 mUseZipLog = true;
903 zipLog.Init(env);
904 // We only log accesses in jar/zip archives within the NS_GRE_DIR
905 // and/or the APK on Android. For the former, we log the archive path
906 // relative to NS_GRE_DIR, and for the latter, the nested-archive
907 // path within the APK. This makes the path match the path of the
908 // archives relative to the packaged dist/$APP_NAME directory in a
909 // build.
910 if (aZipHandle->mFile.IsZip()) {
911 // Nested archive, likely omni.ja in APK.
912 aZipHandle->mFile.GetPath(mURI);
913 } else if (nsDirectoryService::gService) {
914 // We can reach here through the initialization of Omnijar from
915 // XRE_InitCommandLine, which happens before the directory service
916 // is initialized. When that happens, it means the opened archive is
917 // the APK, and we don't care to log that one, so we just skip
918 // when the directory service is not initialized.
919 nsCOMPtr<nsIFile> dir = aZipHandle->mFile.GetBaseFile();
920 nsCOMPtr<nsIFile> gre_dir;
921 nsAutoCString path;
922 if (NS_SUCCEEDED(nsDirectoryService::gService->Get(
923 NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(gre_dir)))) {
924 nsAutoCString leaf;
925 nsCOMPtr<nsIFile> parent;
926 while (NS_SUCCEEDED(dir->GetNativeLeafName(leaf)) &&
927 NS_SUCCEEDED(dir->GetParent(getter_AddRefs(parent)))) {
928 if (!parent) {
929 break;
931 dir = parent;
932 if (path.Length()) {
933 path.Insert('/', 0);
935 path.Insert(leaf, 0);
936 bool equals;
937 if (NS_SUCCEEDED(dir->Equals(gre_dir, &equals)) && equals) {
938 mURI.Assign(path);
939 break;
948 NS_IMPL_ADDREF(nsZipArchive)
949 NS_IMPL_RELEASE(nsZipArchive)
951 nsZipArchive::~nsZipArchive() {
952 LOG(("Closing nsZipArchive[%p]", this));
953 if (mUseZipLog) {
954 zipLog.Release();
958 //------------------------------------------
959 // nsZipFind constructor and destructor
960 //------------------------------------------
962 nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp)
963 : mArchive(aZip),
964 mPattern(aPattern),
965 mItem(nullptr),
966 mSlot(0),
967 mRegExp(aRegExp) {
968 MOZ_COUNT_CTOR(nsZipFind);
971 nsZipFind::~nsZipFind() {
972 free(mPattern);
974 MOZ_COUNT_DTOR(nsZipFind);
977 //------------------------------------------
978 // helper functions
979 //------------------------------------------
982 * HashName
984 * returns a hash key for the entry name
986 MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
987 static uint32_t HashName(const char* aName, uint16_t len) {
988 MOZ_ASSERT(aName != 0);
990 const uint8_t* p = (const uint8_t*)aName;
991 const uint8_t* endp = p + len;
992 uint32_t val = 0;
993 while (p != endp) {
994 val = val * 37 + *p++;
997 return (val % ZIP_TABSIZE);
1001 * x t o i n t
1003 * Converts a two byte ugly endianed integer
1004 * to our platform's integer.
1006 static uint16_t xtoint(const uint8_t* ii) {
1007 return (uint16_t)((ii[0]) | (ii[1] << 8));
1011 * x t o l o n g
1013 * Converts a four byte ugly endianed integer
1014 * to our platform's integer.
1016 static uint32_t xtolong(const uint8_t* ll) {
1017 return (uint32_t)((ll[0] << 0) | (ll[1] << 8) | (ll[2] << 16) |
1018 (ll[3] << 24));
1022 * GetModTime
1024 * returns last modification time in microseconds
1026 static PRTime GetModTime(uint16_t aDate, uint16_t aTime) {
1027 // Note that on DST shift we can't handle correctly the hour that is valid
1028 // in both DST zones
1029 PRExplodedTime time;
1031 time.tm_usec = 0;
1033 time.tm_hour = (aTime >> 11) & 0x1F;
1034 time.tm_min = (aTime >> 5) & 0x3F;
1035 time.tm_sec = (aTime & 0x1F) * 2;
1037 time.tm_year = (aDate >> 9) + 1980;
1038 time.tm_month = ((aDate >> 5) & 0x0F) - 1;
1039 time.tm_mday = aDate & 0x1F;
1041 time.tm_params.tp_gmt_offset = 0;
1042 time.tm_params.tp_dst_offset = 0;
1044 PR_NormalizeTime(&time, PR_GMTParameters);
1045 time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
1046 PR_NormalizeTime(&time, PR_GMTParameters);
1047 time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
1049 return PR_ImplodeTime(&time);
1052 nsZipItem::nsZipItem()
1053 : next(nullptr), central(nullptr), nameLength(0), isSynthetic(false) {}
1055 uint32_t nsZipItem::LocalOffset() { return xtolong(central->localhdr_offset); }
1057 uint32_t nsZipItem::Size() { return isSynthetic ? 0 : xtolong(central->size); }
1059 uint32_t nsZipItem::RealSize() {
1060 return isSynthetic ? 0 : xtolong(central->orglen);
1063 uint32_t nsZipItem::CRC32() {
1064 return isSynthetic ? 0 : xtolong(central->crc32);
1067 uint16_t nsZipItem::Date() {
1068 return isSynthetic ? kSyntheticDate : xtoint(central->date);
1071 uint16_t nsZipItem::Time() {
1072 return isSynthetic ? kSyntheticTime : xtoint(central->time);
1075 uint16_t nsZipItem::Compression() {
1076 return isSynthetic ? STORED : xtoint(central->method);
1079 bool nsZipItem::IsDirectory() {
1080 return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1]));
1083 uint16_t nsZipItem::Mode() {
1084 if (isSynthetic) return 0755;
1085 return ((uint16_t)(central->external_attributes[2]) | 0x100);
1088 const uint8_t* nsZipItem::GetExtraField(uint16_t aTag, uint16_t* aBlockSize) {
1089 if (isSynthetic) return nullptr;
1091 const unsigned char* buf =
1092 ((const unsigned char*)central) + ZIPCENTRAL_SIZE + nameLength;
1093 uint32_t buflen;
1095 MMAP_FAULT_HANDLER_BEGIN_BUFFER(central, ZIPCENTRAL_SIZE + nameLength)
1096 buflen = (uint32_t)xtoint(central->extrafield_len);
1097 MMAP_FAULT_HANDLER_CATCH(nullptr)
1099 uint32_t pos = 0;
1100 uint16_t tag, blocksize;
1102 MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, buflen)
1103 while (buf && (pos + 4) <= buflen) {
1104 tag = xtoint(buf + pos);
1105 blocksize = xtoint(buf + pos + 2);
1107 if (aTag == tag && (pos + 4 + blocksize) <= buflen) {
1108 *aBlockSize = blocksize;
1109 return buf + pos;
1112 pos += blocksize + 4;
1114 MMAP_FAULT_HANDLER_CATCH(nullptr)
1116 return nullptr;
1119 PRTime nsZipItem::LastModTime() {
1120 if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime);
1122 // Try to read timestamp from extra field
1123 uint16_t blocksize;
1124 const uint8_t* tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize);
1125 if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) {
1126 return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC;
1129 return GetModTime(Date(), Time());
1132 nsZipCursor::nsZipCursor(nsZipItem* item, nsZipArchive* aZip, uint8_t* aBuf,
1133 uint32_t aBufSize, bool doCRC)
1134 : mItem(item),
1135 mBuf(aBuf),
1136 mBufSize(aBufSize),
1137 mZs(),
1138 mCRC(0),
1139 mDoCRC(doCRC) {
1140 if (mItem->Compression() == DEFLATED) {
1141 #ifdef DEBUG
1142 nsresult status =
1143 #endif
1144 gZlibInit(&mZs);
1145 NS_ASSERTION(status == NS_OK, "Zlib failed to initialize");
1146 NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem");
1149 mZs.avail_in = item->Size();
1150 mZs.next_in = (Bytef*)aZip->GetData(item);
1152 if (doCRC) mCRC = crc32(0L, Z_NULL, 0);
1155 nsZipCursor::~nsZipCursor() {
1156 if (mItem->Compression() == DEFLATED) {
1157 inflateEnd(&mZs);
1161 uint8_t* nsZipCursor::ReadOrCopy(uint32_t* aBytesRead, bool aCopy) {
1162 int zerr;
1163 uint8_t* buf = nullptr;
1164 bool verifyCRC = true;
1166 if (!mZs.next_in) return nullptr;
1167 MMAP_FAULT_HANDLER_BEGIN_BUFFER(mZs.next_in, mZs.avail_in)
1168 switch (mItem->Compression()) {
1169 case STORED:
1170 if (!aCopy) {
1171 *aBytesRead = mZs.avail_in;
1172 buf = mZs.next_in;
1173 mZs.next_in += mZs.avail_in;
1174 mZs.avail_in = 0;
1175 } else {
1176 *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in;
1177 memcpy(mBuf, mZs.next_in, *aBytesRead);
1178 mZs.avail_in -= *aBytesRead;
1179 mZs.next_in += *aBytesRead;
1181 break;
1182 case DEFLATED:
1183 buf = mBuf;
1184 mZs.next_out = buf;
1185 mZs.avail_out = mBufSize;
1187 zerr = inflate(&mZs, Z_PARTIAL_FLUSH);
1188 if (zerr != Z_OK && zerr != Z_STREAM_END) return nullptr;
1190 *aBytesRead = mZs.next_out - buf;
1191 verifyCRC = (zerr == Z_STREAM_END);
1192 break;
1193 default:
1194 return nullptr;
1197 if (mDoCRC) {
1198 mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
1199 if (verifyCRC && mCRC != mItem->CRC32()) return nullptr;
1201 MMAP_FAULT_HANDLER_CATCH(nullptr)
1202 return buf;
1205 nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive* aZip, const char* aEntryName,
1206 bool doCRC)
1207 : mReturnBuf(nullptr), mReadlen(0) {
1208 // make sure the ziparchive hangs around
1209 mZipHandle = aZip->GetFD();
1211 nsZipItem* item = aZip->GetItem(aEntryName);
1212 if (!item) return;
1214 uint32_t size = 0;
1215 bool compressed = (item->Compression() == DEFLATED);
1216 if (compressed) {
1217 size = item->RealSize();
1218 mAutoBuf = MakeUniqueFallible<uint8_t[]>(size);
1219 if (!mAutoBuf) {
1220 return;
1224 nsZipCursor cursor(item, aZip, mAutoBuf.get(), size, doCRC);
1225 mReturnBuf = cursor.Read(&mReadlen);
1226 if (!mReturnBuf) {
1227 return;
1230 if (mReadlen != item->RealSize()) {
1231 NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow");
1232 mReturnBuf = nullptr;
1233 return;