Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/modules/libjar/zipwriter/nsZipWriter.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#define ZIP_EOCDR_HEADER_SIZE 22
28
0
#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
NS_IMPL_ISUPPORTS(nsZipWriter, nsIZipWriter,
52
                  nsIRequestObserver)
53
54
nsZipWriter::nsZipWriter()
55
  : mCDSOffset(0)
56
  , mCDSDirty(false)
57
  , mInQueue(false)
58
0
{}
59
60
nsZipWriter::~nsZipWriter()
61
0
{
62
0
    if (mStream && !mInQueue)
63
0
        Close();
64
0
}
65
66
NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
67
0
{
68
0
    if (!mStream)
69
0
        return NS_ERROR_NOT_INITIALIZED;
70
0
71
0
    aComment = mComment;
72
0
    return NS_OK;
73
0
}
74
75
NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
76
0
{
77
0
    if (!mStream)
78
0
        return NS_ERROR_NOT_INITIALIZED;
79
0
80
0
    mComment = aComment;
81
0
    mCDSDirty = true;
82
0
    return NS_OK;
83
0
}
84
85
NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
86
0
{
87
0
    *aInQueue = mInQueue;
88
0
    return NS_OK;
89
0
}
90
91
NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
92
0
{
93
0
    if (!mFile)
94
0
        return NS_ERROR_NOT_INITIALIZED;
95
0
96
0
    nsCOMPtr<nsIFile> file;
97
0
    nsresult rv = mFile->Clone(getter_AddRefs(file));
98
0
    NS_ENSURE_SUCCESS(rv, rv);
99
0
100
0
    NS_ADDREF(*aFile = file);
101
0
    return NS_OK;
102
0
}
103
104
/*
105
 * Reads file entries out of an existing zip file.
106
 */
107
nsresult nsZipWriter::ReadFile(nsIFile *aFile)
108
0
{
109
0
    int64_t size;
110
0
    nsresult rv = aFile->GetFileSize(&size);
111
0
    NS_ENSURE_SUCCESS(rv, rv);
112
0
113
0
    // If the file is too short, it cannot be a valid archive, thus we fail
114
0
    // without even attempting to open it
115
0
    NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
116
0
117
0
    nsCOMPtr<nsIInputStream> inputStream;
118
0
    rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
119
0
    NS_ENSURE_SUCCESS(rv, rv);
120
0
121
0
    uint8_t buf[1024];
122
0
    int64_t seek = size - 1024;
123
0
    uint32_t length = 1024;
124
0
125
0
    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
126
0
127
0
    while (true) {
128
0
        if (seek < 0) {
129
0
            length += (int32_t)seek;
130
0
            seek = 0;
131
0
        }
132
0
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
0
        }
138
0
        rv = ZW_ReadData(inputStream, (char *)buf, length);
139
0
        if (NS_FAILED(rv)) {
140
0
            inputStream->Close();
141
0
            return rv;
142
0
        }
143
0
144
0
        /*
145
0
         * We have to backtrack from the end of the file until we find the
146
0
         * CDS signature
147
0
         */
148
0
        // 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
0
                // Skip down to entry count
154
0
                pos += 10;
155
0
                uint32_t entries = READ16(buf, &pos);
156
0
                // Skip past CDS size
157
0
                pos += 4;
158
0
                mCDSOffset = READ32(buf, &pos);
159
0
                uint32_t commentlen = READ16(buf, &pos);
160
0
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
0
                else {
166
0
                    if ((seek + pos + commentlen) > size) {
167
0
                        inputStream->Close();
168
0
                        return NS_ERROR_FILE_CORRUPTED;
169
0
                    }
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
0
                    }
178
0
                    rv = ZW_ReadData(inputStream, field.get(), length);
179
0
                    if (NS_FAILED(rv)) {
180
0
                        inputStream->Close();
181
0
                        return rv;
182
0
                    }
183
0
                    mComment.Assign(field.get(), commentlen);
184
0
                }
