Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/modules/libjar/nsJARInputStream.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* nsJARInputStream.cpp
3
 *
4
 * This Source Code Form is subject to the terms of the Mozilla Public
5
 * License, v. 2.0. If a copy of the MPL was not distributed with this
6
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8
#include "nsJARInputStream.h"
9
#include "zipstruct.h"         // defines ZIP compression codes
10
#ifdef MOZ_JAR_BROTLI
11
#include "brotli/decode.h"  // brotli
12
#endif
13
#include "nsZipArchive.h"
14
15
#include "nsEscape.h"
16
#include "nsIFile.h"
17
#include "nsDebug.h"
18
#include <algorithm>
19
#if defined(XP_WIN)
20
#include <windows.h>
21
#endif
22
23
/*---------------------------------------------
24
 *  nsISupports implementation
25
 *--------------------------------------------*/
26
27
NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
28
29
/*----------------------------------------------------------
30
 * nsJARInputStream implementation
31
 *--------------------------------------------------------*/
32
33
nsresult
34
nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
35
5
{
36
5
    nsresult rv = NS_OK;
37
5
    MOZ_ASSERT(aJar, "Argument may not be null");
38
5
    MOZ_ASSERT(item, "Argument may not be null");
39
5
40
5
    // Mark it as closed, in case something fails in initialisation
41
5
    mMode = MODE_CLOSED;
42
5
    //-- prepare for the compression type
43
5
    switch (item->Compression()) {
44
5
       case STORED:
45
5
           mMode = MODE_COPY;
46
5
           break;
47
5
48
5
       case DEFLATED:
49
0
           rv = gZlibInit(&mZs);
50
0
           NS_ENSURE_SUCCESS(rv, rv);
51
0
52
0
           mMode = MODE_INFLATE;
53
0
           mInCrc = item->CRC32();
54
0
           mOutCrc = crc32(0L, Z_NULL, 0);
55
0
           break;
56
0
57
0
#ifdef MOZ_JAR_BROTLI
58
0
       case MOZ_JAR_BROTLI:
59
0
           mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
60
0
           mMode = MODE_BROTLI;
61
0
           mInCrc = item->CRC32();
62
0
           mOutCrc = crc32(0L, Z_NULL, 0);
63
0
           break;
64
0
#endif
65
0
66
0
       default:
67
0
           return NS_ERROR_NOT_IMPLEMENTED;
68
5
    }
69
5
70
5
    // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
71
5
    mFd = aJar->mZip->GetFD();
72
5
    mZs.next_in = (Bytef *)aJar->mZip->GetData(item);
73
5
    if (!mZs.next_in) {
74
0
        nsZipArchive::sFileCorruptedReason = "nsJARInputStream: !mZs.next_in";
75
0
        return NS_ERROR_FILE_CORRUPTED;
76
0
    }
77
5
    mZs.avail_in = item->Size();
78
5
    mOutSize = item->RealSize();
79
5
    mZs.total_out = 0;
80
5
    return NS_OK;
81
5
}
82
83
nsresult
84
nsJARInputStream::InitDirectory(nsJAR* aJar,
85
                                const nsACString& aJarDirSpec,
86
                                const char* aDir)
