Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / modules / libjar / zipwriter / src / nsZipWriter.cpp
blobaec22fa8790e59ec1eca723fd2fccb52ff5b8a91
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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.
14 * The Original Code is Zip Writer Component.
16 * The Initial Developer of the Original Code is
17 * Dave Townsend <dtownsend@oxymoronical.com>.
19 * Portions created by the Initial Developer are Copyright (C) 2007
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Mook <mook.moz+random.code@gmail.com>
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.
37 * ***** END LICENSE BLOCK *****
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"
55 #define ZIP_EOCDR_HEADER_SIZE 22
56 #define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
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.
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:
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]
77 NS_IMPL_ISUPPORTS2(nsZipWriter, nsIZipWriter,
78 nsIRequestObserver)
80 nsZipWriter::nsZipWriter()
82 mEntryHash.Init();
83 mInQueue = PR_FALSE;
86 nsZipWriter::~nsZipWriter()
88 if (mStream && !mInQueue)
89 Close();
92 /* attribute AString comment; */
93 NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
95 if (!mStream)
96 return NS_ERROR_NOT_INITIALIZED;
98 aComment = mComment;
99 return NS_OK;
102 NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
104 if (!mStream)
105 return NS_ERROR_NOT_INITIALIZED;
107 mComment = aComment;
108 mCDSDirty = PR_TRUE;
109 return NS_OK;
112 /* readonly attribute boolean inQueue; */
113 NS_IMETHODIMP nsZipWriter::GetInQueue(PRBool *aInQueue)
115 *aInQueue = mInQueue;
116 return NS_OK;
119 /* readonly attribute nsIFile file; */
120 NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
122 if (!mFile)
123 return NS_ERROR_NOT_INITIALIZED;
125 nsCOMPtr<nsIFile> file;
126 nsresult rv = mFile->Clone(getter_AddRefs(file));
127 NS_ENSURE_SUCCESS(rv, rv);
129 NS_ADDREF(*aFile = file);
130 return NS_OK;
134 * Reads file entries out of an existing zip file.
136 nsresult nsZipWriter::ReadFile(nsIFile *aFile)
138 PRInt64 size;
139 nsresult rv = aFile->GetFileSize(&size);
140 NS_ENSURE_SUCCESS(rv, rv);
142 nsCOMPtr<nsIInputStream> inputStream;
143 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
144 NS_ENSURE_SUCCESS(rv, rv);
146 char buf[1024];
147 PRInt64 seek = size - 1024;
148 PRUint32 length = 1024;
150 if (seek < 0) {
151 length += seek;
152 seek = 0;
155 PRUint32 pos;
156 PRUint32 sig = 0;
157 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
159 while (true) {
160 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
161 if (NS_FAILED(rv)) {
162 inputStream->Close();
163 return rv;
165 rv = ZW_ReadData(inputStream, buf, length);
166 if (NS_FAILED(rv)) {
167 inputStream->Close();
168 return rv;
172 * We have to backtrack from the end of the file until we find the
173 * CDS signature
175 // We know it's at least this far from the end
176 pos = length - ZIP_EOCDR_HEADER_SIZE;
177 sig = READ32(buf, &pos);
178 pos -= 4;
179 while (pos >=0) {
180 if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
181 // Skip down to entry count
182 pos += 10;
183 PRUint32 entries = READ16(buf, &pos);
184 // Skip past CDS size
185 pos += 4;
186 mCDSOffset = READ32(buf, &pos);
187 PRUint32 commentlen = READ16(buf, &pos);
189 if (commentlen == 0)
190 mComment.Truncate();
191 else if (pos + commentlen <= length)
192 mComment.Assign(buf + pos, commentlen);
193 else {
194 if ((seek + pos + commentlen) > size) {
195 inputStream->Close();
196 return NS_ERROR_FILE_CORRUPTED;
198 nsAutoArrayPtr<char> field(new char[commentlen]);
199 NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
200 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
201 seek + pos);
202 if (NS_FAILED(rv)) {
203 inputStream->Close();
204 return rv;
206 rv = ZW_ReadData(inputStream, field.get(), length);
207 if (NS_FAILED(rv)) {
208 inputStream->Close();
209 return rv;
211 mComment.Assign(field.get(), commentlen);
214 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
215 mCDSOffset);
216 if (NS_FAILED(rv)) {
217 inputStream->Close();
218 return rv;
221 for (PRUint32 entry = 0; entry < entries; entry++) {
222 nsZipHeader* header = new nsZipHeader();
223 if (!header) {
224 inputStream->Close();
225 mEntryHash.Clear();
226 mHeaders.Clear();
227 return NS_ERROR_OUT_OF_MEMORY;
229 rv = header->ReadCDSHeader(inputStream);
230 if (NS_FAILED(rv)) {
231 inputStream->Close();
232 mEntryHash.Clear();
233 mHeaders.Clear();
234 return rv;
236 if (!mEntryHash.Put(header->mName, mHeaders.Count()))
237 return NS_ERROR_OUT_OF_MEMORY;
238 if (!mHeaders.AppendObject(header))
239 return NS_ERROR_OUT_OF_MEMORY;
242 return inputStream->Close();
244 sig = sig << 8;
245 sig += buf[--pos];
248 if (seek == 0) {
249 // We've reached the start with no signature found. Corrupt.
250 inputStream->Close();
251 return NS_ERROR_FILE_CORRUPTED;
254 // Overlap by the size of the end of cdr
255 seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
256 if (seek < 0) {
257 length += seek;
258 seek = 0;
261 // Will never reach here in reality
262 NS_NOTREACHED("Loop should never complete");
263 return NS_ERROR_UNEXPECTED;
266 /* void open (in nsIFile aFile, in PRInt32 aIoFlags); */
267 NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, PRInt32 aIoFlags)
269 if (mStream)
270 return NS_ERROR_ALREADY_INITIALIZED;
272 NS_ENSURE_ARG_POINTER(aFile);
274 // Need to be able to write to the file
275 if (aIoFlags & PR_RDONLY)
276 return NS_ERROR_FAILURE;
278 nsresult rv = aFile->Clone(getter_AddRefs(mFile));
279 NS_ENSURE_SUCCESS(rv, rv);
281 PRBool exists;
282 rv = mFile->Exists(&exists);
283 NS_ENSURE_SUCCESS(rv, rv);
284 if (!exists && !(aIoFlags & PR_CREATE_FILE))
285 return NS_ERROR_FILE_NOT_FOUND;
287 if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
288 rv = ReadFile(mFile);
289 NS_ENSURE_SUCCESS(rv, rv);
290 mCDSDirty = PR_FALSE;
292 else {
293 mCDSOffset = 0;
294 mCDSDirty = PR_TRUE;
295 mComment.Truncate();
298 // Silently drop PR_APPEND
299 aIoFlags &= 0xef;
301 nsCOMPtr<nsIOutputStream> stream;
302 rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
303 if (NS_FAILED(rv)) {
304 mHeaders.Clear();
305 mEntryHash.Clear();
306 return rv;
309 rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream, 0x800);
310 if (NS_FAILED(rv)) {
311 stream->Close();
312 mHeaders.Clear();
313 mEntryHash.Clear();
314 return rv;
317 if (mCDSOffset > 0) {
318 rv = SeekCDS();
319 NS_ENSURE_SUCCESS(rv, rv);
322 return NS_OK;
325 /* nsIZipEntry getEntry (in AString aZipEntry); */
326 NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
327 nsIZipEntry **_retval)
329 PRInt32 pos;
330 if (mEntryHash.Get(aZipEntry, &pos))
331 NS_ADDREF(*_retval = mHeaders[pos]);
332 else
333 *_retval = nsnull;
335 return NS_OK;
338 /* boolean hasEntry (in AString aZipEntry); */
339 NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
340 PRBool *_retval)
342 *_retval = mEntryHash.Get(aZipEntry, nsnull);
344 return NS_OK;
347 /* void addEntryDirectory (in AUTF8String aZipEntry, in PRTime aModTime,
348 * in boolean aQueue); */
349 NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
350 PRTime aModTime, PRBool aQueue)
352 if (!mStream)
353 return NS_ERROR_NOT_INITIALIZED;
355 if (aQueue) {
356 nsZipQueueItem item;
357 item.mOperation = OPERATION_ADD;
358 item.mZipEntry = aZipEntry;
359 item.mModTime = aModTime;
360 item.mPermissions = PERMISSIONS_DIR;
361 if (!mQueue.AppendElement(item))
362 return NS_ERROR_OUT_OF_MEMORY;
363 return NS_OK;
366 if (mInQueue)
367 return NS_ERROR_IN_PROGRESS;
368 return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
371 /* void addEntryFile (in AUTF8String aZipEntry, in PRInt32 aCompression,
372 * in nsIFile aFile, in boolean aQueue); */
373 NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
374 PRInt32 aCompression, nsIFile *aFile,
375 PRBool aQueue)
377 NS_ENSURE_ARG_POINTER(aFile);
378 if (!mStream)
379 return NS_ERROR_NOT_INITIALIZED;
381 nsresult rv;
382 if (aQueue) {
383 nsZipQueueItem item;
384 item.mOperation = OPERATION_ADD;
385 item.mZipEntry = aZipEntry;
386 item.mCompression = aCompression;
387 rv = aFile->Clone(getter_AddRefs(item.mFile));
388 NS_ENSURE_SUCCESS(rv, rv);
389 if (!mQueue.AppendElement(item))
390 return NS_ERROR_OUT_OF_MEMORY;
391 return NS_OK;
394 if (mInQueue)
395 return NS_ERROR_IN_PROGRESS;
397 PRBool exists;
398 rv = aFile->Exists(&exists);
399 NS_ENSURE_SUCCESS(rv, rv);
400 if (!exists)
401 return NS_ERROR_FILE_NOT_FOUND;
403 PRBool isdir;
404 rv = aFile->IsDirectory(&isdir);
405 NS_ENSURE_SUCCESS(rv, rv);
407 PRInt64 modtime;
408 rv = aFile->GetLastModifiedTime(&modtime);
409 NS_ENSURE_SUCCESS(rv, rv);
410 modtime *= PR_USEC_PER_MSEC;
412 PRUint32 permissions;
413 rv = aFile->GetPermissions(&permissions);
414 NS_ENSURE_SUCCESS(rv, rv);
416 if (isdir)
417 return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
419 if (mEntryHash.Get(aZipEntry, nsnull))
420 return NS_ERROR_FILE_ALREADY_EXISTS;
422 nsCOMPtr<nsIInputStream> inputStream;
423 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
424 aFile);
425 NS_ENSURE_SUCCESS(rv, rv);
427 rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
428 PR_FALSE, permissions);
429 NS_ENSURE_SUCCESS(rv, rv);
431 return inputStream->Close();
434 /* void addEntryChannel (in AUTF8String aZipEntry, in PRTime aModTime,
435 * in PRInt32 aCompression, in nsIChannel aChannel,
436 * in boolean aQueue); */
437 NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
438 PRTime aModTime,
439 PRInt32 aCompression,
440 nsIChannel *aChannel,
441 PRBool aQueue)
443 NS_ENSURE_ARG_POINTER(aChannel);
444 if (!mStream)
445 return NS_ERROR_NOT_INITIALIZED;
447 if (aQueue) {
448 nsZipQueueItem item;
449 item.mOperation = OPERATION_ADD;
450 item.mZipEntry = aZipEntry;
451 item.mModTime = aModTime;
452 item.mCompression = aCompression;
453 item.mPermissions = PERMISSIONS_FILE;
454 item.mChannel = aChannel;
455 if (!mQueue.AppendElement(item))
456 return NS_ERROR_OUT_OF_MEMORY;
457 return NS_OK;
460 if (mInQueue)
461 return NS_ERROR_IN_PROGRESS;
462 if (mEntryHash.Get(aZipEntry, nsnull))
463 return NS_ERROR_FILE_ALREADY_EXISTS;
465 nsCOMPtr<nsIInputStream> inputStream;
466 nsresult rv = aChannel->Open(getter_AddRefs(inputStream));
467 NS_ENSURE_SUCCESS(rv, rv);
469 rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
470 PR_FALSE, PERMISSIONS_FILE);
471 NS_ENSURE_SUCCESS(rv, rv);
473 return inputStream->Close();
476 /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
477 * in PRInt32 aCompression, in nsIInputStream aStream,
478 * in boolean aQueue); */
479 NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
480 PRTime aModTime,
481 PRInt32 aCompression,
482 nsIInputStream *aStream,
483 PRBool aQueue)
485 return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
486 PERMISSIONS_FILE);
489 /* void addEntryStream (in AUTF8String aZipEntry, in PRTime aModTime,
490 * in PRInt32 aCompression, in nsIInputStream aStream,
491 * in boolean aQueue, in unsigned long aPermissions); */
492 nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
493 PRTime aModTime,
494 PRInt32 aCompression,
495 nsIInputStream *aStream,
496 PRBool aQueue,
497 PRUint32 aPermissions)
499 NS_ENSURE_ARG_POINTER(aStream);
500 if (!mStream)
501 return NS_ERROR_NOT_INITIALIZED;
503 if (aQueue) {
504 nsZipQueueItem item;
505 item.mOperation = OPERATION_ADD;
506 item.mZipEntry = aZipEntry;
507 item.mModTime = aModTime;
508 item.mCompression = aCompression;
509 item.mPermissions = aPermissions;
510 item.mStream = aStream;
511 if (!mQueue.AppendElement(item))
512 return NS_ERROR_OUT_OF_MEMORY;
513 return NS_OK;
516 if (mInQueue)
517 return NS_ERROR_IN_PROGRESS;
518 if (mEntryHash.Get(aZipEntry, nsnull))
519 return NS_ERROR_FILE_ALREADY_EXISTS;
521 nsRefPtr<nsZipHeader> header = new nsZipHeader();
522 NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
523 header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
524 mCDSOffset);
525 nsresult rv = header->WriteFileHeader(mStream);
526 if (NS_FAILED(rv)) {
527 SeekCDS();
528 return rv;
531 nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
532 if (!stream) {
533 SeekCDS();
534 return NS_ERROR_OUT_OF_MEMORY;
536 rv = stream->Init(this, mStream, header, aCompression);
537 if (NS_FAILED(rv)) {
538 SeekCDS();
539 return rv;
542 rv = stream->ReadStream(aStream);
543 if (NS_FAILED(rv))
544 SeekCDS();
545 return rv;
548 /* void removeEntry (in AUTF8String aZipEntry, in boolean aQueue); */
549 NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
550 PRBool aQueue)
552 if (!mStream)
553 return NS_ERROR_NOT_INITIALIZED;
555 if (aQueue) {
556 nsZipQueueItem item;
557 item.mOperation = OPERATION_REMOVE;
558 item.mZipEntry = aZipEntry;
559 if (!mQueue.AppendElement(item))
560 return NS_ERROR_OUT_OF_MEMORY;
561 return NS_OK;
564 if (mInQueue)
565 return NS_ERROR_IN_PROGRESS;
567 PRInt32 pos;
568 if (mEntryHash.Get(aZipEntry, &pos)) {
569 // Flush any remaining data before we seek.
570 nsresult rv = mStream->Flush();
571 NS_ENSURE_SUCCESS(rv, rv);
572 if (pos < mHeaders.Count() - 1) {
573 // This is not the last entry, pull back the data.
574 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
575 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
576 mHeaders[pos]->mOffset);
577 NS_ENSURE_SUCCESS(rv, rv);
579 nsCOMPtr<nsIInputStream> inputStream;
580 rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
581 mFile);
582 NS_ENSURE_SUCCESS(rv, rv);
583 seekable = do_QueryInterface(inputStream);
584 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
585 mHeaders[pos + 1]->mOffset);
586 if (NS_FAILED(rv)) {
587 inputStream->Close();
588 return rv;
591 PRUint32 count = mCDSOffset - mHeaders[pos + 1]->mOffset;
592 PRUint32 read = 0;
593 char buf[4096];
594 while (count > 0) {
595 if (count < sizeof(buf))
596 read = count;
597 else
598 read = sizeof(buf);
600 rv = inputStream->Read(buf, read, &read);
601 if (NS_FAILED(rv)) {
602 inputStream->Close();
603 Cleanup();
604 return rv;
607 rv = ZW_WriteData(mStream, buf, read);
608 if (NS_FAILED(rv)) {
609 inputStream->Close();
610 Cleanup();
611 return rv;
614 count -= read;
616 inputStream->Close();
618 // Rewrite header offsets and update hash
619 PRUint32 shift = (mHeaders[pos + 1]->mOffset -
620 mHeaders[pos]->mOffset);
621 mCDSOffset -= shift;
622 PRInt32 pos2 = pos + 1;
623 while (pos2 < mHeaders.Count()) {
624 if (!mEntryHash.Put(mHeaders[pos2]->mName, pos2-1)) {
625 Cleanup();
626 return NS_ERROR_OUT_OF_MEMORY;
628 mHeaders[pos2]->mOffset -= shift;
629 pos2++;
632 else {
633 // Remove the last entry is just a case of moving the CDS
634 mCDSOffset = mHeaders[pos]->mOffset;
635 rv = SeekCDS();
636 NS_ENSURE_SUCCESS(rv, rv);
639 mEntryHash.Remove(mHeaders[pos]->mName);
640 mHeaders.RemoveObjectAt(pos);
641 mCDSDirty = PR_TRUE;
643 return NS_OK;
646 return NS_ERROR_FILE_NOT_FOUND;
649 /* void processQueue (in nsIRequestObserver aObserver,
650 * in nsISupports aContext); */
651 NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
652 nsISupports *aContext)
654 if (!mStream)
655 return NS_ERROR_NOT_INITIALIZED;
656 if (mInQueue)
657 return NS_ERROR_IN_PROGRESS;
659 mProcessObserver = aObserver;
660 mProcessContext = aContext;
661 mInQueue = PR_TRUE;
663 if (mProcessObserver)
664 mProcessObserver->OnStartRequest(nsnull, mProcessContext);
666 BeginProcessingNextItem();
668 return NS_OK;
671 /* void close (); */
672 NS_IMETHODIMP nsZipWriter::Close()
674 if (!mStream)
675 return NS_ERROR_NOT_INITIALIZED;
676 if (mInQueue)
677 return NS_ERROR_IN_PROGRESS;
679 if (mCDSDirty) {
680 PRUint32 size = 0;
681 for (PRInt32 i = 0; i < mHeaders.Count(); i++) {
682 nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
683 if (NS_FAILED(rv)) {
684 Cleanup();
685 return rv;
687 size += mHeaders[i]->GetCDSHeaderLength();
690 char buf[ZIP_EOCDR_HEADER_SIZE];
691 PRUint32 pos = 0;
692 WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
693 WRITE16(buf, &pos, 0);
694 WRITE16(buf, &pos, 0);
695 WRITE16(buf, &pos, mHeaders.Count());
696 WRITE16(buf, &pos, mHeaders.Count());
697 WRITE32(buf, &pos, size);
698 WRITE32(buf, &pos, mCDSOffset);
699 WRITE16(buf, &pos, mComment.Length());
701 nsresult rv = ZW_WriteData(mStream, buf, pos);
702 if (NS_FAILED(rv)) {
703 Cleanup();
704 return rv;
707 rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
708 if (NS_FAILED(rv)) {
709 Cleanup();
710 return rv;
713 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
714 rv = seekable->SetEOF();
715 if (NS_FAILED(rv)) {
716 Cleanup();
717 return rv;
721 nsresult rv = mStream->Close();
722 mStream = nsnull;
723 mHeaders.Clear();
724 mEntryHash.Clear();
725 mQueue.Clear();
727 return rv;
730 // Our nsIRequestObserver monitors removal operations performed on the queue
731 /* void onStartRequest (in nsIRequest aRequest, in nsISupports aContext); */
732 NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
733 nsISupports *aContext)
735 return NS_OK;
738 /* void onStopRequest (in nsIRequest aRequest, in nsISupports aContext,
739 * in nsresult aStatusCode); */
740 NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
741 nsISupports *aContext,
742 nsresult aStatusCode)
744 if (NS_FAILED(aStatusCode)) {
745 FinishQueue(aStatusCode);
746 Cleanup();
749 nsresult rv = mStream->Flush();
750 if (NS_FAILED(rv)) {
751 FinishQueue(rv);
752 Cleanup();
753 return rv;
755 rv = SeekCDS();
756 if (NS_FAILED(rv)) {
757 FinishQueue(rv);
758 return rv;
761 BeginProcessingNextItem();
763 return NS_OK;
766 nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
767 PRTime aModTime,
768 PRUint32 aPermissions)
770 nsRefPtr<nsZipHeader> header = new nsZipHeader();
771 NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
773 PRUint32 zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
775 if (aZipEntry.Last() != '/') {
776 nsCString dirPath;
777 dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
778 header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
780 else
781 header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
783 if (mEntryHash.Get(header->mName, nsnull))
784 return NS_ERROR_FILE_ALREADY_EXISTS;
786 nsresult rv = header->WriteFileHeader(mStream);
787 if (NS_FAILED(rv)) {
788 Cleanup();
789 return rv;
792 mCDSDirty = PR_TRUE;
793 mCDSOffset += header->GetFileHeaderLength();
794 if (!mEntryHash.Put(header->mName, mHeaders.Count())) {
795 Cleanup();
796 return NS_ERROR_OUT_OF_MEMORY;
798 if (!mHeaders.AppendObject(header)) {
799 Cleanup();
800 return NS_ERROR_OUT_OF_MEMORY;
803 return NS_OK;
807 * Recovering from an error while adding a new entry is simply a case of
808 * seeking back to the CDS. If we fail trying to do that though then cleanup
809 * and bail out.
811 nsresult nsZipWriter::SeekCDS()
813 nsresult rv;
814 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
815 if (NS_FAILED(rv)) {
816 Cleanup();
817 return rv;
819 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
820 if (NS_FAILED(rv))
821 Cleanup();
822 return rv;
826 * In a bad error condition this essentially closes down the component as best
827 * it can.
829 void nsZipWriter::Cleanup()
831 mHeaders.Clear();
832 mEntryHash.Clear();
833 if (mStream)
834 mStream->Close();
835 mStream = nsnull;
836 mFile = nsnull;
840 * Called when writing a file to the zip is complete.
842 nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
843 nsresult aStatus)
845 if (NS_SUCCEEDED(aStatus)) {
846 if (!mEntryHash.Put(aHeader->mName, mHeaders.Count())) {
847 SeekCDS();
848 return NS_ERROR_OUT_OF_MEMORY;
850 if (!mHeaders.AppendObject(aHeader)) {
851 mEntryHash.Remove(aHeader->mName);
852 SeekCDS();
853 return NS_ERROR_OUT_OF_MEMORY;
855 mCDSDirty = PR_TRUE;
856 mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
858 if (mInQueue)
859 BeginProcessingNextItem();
861 return NS_OK;
864 nsresult rv = SeekCDS();
865 if (mInQueue)
866 FinishQueue(aStatus);
867 return rv;
870 inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
871 PRBool* complete)
873 if (aItem->mFile) {
874 PRBool exists;
875 nsresult rv = aItem->mFile->Exists(&exists);
876 NS_ENSURE_SUCCESS(rv, rv);
878 if (!exists) return NS_ERROR_FILE_NOT_FOUND;
880 PRBool isdir;
881 rv = aItem->mFile->IsDirectory(&isdir);
882 NS_ENSURE_SUCCESS(rv, rv);
884 rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
885 NS_ENSURE_SUCCESS(rv, rv);
886 aItem->mModTime *= PR_USEC_PER_MSEC;
888 rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
889 NS_ENSURE_SUCCESS(rv, rv);
891 if (!isdir) {
892 // Set up for fall through to stream reader
893 rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
894 aItem->mFile);
895 NS_ENSURE_SUCCESS(rv, rv);
897 // If a dir then this will fall through to the plain dir addition
900 PRUint32 zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
902 if (aItem->mStream) {
903 nsRefPtr<nsZipHeader> header = new nsZipHeader();
904 NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
906 header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
907 mCDSOffset);
908 nsresult rv = header->WriteFileHeader(mStream);
909 NS_ENSURE_SUCCESS(rv, rv);
911 nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
912 rv = stream->Init(this, mStream, header, aItem->mCompression);
913 NS_ENSURE_SUCCESS(rv, rv);
915 nsCOMPtr<nsIInputStreamPump> pump;
916 rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream, -1,
917 -1, 0, 0, PR_TRUE);
918 NS_ENSURE_SUCCESS(rv, rv);
920 rv = pump->AsyncRead(stream, nsnull);
921 NS_ENSURE_SUCCESS(rv, rv);
923 return NS_OK;
926 if (aItem->mChannel) {
927 nsRefPtr<nsZipHeader> header = new nsZipHeader();
928 NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
930 header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
931 mCDSOffset);
933 nsRefPtr<nsZipDataStream> stream = new nsZipDataStream();
934 NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
935 nsresult rv = stream->Init(this, mStream, header, aItem->mCompression);
936 NS_ENSURE_SUCCESS(rv, rv);
937 rv = aItem->mChannel->AsyncOpen(stream, nsnull);
938 NS_ENSURE_SUCCESS(rv, rv);
940 return NS_OK;
943 // Must be plain directory addition
944 *complete = PR_TRUE;
945 return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
946 aItem->mPermissions);
949 inline nsresult nsZipWriter::BeginProcessingRemoval(PRInt32 aPos)
951 // Open the zip file for reading
952 nsCOMPtr<nsIInputStream> inputStream;
953 nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
954 mFile);
955 NS_ENSURE_SUCCESS(rv, rv);
956 nsCOMPtr<nsIInputStreamPump> pump;
957 rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, -1, -1, 0,
958 0, PR_TRUE);
959 if (NS_FAILED(rv)) {
960 inputStream->Close();
961 return rv;
963 nsCOMPtr<nsIStreamListener> listener;
964 rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
965 if (NS_FAILED(rv)) {
966 inputStream->Close();
967 return rv;
970 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
971 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
972 mHeaders[aPos]->mOffset);
973 if (NS_FAILED(rv)) {
974 inputStream->Close();
975 return rv;
978 PRUint32 shift = (mHeaders[aPos + 1]->mOffset -
979 mHeaders[aPos]->mOffset);
980 mCDSOffset -= shift;
981 PRInt32 pos2 = aPos + 1;
982 while (pos2 < mHeaders.Count()) {
983 mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
984 mHeaders[pos2]->mOffset -= shift;
985 pos2++;
988 mEntryHash.Remove(mHeaders[aPos]->mName);
989 mHeaders.RemoveObjectAt(aPos);
990 mCDSDirty = PR_TRUE;
992 rv = pump->AsyncRead(listener, nsnull);
993 if (NS_FAILED(rv)) {
994 inputStream->Close();
995 Cleanup();
996 return rv;
998 return NS_OK;
1002 * Starts processing on the next item in the queue.
1004 void nsZipWriter::BeginProcessingNextItem()
1006 while (!mQueue.IsEmpty()) {
1008 nsZipQueueItem next = mQueue[0];
1009 mQueue.RemoveElementAt(0);
1011 if (next.mOperation == OPERATION_REMOVE) {
1012 PRInt32 pos = -1;
1013 if (mEntryHash.Get(next.mZipEntry, &pos)) {
1014 if (pos < mHeaders.Count() - 1) {
1015 nsresult rv = BeginProcessingRemoval(pos);
1016 if (NS_FAILED(rv)) FinishQueue(rv);
1017 return;
1020 mCDSOffset = mHeaders[pos]->mOffset;
1021 nsresult rv = SeekCDS();
1022 if (NS_FAILED(rv)) {
1023 FinishQueue(rv);
1024 return;
1026 mEntryHash.Remove(mHeaders[pos]->mName);
1027 mHeaders.RemoveObjectAt(pos);
1029 else {
1030 FinishQueue(NS_ERROR_FILE_NOT_FOUND);
1031 return;
1034 else if (next.mOperation == OPERATION_ADD) {
1035 if (mEntryHash.Get(next.mZipEntry, nsnull)) {
1036 FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
1037 return;
1040 PRBool complete = PR_FALSE;
1041 nsresult rv = BeginProcessingAddition(&next, &complete);
1042 if (NS_FAILED(rv)) {
1043 SeekCDS();
1044 FinishQueue(rv);
1045 return;
1047 if (!complete)
1048 return;
1052 FinishQueue(NS_OK);
1056 * Ends processing with the given status.
1058 void nsZipWriter::FinishQueue(nsresult aStatus)
1060 nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
1061 nsCOMPtr<nsISupports> context = mProcessContext;
1062 // Clean up everything first in case the observer decides to queue more
1063 // things
1064 mProcessObserver = nsnull;
1065 mProcessContext = nsnull;
1066 mInQueue = PR_FALSE;
1068 if (observer)
1069 observer->OnStopRequest(nsnull, context, aStatus);