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