185
0
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
0
                }
192
0
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
0
                    }
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
0
                    }
208
0
                    mEntryHash.Put(header->mName, mHeaders.Count());
209
0
                    if (!mHeaders.AppendObject(header))
210
0
                        return NS_ERROR_OUT_OF_MEMORY;
211
0
                }
212
0
213
0
                return inputStream->Close();
214
0
            }
215
0
        }
216
0
217
0
        if (seek == 0) {
218
0
            // We've reached the start with no signature found. Corrupt.
219
0
            inputStream->Close();
220
0
            return NS_ERROR_FILE_CORRUPTED;
221
0
        }
222
0
223
0
        // Overlap by the size of the end of cdr
224
0
        seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
225
0
    }
226
0
    // Will never reach here in reality
227
0
    MOZ_ASSERT_UNREACHABLE("Loop should never complete");
228
0
    return NS_ERROR_UNEXPECTED;
229
0
}
230
231
NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, int32_t aIoFlags)
232
0
{
233
0
    if (mStream)
234
0
        return NS_ERROR_ALREADY_INITIALIZED;
235
0
236
0
    NS_ENSURE_ARG_POINTER(aFile);
237
0
238
0
    // Need to be able to write to the file
239
0
    if (aIoFlags & PR_RDONLY)
240
0
        return NS_ERROR_FAILURE;
241
0
242
0
    nsresult rv = aFile->Clone(getter_AddRefs(mFile));
243
0
    NS_ENSURE_SUCCESS(rv, rv);
244
0
245
0
    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
0
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
0
    }
256
0
    else {
257
0
        mCDSOffset = 0;
258
0
        mCDSDirty = true;
259
0
        mComment.Truncate();
260
0
    }
261
0
262
0
    // Silently drop PR_APPEND
263
0
    aIoFlags &= 0xef;
264
0
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
0
    }
272
0
273
0
    rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream.forget(),
274
0
                                    64 * 1024);
275
0
    if (NS_FAILED(rv)) {
276
0
        mHeaders.Clear();
277
0
        mEntryHash.Clear();
278
0
        return rv;
279
0
    }
280
0
281
0
    if (mCDSOffset > 0) {
282
0
        rv = SeekCDS();
283
0
        NS_ENSURE_SUCCESS(rv, rv);
284
0
    }
285
0
286
0
    return NS_OK;
287
0
}
288
289
NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
290
                                    nsIZipEntry **_retval)
291
0
{
292
0
    int32_t pos;
293
0
    if (mEntryHash.Get(aZipEntry, &pos))
294
0
        NS_ADDREF(*_retval = mHeaders[pos]);
295
0
    else
296
0
        *_retval = nullptr;
297
0
298
0
    return NS_OK;
299
0
}
300
301
NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
302
                                    bool *_retval)
303
0
{
304
0
    *_retval = mEntryHash.Get(aZipEntry, nullptr);
305
0
306
0
    return NS_OK;
307
0
}
308
309
NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
310
                                             PRTime aModTime, bool aQueue)
311
0
{
312
0
    if (!mStream)
313
0
        return NS_ERROR_NOT_INITIALIZED;
314
0
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
0
    }
325
0
326
0
    if (mInQueue)
327
0
        return NS_ERROR_IN_PROGRESS;
328
0
    return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
329
0
}
330
331
NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
332
                                        int32_t aCompression, nsIFile *aFile,
333
                                        bool aQueue)
334
0
{
335
0
    NS_ENSURE_ARG_POINTER(aFile);
336
0
    if (!mStream)
337
0
        return NS_ERROR_NOT_INITIALIZED;
338
0
339
0
    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
0
    }
351
0
352
0
    if (mInQueue)
353
0
        return NS_ERROR_IN_PROGRESS;
354
0
355
0
    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
0
361
0
    bool isdir;
362
0
    rv = aFile->IsDirectory(&isdir);
363
0
    NS_ENSURE_SUCCESS(rv, rv);
364
0
365
0
    PRTime modtime;
