modules/libjar/zipwriter/src/nsZipWriter.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     4  */
     6 #include "nsZipWriter.h"
     8 #include <algorithm>
    10 #include "StreamFunctions.h"
    11 #include "nsZipDataStream.h"
    12 #include "nsISeekableStream.h"
    13 #include "nsIAsyncStreamCopier.h"
    14 #include "nsIStreamListener.h"
    15 #include "nsIInputStreamPump.h"
    16 #include "nsComponentManagerUtils.h"
    17 #include "nsMemory.h"
    18 #include "nsError.h"
    19 #include "nsStreamUtils.h"
    20 #include "nsThreadUtils.h"
    21 #include "nsNetUtil.h"
    22 #include "prio.h"
    24 #define ZIP_EOCDR_HEADER_SIZE 22
    25 #define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
    27 /**
    28  * nsZipWriter is used to create and add to zip files.
    29  * It is based on the spec available at
    30  * http://www.pkware.com/documents/casestudies/APPNOTE.TXT.
    31  * 
    32  * The basic structure of a zip file created is slightly simpler than that
    33  * illustrated in the spec because certain features of the zip format are
    34  * unsupported:
    35  * 
    36  * [local file header 1]
    37  * [file data 1]
    38  * . 
    39  * .
    40  * .
    41  * [local file header n]
    42  * [file data n]
    43  * [central directory]
    44  * [end of central directory record]
    45  */
    46 NS_IMPL_ISUPPORTS(nsZipWriter, nsIZipWriter,
    47                   nsIRequestObserver)
    49 nsZipWriter::nsZipWriter()
    50 {
    51     mInQueue = false;
    52 }
    54 nsZipWriter::~nsZipWriter()
    55 {
    56     if (mStream && !mInQueue)
    57         Close();
    58 }
    60 /* attribute AString comment; */
    61 NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
    62 {
    63     if (!mStream)
    64         return NS_ERROR_NOT_INITIALIZED;
    66     aComment = mComment;
    67     return NS_OK;
    68 }
    70 NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
    71 {
    72     if (!mStream)
    73         return NS_ERROR_NOT_INITIALIZED;
    75     mComment = aComment;
    76     mCDSDirty = true;
    77     return NS_OK;
    78 }
    80 /* readonly attribute boolean inQueue; */
    81 NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
    82 {
    83     *aInQueue = mInQueue;
    84     return NS_OK;
    85 }
    87 /* readonly attribute nsIFile file; */
    88 NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
    89 {
    90     if (!mFile)
    91         return NS_ERROR_NOT_INITIALIZED;
    93     nsCOMPtr<nsIFile> file;
    94     nsresult rv = mFile->Clone(getter_AddRefs(file));
    95     NS_ENSURE_SUCCESS(rv, rv);
    97     NS_ADDREF(*aFile = file);
    98     return NS_OK;
    99 }
   101 /*
   102  * Reads file entries out of an existing zip file.
   103  */
   104 nsresult nsZipWriter::ReadFile(nsIFile *aFile)
   105 {
   106     int64_t size;
   107     nsresult rv = aFile->GetFileSize(&size);
   108     NS_ENSURE_SUCCESS(rv, rv);
   110     // If the file is too short, it cannot be a valid archive, thus we fail
   111     // without even attempting to open it
   112     NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
   114     nsCOMPtr<nsIInputStream> inputStream;
   115     rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
   116     NS_ENSURE_SUCCESS(rv, rv);
   118     uint8_t buf[1024];
   119     int64_t seek = size - 1024;
   120     uint32_t length = 1024;
   122     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
   124     while (true) {
   125         if (seek < 0) {
   126             length += (int32_t)seek;
   127             seek = 0;
   128         }
   130         rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
   131         if (NS_FAILED(rv)) {
   132             inputStream->Close();
   133             return rv;
   134         }
   135         rv = ZW_ReadData(inputStream, (char *)buf, length);
   136         if (NS_FAILED(rv)) {
   137             inputStream->Close();
   138             return rv;
   139         }
   141         /*
   142          * We have to backtrack from the end of the file until we find the
   143          * CDS signature
   144          */
   145         // We know it's at least this far from the end
   146         for (uint32_t pos = length - ZIP_EOCDR_HEADER_SIZE;
   147              (int32_t)pos >= 0; pos--) {
   148             uint32_t sig = PEEK32(buf + pos);
   149             if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
   150                 // Skip down to entry count
   151                 pos += 10;
   152                 uint32_t entries = READ16(buf, &pos);
   153                 // Skip past CDS size
   154                 pos += 4;
   155                 mCDSOffset = READ32(buf, &pos);
   156                 uint32_t commentlen = READ16(buf, &pos);
   158                 if (commentlen == 0)
   159                     mComment.Truncate();
   160                 else if (pos + commentlen <= length)
   161                     mComment.Assign((const char *)buf + pos, commentlen);
   162                 else {
   163                     if ((seek + pos + commentlen) > size) {
   164                         inputStream->Close();
   165                         return NS_ERROR_FILE_CORRUPTED;
   166                     }
   167                     nsAutoArrayPtr<char> field(new char[commentlen]);
   168                     NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
   169                     rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   170                                         seek + pos);
   171                     if (NS_FAILED(rv)) {
   172                         inputStream->Close();
   173                         return rv;
   174                     }
   175                     rv = ZW_ReadData(inputStream, field.get(), length);
   176                     if (NS_FAILED(rv)) {
   177                         inputStream->Close();
   178                         return rv;
   179                     }
   180                     mComment.Assign(field.get(), commentlen);
   181                 }
   183                 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   184                                     mCDSOffset);
   185                 if (NS_FAILED(rv)) {
   186                     inputStream->Close();
   187                     return rv;
   188                 }
   190                 for (uint32_t entry = 0; entry < entries; entry++) {
   191                     nsZipHeader* header = new nsZipHeader();
   192                     if (!header) {
   193                         inputStream->Close();
   194                         mEntryHash.Clear();
   195                         mHeaders.Clear();
   196                         return NS_ERROR_OUT_OF_MEMORY;
   197                     }
   198                     rv = header->ReadCDSHeader(inputStream);
   199                     if (NS_FAILED(rv)) {
   200                         inputStream->Close();
   201                         mEntryHash.Clear();
   202                         mHeaders.Clear();
   203                         return rv;
   204                     }
   205                     mEntryHash.Put(header->mName, mHeaders.Count());
   206                     if (!mHeaders.AppendObject(header))
   207                         return NS_ERROR_OUT_OF_MEMORY;
   208                 }
   210                 return inputStream->Close();
   211             }
   212         }
   214         if (seek == 0) {
   215             // We've reached the start with no signature found. Corrupt.
   216             inputStream->Close();
   217             return NS_ERROR_FILE_CORRUPTED;
   218         }
   220         // Overlap by the size of the end of cdr
   221         seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
   222     }
   223     // Will never reach here in reality
   224     NS_NOTREACHED("Loop should never complete");
   225     return NS_ERROR_UNEXPECTED;
   226 }
   228 /* void open (in nsIFile aFile, in int32_t aIoFlags); */
   229 NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, int32_t aIoFlags)
   230 {
   231     if (mStream)
   232         return NS_ERROR_ALREADY_INITIALIZED;
   234     NS_ENSURE_ARG_POINTER(aFile);
   236     // Need to be able to write to the file
   237     if (aIoFlags & PR_RDONLY)
   238         return NS_ERROR_FAILURE;
   240     nsresult rv = aFile->Clone(getter_AddRefs(mFile));
   241     NS_ENSURE_SUCCESS(rv, rv);
   243     bool exists;
   244     rv = mFile->Exists(&exists);
   245     NS_ENSURE_SUCCESS(rv, rv);
   246     if (!exists && !(aIoFlags & PR_CREATE_FILE))
   247         return NS_ERROR_FILE_NOT_FOUND;
   249     if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
   250         rv = ReadFile(mFile);
   251         NS_ENSURE_SUCCESS(rv, rv);
   252         mCDSDirty = false;
   253     }
   254     else {
   255         mCDSOffset = 0;
   256         mCDSDirty = true;
   257         mComment.Truncate();
   258     }
   260     // Silently drop PR_APPEND
   261     aIoFlags &= 0xef;
   263     nsCOMPtr<nsIOutputStream> stream;
   264     rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
   265     if (NS_FAILED(rv)) {
   266         mHeaders.Clear();
   267         mEntryHash.Clear();
   268         return rv;
   269     }
   271     rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream, 64 * 1024);
   272     if (NS_FAILED(rv)) {
   273         stream->Close();
   274         mHeaders.Clear();
   275         mEntryHash.Clear();
   276         return rv;
   277     }
   279     if (mCDSOffset > 0) {
   280         rv = SeekCDS();
   281         NS_ENSURE_SUCCESS(rv, rv);
   282     }
   284     return NS_OK;
   285 }
   287 /* nsIZipEntry getEntry (in AString aZipEntry); */
   288 NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
   289                                     nsIZipEntry **_retval)
   290 {
   291     int32_t pos;
   292     if (mEntryHash.Get(aZipEntry, &pos))
   293         NS_ADDREF(*_retval = mHeaders[pos]);
   294     else
   295         *_retval = nullptr;
   297     return NS_OK;
   298 }
   300 /* boolean hasEntry (in AString aZipEntry); */
   301 NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
   302                                     bool *_retval)
   303 {
   304     *_retval = mEntryHash.Get(aZipEntry, nullptr);
   306     return NS_OK;
   307 }
   309 /* void addEntryDirectory (in AUTF8String aZipEntry, in PRTime aModTime,
   310  *                         in boolean aQueue); */
   311 NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
   312                                              PRTime aModTime, bool aQueue)
   313 {
   314     if (!mStream)
   315         return NS_ERROR_NOT_INITIALIZED;
   317     if (aQueue) {
   318         nsZipQueueItem item;
   319         item.mOperation = OPERATION_ADD;
   320         item.mZipEntry = aZipEntry;
   321         item.mModTime = aModTime;
   322         item.mPermissions = PERMISSIONS_DIR;
   323         if (!mQueue.AppendElement(item))
   324             return NS_ERROR_OUT_OF_MEMORY;
   325         return NS_OK;
   326     }
   328     if (mInQueue)
   329         return NS_ERROR_IN_PROGRESS;
   330     return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
   331 }
   333 /* void addEntryFile (in AUTF8String aZipEntry, in int32_t aCompression,
   334  *                    in nsIFile aFile, in boolean aQueue); */
   335 NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
   336                                         int32_t aCompression, nsIFile *aFile,
   337                                         bool aQueue)
   338 {
   339     NS_ENSURE_ARG_POINTER(aFile);
   340     if (!mStream)
   341         return NS_ERROR_NOT_INITIALIZED;
   343     nsresult rv;
   344     if (aQueue) {
   345         nsZipQueueItem item;
   346         item.mOperation = OPERATION_ADD;
   347         item.mZipEntry = aZipEntry;
   348         item.mCompression = aCompression;
   349         rv = aFile->Clone(getter_AddRefs(item.mFile));
   350         NS_ENSURE_SUCCESS(rv, rv);
   351         if (!mQueue.AppendElement(item))
   352             return NS_ERROR_OUT_OF_MEMORY;
   353         return NS_OK;
   354     }
   356     if (mInQueue)
   357         return NS_ERROR_IN_PROGRESS;
   359     bool exists;
   360     rv = aFile->Exists(&exists);
   361     NS_ENSURE_SUCCESS(rv, rv);
   362     if (!exists)
   363         return NS_ERROR_FILE_NOT_FOUND;
   365     bool isdir;
   366     rv = aFile->IsDirectory(&isdir);
   367     NS_ENSURE_SUCCESS(rv, rv);
   369     PRTime modtime;
   370     rv = aFile->GetLastModifiedTime(&modtime);
   371     NS_ENSURE_SUCCESS(rv, rv);
   372     modtime *= PR_USEC_PER_MSEC;
   374     uint32_t permissions;
   375     rv = aFile->GetPermissions(&permissions);
   376     NS_ENSURE_SUCCESS(rv, rv);
   378     if (isdir)
   379         return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
   381     if (mEntryHash.Get(aZipEntry, nullptr))
   382         return NS_ERROR_FILE_ALREADY_EXISTS;
   384     nsCOMPtr<nsIInputStream> inputStream;
   385     rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
   386                                     aFile);
   387     NS_ENSURE_SUCCESS(rv, rv);
   389     rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
   390                         false, permissions);
   391     NS_ENSURE_SUCCESS(rv, rv);
   393     return inputStream->Close();
   394 }
   396 /* void addEntryChannel (in AUTF8String aZipEntry, in PRTime aModTime,
   397  *                       in int32_t aCompression, in nsIChannel aChannel,
   398  *                       in boolean aQueue); */
   399 NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
   400                                            PRTime aModTime,
   401                                            int32_t aCompression,
   402                                            nsIChannel *aChannel,
   403                                            bool aQueue)
   404 {
   405     NS_ENSURE_ARG_POINTER(aChannel);
   406     if (!mStream)
   407         return NS_ERROR_NOT_INITIALIZED;
   409     if (aQueue) {
   410         nsZipQueueItem item;
   411         item.mOperation = OPERATION_ADD;
   412         item.mZipEntry = aZipEntry;
   413         item.mModTime = aModTime;
   414         item.mCompression = aCompression;
   415         item.mPermissions = PERMISSIONS_FILE;
   416         item.mChannel = aChannel;
   417         if (!mQueue.AppendElement(item))
   418             return NS_ERROR_OUT_OF_MEMORY;
   419         return NS_OK;
   420     }
   422     if (mInQueue)
   423         return NS_ERROR_IN_PROGRESS;
   424     if (mEntryHash.Get(aZipEntry, nullptr))
   425         return NS_ERROR_FILE_ALREADY_EXISTS;
   427     nsCOMPtr<nsIInputStream> inputStream;
   428     nsresult rv = aChannel->Open(getter_AddRefs(inputStream));
   429     NS_ENSURE_SUCCESS(rv, rv);
   431     rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
   432                         false, PERMISSIONS_FILE);
   433     NS_ENSURE_SUCCESS(rv, rv);
   435     return inputStream->Close();
   436 }
   438 /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
   439  *                      in int32_t aCompression, in nsIInputStream aStream,
   440  *                      in boolean aQueue); */
   441 NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
   442                                           PRTime aModTime,
   443                                           int32_t aCompression,
   444                                           nsIInputStream *aStream,
   445                                           bool aQueue)
   446 {
   447     return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
   448                           PERMISSIONS_FILE);
   449 }
   451 /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
   452  *                      in int32_t aCompression, in nsIInputStream aStream,
   453  *                      in boolean aQueue, in unsigned long aPermissions); */
   454 nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
   455                                      PRTime aModTime,
   456                                      int32_t aCompression,
   457                                      nsIInputStream *aStream,
   458                                      bool aQueue,
   459                                      uint32_t aPermissions)
   460 {
   461     NS_ENSURE_ARG_POINTER(aStream);
   462     if (!mStream)
   463         return NS_ERROR_NOT_INITIALIZED;
   465     if (aQueue) {
   466         nsZipQueueItem item;
   467         item.mOperation = OPERATION_ADD;
   468         item.mZipEntry = aZipEntry;
   469         item.mModTime = aModTime;
   470         item.mCompression = aCompression;
   471         item.mPermissions = aPermissions;
   472         item.mStream = aStream;
   473         if (!mQueue.AppendElement(item))
   474             return NS_ERROR_OUT_OF_MEMORY;
   475         return NS_OK;
   476     }
   478     if (mInQueue)
   479         return NS_ERROR_IN_PROGRESS;
   480     if (mEntryHash.Get(aZipEntry, nullptr))
   481         return NS_ERROR_FILE_ALREADY_EXISTS;
   483     nsRefPtr<nsZipHeader> header = new nsZipHeader();
   484     NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
   485     header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
   486                  mCDSOffset);
   487     nsresult rv = header->WriteFileHeader(mStream);
   488     if (NS_FAILED(rv)) {
   489         SeekCDS();
   490         return rv;
   491     }
   493     nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
   494     if (!stream) {
   495         SeekCDS();
   496         return NS_ERROR_OUT_OF_MEMORY;
   497     }
   498     rv = stream->Init(this, mStream, header, aCompression);
   499     if (NS_FAILED(rv)) {
   500         SeekCDS();
   501         return rv;
   502     }
   504     rv = stream->ReadStream(aStream);
   505     if (NS_FAILED(rv))
   506         SeekCDS();
   507     return rv;
   508 }
   510 /* void removeEntry (in AUTF8String aZipEntry, in boolean aQueue); */
   511 NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
   512                                        bool aQueue)
   513 {
   514     if (!mStream)
   515         return NS_ERROR_NOT_INITIALIZED;
   517     if (aQueue) {
   518         nsZipQueueItem item;
   519         item.mOperation = OPERATION_REMOVE;
   520         item.mZipEntry = aZipEntry;
   521         if (!mQueue.AppendElement(item))
   522             return NS_ERROR_OUT_OF_MEMORY;
   523         return NS_OK;
   524     }
   526     if (mInQueue)
   527         return NS_ERROR_IN_PROGRESS;
   529     int32_t pos;
   530     if (mEntryHash.Get(aZipEntry, &pos)) {
   531         // Flush any remaining data before we seek.
   532         nsresult rv = mStream->Flush();
   533         NS_ENSURE_SUCCESS(rv, rv);
   534         if (pos < mHeaders.Count() - 1) {
   535             // This is not the last entry, pull back the data.
   536             nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
   537             rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   538                                 mHeaders[pos]->mOffset);
   539             NS_ENSURE_SUCCESS(rv, rv);
   541             nsCOMPtr<nsIInputStream> inputStream;
   542             rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
   543                                             mFile);
   544             NS_ENSURE_SUCCESS(rv, rv);
   545             seekable = do_QueryInterface(inputStream);
   546             rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   547                                 mHeaders[pos + 1]->mOffset);
   548             if (NS_FAILED(rv)) {
   549                 inputStream->Close();
   550                 return rv;
   551             }
   553             uint32_t count = mCDSOffset - mHeaders[pos + 1]->mOffset;
   554             uint32_t read = 0;
   555             char buf[4096];
   556             while (count > 0) {
   557                 read = std::min(count, (uint32_t) sizeof(buf));
   559                 rv = inputStream->Read(buf, read, &read);
   560                 if (NS_FAILED(rv)) {
   561                     inputStream->Close();
   562                     Cleanup();
   563                     return rv;
   564                 }
   566                 rv = ZW_WriteData(mStream, buf, read);
   567                 if (NS_FAILED(rv)) {
   568                     inputStream->Close();
   569                     Cleanup();
   570                     return rv;
   571                 }
   573                 count -= read;
   574             }
   575             inputStream->Close();
   577             // Rewrite header offsets and update hash
   578             uint32_t shift = (mHeaders[pos + 1]->mOffset -
   579                               mHeaders[pos]->mOffset);
   580             mCDSOffset -= shift;
   581             int32_t pos2 = pos + 1;
   582             while (pos2 < mHeaders.Count()) {
   583                 mEntryHash.Put(mHeaders[pos2]->mName, pos2-1);
   584                 mHeaders[pos2]->mOffset -= shift;
   585                 pos2++;
   586             }
   587         }
   588         else {
   589             // Remove the last entry is just a case of moving the CDS
   590             mCDSOffset = mHeaders[pos]->mOffset;
   591             rv = SeekCDS();
   592             NS_ENSURE_SUCCESS(rv, rv);
   593         }
   595         mEntryHash.Remove(mHeaders[pos]->mName);
   596         mHeaders.RemoveObjectAt(pos);
   597         mCDSDirty = true;
   599         return NS_OK;
   600     }
   602     return NS_ERROR_FILE_NOT_FOUND;
   603 }
   605 /* void processQueue (in nsIRequestObserver aObserver,
   606  *                    in nsISupports aContext); */
   607 NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
   608                                         nsISupports *aContext)
   609 {
   610     if (!mStream)
   611         return NS_ERROR_NOT_INITIALIZED;
   612     if (mInQueue)
   613         return NS_ERROR_IN_PROGRESS;
   615     mProcessObserver = aObserver;
   616     mProcessContext = aContext;
   617     mInQueue = true;
   619     if (mProcessObserver)
   620         mProcessObserver->OnStartRequest(nullptr, mProcessContext);
   622     BeginProcessingNextItem();
   624     return NS_OK;
   625 }
   627 /* void close (); */
   628 NS_IMETHODIMP nsZipWriter::Close()
   629 {
   630     if (!mStream)
   631         return NS_ERROR_NOT_INITIALIZED;
   632     if (mInQueue)
   633         return NS_ERROR_IN_PROGRESS;
   635     if (mCDSDirty) {
   636         uint32_t size = 0;
   637         for (int32_t i = 0; i < mHeaders.Count(); i++) {
   638             nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
   639             if (NS_FAILED(rv)) {
   640                 Cleanup();
   641                 return rv;
   642             }
   643             size += mHeaders[i]->GetCDSHeaderLength();
   644         }
   646         uint8_t buf[ZIP_EOCDR_HEADER_SIZE];
   647         uint32_t pos = 0;
   648         WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
   649         WRITE16(buf, &pos, 0);
   650         WRITE16(buf, &pos, 0);
   651         WRITE16(buf, &pos, mHeaders.Count());
   652         WRITE16(buf, &pos, mHeaders.Count());
   653         WRITE32(buf, &pos, size);
   654         WRITE32(buf, &pos, mCDSOffset);
   655         WRITE16(buf, &pos, mComment.Length());
   657         nsresult rv = ZW_WriteData(mStream, (const char *)buf, pos);
   658         if (NS_FAILED(rv)) {
   659             Cleanup();
   660             return rv;
   661         }
   663         rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
   664         if (NS_FAILED(rv)) {
   665             Cleanup();
   666             return rv;
   667         }
   669         nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
   670         rv = seekable->SetEOF();
   671         if (NS_FAILED(rv)) {
   672             Cleanup();
   673             return rv;
   674         }
   676         // Go back and rewrite the file headers
   677         for (int32_t i = 0; i < mHeaders.Count(); i++) {
   678             nsZipHeader *header = mHeaders[i];
   679             if (!header->mWriteOnClose)
   680               continue;
   682             rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, header->mOffset);
   683             if (NS_FAILED(rv)) {
   684                Cleanup();
   685                return rv;
   686             }
   687             rv = header->WriteFileHeader(mStream);
   688             if (NS_FAILED(rv)) {
   689                Cleanup();
   690                return rv;
   691             }
   692         }
   693     }
   695     nsresult rv = mStream->Close();
   696     mStream = nullptr;
   697     mHeaders.Clear();
   698     mEntryHash.Clear();
   699     mQueue.Clear();
   701     return rv;
   702 }
   704 // Our nsIRequestObserver monitors removal operations performed on the queue
   705 /* void onStartRequest (in nsIRequest aRequest, in nsISupports aContext); */
   706 NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
   707                                           nsISupports *aContext)
   708 {
   709     return NS_OK;
   710 }
   712 /* void onStopRequest (in nsIRequest aRequest, in nsISupports aContext,
   713  *                                             in nsresult aStatusCode); */
   714 NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
   715                                          nsISupports *aContext,
   716                                          nsresult aStatusCode)
   717 {
   718     if (NS_FAILED(aStatusCode)) {
   719         FinishQueue(aStatusCode);
   720         Cleanup();
   721     }
   723     nsresult rv = mStream->Flush();
   724     if (NS_FAILED(rv)) {
   725         FinishQueue(rv);
   726         Cleanup();
   727         return rv;
   728     }
   729     rv = SeekCDS();
   730     if (NS_FAILED(rv)) {
   731         FinishQueue(rv);
   732         return rv;
   733     }
   735     BeginProcessingNextItem();
   737     return NS_OK;
   738 }
   740 /*
   741  * Make all stored(uncompressed) files align to given alignment size.
   742  */
   743 NS_IMETHODIMP nsZipWriter::AlignStoredFiles(uint16_t aAlignSize)
   744 {
   745     nsresult rv;
   747     // Check for range and power of 2.
   748     if (aAlignSize < 2 || aAlignSize > 32768 ||
   749         (aAlignSize & (aAlignSize - 1)) != 0) {
   750         return NS_ERROR_INVALID_ARG;
   751     }
   753     for (int i = 0; i < mHeaders.Count(); i++) {
   754         nsZipHeader *header = mHeaders[i];
   756         // Check whether this entry is file and compression method is stored.
   757         bool isdir;
   758         rv = header->GetIsDirectory(&isdir);
   759         if (NS_FAILED(rv)) {
   760             return rv;
   761         }
   762         if (isdir || header->mMethod != 0) {
   763             continue;
   764         }
   765         // Pad extra field to align data starting position to specified size.
   766         uint32_t old_len = header->mLocalFieldLength;
   767         rv = header->PadExtraField(header->mOffset, aAlignSize);
   768         if (NS_FAILED(rv)) {
   769             continue;
   770         }
   771         // No padding means data already aligned.
   772         uint32_t shift = header->mLocalFieldLength - old_len;
   773         if (shift == 0) {
   774             continue;
   775         }
   777         // Flush any remaining data before we start.
   778         rv = mStream->Flush();
   779         if (NS_FAILED(rv)) {
   780             return rv;
   781         }
   783         // Open zip file for reading.
   784         nsCOMPtr<nsIInputStream> inputStream;
   785         rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mFile);
   786         if (NS_FAILED(rv)) {
   787             return rv;
   788         }
   790         nsCOMPtr<nsISeekableStream> in_seekable = do_QueryInterface(inputStream);
   791         nsCOMPtr<nsISeekableStream> out_seekable = do_QueryInterface(mStream);
   793         uint32_t data_offset = header->mOffset + header->GetFileHeaderLength() - shift;
   794         uint32_t count = mCDSOffset - data_offset;
   795         uint32_t read;
   796         char buf[4096];
   798         // Shift data to aligned postion.
   799         while (count > 0) {
   800             read = std::min(count, (uint32_t) sizeof(buf));
   802             rv = in_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   803                                    data_offset + count - read);
   804             if (NS_FAILED(rv)) {
   805                 break;
   806              }
   808             rv = inputStream->Read(buf, read, &read);
   809             if (NS_FAILED(rv)) {
   810                 break;
   811             }
   813             rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   814                                     data_offset + count - read + shift);
   815             if (NS_FAILED(rv)) {
   816                 break;
   817              }
   819             rv = ZW_WriteData(mStream, buf, read);
   820             if (NS_FAILED(rv)) {
   821                 break;
   822             }
   824             count -= read;
   825         }
   826         inputStream->Close();
   827         if (NS_FAILED(rv)) {
   828             Cleanup();
   829             return rv;
   830         }
   832         // Update current header
   833         rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
   834                                 header->mOffset);
   835         if (NS_FAILED(rv)) {
   836             Cleanup();
   837             return rv;
   838         }
   839         rv = header->WriteFileHeader(mStream);
   840         if (NS_FAILED(rv)) {
   841             Cleanup();
   842             return rv;
   843         }
   845         // Update offset of all other headers
   846         int pos = i + 1;
   847         while (pos < mHeaders.Count()) {
   848             mHeaders[pos]->mOffset += shift;
   849             pos++;
   850         }
   851         mCDSOffset += shift;
   852         rv = SeekCDS();
   853         if (NS_FAILED(rv)) {
   854             return rv;
   855         }
   856         mCDSDirty = true;
   857     }
   859     return NS_OK;
   860 }
   862 nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
   863                                                 PRTime aModTime,
   864                                                 uint32_t aPermissions)
   865 {
   866     nsRefPtr<nsZipHeader> header = new nsZipHeader();
   867     NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
   869     uint32_t zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
   871     if (aZipEntry.Last() != '/') {
   872         nsCString dirPath;
   873         dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
   874         header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
   875     }
   876     else
   877         header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
   879     if (mEntryHash.Get(header->mName, nullptr))
   880         return NS_ERROR_FILE_ALREADY_EXISTS;
   882     nsresult rv = header->WriteFileHeader(mStream);
   883     if (NS_FAILED(rv)) {
   884         Cleanup();
   885         return rv;
   886     }
   888     mCDSDirty = true;
   889     mCDSOffset += header->GetFileHeaderLength();
   890     mEntryHash.Put(header->mName, mHeaders.Count());
   892     if (!mHeaders.AppendObject(header)) {
   893         Cleanup();
   894         return NS_ERROR_OUT_OF_MEMORY;
   895     }
   897     return NS_OK;
   898 }
   900 /*
   901  * Recovering from an error while adding a new entry is simply a case of
   902  * seeking back to the CDS. If we fail trying to do that though then cleanup
   903  * and bail out.
   904  */
   905 nsresult nsZipWriter::SeekCDS()
   906 {
   907     nsresult rv;
   908     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
   909     if (NS_FAILED(rv)) {
   910         Cleanup();
   911         return rv;
   912     }
   913     rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
   914     if (NS_FAILED(rv))
   915         Cleanup();
   916     return rv;
   917 }
   919 /*
   920  * In a bad error condition this essentially closes down the component as best
   921  * it can.
   922  */
   923 void nsZipWriter::Cleanup()
   924 {
   925     mHeaders.Clear();
   926     mEntryHash.Clear();
   927     if (mStream)
   928         mStream->Close();
   929     mStream = nullptr;
   930     mFile = nullptr;
   931 }
   933 /*
   934  * Called when writing a file to the zip is complete.
   935  */
   936 nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
   937                                             nsresult aStatus)
   938 {
   939     if (NS_SUCCEEDED(aStatus)) {
   940         mEntryHash.Put(aHeader->mName, mHeaders.Count());
   941         if (!mHeaders.AppendObject(aHeader)) {
   942             mEntryHash.Remove(aHeader->mName);
   943             SeekCDS();
   944             return NS_ERROR_OUT_OF_MEMORY;
   945         }
   946         mCDSDirty = true;
   947         mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
   949         if (mInQueue)
   950             BeginProcessingNextItem();
   952         return NS_OK;
   953     }
   955     nsresult rv = SeekCDS();
   956     if (mInQueue)
   957         FinishQueue(aStatus);
   958     return rv;
   959 }
   961 inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
   962                                                      bool* complete)
   963 {
   964     if (aItem->mFile) {
   965         bool exists;
   966         nsresult rv = aItem->mFile->Exists(&exists);
   967         NS_ENSURE_SUCCESS(rv, rv);
   969         if (!exists) return NS_ERROR_FILE_NOT_FOUND;
   971         bool isdir;
   972         rv = aItem->mFile->IsDirectory(&isdir);
   973         NS_ENSURE_SUCCESS(rv, rv);
   975         rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
   976         NS_ENSURE_SUCCESS(rv, rv);
   977         aItem->mModTime *= PR_USEC_PER_MSEC;
   979         rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
   980         NS_ENSURE_SUCCESS(rv, rv);
   982         if (!isdir) {
   983             // Set up for fall through to stream reader
   984             rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
   985                                             aItem->mFile);
   986             NS_ENSURE_SUCCESS(rv, rv);
   987         }
   988         // If a dir then this will fall through to the plain dir addition
   989     }
   991     uint32_t zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
   993     if (aItem->mStream || aItem->mChannel) {
   994         nsRefPtr<nsZipHeader> header = new nsZipHeader();
   995         NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
   997         header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
   998                      mCDSOffset);
   999         nsresult rv = header->WriteFileHeader(mStream);
  1000         NS_ENSURE_SUCCESS(rv, rv);
  1002         nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
  1003         NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
  1004         rv = stream->Init(this, mStream, header, aItem->mCompression);
  1005         NS_ENSURE_SUCCESS(rv, rv);
  1007         if (aItem->mStream) {
  1008             nsCOMPtr<nsIInputStreamPump> pump;
  1009             rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream,
  1010                                        -1, -1, 0, 0, true);
  1011             NS_ENSURE_SUCCESS(rv, rv);
  1013             rv = pump->AsyncRead(stream, nullptr);
  1014             NS_ENSURE_SUCCESS(rv, rv);
  1016         else {
  1017             rv = aItem->mChannel->AsyncOpen(stream, nullptr);
  1018             NS_ENSURE_SUCCESS(rv, rv);
  1021         return NS_OK;
  1024     // Must be plain directory addition
  1025     *complete = true;
  1026     return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
  1027                                      aItem->mPermissions);
  1030 inline nsresult nsZipWriter::BeginProcessingRemoval(int32_t aPos)
  1032     // Open the zip file for reading
  1033     nsCOMPtr<nsIInputStream> inputStream;
  1034     nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
  1035                                              mFile);
  1036     NS_ENSURE_SUCCESS(rv, rv);
  1037     nsCOMPtr<nsIInputStreamPump> pump;
  1038     rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, -1, -1, 0,
  1039                                0, true);
  1040     if (NS_FAILED(rv)) {
  1041         inputStream->Close();
  1042         return rv;
  1044     nsCOMPtr<nsIStreamListener> listener;
  1045     rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
  1046     if (NS_FAILED(rv)) {
  1047         inputStream->Close();
  1048         return rv;
  1051     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
  1052     rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
  1053                         mHeaders[aPos]->mOffset);
  1054     if (NS_FAILED(rv)) {
  1055         inputStream->Close();
  1056         return rv;
  1059     uint32_t shift = (mHeaders[aPos + 1]->mOffset -
  1060                       mHeaders[aPos]->mOffset);
  1061     mCDSOffset -= shift;
  1062     int32_t pos2 = aPos + 1;
  1063     while (pos2 < mHeaders.Count()) {
  1064         mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
  1065         mHeaders[pos2]->mOffset -= shift;
  1066         pos2++;
  1069     mEntryHash.Remove(mHeaders[aPos]->mName);
  1070     mHeaders.RemoveObjectAt(aPos);
  1071     mCDSDirty = true;
  1073     rv = pump->AsyncRead(listener, nullptr);
  1074     if (NS_FAILED(rv)) {
  1075         inputStream->Close();
  1076         Cleanup();
  1077         return rv;
  1079     return NS_OK;
  1082 /*
  1083  * Starts processing on the next item in the queue.
  1084  */
  1085 void nsZipWriter::BeginProcessingNextItem()
  1087     while (!mQueue.IsEmpty()) {
  1089         nsZipQueueItem next = mQueue[0];
  1090         mQueue.RemoveElementAt(0);
  1092         if (next.mOperation == OPERATION_REMOVE) {
  1093             int32_t pos = -1;
  1094             if (mEntryHash.Get(next.mZipEntry, &pos)) {
  1095                 if (pos < mHeaders.Count() - 1) {
  1096                     nsresult rv = BeginProcessingRemoval(pos);
  1097                     if (NS_FAILED(rv)) FinishQueue(rv);
  1098                     return;
  1101                 mCDSOffset = mHeaders[pos]->mOffset;
  1102                 nsresult rv = SeekCDS();
  1103                 if (NS_FAILED(rv)) {
  1104                     FinishQueue(rv);
  1105                     return;
  1107                 mEntryHash.Remove(mHeaders[pos]->mName);
  1108                 mHeaders.RemoveObjectAt(pos);
  1110             else {
  1111                 FinishQueue(NS_ERROR_FILE_NOT_FOUND);
  1112                 return;
  1115         else if (next.mOperation == OPERATION_ADD) {
  1116             if (mEntryHash.Get(next.mZipEntry, nullptr)) {
  1117                 FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
  1118                 return;
  1121             bool complete = false;
  1122             nsresult rv = BeginProcessingAddition(&next, &complete);
  1123             if (NS_FAILED(rv)) {
  1124                 SeekCDS();
  1125                 FinishQueue(rv);
  1126                 return;
  1128             if (!complete)
  1129                 return;
  1133     FinishQueue(NS_OK);
  1136 /*
  1137  * Ends processing with the given status.
  1138  */
  1139 void nsZipWriter::FinishQueue(nsresult aStatus)
  1141     nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
  1142     nsCOMPtr<nsISupports> context = mProcessContext;
  1143     // Clean up everything first in case the observer decides to queue more
  1144     // things
  1145     mProcessObserver = nullptr;
  1146     mProcessContext = nullptr;
  1147     mInQueue = false;
  1149     if (observer)
  1150         observer->OnStopRequest(nullptr, context, aStatus);

mercurial