87
0
{
88
0
    MOZ_ASSERT(aJar, "Argument may not be null");
89
0
    MOZ_ASSERT(aDir, "Argument may not be null");
90
0
91
0
    // Mark it as closed, in case something fails in initialisation
92
0
    mMode = MODE_CLOSED;
93
0
94
0
    // Keep the zipReader for getting the actual zipItems
95
0
    mJar = aJar;
96
0
    nsZipFind *find;
97
0
    nsresult rv;
98
0
    // We can get aDir's contents as strings via FindEntries
99
0
    // with the following pattern (see nsIZipReader.findEntries docs)
100
0
    // assuming dirName is properly escaped:
101
0
    //
102
0
    //   dirName + "?*~" + dirName + "?*/?*"
103
0
    nsDependentCString dirName(aDir);
104
0
    mNameLen = dirName.Length();
105
0
106
0
    // iterate through dirName and copy it to escDirName, escaping chars
107
0
    // which are special at the "top" level of the regexp so FindEntries
108
0
    // works correctly
109
0
    nsAutoCString escDirName;
110
0
    const char* curr = dirName.BeginReading();
111
0
    const char* end  = dirName.EndReading();
112
0
    while (curr != end) {
113
0
        switch (*curr) {
114
0
            case '*':
115
0
            case '?':
116
0
            case '$':
117
0
            case '[':
118
0
            case ']':
119
0
            case '^':
120
0
            case '~':
121
0
            case '(':
122
0
            case ')':
123
0
            case '\\':
124
0
                escDirName.Append('\\');
125
0
                MOZ_FALLTHROUGH;
126
0
            default:
127
0
                escDirName.Append(*curr);
128
0
        }
129
0
        ++curr;
130
0
    }
131
0
    nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") +
132
0
                            escDirName + NS_LITERAL_CSTRING("?*/?*");
133
0
    rv = mJar->mZip->FindInit(pattern.get(), &find);
134
0
    if (NS_FAILED(rv)) return rv;
135
0
136
0
    const char *name;
137
0
    uint16_t nameLen;
138
0
    while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
139
0
        // Must copy, to make it zero-terminated
140
0
        mArray.AppendElement(nsCString(name,nameLen));
141
0
    }
142
0
    delete find;
143
0
144
0
    if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
145
0
        return NS_ERROR_FAILURE;    // no error translation
146
0
    }
147
0
148
0
    // Sort it
149
0
    mArray.Sort();
150
0
151
0
    mBuffer.AssignLiteral("300: ");
152
0
    mBuffer.Append(aJarDirSpec);
153
0
    mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
154
0
155
0
    // Open for reading
156
0
    mMode = MODE_DIRECTORY;
157
0
    mZs.total_out = 0;
158
0
    mArrPos = 0;
159
0
    return NS_OK;
160
0
}
161
162
NS_IMETHODIMP
163
nsJARInputStream::Available(uint64_t *_retval)
164
10
{
165
10
    // A lot of callers don't check the error code.
166
10
    // They just use the _retval value.
167
10
    *_retval = 0;
168
10
169
10
    switch (mMode) {
170
10
      case MODE_NOTINITED:
171
0
        break;
172
10
173
10
      case MODE_CLOSED:
174
0
        return NS_BASE_STREAM_CLOSED;
175
10
176
10
      case MODE_DIRECTORY:
177
0
        *_retval = mBuffer.Length();
178
0
        break;
179
10
180
10
      case MODE_INFLATE:
181
10
#ifdef MOZ_JAR_BROTLI
182
10
      case MODE_BROTLI:
183
10
#endif
184
10
      case MODE_COPY:
185
10
        *_retval = mOutSize - mZs.total_out;
186
10
        break;
187
10
    }
188
10
189
10
    return NS_OK;
190
10
}
191
192
NS_IMETHODIMP
193
nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
194
5
{
195
5
    NS_ENSURE_ARG_POINTER(aBuffer);
196
5
    NS_ENSURE_ARG_POINTER(aBytesRead);
197
5
198
5
    *aBytesRead = 0;
199
5
200
5
    nsresult rv = NS_OK;
201
5
MOZ_WIN_MEM_TRY_BEGIN
202
5
    switch (mMode) {
203
5
      case MODE_NOTINITED:
204
0
        return NS_OK;
205
5
206
5
      case MODE_CLOSED:
207
0
        return NS_BASE_STREAM_CLOSED;
208
5
209
5
      case MODE_DIRECTORY:
210
0
        return ReadDirectory(aBuffer, aCount, aBytesRead);
211
5
212
5
      case MODE_INFLATE:
213
0
#ifdef MOZ_JAR_BROTLI
214
0
      case MODE_BROTLI:
215
0
#endif
216
0
        if (mZs.total_out < mOutSize) {
217
0
          rv = ContinueInflate(aBuffer, aCount, aBytesRead);
218
0
        }
219
0
        // be aggressive about releasing the file!
220
0
        // note that sometimes, we will release  mFd before we've finished
221
0
        // deflating - this is because zlib buffers the input
222
0
        if (mZs.avail_in == 0) {
223
0
            mFd = nullptr;
224
0
        }
225
0
        break;
226
0
227
5
      case MODE_COPY:
228
5
        if (mFd) {
229
5
          uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
230
5
          if (count) {
231
5
              memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
232
5
              mZs.total_out += count;
233
5
          }
234
5
          *aBytesRead = count;
235
5
        }
236
5
        // be aggressive about releasing the file!
237
5
        // note that sometimes, we will release mFd before we've finished copying.
238
5
        if (mZs.total_out >= mOutSize) {
239
5
            mFd = nullptr;
240
5
        }
241
5
        break;
242
5
    }
243
5
MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
244
5
    return rv;
245
5
}
246
247
NS_IMETHODIMP
248
nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
249
0
{
250
0
    // don't have a buffer to read from, so this better not be called!
251
0
    return NS_ERROR_NOT_IMPLEMENTED;
252
0
}
253
254
NS_IMETHODIMP
255
nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
256
0
{
257
0
    *aNonBlocking = false;
258
0
    return NS_OK;
259
0
}
260
261
NS_IMETHODIMP
262
nsJARInputStream::Close()
263
10
{
264
10
    if (mMode == MODE_INFLATE) {
265
0
        inflateEnd(&mZs);
266
0
    }
267
10
#ifdef MOZ_JAR_BROTLI
268
10
    if (mMode == MODE_BROTLI) {
269
0
        BrotliDecoderDestroyInstance(mBrotliState);
270
0
    }
271
10
#endif
272
10
    mMode = MODE_CLOSED;
273
10
    mFd = nullptr;
274
10
    return NS_OK;
275
10
}
276
277
nsresult
278
nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount,
279
                                  uint32_t* aBytesRead)
