1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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
10 #include "nsZipArchive.h"
11 #include "mozilla/MmapFaultHandler.h"
12 #include "mozilla/StaticPrefs_network.h"
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/UniquePtrExtensions.h"
24 /*---------------------------------------------
25 * nsISupports implementation
26 *--------------------------------------------*/
28 NS_IMPL_ISUPPORTS(nsJARInputStream
, nsIInputStream
)
30 /*----------------------------------------------------------
31 * nsJARInputStream implementation
32 * Takes ownership of |fd|, even on failure
33 *--------------------------------------------------------*/
35 nsresult
nsJARInputStream::InitFile(nsZipHandle
* aFd
, const uint8_t* aData
,
38 MOZ_DIAGNOSTIC_ASSERT(aFd
, "Argument may not be null");
40 return NS_ERROR_INVALID_ARG
;
42 MOZ_ASSERT(aItem
, "Argument may not be null");
44 // Mark it as closed, in case something fails in initialisation
46 //-- prepare for the compression type
47 switch (aItem
->Compression()) {
54 NS_ENSURE_SUCCESS(rv
, rv
);
57 mInCrc
= aItem
->CRC32();
58 mOutCrc
= crc32(0L, Z_NULL
, 0);
63 return NS_ERROR_NOT_IMPLEMENTED
;
66 // Must keep handle to filepointer and mmap structure as long as we need
67 // access to the mmapped data
69 mZs
.next_in
= (Bytef
*)aData
;
71 return NS_ERROR_FILE_CORRUPTED
;
73 mZs
.avail_in
= aItem
->Size();
74 mOutSize
= aItem
->RealSize();
79 nsresult
nsJARInputStream::InitDirectory(nsJAR
* aJar
, const char* aDir
) {
80 MOZ_ASSERT(aJar
, "Argument may not be null");
81 MOZ_ASSERT(aDir
, "Argument may not be null");
83 // Mark it as closed, in case something fails in initialisation
86 // Keep the zipReader for getting the actual zipItems
88 mJar
->mLock
.AssertCurrentThreadIn();
89 mozilla::UniquePtr
<nsZipFind
> find
;
91 // We can get aDir's contents as strings via FindEntries
92 // with the following pattern (see nsIZipReader.findEntries docs)
93 // assuming dirName is properly escaped:
95 // dirName + "?*~" + dirName + "?*/?*"
96 nsDependentCString
dirName(aDir
);
97 mNameLen
= dirName
.Length();
99 // iterate through dirName and copy it to escDirName, escaping chars
100 // which are special at the "top" level of the regexp so FindEntries
102 nsAutoCString escDirName
;
103 const char* curr
= dirName
.BeginReading();
104 const char* end
= dirName
.EndReading();
105 while (curr
!= end
) {
117 escDirName
.Append('\\');
120 escDirName
.Append(*curr
);
124 nsAutoCString pattern
= escDirName
+ "?*~"_ns
+ escDirName
+ "?*/?*"_ns
;
125 rv
= mJar
->mZip
->FindInit(pattern
.get(), getter_Transfers(find
));
126 if (NS_FAILED(rv
)) return rv
;
130 while ((rv
= find
->FindNext(&name
, &nameLen
)) == NS_OK
) {
131 // Must copy, to make it zero-terminated
132 mArray
.AppendElement(nsCString(name
, nameLen
));
135 if (rv
!= NS_ERROR_FILE_NOT_FOUND
&& NS_FAILED(rv
)) {
136 return NS_ERROR_FAILURE
; // no error translation
142 mBuffer
.AppendLiteral(
143 "200: filename content-length last-modified file-type\n");
146 mMode
= MODE_DIRECTORY
;
153 nsJARInputStream::Available(uint64_t* _retval
) {
154 // A lot of callers don't check the error code.
155 // They just use the _retval value.
158 uint64_t maxAvailableSize
= 0;
165 return NS_BASE_STREAM_CLOSED
;
168 *_retval
= mBuffer
.Length();
173 maxAvailableSize
= mozilla::StaticPrefs::network_jar_max_available_size();
174 if (!maxAvailableSize
) {
175 maxAvailableSize
= std::numeric_limits
<uint64_t>::max();
177 *_retval
= std::min
<uint64_t>(mOutSize
- mZs
.total_out
, maxAvailableSize
);
185 nsJARInputStream::StreamStatus() {
186 return mMode
== MODE_CLOSED
? NS_BASE_STREAM_CLOSED
: NS_OK
;
190 nsJARInputStream::Read(char* aBuffer
, uint32_t aCount
, uint32_t* aBytesRead
) {
191 NS_ENSURE_ARG_POINTER(aBuffer
);
192 NS_ENSURE_ARG_POINTER(aBytesRead
);
197 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd
)
203 return NS_BASE_STREAM_CLOSED
;
206 return ReadDirectory(aBuffer
, aCount
, aBytesRead
);
209 if (mZs
.total_out
< mOutSize
) {
210 rv
= ContinueInflate(aBuffer
, aCount
, aBytesRead
);
212 // be aggressive about releasing the file!
213 // note that sometimes, we will release mFd before we've finished
214 // deflating - this is because zlib buffers the input
215 if (mZs
.avail_in
== 0) {
222 MOZ_DIAGNOSTIC_ASSERT(mOutSize
>= mZs
.total_out
,
223 "Did we read more than expected?");
224 uint32_t count
= std::min(aCount
, mOutSize
- uint32_t(mZs
.total_out
));
226 std::copy(mZs
.next_in
+ mZs
.total_out
,
227 mZs
.next_in
+ mZs
.total_out
+ count
, aBuffer
);
228 mZs
.total_out
+= count
;
232 // be aggressive about releasing the file!
233 // note that sometimes, we will release mFd before we've finished copying.
234 if (mZs
.total_out
>= mOutSize
) {
239 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE
)
244 nsJARInputStream::ReadSegments(nsWriteSegmentFun writer
, void* closure
,
245 uint32_t count
, uint32_t* _retval
) {
246 // don't have a buffer to read from, so this better not be called!
247 return NS_ERROR_NOT_IMPLEMENTED
;
251 nsJARInputStream::IsNonBlocking(bool* aNonBlocking
) {
252 *aNonBlocking
= false;
257 nsJARInputStream::Close() {
258 if (mMode
== MODE_INFLATE
) {
266 nsresult
nsJARInputStream::ContinueInflate(char* aBuffer
, uint32_t aCount
,
267 uint32_t* aBytesRead
) {
268 bool finished
= false;
270 // No need to check the args, ::Read did that, but assert them at least
271 NS_ASSERTION(aBuffer
, "aBuffer parameter must not be null");
272 NS_ASSERTION(aBytesRead
, "aBytesRead parameter must not be null");
274 // Keep old total_out count
275 const uint32_t oldTotalOut
= mZs
.total_out
;
277 // make sure we aren't reading too much
278 mZs
.avail_out
= std::min(aCount
, (mOutSize
- oldTotalOut
));
279 mZs
.next_out
= (unsigned char*)aBuffer
;
281 if (mMode
== MODE_INFLATE
) {
283 int zerr
= inflate(&mZs
, Z_SYNC_FLUSH
);
284 if ((zerr
!= Z_OK
) && (zerr
!= Z_STREAM_END
)) {
285 return NS_ERROR_FILE_CORRUPTED
;
287 // If inflating did not read anything more, then the stream is finished.
288 finished
= (zerr
== Z_STREAM_END
) ||
289 (mZs
.avail_out
&& mZs
.total_out
== oldTotalOut
);
292 *aBytesRead
= (mZs
.total_out
- oldTotalOut
);
294 // Calculate the CRC on the output
295 mOutCrc
= crc32(mOutCrc
, (unsigned char*)aBuffer
, *aBytesRead
);
297 // be aggressive about ending the inflation
298 // for some reason we don't always get Z_STREAM_END
299 if (finished
|| mZs
.total_out
>= mOutSize
) {
300 if (mMode
== MODE_INFLATE
) {
301 int zerr
= inflateEnd(&mZs
);
303 return NS_ERROR_FILE_CORRUPTED
;
306 // Stream is finished but has a different size from what
308 if (mZs
.total_out
!= mOutSize
) {
309 return NS_ERROR_FILE_CORRUPTED
;
313 // stop returning valid data as soon as we know we have a bad CRC
314 if (mOutCrc
!= mInCrc
) {
315 return NS_ERROR_FILE_CORRUPTED
;
322 nsresult
nsJARInputStream::ReadDirectory(char* aBuffer
, uint32_t aCount
,
323 uint32_t* aBytesRead
) {
324 // No need to check the args, ::Read did that, but assert them at least
325 NS_ASSERTION(aBuffer
, "aBuffer parameter must not be null");
326 NS_ASSERTION(aBytesRead
, "aBytesRead parameter must not be null");
328 // If the buffer contains data, copy what's there up to the desired amount
329 uint32_t numRead
= CopyDataToBuffer(aBuffer
, aCount
);
332 mozilla::RecursiveMutexAutoLock
lock(mJar
->mLock
);
333 // empty the buffer and start writing directory entry lines to it
336 const uint32_t arrayLen
= mArray
.Length();
338 for (; aCount
> mBuffer
.Length(); mArrPos
++) {
339 // have we consumed all the directory contents?
340 if (arrayLen
<= mArrPos
) break;
342 const char* entryName
= mArray
[mArrPos
].get();
343 uint32_t entryNameLen
= mArray
[mArrPos
].Length();
344 nsZipItem
* ze
= mJar
->mZip
->GetItem(entryName
);
345 NS_ENSURE_TRUE(ze
, NS_ERROR_FILE_NOT_FOUND
);
347 // Last Modified Time
349 PR_ExplodeTime(ze
->LastModTime(), PR_GMTParameters
, &tm
);
350 char itemLastModTime
[65];
351 PR_FormatTimeUSEnglish(itemLastModTime
, sizeof(itemLastModTime
),
352 " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm
);
354 // write a 201: line to the buffer for this item
355 // 200: filename content-length last-modified file-type
356 mBuffer
.AppendLiteral("201: ");
358 // Names must be escaped and relative, so use the pre-calculated length
359 // of the directory name as the offset into the string
360 // NS_EscapeURL adds the escaped URL to the give string buffer
361 NS_EscapeURL(entryName
+ mNameLen
, entryNameLen
- mNameLen
,
362 esc_Minimal
| esc_AlwaysCopy
, mBuffer
);
365 mBuffer
.AppendInt(ze
->RealSize(), 10);
366 mBuffer
.Append(itemLastModTime
); // starts/ends with ' '
367 if (ze
->IsDirectory())
368 mBuffer
.AppendLiteral("DIRECTORY\n");
370 mBuffer
.AppendLiteral("FILE\n");
373 // Copy up to the desired amount of data to buffer
374 numRead
+= CopyDataToBuffer(aBuffer
, aCount
);
377 *aBytesRead
= numRead
;
381 uint32_t nsJARInputStream::CopyDataToBuffer(char*& aBuffer
, uint32_t& aCount
) {
382 const uint32_t writeLength
=
383 std::min
<uint32_t>(aCount
, mBuffer
.Length() - mCurPos
);
385 if (writeLength
> 0) {
386 std::copy(mBuffer
.get() + mCurPos
, mBuffer
.get() + mCurPos
+ writeLength
,
388 mCurPos
+= writeLength
;
389 aCount
-= writeLength
;
390 aBuffer
+= writeLength
;
393 // return number of bytes copied to the buffer so the
394 // Read method can return the number of bytes copied