modules/libjar/zipwriter/src/nsZipWriter.cpp

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

mercurial