280
0
{
281
0
    bool finished = false;
282
0
283
0
    // No need to check the args, ::Read did that, but assert them at least
284
0
    NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
285
0
    NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
286
0
287
0
    // Keep old total_out count
288
0
    const uint32_t oldTotalOut = mZs.total_out;
289
0
290
0
    // make sure we aren't reading too much
291
0
    mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut));
292
0
    mZs.next_out = (unsigned char*)aBuffer;
293
0
294
#ifndef MOZ_JAR_BROTLI
295
    MOZ_ASSERT(mMode == MODE_INFLATE);
296
#endif
297
0
    if (mMode == MODE_INFLATE) {
298
0
        // now inflate
299
0
        int zerr = inflate(&mZs, Z_SYNC_FLUSH);
300
0
        if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) {
301
0
            nsZipArchive::sFileCorruptedReason = "nsJARInputStream: error while inflating";
302
0
            return NS_ERROR_FILE_CORRUPTED;
303
0
        }
304
0
        finished = (zerr == Z_STREAM_END);
305
0
#ifdef MOZ_JAR_BROTLI
306
0
    } else {
307
0
        MOZ_ASSERT(mMode == MODE_BROTLI);
308
0
        /* The brotli library wants size_t, but z_stream only contains
309
0
         * unsigned int for avail_* and unsigned long for total_*.
310
0
         * So use temporary stack values. */
311
0
        size_t avail_in = mZs.avail_in;
312
0
        size_t avail_out = mZs.avail_out;
313
0
        size_t total_out = mZs.total_out;
314
0
        BrotliDecoderResult result = BrotliDecoderDecompressStream(
315
0
            mBrotliState,
316
0
            &avail_in, const_cast<const unsigned char**>(&mZs.next_in),
317
0
            &avail_out, &mZs.next_out, &total_out);
318
0
        /* We don't need to update avail_out, it's not used outside this
319
0
         * function. */
320
0
        mZs.total_out = total_out;
321
0
        mZs.avail_in = avail_in;
322
0
        if (result == BROTLI_DECODER_RESULT_ERROR) {
323
0
            nsZipArchive::sFileCorruptedReason = "nsJARInputStream: brotli decompression error";
324
0
            return NS_ERROR_FILE_CORRUPTED;
325
0
        }
326
0
        finished = (result == BROTLI_DECODER_RESULT_SUCCESS);
327
0
#endif
328
0
    }