366
0
    rv = aFile->GetLastModifiedTime(&modtime);
367
0
    NS_ENSURE_SUCCESS(rv, rv);
368
0
    modtime *= PR_USEC_PER_MSEC;
369
0
370
0
    uint32_t permissions;
371
0
    rv = aFile->GetPermissions(&permissions);
372
0
    NS_ENSURE_SUCCESS(rv, rv);
373
0
374
0
    if (isdir)
375
0
        return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
376
0
377
0
    if (mEntryHash.Get(aZipEntry, nullptr))
378
0
        return NS_ERROR_FILE_ALREADY_EXISTS;
379
0
380
0
    nsCOMPtr<nsIInputStream> inputStream;
381
0
    rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
382
0
                                    aFile);
383
0
    NS_ENSURE_SUCCESS(rv, rv);
384
0
385
0
    rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
386
0
                        false, permissions);
387
0
    NS_ENSURE_SUCCESS(rv, rv);
388
0
389
0
    return inputStream->Close();
390
0
}
391
392
NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
393
                                           PRTime aModTime,
394
                                           int32_t aCompression,
395
                                           nsIChannel *aChannel,
396
                                           bool aQueue)
397
0
{
398
0
    NS_ENSURE_ARG_POINTER(aChannel);
399
0
    if (!mStream)
400
0
        return NS_ERROR_NOT_INITIALIZED;
401
0
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
0
    }
414
0
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
0
420
0
    nsCOMPtr<nsIInputStream> inputStream;
421
0
    nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel,
422
0
                    getter_AddRefs(inputStream));
423
0
424
0
    NS_ENSURE_SUCCESS(rv, rv);
425
0
426
0
    rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
427
0
                        false, PERMISSIONS_FILE);
428
0
    NS_ENSURE_SUCCESS(rv, rv);
429
0
430
0
    return inputStream->Close();
431
0
}
432
433
NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
434
                                          PRTime aModTime,
435
                                          int32_t aCompression,
436
                                          nsIInputStream *aStream,
437
                                          bool aQueue)
438
0
{
439
0
    return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
440
0
                          PERMISSIONS_FILE);
441
0
}
442
443
nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
444
                                     PRTime aModTime,
445
                                     int32_t aCompression,
446
                                     nsIInputStream *aStream,
447
                                     bool aQueue,
448
                                     uint32_t aPermissions)
449
0
{
450
0
    NS_ENSURE_ARG_POINTER(aStream);
451
0
    if (!mStream)
452
0
        return NS_ERROR_NOT_INITIALIZED;
453
0
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
0
    }
466
0
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
0
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
0
    }
481
0
482
0
    RefPtr<nsZipDataStream> stream = new nsZipDataStream();
483
0
    if (!stream) {
484
0
        SeekCDS();
485
0
        return NS_ERROR_OUT_OF_MEMORY;
486
0
    }
487
0
    rv = stream->Init(this, mStream, header, aCompression);
488
0
    if (NS_FAILED(rv)) {
489
0
        SeekCDS();
490
0
        return rv;
491
0
    }
492
0
493
0
    rv = stream->ReadStream(aStream);
494
0
    if (NS_FAILED(rv))
495
0
        SeekCDS();
496
0
    return rv;
497
0
}
498
499
NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
500
                                       bool aQueue)
501
0
{
502
0
    if (!mStream)
503
0
        return NS_ERROR_NOT_INITIALIZED;
504
0
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
0
    }
513
0
514
0
    if (mInQueue)
515
0
        return NS_ERROR_IN_PROGRESS;
516
0
517
0
    int32_t pos;
518
0
    if (mEntryHash.Get(aZipEntry, &pos)) {
519
0
        // 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
0
            // 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
0
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
0
            }
540
0
541
0
            uint32_t count = mCDSOffset - mHeaders[pos + 1]->mOffset;
542
0
            uint32_t read = 0;
543
0
            char buf[4096];
