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