329
0
330
0
    *aBytesRead = (mZs.total_out - oldTotalOut);
331
0
332
0
    // Calculate the CRC on the output
333
0
    mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
334
0
335
0
    // be aggressive about ending the inflation
336
0
    // for some reason we don't always get Z_STREAM_END
337
0
    if (finished || mZs.total_out == mOutSize) {
338
0
        if (mMode == MODE_INFLATE) {
339
0
            inflateEnd(&mZs);
340
0
        }
341
0
342
0
        // stop returning valid data as soon as we know we have a bad CRC
343
0
        if (mOutCrc != mInCrc) {
344
0
            nsZipArchive::sFileCorruptedReason = "nsJARInputStream: crc mismatch";
345
0
            return NS_ERROR_FILE_CORRUPTED;
346
0
        }
347
0
    }
348
0
349
0
    return NS_OK;
350
0
}
351
352
nsresult
353
nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
354
0
{
355
0
    // No need to check the args, ::Read did that, but assert them at least
356
0
    NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
357
0
    NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
358
0
359
0
    // If the buffer contains data, copy what's there up to the desired amount
360
0
    uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
361
0
362
0
    if (aCount > 0) {
363
0
        // empty the buffer and start writing directory entry lines to it
364
0
        mBuffer.Truncate();
365
0
        mCurPos = 0;
366
0
        const uint32_t arrayLen = mArray.Length();
367
0
368
0
        for ( ;aCount > mBuffer.Length(); mArrPos++) {
369
0
            // have we consumed all the directory contents?
370
0
            if (arrayLen <= mArrPos)
371
0
                break;
372
0
373
0
            const char * entryName = mArray[mArrPos].get();
374
0
            uint32_t entryNameLen = mArray[mArrPos].Length();
375
0
            nsZipItem* ze = mJar->mZip->GetItem(entryName);
376
0
            NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
377
0
378
0
            // Last Modified Time
379
0
            PRExplodedTime tm;
380
0
            PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
381
0
            char itemLastModTime[65];
382
0
            PR_FormatTimeUSEnglish(itemLastModTime,
383
0
                                   sizeof(itemLastModTime),
384
0
                                   " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
385
0
                                   &tm);
386
0
387
0
            // write a 201: line to the buffer for this item
388
0
            // 200: filename content-length last-modified file-type
389
0
            mBuffer.AppendLiteral("201: ");
390
0
391
0
            // Names must be escaped and relative, so use the pre-calculated length
392
0
            // of the directory name as the offset into the string
393
0
            // NS_EscapeURL adds the escaped URL to the give string buffer
394
0
            NS_EscapeURL(entryName + mNameLen,
395
0
                         entryNameLen - mNameLen,
396
0
                         esc_Minimal | esc_AlwaysCopy,
397
0
                         mBuffer);
398
0
399
0
            mBuffer.Append(' ');
400
0
            mBuffer.AppendInt(ze->RealSize(), 10);
401
0
            mBuffer.Append(itemLastModTime); // starts/ends with ' '
402
0
            if (ze->IsDirectory())
403
0
                mBuffer.AppendLiteral("DIRECTORY\n");
404
0
            else
405
0
                mBuffer.AppendLiteral("FILE\n");
406
0
        }
407
0
408
0
        // Copy up to the desired amount of data to buffer
409
0
        numRead += CopyDataToBuffer(aBuffer, aCount);
410
0
    }
411
0
412
0
    *aBytesRead = numRead;
413
0
    return NS_OK;
414
0
}
415
416
uint32_t
417
nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount)
418
0
{
419
0
    const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos);
420
0
421
0
    if (writeLength > 0) {
422
0
        memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength);
423
0
        mCurPos += writeLength;
424
0
        aCount  -= writeLength;
425
0
        aBuffer += writeLength;
426
0
    }
427
0
428
0
    // return number of bytes copied to the buffer so the
429
0
    // Read method can return the number of bytes copied
430
0
    return writeLength;
431
0
}