544
0
            while (count > 0) {
545
0
                read = std::min(count, (uint32_t) sizeof(buf));
546
0
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
0
                }
553
0
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
0
                }
560
0
561
0
                count -= read;
562
0
            }
563
0
            inputStream->Close();
564
0
565
0
            // 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
0
            }
575
0
        }
576
0
        else {
577
0
            // 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
0
        }
582
0
583
0
        mEntryHash.Remove(mHeaders[pos]->mName);
584
0
        mHeaders.RemoveObjectAt(pos);
585
0
        mCDSDirty = true;
586
0
587
0
        return NS_OK;
588
0
    }
589
0
590
0
    return NS_ERROR_FILE_NOT_FOUND;
591
0
}
592
593
NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
594
                                        nsISupports *aContext)
595
0
{
596
0
    if (!mStream)
597
0
        return NS_ERROR_NOT_INITIALIZED;
598
0
    if (mInQueue)
599
0
        return NS_ERROR_IN_PROGRESS;
600
0
601
0
    mProcessObserver = aObserver;
602
0
    mProcessContext = aContext;
603
0
    mInQueue = true;
604
0
605
0
    if (mProcessObserver)
606
0
        mProcessObserver->OnStartRequest(nullptr, mProcessContext);
607
0
608
0
    BeginProcessingNextItem();
609
0
610
0
    return NS_OK;
611
0
}
612
613
NS_IMETHODIMP nsZipWriter::Close()
614
0
{
615
0
    if (!mStream)
616
0
        return NS_ERROR_NOT_INITIALIZED;
617
0
    if (mInQueue)
618
0
        return NS_ERROR_IN_PROGRESS;
619
0
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
0
            }
628
0
            size += mHeaders[i]->GetCDSHeaderLength();
629
0
        }
630
0
631
0
        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
0
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
0
        }
647
0
648
0
        rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
649
0
        if (NS_FAILED(rv)) {
650
0
            Cleanup();
651
0
            return rv;
652
0
        }
653
0
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
0
        }
660
0
661
0
        // 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
0
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
0
            }
672
0
            rv = header->WriteFileHeader(mStream);
673
0
            if (NS_FAILED(rv)) {
674
0
               Cleanup();
675
0
               return rv;
676
0
            }
677
0
        }
678
0
    }
679
0
680
0
    nsresult rv = mStream->Close();
681
0
    mStream = nullptr;
682
0
    mHeaders.Clear();
683
0
    mEntryHash.Clear();
684
0
    mQueue.Clear();
685
0
686
0
    return rv;
687
0
}
688
689
// Our nsIRequestObserver monitors removal operations performed on the queue
690
NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
691
                                          nsISupports *aContext)
692
0
{
693
0
    return NS_OK;
694
0
}
695
696
NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
697
                                         nsISupports *aContext,
698
                                         nsresult aStatusCode)
699
0
{
700
0
    if (NS_FAILED(aStatusCode)) {
701
0
        FinishQueue(aStatusCode);
702
0
        Cleanup();
703
0
    }
704
0
705
0
    nsresult rv = mStream->Flush();
706
0
    if (NS_FAILED(rv)) {
707
0
        FinishQueue(rv);
708
0
        Cleanup();
709
0
        return rv;
710
0
    }
711
0
    rv = SeekCDS();
712
0
    if (NS_FAILED(rv)) {
713
0
        FinishQueue(rv);
714
0
        return rv;
715
0
    }
716
0
717
0
    BeginProcessingNextItem();
718
0
719
0
    return NS_OK;
720
0
}
721
722
/*
723
 * Make all stored(uncompressed) files align to given alignment size.
724
 */
725
NS_IMETHODIMP nsZipWriter::AlignStoredFiles(uint16_t aAlignSize)
726
0
{
727
0
    nsresult rv;
728
0
729
0
    // 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
0
    }
