1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* nsJARInputStream.cpp
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/. */
8 #include "nsJARInputStream.h"
9 #include "zipstruct.h" // defines ZIP compression codes
11 #include "brotli/decode.h" // brotli
13 #include "nsZipArchive.h"
23 /*---------------------------------------------
24 * nsISupports implementation
25 *--------------------------------------------*/
27 NS_IMPL_ISUPPORTS(nsJARInputStream
, nsIInputStream
)
29 /*----------------------------------------------------------
30 * nsJARInputStream implementation
31 *--------------------------------------------------------*/
34 nsJARInputStream::InitFile(nsJAR
*aJar
, nsZipItem
*item
)
37 MOZ_ASSERT(aJar
, "Argument may not be null");
38 MOZ_ASSERT(item
, "Argument may not be null");
40 // Mark it as closed, in case something fails in initialisation
42 //-- prepare for the compression type
43 switch (item
->Compression()) {
50 NS_ENSURE_SUCCESS(rv
, rv
);
53 mInCrc
= item
->CRC32();
54 mOutCrc
= crc32(0L, Z_NULL
, 0);
59 mBrotliState
= BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
61 mInCrc
= item
->CRC32();
62 mOutCrc
= crc32(0L, Z_NULL
, 0);
67 return NS_ERROR_NOT_IMPLEMENTED
;
70 // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
71 mFd
= aJar
->mZip
->GetFD();
72 mZs
.next_in
= (Bytef
*)aJar
->mZip
->GetData(item
);
74 nsZipArchive::sFileCorruptedReason
= "nsJARInputStream: !mZs.next_in";
75 return NS_ERROR_FILE_CORRUPTED
;
77 mZs
.avail_in
= item
->Size();
78 mOutSize
= item
->RealSize();
84 nsJARInputStream::InitDirectory(nsJAR
* aJar
,
85 const nsACString
& aJarDirSpec
,
88 MOZ_ASSERT(aJar
, "Argument may not be null");
89 MOZ_ASSERT(aDir
, "Argument may not be null");
91 // Mark it as closed, in case something fails in initialisation
94 // Keep the zipReader for getting the actual zipItems
98 // We can get aDir's contents as strings via FindEntries
99 // with the following pattern (see nsIZipReader.findEntries docs)
100 // assuming dirName is properly escaped:
102 // dirName + "?*~" + dirName + "?*/?*"
103 nsDependentCString
dirName(aDir
);
104 mNameLen
= dirName
.Length();
106 // iterate through dirName and copy it to escDirName, escaping chars
107 // which are special at the "top" level of the regexp so FindEntries
109 nsAutoCString escDirName
;
110 const char* curr
= dirName
.BeginReading();
111 const char* end
= dirName
.EndReading();
112 while (curr
!= end
) {
124 escDirName
.Append('\\');
127 escDirName
.Append(*curr
);
131 nsAutoCString pattern
= escDirName
+ NS_LITERAL_CSTRING("?*~") +
132 escDirName
+ NS_LITERAL_CSTRING("?*/?*");
133 rv
= mJar
->mZip
->FindInit(pattern
.get(), &find
);
134 if (NS_FAILED(rv
)) return rv
;
138 while ((rv
= find
->FindNext( &name
, &nameLen
)) == NS_OK
) {
139 // Must copy, to make it zero-terminated
140 mArray
.AppendElement(nsCString(name
,nameLen
));
144 if (rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
&& NS_FAILED(rv
)) {
145 return NS_ERROR_FAILURE
; // no error translation
151 mBuffer
.AssignLiteral("300: ");
152 mBuffer
.Append(aJarDirSpec
);
153 mBuffer
.AppendLiteral("\n200: filename content-length last-modified file-type\n");
156 mMode
= MODE_DIRECTORY
;
163 nsJARInputStream::Available(uint64_t *_retval
)
165 // A lot of callers don't check the error code.
166 // They just use the _retval value.
174 return NS_BASE_STREAM_CLOSED
;
177 *_retval
= mBuffer
.Length();
181 #ifdef MOZ_JAR_BROTLI
185 *_retval
= mOutSize
- mZs
.total_out
;
193 nsJARInputStream::Read(char* aBuffer
, uint32_t aCount
, uint32_t *aBytesRead
)
195 NS_ENSURE_ARG_POINTER(aBuffer
);
196 NS_ENSURE_ARG_POINTER(aBytesRead
);
201 MOZ_WIN_MEM_TRY_BEGIN
207 return NS_BASE_STREAM_CLOSED
;
210 return ReadDirectory(aBuffer
, aCount
, aBytesRead
);
213 #ifdef MOZ_JAR_BROTLI
216 if (mZs
.total_out
< mOutSize
) {
217 rv
= ContinueInflate(aBuffer
, aCount
, aBytesRead
);
219 // be aggressive about releasing the file!
220 // note that sometimes, we will release mFd before we've finished
221 // deflating - this is because zlib buffers the input
222 if (mZs
.avail_in
== 0) {
229 uint32_t count
= std::min(aCount
, mOutSize
- uint32_t(mZs
.total_out
));
231 memcpy(aBuffer
, mZs
.next_in
+ mZs
.total_out
, count
);
232 mZs
.total_out
+= count
;
236 // be aggressive about releasing the file!
237 // note that sometimes, we will release mFd before we've finished copying.
238 if (mZs
.total_out
>= mOutSize
) {
243 MOZ_WIN_MEM_TRY_CATCH(rv
= NS_ERROR_FAILURE
)
248 nsJARInputStream::ReadSegments(nsWriteSegmentFun writer
, void * closure
, uint32_t count
, uint32_t *_retval
)
250 // don't have a buffer to read from, so this better not be called!
251 return NS_ERROR_NOT_IMPLEMENTED
;
255 nsJARInputStream::IsNonBlocking(bool *aNonBlocking
)
257 *aNonBlocking
= false;
262 nsJARInputStream::Close()
264 if (mMode
== MODE_INFLATE
) {
267 #ifdef MOZ_JAR_BROTLI
268 if (mMode
== MODE_BROTLI
) {
269 BrotliDecoderDestroyInstance(mBrotliState
);
278 nsJARInputStream::ContinueInflate(char* aBuffer
, uint32_t aCount
,
279 uint32_t* aBytesRead
)
281 bool finished
= false;
283 // No need to check the args, ::Read did that, but assert them at least
284 NS_ASSERTION(aBuffer
,"aBuffer parameter must not be null");
285 NS_ASSERTION(aBytesRead
,"aBytesRead parameter must not be null");
287 // Keep old total_out count
288 const uint32_t oldTotalOut
= mZs
.total_out
;
290 // make sure we aren't reading too much
291 mZs
.avail_out
= std::min(aCount
, (mOutSize
-oldTotalOut
));
292 mZs
.next_out
= (unsigned char*)aBuffer
;
294 #ifndef MOZ_JAR_BROTLI
295 MOZ_ASSERT(mMode
== MODE_INFLATE
);
297 if (mMode
== MODE_INFLATE
) {
299 int zerr
= inflate(&mZs
, Z_SYNC_FLUSH
);
300 if ((zerr
!= Z_OK
) && (zerr
!= Z_STREAM_END
)) {
301 nsZipArchive::sFileCorruptedReason
= "nsJARInputStream: error while inflating";
302 return NS_ERROR_FILE_CORRUPTED
;
304 finished
= (zerr
== Z_STREAM_END
);
305 #ifdef MOZ_JAR_BROTLI
307 MOZ_ASSERT(mMode
== MODE_BROTLI
);
308 /* The brotli library wants size_t, but z_stream only contains
309 * unsigned int for avail_* and unsigned long for total_*.
310 * So use temporary stack values. */
311 size_t avail_in
= mZs
.avail_in
;
312 size_t avail_out
= mZs
.avail_out
;
313 size_t total_out
= mZs
.total_out
;
314 BrotliDecoderResult result
= BrotliDecoderDecompressStream(
316 &avail_in
, const_cast<const unsigned char**>(&mZs
.next_in
),
317 &avail_out
, &mZs
.next_out
, &total_out
);
318 /* We don't need to update avail_out, it's not used outside this
320 mZs
.total_out
= total_out
;
321 mZs
.avail_in
= avail_in
;
322 if (result
== BROTLI_DECODER_RESULT_ERROR
) {
323 nsZipArchive::sFileCorruptedReason
= "nsJARInputStream: brotli decompression error";
324 return NS_ERROR_FILE_CORRUPTED
;
326 finished
= (result
== BROTLI_DECODER_RESULT_SUCCESS
);
330 *aBytesRead
= (mZs
.total_out
- oldTotalOut
);
332 // Calculate the CRC on the output
333 mOutCrc
= crc32(mOutCrc
, (unsigned char*)aBuffer
, *aBytesRead
);
335 // be aggressive about ending the inflation
336 // for some reason we don't always get Z_STREAM_END
337 if (finished
|| mZs
.total_out
== mOutSize
) {
338 if (mMode
== MODE_INFLATE
) {
342 // stop returning valid data as soon as we know we have a bad CRC
343 if (mOutCrc
!= mInCrc
) {
344 nsZipArchive::sFileCorruptedReason
= "nsJARInputStream: crc mismatch";
345 return NS_ERROR_FILE_CORRUPTED
;
353 nsJARInputStream::ReadDirectory(char* aBuffer
, uint32_t aCount
, uint32_t *aBytesRead
)
355 // No need to check the args, ::Read did that, but assert them at least
356 NS_ASSERTION(aBuffer
,"aBuffer parameter must not be null");
357 NS_ASSERTION(aBytesRead
,"aBytesRead parameter must not be null");
359 // If the buffer contains data, copy what's there up to the desired amount
360 uint32_t numRead
= CopyDataToBuffer(aBuffer
, aCount
);
363 // empty the buffer and start writing directory entry lines to it
366 const uint32_t arrayLen
= mArray
.Length();
368 for ( ;aCount
> mBuffer
.Length(); mArrPos
++) {
369 // have we consumed all the directory contents?
370 if (arrayLen
<= mArrPos
)
373 const char * entryName
= mArray
[mArrPos
].get();
374 uint32_t entryNameLen
= mArray
[mArrPos
].Length();
375 nsZipItem
* ze
= mJar
->mZip
->GetItem(entryName
);
376 NS_ENSURE_TRUE(ze
, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
);
378 // Last Modified Time
380 PR_ExplodeTime(ze
->LastModTime(), PR_GMTParameters
, &tm
);
381 char itemLastModTime
[65];
382 PR_FormatTimeUSEnglish(itemLastModTime
,
383 sizeof(itemLastModTime
),
384 " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
387 // write a 201: line to the buffer for this item
388 // 200: filename content-length last-modified file-type
389 mBuffer
.AppendLiteral("201: ");
391 // Names must be escaped and relative, so use the pre-calculated length
392 // of the directory name as the offset into the string
393 // NS_EscapeURL adds the escaped URL to the give string buffer
394 NS_EscapeURL(entryName
+ mNameLen
,
395 entryNameLen
- mNameLen
,
396 esc_Minimal
| esc_AlwaysCopy
,
400 mBuffer
.AppendInt(ze
->RealSize(), 10);
401 mBuffer
.Append(itemLastModTime
); // starts/ends with ' '
402 if (ze
->IsDirectory())
403 mBuffer
.AppendLiteral("DIRECTORY\n");
405 mBuffer
.AppendLiteral("FILE\n");
408 // Copy up to the desired amount of data to buffer
409 numRead
+= CopyDataToBuffer(aBuffer
, aCount
);
412 *aBytesRead
= numRead
;
417 nsJARInputStream::CopyDataToBuffer(char* &aBuffer
, uint32_t &aCount
)
419 const uint32_t writeLength
= std::min(aCount
, mBuffer
.Length() - mCurPos
);
421 if (writeLength
> 0) {
422 memcpy(aBuffer
, mBuffer
.get() + mCurPos
, writeLength
);
423 mCurPos
+= writeLength
;
424 aCount
-= writeLength
;
425 aBuffer
+= writeLength
;
428 // return number of bytes copied to the buffer so the
429 // Read method can return the number of bytes copied