PageRenderTime 83ms CodeModel.GetById 13ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 1ms

/modules/libjar/zipwriter/src/nsZipWriter.cpp

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