734
0
735
0
    for (int i = 0; i < mHeaders.Count(); i++) {
736
0
        nsZipHeader *header = mHeaders[i];
737
0
738
0
        // Check whether this entry is file and compression method is stored.
739
0
        bool isdir;
740
0
        rv = header->GetIsDirectory(&isdir);
741
0
        if (NS_FAILED(rv)) {
742
0
            return rv;
743
0
        }
744
0
        if (isdir || header->mMethod != 0) {
745
0
            continue;
746
0
        }
747
0
        // 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
0
        }
753
0
        // No padding means data already aligned.
754
0
        uint32_t shift = header->mLocalFieldLength - old_len;
755
0
        if (shift == 0) {
756
0
            continue;
757
0
        }
758
0
759
0
        // Flush any remaining data before we start.
760
0
        rv = mStream->Flush();
761
0
        if (NS_FAILED(rv)) {
762
0
            return rv;
763
0
        }
764
0
765
0
        // 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
0
        }
771
0
772
0
        nsCOMPtr<nsISeekableStream> in_seekable = do_QueryInterface(inputStream);
773
0
        nsCOMPtr<nsISeekableStream> out_seekable = do_QueryInterface(mStream);
774
0
775
0
        uint32_t data_offset = header->mOffset + header->GetFileHeaderLength() - shift;
776
0
        uint32_t count = mCDSOffset - data_offset;
777
0
        uint32_t read;
778
0
        char buf[4096];
779
0
780
0
        // Shift data to aligned postion.
781
0
        while (count > 0) {
782
0
            read = std::min(count, (uint32_t) sizeof(buf));
783
0
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
0
             }
789
0
790
0
            rv = inputStream->Read(buf, read, &read);
791
0
            if (NS_FAILED(rv)) {
792
0
                break;
793
0
            }
794
0
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
0
             }
800
0
801
0
            rv = ZW_WriteData(mStream, buf, read);
802
0
            if (NS_FAILED(rv)) {
803
0
                break;
804
0
            }
805
0
806
0
            count -= read;
807
0
        }
808
0
        inputStream->Close();
809
0
        if (NS_FAILED(rv)) {
810
0
            Cleanup();
811
0
            return rv;
812
0
        }
813
0
814
0
        // 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
0
        }
821
0
        rv = header->WriteFileHeader(mStream);
822
0
        if (NS_FAILED(rv)) {
823
0
            Cleanup();
824
0
            return rv;
825
0
        }
826
0
827
0
        // 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
0
        }
833
0
        mCDSOffset += shift;
834
0
        rv = SeekCDS();
835
0
        if (NS_FAILED(rv)) {
836
0
            return rv;
837
0
        }
838
0
        mCDSDirty = true;
839
0
    }
840
0
841
0
    return NS_OK;
842
0
}
843
844
nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
845
                                                PRTime aModTime,
846
                                                uint32_t aPermissions)
847
0
{
848
0
    RefPtr<nsZipHeader> header = new nsZipHeader();
849
0
    NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
850
0
851
0
    uint32_t zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
852
0
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
0
    }
858
0
    else
859
0
        header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
860
0
861
0
    if (mEntryHash.Get(header->mName, nullptr))
862
0
        return NS_ERROR_FILE_ALREADY_EXISTS;
863
0
864
0
    nsresult rv = header->WriteFileHeader(mStream);
865
0
    if (NS_FAILED(rv)) {
866
0
        Cleanup();
867
0
        return rv;
868
0
    }
869
0
870
0
    mCDSDirty = true;
871
0
    mCDSOffset += header->GetFileHeaderLength();
872
0
    mEntryHash.Put(header->mName, mHeaders.Count());
873
0
874
0
    if (!mHeaders.AppendObject(header)) {
875
0
        Cleanup();
876
0
        return NS_ERROR_OUT_OF_MEMORY;
877
0
    }
878
0
879
0
    return NS_OK;
880
0
}
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
nsresult nsZipWriter::SeekCDS()
888
0
{
889
0
    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
0
    }
895
0
    rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
896
0
    if (NS_FAILED(rv))
897
0
        Cleanup();
