tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsZipWriter.cpp (29495B)


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