Bug 1492992 [wpt PR 11769] - Various test fixes for python3 support., a=testonly
[gecko.git] / modules / libjar / nsZipArchive.cpp
blobc8e31fccc441ef9fed4ce3b68ab286ac3102f79e
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.
9 * The underlying nsZipArchive is NOT thread-safe. Do not pass references
10 * or pointers to it across thread boundaries.
13 #define READTYPE int32_t
14 #include "zlib.h"
15 #ifdef MOZ_JAR_BROTLI
16 #include "brotli/decode.h" // brotli
17 #endif
18 #include "nsISupportsUtils.h"
19 #include "prio.h"
20 #include "plstr.h"
21 #include "mozilla/Attributes.h"
22 #include "mozilla/Logging.h"
23 #include "mozilla/UniquePtrExtensions.h"
24 #include "stdlib.h"
25 #include "nsWildCard.h"
26 #include "nsZipArchive.h"
27 #include "nsString.h"
28 #include "prenv.h"
29 #if defined(XP_WIN)
30 #include <windows.h>
31 #endif
33 // For placement new used for arena allocations of zip file list
34 #include <new>
35 #define ZIP_ARENABLOCKSIZE (1*1024)
37 #ifdef XP_UNIX
38 #include <sys/mman.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <limits.h>
42 #include <unistd.h>
43 #elif defined(XP_WIN)
44 #include <io.h>
45 #endif
47 #ifdef __SYMBIAN32__
48 #include <sys/syslimits.h>
49 #endif /*__SYMBIAN32__*/
52 #ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */
53 # ifndef S_IFMT
54 # define S_IFMT 0170000
55 # endif
56 # ifndef S_IFLNK
57 # define S_IFLNK 0120000
58 # endif
59 # ifndef PATH_MAX
60 # define PATH_MAX 1024
61 # endif
62 #endif /* XP_UNIX */
64 #ifdef XP_WIN
65 #include "private/pprio.h" // To get PR_ImportFile
66 #endif
68 using namespace mozilla;
70 static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */
71 // For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00.
72 static const uint16_t kSyntheticTime = 0;
73 static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9));
75 static uint16_t xtoint(const uint8_t *ii);
76 static uint32_t xtolong(const uint8_t *ll);
77 static uint32_t HashName(const char* aName, uint16_t nameLen);
79 class ZipArchiveLogger {
80 public:
81 void Write(const nsACString &zip, const char *entry) const {
82 if (!fd) {
83 char *env = PR_GetEnv("MOZ_JAR_LOG_FILE");
84 if (!env)
85 return;
87 nsCOMPtr<nsIFile> logFile;
88 nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(logFile));
89 if (NS_FAILED(rv))
90 return;
92 // Create the log file and its parent directory (in case it doesn't exist)
93 rv = logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
94 if (NS_FAILED(rv))
95 return;
97 PRFileDesc* file;
98 #ifdef XP_WIN
99 // PR_APPEND is racy on Windows, so open a handle ourselves with flags that
100 // will work, and use PR_ImportFile to make it a PRFileDesc.
101 // This can go away when bug 840435 is fixed.
102 nsAutoString path;
103 logFile->GetPath(path);
104 if (path.IsEmpty())
105 return;
106 HANDLE handle = CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE,
107 nullptr, OPEN_ALWAYS, 0, nullptr);
108 if (handle == INVALID_HANDLE_VALUE)
109 return;
110 file = PR_ImportFile((PROsfd)handle);
111 if (!file)
112 return;
113 #else
114 rv = logFile->OpenNSPRFileDesc(PR_WRONLY|PR_CREATE_FILE|PR_APPEND, 0644, &file);
115 if (NS_FAILED(rv))
116 return;
117 #endif
118 fd = file;
120 nsCString buf(zip);
121 buf.Append(' ');
122 buf.Append(entry);
123 buf.Append('\n');
124 PR_Write(fd, buf.get(), buf.Length());
127 void AddRef() {
128 MOZ_ASSERT(refCnt >= 0);
129 ++refCnt;
132 void Release() {
133 MOZ_ASSERT(refCnt > 0);
134 if ((0 == --refCnt) && fd) {
135 PR_Close(fd);
136 fd = nullptr;
139 private:
140 int refCnt;
141 mutable PRFileDesc *fd;
144 static ZipArchiveLogger zipLog;
146 //***********************************************************
147 // For every inflation the following allocations are done:
148 // malloc(1 * 9520)
149 // malloc(32768 * 1)
150 //***********************************************************
152 nsresult gZlibInit(z_stream *zs)
154 memset(zs, 0, sizeof(z_stream));
155 int zerr = inflateInit2(zs, -MAX_WBITS);
156 if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
158 return NS_OK;
161 nsZipHandle::nsZipHandle()
162 : mFileData(nullptr)
163 , mLen(0)
164 , mMap(nullptr)
165 , mRefCnt(0)
166 , mFileStart(nullptr)
167 , mTotalLen(0)
171 NS_IMPL_ADDREF(nsZipHandle)
172 NS_IMPL_RELEASE(nsZipHandle)
174 nsresult nsZipHandle::Init(nsIFile *file, nsZipHandle **ret,
175 PRFileDesc **aFd)
177 mozilla::AutoFDClose fd;
178 int32_t flags = PR_RDONLY;
179 #if defined(XP_WIN)
180 flags |= nsIFile::OS_READAHEAD;
181 #endif
182 nsresult rv = file->OpenNSPRFileDesc(flags, 0000, &fd.rwget());
183 if (NS_FAILED(rv))
184 return rv;
186 int64_t size = PR_Available64(fd);
187 if (size >= INT32_MAX)
188 return NS_ERROR_FILE_TOO_BIG;
190 PRFileMap *map = PR_CreateFileMap(fd, size, PR_PROT_READONLY);
191 if (!map)
192 return NS_ERROR_FAILURE;
194 uint8_t *buf = (uint8_t*) PR_MemMap(map, 0, (uint32_t) size);
195 // Bug 525755: PR_MemMap fails when fd points at something other than a normal file.
196 if (!buf) {
197 PR_CloseFileMap(map);
198 return NS_ERROR_FAILURE;
201 RefPtr<nsZipHandle> handle = new nsZipHandle();
202 if (!handle) {
203 PR_MemUnmap(buf, (uint32_t) size);
204 PR_CloseFileMap(map);
205 return NS_ERROR_OUT_OF_MEMORY;
208 #if defined(XP_WIN)
209 if (aFd) {
210 *aFd = fd.forget();
212 #else
213 handle->mNSPRFileDesc = fd.forget();
214 #endif
215 handle->mMap = map;
216 handle->mFile.Init(file);
217 handle->mTotalLen = (uint32_t) size;
218 handle->mFileStart = buf;
219 rv = handle->findDataStart();
220 if (NS_FAILED(rv)) {
221 PR_MemUnmap(buf, (uint32_t) size);
222 PR_CloseFileMap(map);
223 return rv;
225 handle.forget(ret);
226 return NS_OK;
229 nsresult nsZipHandle::Init(nsZipArchive *zip, const char *entry,
230 nsZipHandle **ret)
232 RefPtr<nsZipHandle> handle = new nsZipHandle();
233 if (!handle)
234 return NS_ERROR_OUT_OF_MEMORY;
236 handle->mBuf = new nsZipItemPtr<uint8_t>(zip, entry);
237 if (!handle->mBuf)
238 return NS_ERROR_OUT_OF_MEMORY;
240 if (!handle->mBuf->Buffer())
241 return NS_ERROR_UNEXPECTED;
243 handle->mMap = nullptr;
244 handle->mFile.Init(zip, entry);
245 handle->mTotalLen = handle->mBuf->Length();
246 handle->mFileStart = handle->mBuf->Buffer();
247 nsresult rv = handle->findDataStart();
248 if (NS_FAILED(rv)) {
249 return rv;
251 handle.forget(ret);
252 return NS_OK;
255 nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen,
256 nsZipHandle **aRet)
258 RefPtr<nsZipHandle> handle = new nsZipHandle();
260 handle->mFileStart = aData;
261 handle->mTotalLen = aLen;
262 nsresult rv = handle->findDataStart();
263 if (NS_FAILED(rv)) {
264 return rv;
266 handle.forget(aRet);
267 return NS_OK;
270 // This function finds the start of the ZIP data. If the file is a regular ZIP,
271 // this is just the start of the file. If the file is a CRX file, the start of
272 // the data is after the CRX header.
273 // CRX header reference: (CRX version 2)
274 // Header requires little-endian byte ordering with 4-byte alignment.
275 // 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|.
276 // Equivilant to |uint32_t m = 0x34327243|.
277 // 32 bits : version - Unsigned integer representing the CRX file
278 // format version. Currently equal to 2.
279 // 32 bits : pubKeyLength - Unsigned integer representing the length
280 // of the public key in bytes.
281 // 32 bits : sigLength - Unsigned integer representing the length
282 // of the signature in bytes.
283 // pubKeyLength : publicKey - Contents of the author's public key.
284 // sigLength : signature - Signature of the ZIP content.
285 // Signature is created using the RSA
286 // algorithm with the SHA-1 hash function.
287 nsresult nsZipHandle::findDataStart()
289 // In the CRX header, integers are 32 bits. Our pointer to the file is of
290 // type |uint8_t|, which is guaranteed to be 8 bits.
291 const uint32_t CRXIntSize = 4;
293 MOZ_WIN_MEM_TRY_BEGIN
294 if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) {
295 const uint8_t* headerData = mFileStart;
296 headerData += CRXIntSize * 2; // Skip magic number and version number
297 uint32_t pubKeyLength = xtolong(headerData);
298 headerData += CRXIntSize;
299 uint32_t sigLength = xtolong(headerData);
300 uint32_t headerSize = CRXIntSize * 4 + pubKeyLength + sigLength;
301 if (mTotalLen > headerSize) {
302 mLen = mTotalLen - headerSize;
303 mFileData = mFileStart + headerSize;
304 return NS_OK;
307 mLen = mTotalLen;
308 mFileData = mFileStart;
309 MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
310 return NS_OK;
313 int64_t nsZipHandle::SizeOfMapping()
315 return mTotalLen;
318 nsresult nsZipHandle::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
320 if (!aNSPRFileDesc) {
321 return NS_ERROR_ILLEGAL_VALUE;
324 *aNSPRFileDesc = mNSPRFileDesc;
325 if (!mNSPRFileDesc) {
326 return NS_ERROR_NOT_AVAILABLE;
329 return NS_OK;
332 nsZipHandle::~nsZipHandle()
334 if (mMap) {
335 PR_MemUnmap((void *)mFileStart, mTotalLen);
336 PR_CloseFileMap(mMap);
338 mFileStart = nullptr;
339 mFileData = nullptr;
340 mMap = nullptr;
341 mBuf = nullptr;
344 //***********************************************************
345 // nsZipArchive -- public methods
346 //***********************************************************
348 //---------------------------------------------
349 // nsZipArchive::OpenArchive
350 //---------------------------------------------
351 nsresult nsZipArchive::OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd)
353 mFd = aZipHandle;
355 //-- get table of contents for archive
356 nsresult rv = BuildFileList(aFd);
357 if (NS_SUCCEEDED(rv)) {
358 if (aZipHandle->mFile)
359 aZipHandle->mFile.GetURIString(mURI);
361 return rv;
364 nsresult nsZipArchive::OpenArchive(nsIFile *aFile)
366 RefPtr<nsZipHandle> handle;
367 #if defined(XP_WIN)
368 mozilla::AutoFDClose fd;
369 nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle),
370 &fd.rwget());
371 #else
372 nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle));
373 #endif
374 if (NS_FAILED(rv))
375 return rv;
377 #if defined(XP_WIN)
378 return OpenArchive(handle, fd.get());
379 #else
380 return OpenArchive(handle);
381 #endif
384 //---------------------------------------------
385 // nsZipArchive::Test
386 //---------------------------------------------
387 nsresult nsZipArchive::Test(const char *aEntryName)
389 nsZipItem* currItem;
391 if (aEntryName) // only test specified item
393 currItem = GetItem(aEntryName);
394 if (!currItem)
395 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
396 //-- don't test (synthetic) directory items
397 if (currItem->IsDirectory())
398 return NS_OK;
399 return ExtractFile(currItem, 0, 0);
402 // test all items in archive
403 for (auto* item : mFiles) {
404 for (currItem = item; currItem; currItem = currItem->next) {
405 //-- don't test (synthetic) directory items
406 if (currItem->IsDirectory())
407 continue;
408 nsresult rv = ExtractFile(currItem, 0, 0);
409 if (rv != NS_OK)
410 return rv;
414 return NS_OK;
417 //---------------------------------------------
418 // nsZipArchive::CloseArchive
419 //---------------------------------------------
420 nsresult nsZipArchive::CloseArchive()
422 if (mFd) {
423 mArena.Clear();
424 mFd = nullptr;
427 // CAUTION:
428 // We don't need to delete each of the nsZipItem as the memory for
429 // the zip item and the filename it holds are both allocated from the Arena.
430 // Hence, destroying the Arena is like destroying all the memory
431 // for all the nsZipItem in one shot. But if the ~nsZipItem is doing
432 // anything more than cleaning up memory, we should start calling it.
433 // Let us also cleanup the mFiles table for re-use on the next 'open' call
434 memset(mFiles, 0, sizeof(mFiles));
435 mBuiltSynthetics = false;
436 return NS_OK;
439 //---------------------------------------------
440 // nsZipArchive::GetItem
441 //---------------------------------------------
442 nsZipItem* nsZipArchive::GetItem(const char * aEntryName)
444 if (aEntryName) {
445 uint32_t len = strlen(aEntryName);
446 //-- If the request is for a directory, make sure that synthetic entries
447 //-- are created for the directories without their own entry.
448 if (!mBuiltSynthetics) {
449 if ((len > 0) && (aEntryName[len-1] == '/')) {
450 if (BuildSynthetics() != NS_OK)
451 return 0;
454 MOZ_WIN_MEM_TRY_BEGIN
455 nsZipItem* item = mFiles[ HashName(aEntryName, len) ];
456 while (item) {
457 if ((len == item->nameLength) &&
458 (!memcmp(aEntryName, item->Name(), len))) {
460 // Successful GetItem() is a good indicator that the file is about to be read
461 zipLog.Write(mURI, aEntryName);
462 return item; //-- found it
464 item = item->next;
466 MOZ_WIN_MEM_TRY_CATCH(return nullptr)
468 return nullptr;
471 //---------------------------------------------
472 // nsZipArchive::ExtractFile
473 // This extracts the item to the filehandle provided.
474 // If 'aFd' is null, it only tests the extraction.
475 // On extraction error(s) it removes the file.
476 //---------------------------------------------
477 nsresult nsZipArchive::ExtractFile(nsZipItem *item, nsIFile* outFile,
478 PRFileDesc* aFd)
480 if (!item)
481 return NS_ERROR_ILLEGAL_VALUE;
482 if (!mFd)
483 return NS_ERROR_FAILURE;
485 // Directory extraction is handled in nsJAR::Extract,
486 // so the item to be extracted should never be a directory
487 MOZ_ASSERT(!item->IsDirectory());
489 Bytef outbuf[ZIP_BUFLEN];
491 nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true);
493 nsresult rv = NS_OK;
495 while (true) {
496 uint32_t count = 0;
497 uint8_t* buf = cursor.Read(&count);
498 if (!buf) {
499 nsZipArchive::sFileCorruptedReason = "nsZipArchive: Read() failed to return a buffer";
500 rv = NS_ERROR_FILE_CORRUPTED;
501 break;
503 if (count == 0) {
504 break;
507 if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) {
508 rv = NS_ERROR_FILE_DISK_FULL;
509 break;
513 //-- delete the file on errors
514 if (aFd) {
515 PR_Close(aFd);
516 if (NS_FAILED(rv) && outFile) {
517 outFile->Remove(false);
521 return rv;
524 //---------------------------------------------
525 // nsZipArchive::FindInit
526 //---------------------------------------------
527 nsresult
528 nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind)
530 if (!aFind)
531 return NS_ERROR_ILLEGAL_VALUE;
533 // null out param in case an error happens
534 *aFind = nullptr;
536 bool regExp = false;
537 char* pattern = 0;
539 // Create synthetic directory entries on demand
540 nsresult rv = BuildSynthetics();
541 if (rv != NS_OK)
542 return rv;
544 // validate the pattern
545 if (aPattern)
547 switch (NS_WildCardValid((char*)aPattern))
549 case INVALID_SXP:
550 return NS_ERROR_ILLEGAL_VALUE;
552 case NON_SXP:
553 regExp = false;
554 break;
556 case VALID_SXP:
557 regExp = true;
558 break;
560 default:
561 // undocumented return value from RegExpValid!
562 MOZ_ASSERT(false);
563 return NS_ERROR_ILLEGAL_VALUE;
566 pattern = PL_strdup(aPattern);
567 if (!pattern)
568 return NS_ERROR_OUT_OF_MEMORY;
571 *aFind = new nsZipFind(this, pattern, regExp);
572 if (!*aFind) {
573 PL_strfree(pattern);
574 return NS_ERROR_OUT_OF_MEMORY;
577 return NS_OK;
582 //---------------------------------------------
583 // nsZipFind::FindNext
584 //---------------------------------------------
585 nsresult nsZipFind::FindNext(const char ** aResult, uint16_t *aNameLen)
587 if (!mArchive || !aResult || !aNameLen)
588 return NS_ERROR_ILLEGAL_VALUE;
590 *aResult = 0;
591 *aNameLen = 0;
592 MOZ_WIN_MEM_TRY_BEGIN
593 // we start from last match, look for next
594 while (mSlot < ZIP_TABSIZE)
596 // move to next in current chain, or move to new slot
597 mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
599 bool found = false;
600 if (!mItem)
601 ++mSlot; // no more in this chain, move to next slot
602 else if (!mPattern)
603 found = true; // always match
604 else if (mRegExp)
606 char buf[kMaxNameLength+1];
607 memcpy(buf, mItem->Name(), mItem->nameLength);
608 buf[mItem->nameLength]='\0';
609 found = (NS_WildCardMatch(buf, mPattern, false) == MATCH);
611 else
612 found = ((mItem->nameLength == strlen(mPattern)) &&
613 (memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0));
614 if (found) {
615 // Need also to return the name length, as it is NOT zero-terminatdd...
616 *aResult = mItem->Name();
617 *aNameLen = mItem->nameLength;
618 return NS_OK;
621 MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
622 return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
625 //***********************************************************
626 // nsZipArchive -- private implementation
627 //***********************************************************
629 //---------------------------------------------
630 // nsZipArchive::CreateZipItem
631 //---------------------------------------------
632 nsZipItem* nsZipArchive::CreateZipItem()
634 // Arena allocate the nsZipItem
635 return (nsZipItem*)mArena.Allocate(sizeof(nsZipItem));
638 //---------------------------------------------
639 // nsZipArchive::BuildFileList
640 //---------------------------------------------
641 nsresult nsZipArchive::BuildFileList(PRFileDesc *aFd)
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 MOZ_WIN_MEM_TRY_BEGIN
648 uint32_t centralOffset = 4;
649 if (mFd->mLen > ZIPCENTRAL_SIZE && xtolong(startp + centralOffset) == CENTRALSIG) {
650 // Success means optimized jar layout from bug 559961 is in effect
651 uint32_t readaheadLength = xtolong(startp);
652 if (readaheadLength) {
653 #if defined(XP_SOLARIS)
654 posix_madvise(const_cast<uint8_t*>(startp), readaheadLength, POSIX_MADV_WILLNEED);
655 #elif defined(XP_UNIX)
656 madvise(const_cast<uint8_t*>(startp), readaheadLength, MADV_WILLNEED);
657 #elif defined(XP_WIN)
658 if (aFd) {
659 HANDLE hFile = (HANDLE) PR_FileDesc2NativeHandle(aFd);
660 mozilla::ReadAhead(hFile, 0, readaheadLength);
662 #endif
664 } else {
665 for (buf = endp - ZIPEND_SIZE; buf > startp; buf--)
667 if (xtolong(buf) == ENDSIG) {
668 centralOffset = xtolong(((ZipEnd *)buf)->offset_central_dir);
669 break;
674 if (!centralOffset) {
675 nsZipArchive::sFileCorruptedReason = "nsZipArchive: no central offset";
676 return NS_ERROR_FILE_CORRUPTED;
679 buf = startp + centralOffset;
681 // avoid overflow of startp + centralOffset.
682 if (buf < startp) {
683 nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for central directory";
684 return NS_ERROR_FILE_CORRUPTED;
687 //-- Read the central directory headers
688 uint32_t sig = 0;
689 while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
690 (buf + int32_t(sizeof(uint32_t)) <= endp) &&
691 ((sig = xtolong(buf)) == CENTRALSIG)) {
692 // Make sure there is enough data available.
693 if ((buf > endp) || (endp - buf < ZIPCENTRAL_SIZE)) {
694 nsZipArchive::sFileCorruptedReason = "nsZipArchive: central directory too small";
695 return NS_ERROR_FILE_CORRUPTED;
698 // Read the fixed-size data.
699 ZipCentral* central = (ZipCentral*)buf;
701 uint16_t namelen = xtoint(central->filename_len);
702 uint16_t extralen = xtoint(central->extrafield_len);
703 uint16_t commentlen = xtoint(central->commentfield_len);
704 uint32_t diff = ZIPCENTRAL_SIZE + namelen + extralen + commentlen;
706 // Sanity check variable sizes and refuse to deal with
707 // anything too big: it's likely a corrupt archive.
708 if (namelen < 1 ||
709 namelen > kMaxNameLength) {
710 nsZipArchive::sFileCorruptedReason = "nsZipArchive: namelen out of range";
711 return NS_ERROR_FILE_CORRUPTED;
713 if (buf >= buf + diff || // No overflow
714 buf >= endp - diff) {
715 nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for next item";
716 return NS_ERROR_FILE_CORRUPTED;
719 // Point to the next item at the top of loop
720 buf += diff;
722 nsZipItem* item = CreateZipItem();
723 if (!item)
724 return NS_ERROR_OUT_OF_MEMORY;
726 item->central = central;
727 item->nameLength = namelen;
728 item->isSynthetic = false;
730 // Add item to file table
731 uint32_t hash = HashName(item->Name(), namelen);
732 item->next = mFiles[hash];
733 mFiles[hash] = item;
735 sig = 0;
736 } /* while reading central directory records */
738 if (sig != ENDSIG) {
739 nsZipArchive::sFileCorruptedReason = "nsZipArchive: unexpected sig";
740 return NS_ERROR_FILE_CORRUPTED;
743 // Make the comment available for consumers.
744 if ((endp >= buf) && (endp - buf >= ZIPEND_SIZE)) {
745 ZipEnd *zipend = (ZipEnd *)buf;
747 buf += ZIPEND_SIZE;
748 uint16_t commentlen = xtoint(zipend->commentfield_len);
749 if (endp - buf >= commentlen) {
750 mCommentPtr = (const char *)buf;
751 mCommentLen = commentlen;
755 MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
756 return NS_OK;
759 //---------------------------------------------
760 // nsZipArchive::BuildSynthetics
761 //---------------------------------------------
762 nsresult nsZipArchive::BuildSynthetics()
764 if (mBuiltSynthetics)
765 return NS_OK;
766 mBuiltSynthetics = true;
768 MOZ_WIN_MEM_TRY_BEGIN
769 // Create synthetic entries for any missing directories.
770 // Do this when all ziptable has scanned to prevent double entries.
771 for (auto* item : mFiles)
773 for (; item != nullptr; item = item->next)
775 if (item->isSynthetic)
776 continue;
778 //-- add entries for directories in the current item's path
779 //-- go from end to beginning, because then we can stop trying
780 //-- to create diritems if we find that the diritem we want to
781 //-- create already exists
782 //-- start just before the last char so as to not add the item
783 //-- twice if it's a directory
784 uint16_t namelen = item->nameLength;
785 MOZ_ASSERT(namelen > 0, "Attempt to build synthetic for zero-length entry name!");
786 const char *name = item->Name();
787 for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--)
789 if (name[dirlen-1] != '/')
790 continue;
792 // The character before this is '/', so if this is also '/' then we
793 // have an empty path component. Skip it.
794 if (name[dirlen] == '/')
795 continue;
797 // Is the directory already in the file table?
798 uint32_t hash = HashName(item->Name(), dirlen);
799 bool found = false;
800 for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next)
802 if ((dirlen == zi->nameLength) &&
803 (0 == memcmp(item->Name(), zi->Name(), dirlen)))
805 // we've already added this dir and all its parents
806 found = true;
807 break;
810 // if the directory was found, break out of the directory
811 // creation loop now that we know all implicit directories
812 // are there -- otherwise, start creating the zip item
813 if (found)
814 break;
816 nsZipItem* diritem = CreateZipItem();
817 if (!diritem)
818 return NS_ERROR_OUT_OF_MEMORY;
820 // Point to the central record of the original item for the name part.
821 diritem->central = item->central;
822 diritem->nameLength = dirlen;
823 diritem->isSynthetic = true;
825 // add diritem to the file table
826 diritem->next = mFiles[hash];
827 mFiles[hash] = diritem;
828 } /* end processing of dirs in item's name */
831 MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
832 return NS_OK;
835 nsZipHandle* nsZipArchive::GetFD()
837 if (!mFd)
838 return nullptr;
839 return mFd.get();
842 //---------------------------------------------
843 // nsZipArchive::GetDataOffset
844 //---------------------------------------------
845 uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem)
847 MOZ_ASSERT(aItem);
848 MOZ_WIN_MEM_TRY_BEGIN
849 //-- read local header to get variable length values and calculate
850 //-- the real data offset
851 uint32_t len = mFd->mLen;
852 const uint8_t* data = mFd->mFileData;
853 uint32_t offset = aItem->LocalOffset();
854 if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE)
855 return 0;
857 // -- check signature before using the structure, in case the zip file is corrupt
858 ZipLocal* Local = (ZipLocal*)(data + offset);
859 if ((xtolong(Local->signature) != LOCALSIG))
860 return 0;
862 //-- NOTE: extralen is different in central header and local header
863 //-- for archives created using the Unix "zip" utility. To set
864 //-- the offset accurately we need the _local_ extralen.
865 offset += ZIPLOCAL_SIZE +
866 xtoint(Local->filename_len) +
867 xtoint(Local->extrafield_len);
869 return offset;
870 MOZ_WIN_MEM_TRY_CATCH(return 0)
873 //---------------------------------------------
874 // nsZipArchive::GetData
875 //---------------------------------------------
876 const uint8_t* nsZipArchive::GetData(nsZipItem* aItem)
878 MOZ_ASSERT(aItem);
879 MOZ_WIN_MEM_TRY_BEGIN
880 uint32_t offset = GetDataOffset(aItem);
882 // -- check if there is enough source data in the file
883 if (!offset ||
884 mFd->mLen < aItem->Size() ||
885 offset > mFd->mLen - aItem->Size() ||
886 (aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) {
887 return nullptr;
890 return mFd->mFileData + offset;
891 MOZ_WIN_MEM_TRY_CATCH(return nullptr)
894 // nsZipArchive::GetComment
895 bool nsZipArchive::GetComment(nsACString &aComment)
897 MOZ_WIN_MEM_TRY_BEGIN
898 aComment.Assign(mCommentPtr, mCommentLen);
899 MOZ_WIN_MEM_TRY_CATCH(return false)
900 return true;
903 //---------------------------------------------
904 // nsZipArchive::SizeOfMapping
905 //---------------------------------------------
906 int64_t nsZipArchive::SizeOfMapping()
908 return mFd ? mFd->SizeOfMapping() : 0;
911 //------------------------------------------
912 // nsZipArchive constructor and destructor
913 //------------------------------------------
915 nsZipArchive::nsZipArchive()
916 : mRefCnt(0)
917 , mCommentPtr(nullptr)
918 , mCommentLen(0)
919 , mBuiltSynthetics(false)
921 zipLog.AddRef();
923 // initialize the table to nullptr
924 memset(mFiles, 0, sizeof(mFiles));
927 NS_IMPL_ADDREF(nsZipArchive)
928 NS_IMPL_RELEASE(nsZipArchive)
930 nsZipArchive::~nsZipArchive()
932 CloseArchive();
934 zipLog.Release();
938 //------------------------------------------
939 // nsZipFind constructor and destructor
940 //------------------------------------------
942 nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp)
943 : mArchive(aZip)
944 , mPattern(aPattern)
945 , mItem(nullptr)
946 , mSlot(0)
947 , mRegExp(aRegExp)
949 MOZ_COUNT_CTOR(nsZipFind);
952 nsZipFind::~nsZipFind()
954 PL_strfree(mPattern);
956 MOZ_COUNT_DTOR(nsZipFind);
959 //------------------------------------------
960 // helper functions
961 //------------------------------------------
964 * HashName
966 * returns a hash key for the entry name
968 MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
969 static uint32_t HashName(const char* aName, uint16_t len)
971 MOZ_ASSERT(aName != 0);
973 const uint8_t* p = (const uint8_t*)aName;
974 const uint8_t* endp = p + len;
975 uint32_t val = 0;
976 while (p != endp) {
977 val = val*37 + *p++;
980 return (val % ZIP_TABSIZE);
984 * x t o i n t
986 * Converts a two byte ugly endianed integer
987 * to our platform's integer.
989 static uint16_t xtoint (const uint8_t *ii)
991 return (uint16_t) ((ii [0]) | (ii [1] << 8));
995 * x t o l o n g
997 * Converts a four byte ugly endianed integer
998 * to our platform's integer.
1000 static uint32_t xtolong (const uint8_t *ll)
1002 return (uint32_t)( (ll [0] << 0) |
1003 (ll [1] << 8) |
1004 (ll [2] << 16) |
1005 (ll [3] << 24) );
1009 * GetModTime
1011 * returns last modification time in microseconds
1013 static PRTime GetModTime(uint16_t aDate, uint16_t aTime)
1015 // Note that on DST shift we can't handle correctly the hour that is valid
1016 // in both DST zones
1017 PRExplodedTime time;
1019 time.tm_usec = 0;
1021 time.tm_hour = (aTime >> 11) & 0x1F;
1022 time.tm_min = (aTime >> 5) & 0x3F;
1023 time.tm_sec = (aTime & 0x1F) * 2;
1025 time.tm_year = (aDate >> 9) + 1980;
1026 time.tm_month = ((aDate >> 5) & 0x0F) - 1;
1027 time.tm_mday = aDate & 0x1F;
1029 time.tm_params.tp_gmt_offset = 0;
1030 time.tm_params.tp_dst_offset = 0;
1032 PR_NormalizeTime(&time, PR_GMTParameters);
1033 time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
1034 PR_NormalizeTime(&time, PR_GMTParameters);
1035 time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
1037 return PR_ImplodeTime(&time);
1040 nsZipItem::nsZipItem()
1041 : next(nullptr)
1042 , central(nullptr)
1043 , nameLength(0)
1044 , isSynthetic(false)
1047 uint32_t nsZipItem::LocalOffset()
1049 return xtolong(central->localhdr_offset);
1052 uint32_t nsZipItem::Size()
1054 return isSynthetic ? 0 : xtolong(central->size);
1057 uint32_t nsZipItem::RealSize()
1059 return isSynthetic ? 0 : xtolong(central->orglen);
1062 uint32_t nsZipItem::CRC32()
1064 return isSynthetic ? 0 : xtolong(central->crc32);
1067 uint16_t nsZipItem::Date()
1069 return isSynthetic ? kSyntheticDate : xtoint(central->date);
1072 uint16_t nsZipItem::Time()
1074 return isSynthetic ? kSyntheticTime : xtoint(central->time);
1077 uint16_t nsZipItem::Compression()
1079 return isSynthetic ? STORED : xtoint(central->method);
1082 bool nsZipItem::IsDirectory()
1084 return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1]));
1087 uint16_t nsZipItem::Mode()
1089 if (isSynthetic) return 0755;
1090 return ((uint16_t)(central->external_attributes[2]) | 0x100);
1093 const uint8_t * nsZipItem::GetExtraField(uint16_t aTag, uint16_t *aBlockSize)
1095 if (isSynthetic) return nullptr;
1096 MOZ_WIN_MEM_TRY_BEGIN
1097 const unsigned char *buf = ((const unsigned char*)central) + ZIPCENTRAL_SIZE +
1098 nameLength;
1099 uint32_t buflen = (uint32_t)xtoint(central->extrafield_len);
1100 uint32_t pos = 0;
1101 uint16_t tag, blocksize;
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;
1115 MOZ_WIN_MEM_TRY_CATCH(return nullptr)
1116 return nullptr;
1120 PRTime nsZipItem::LastModTime()
1122 if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime);
1124 // Try to read timestamp from extra field
1125 uint16_t blocksize;
1126 const uint8_t *tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize);
1127 if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) {
1128 return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC;
1131 return GetModTime(Date(), Time());
1134 nsZipCursor::nsZipCursor(nsZipItem *item, nsZipArchive *aZip, uint8_t* aBuf,
1135 uint32_t aBufSize, bool doCRC)
1136 : mItem(item)
1137 , mBuf(aBuf)
1138 , mBufSize(aBufSize)
1139 , mZs()
1140 #ifdef MOZ_JAR_BROTLI
1141 , mBrotliState(nullptr)
1142 #endif
1143 , mCRC(0)
1144 , mDoCRC(doCRC)
1146 if (mItem->Compression() == DEFLATED) {
1147 #ifdef DEBUG
1148 nsresult status =
1149 #endif
1150 gZlibInit(&mZs);
1151 NS_ASSERTION(status == NS_OK, "Zlib failed to initialize");
1152 NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem");
1155 mZs.avail_in = item->Size();
1156 mZs.next_in = (Bytef*)aZip->GetData(item);
1158 #ifdef MOZ_JAR_BROTLI
1159 if (mItem->Compression() == MOZ_JAR_BROTLI) {
1160 mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
1162 #endif
1164 if (doCRC)
1165 mCRC = crc32(0L, Z_NULL, 0);
1168 nsZipCursor::~nsZipCursor()
1170 if (mItem->Compression() == DEFLATED) {
1171 inflateEnd(&mZs);
1173 #ifdef MOZ_JAR_BROTLI
1174 if (mItem->Compression() == MOZ_JAR_BROTLI) {
1175 BrotliDecoderDestroyInstance(mBrotliState);
1177 #endif
1180 uint8_t* nsZipCursor::ReadOrCopy(uint32_t *aBytesRead, bool aCopy) {
1181 int zerr;
1182 uint8_t *buf = nullptr;
1183 bool verifyCRC = true;
1185 if (!mZs.next_in)
1186 return nullptr;
1187 MOZ_WIN_MEM_TRY_BEGIN
1188 switch (mItem->Compression()) {
1189 case STORED:
1190 if (!aCopy) {
1191 *aBytesRead = mZs.avail_in;
1192 buf = mZs.next_in;
1193 mZs.next_in += mZs.avail_in;
1194 mZs.avail_in = 0;
1195 } else {
1196 *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in;
1197 memcpy(mBuf, mZs.next_in, *aBytesRead);
1198 mZs.avail_in -= *aBytesRead;
1199 mZs.next_in += *aBytesRead;
1201 break;
1202 case DEFLATED:
1203 buf = mBuf;
1204 mZs.next_out = buf;
1205 mZs.avail_out = mBufSize;
1207 zerr = inflate(&mZs, Z_PARTIAL_FLUSH);
1208 if (zerr != Z_OK && zerr != Z_STREAM_END)
1209 return nullptr;
1211 *aBytesRead = mZs.next_out - buf;
1212 verifyCRC = (zerr == Z_STREAM_END);
1213 break;
1214 #ifdef MOZ_JAR_BROTLI
1215 case MOZ_JAR_BROTLI: {
1216 buf = mBuf;
1217 mZs.next_out = buf;
1218 /* The brotli library wants size_t, but z_stream only contains
1219 * unsigned int for avail_*. So use temporary stack values. */
1220 size_t avail_out = mBufSize;
1221 size_t avail_in = mZs.avail_in;
1222 BrotliDecoderResult result = BrotliDecoderDecompressStream(
1223 mBrotliState,
1224 &avail_in, const_cast<const unsigned char**>(&mZs.next_in),
1225 &avail_out, &mZs.next_out, nullptr);
1226 /* We don't need to update avail_out, it's not used outside this
1227 * function. */
1228 mZs.avail_in = avail_in;
1230 if (result == BROTLI_DECODER_RESULT_ERROR) {
1231 return nullptr;
1234 *aBytesRead = mZs.next_out - buf;
1235 verifyCRC = (result == BROTLI_DECODER_RESULT_SUCCESS);
1236 break;
1238 #endif
1239 default:
1240 return nullptr;
1243 if (mDoCRC) {
1244 mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
1245 if (verifyCRC && mCRC != mItem->CRC32())
1246 return nullptr;
1248 MOZ_WIN_MEM_TRY_CATCH(return nullptr)
1249 return buf;
1252 nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive *aZip,
1253 const char * aEntryName, bool doCRC)
1254 : mReturnBuf(nullptr)
1255 , mReadlen(0)
1257 // make sure the ziparchive hangs around
1258 mZipHandle = aZip->GetFD();
1260 nsZipItem* item = aZip->GetItem(aEntryName);
1261 if (!item)
1262 return;
1264 uint32_t size = 0;
1265 bool compressed = (item->Compression() == DEFLATED);
1266 #ifdef MOZ_JAR_BROTLI
1267 compressed |= (item->Compression() == MOZ_JAR_BROTLI);
1268 #endif
1269 if (compressed) {
1270 size = item->RealSize();
1271 mAutoBuf = MakeUniqueFallible<uint8_t[]>(size);
1272 if (!mAutoBuf) {
1273 return;
1277 nsZipCursor cursor(item, aZip, mAutoBuf.get(), size, doCRC);
1278 mReturnBuf = cursor.Read(&mReadlen);
1279 if (!mReturnBuf) {
1280 return;
1283 if (mReadlen != item->RealSize()) {
1284 NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow");
1285 mReturnBuf = nullptr;
1286 return;
1290 /* static */ const char*
1291 nsZipArchive::sFileCorruptedReason = nullptr;