898
0
    return rv;
899
0
}
900
901
/*
902
 * In a bad error condition this essentially closes down the component as best
903
 * it can.
904
 */
905
void nsZipWriter::Cleanup()
906
0
{
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
nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
919
                                            nsresult aStatus)
920
0
{
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
0
        }
928
0
        mCDSDirty = true;
929
0
        mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
930
0
931
0
        if (mInQueue)
932
0
            BeginProcessingNextItem();
933
0
934
0
        return NS_OK;
935
0
    }
936
0
937
0
    nsresult rv = SeekCDS();
938
0
    if (mInQueue)
939
0
        FinishQueue(aStatus);
940
0
    return rv;
941
0
}
942
943
inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
944
                                                     bool* complete)
945
0
{
946
0
    if (aItem->mFile) {
947
0
        bool exists;
948
0
        nsresult rv = aItem->mFile->Exists(&exists);
949
0
        NS_ENSURE_SUCCESS(rv, rv);
950
0
951
0
        if (!exists) return NS_ERROR_FILE_NOT_FOUND;
952
0
953
0
        bool isdir;
954
0
        rv = aItem->mFile->IsDirectory(&isdir);
955
0
        NS_ENSURE_SUCCESS(rv, rv);
956
0
957
0
        rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
958
0
        NS_ENSURE_SUCCESS(rv, rv);
959
0
        aItem->mModTime *= PR_USEC_PER_MSEC;
960
0
961
0
        rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
962
0
        NS_ENSURE_SUCCESS(rv, rv);
963
0
964
0
        if (!isdir) {
965
0
            // 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
0
        }
970
0
        // If a dir then this will fall through to the plain dir addition
971
0
    }
972
0
973
0
    uint32_t zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
974
0
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
0
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
0
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
0
989
0
        if (aItem->mStream) {
990
0
            nsCOMPtr<nsIInputStreamPump> pump;
991
0
            nsCOMPtr<nsIInputStream> tmpStream = aItem->mStream;
992
0
            rv = NS_NewInputStreamPump(getter_AddRefs(pump),
993
0
                                       tmpStream.forget(), 0, 0, true);
994
0
            NS_ENSURE_SUCCESS(rv, rv);
995
0
996
0
            rv = pump->AsyncRead(stream, nullptr);
997
0
            NS_ENSURE_SUCCESS(rv, rv);
998
0
        }
999
0
        else {
1000
0
            rv = NS_MaybeOpenChannelUsingAsyncOpen2(aItem->mChannel, stream);
1001
0
            NS_ENSURE_SUCCESS(rv, rv);
1002
0
        }
1003
0
1004
0
        return NS_OK;
1005
0
    }
1006
0
1007
0
    // Must be plain directory addition
1008
0
    *complete = true;
1009
0
    return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
1010
0
                                     aItem->mPermissions);
1011
0
}
1012
1013
inline nsresult nsZipWriter::BeginProcessingRemoval(int32_t aPos)
1014
0
{
1015
0
    // Open the zip file for reading
1016
0
    nsCOMPtr<nsIInputStream> inputStream;
1017
0
    nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
1018
0
                                             mFile);
1019
0
    NS_ENSURE_SUCCESS(rv, rv);
1020
0
    nsCOMPtr<nsIInputStreamPump> pump;
1021
0
    nsCOMPtr<nsIInputStream> tmpStream = inputStream;
1022
0
    rv = NS_NewInputStreamPump(getter_AddRefs(pump), tmpStream.forget(), 0, 0,
1023
0
                               true);
1024
0
    if (NS_FAILED(rv)) {
1025
0
        inputStream->Close();
1026
0
        return rv;
1027
0
    }
1028
0
    nsCOMPtr<nsIStreamListener> listener;
1029
0
    rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
1030
0
    if (NS_FAILED(rv)) {
1031
0
        inputStream->Close();
1032
0
        return rv;
1033
0
    }
1034
0
1035
0
    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
1036
0
    rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
1037
0
                        mHeaders[aPos]->mOffset);
1038
0
    if (NS_FAILED(rv)) {
1039
0
        inputStream->Close();
1040
0
        return rv;
1041
0
    }
1042
0
1043
0
    uint32_t shift = (mHeaders[aPos + 1]->mOffset -
1044
0
                      mHeaders[aPos]->mOffset);
1045
0
    mCDSOffset -= shift;
1046
0
    int32_t pos2 = aPos + 1;
1047
0
    while (pos2 < mHeaders.Count()) {
1048
0
        mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
1049
0
        mHeaders[pos2]->mOffset -= shift;
1050
0
        pos2++;
1051
0
    }
1052
0
1053
0
    mEntryHash.Remove(mHeaders[aPos]->mName);
1054
0
    mHeaders.RemoveObjectAt(aPos);
1055
0
    mCDSDirty = true;
1056
0
1057
0
    rv = pump->AsyncRead(listener, nullptr);
1058
0
    if (NS_FAILED(rv)) {
1059
0
        inputStream->Close();
1060
0
        Cleanup();
1061
0
        return rv;
1062
0
    }
1063
0
    return NS_OK;
1064
0
}
1065
1066
/*
1067
 * Starts processing on the next item in the queue.
1068
 */
1069
void nsZipWriter::BeginProcessingNextItem()
1070
0
{
1071
0
    while (!mQueue.IsEmpty()) {
1072
0
1073
0
        nsZipQueueItem next = mQueue[0];
1074
0
        mQueue.RemoveElementAt(0);
1075
0
1076
0
        if (next.mOperation == OPERATION_REMOVE) {
1077
0
            int32_t pos = -1;
1078
0
            if (mEntryHash.Get(next.mZipEntry, &pos)) {
1079
0
                if (pos < mHeaders.Count() - 1) {
1080
0
                    nsresult rv = BeginProcessingRemoval(pos);
1081
0
                    if (NS_FAILED(rv)) FinishQueue(rv);
1082
0
                    return;
1083
0
                }
1084
0
1085
0
                mCDSOffset = mHeaders[pos]->mOffset;
1086
0
                nsresult rv = SeekCDS();
1087
0
                if (NS_FAILED(rv)) {
1088
0
                    FinishQueue(rv);
1089
0
                    return;
1090
0
                }
1091
0
                mEntryHash.Remove(mHeaders[pos]->mName);
1092
0
                mHeaders.RemoveObjectAt(pos);
1093
0
            }
1094
0
            else {
1095
0
                FinishQueue(NS_ERROR_FILE_NOT_FOUND);
1096
0
                return;
1097
0
            }
1098
0
        }
1099
0
        else if (next.mOperation == OPERATION_ADD) {
1100
0
            if (mEntryHash.Get(next.mZipEntry, nullptr)) {
1101
0
                FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
1102
0
                return;
1103
0
            }
1104
0
1105
0
            bool complete = false;
1106
0
            nsresult rv = BeginProcessingAddition(&next, &complete);
1107
0
            if (NS_FAILED(rv)) {
1108
0
                SeekCDS();
1109
0
                FinishQueue(rv);
1110
0
                return;
1111
0
            }
1112
0
            if (!complete)
1113
0
                return;
1114
0
        }
1115
0
    }
1116
0
1117
0
    FinishQueue(NS_OK);
1118
0
}
1119
1120
/*
1121
 * Ends processing with the given status.
1122
 */
1123
void nsZipWriter::FinishQueue(nsresult aStatus)
1124
0
{
1125
0
    nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
1126
0
    nsCOMPtr<nsISupports> context = mProcessContext;
1127
0
    // Clean up everything first in case the observer decides to queue more
1128
0
    // things
1129
0
    mProcessObserver = nullptr;
1130
0
    mProcessContext = nullptr;
1131
0
    mInQueue = false;
1132
0
1133
0
    if (observer)
1134
0
        observer->OnStopRequest(nullptr, context, aStatus);
1135
0
}