Bug 1888033 - [Menu Redesign] Add a secret setting and feature flag for the menu...
[gecko.git] / modules / libjar / nsJARInputStream.cpp
blobed29d67295de2a14a620a8f9704699000248eaac
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"
16 #include "nsEscape.h"
17 #include "nsDebug.h"
18 #include <algorithm>
19 #include <limits>
20 #if defined(XP_WIN)
21 # include <windows.h>
22 #endif
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,
36 nsZipItem* aItem) {
37 nsresult rv = NS_OK;
38 MOZ_DIAGNOSTIC_ASSERT(aFd, "Argument may not be null");
39 if (!aFd) {
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
45 mMode = MODE_CLOSED;
46 //-- prepare for the compression type
47 switch (aItem->Compression()) {
48 case STORED:
49 mMode = MODE_COPY;
50 break;
52 case DEFLATED:
53 rv = gZlibInit(&mZs);
54 NS_ENSURE_SUCCESS(rv, rv);
56 mMode = MODE_INFLATE;
57 mInCrc = aItem->CRC32();
58 mOutCrc = crc32(0L, Z_NULL, 0);
59 break;
61 default:
62 mFd = aFd;
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
68 mFd = aFd;
69 mZs.next_in = (Bytef*)aData;
70 if (!mZs.next_in) {
71 return NS_ERROR_FILE_CORRUPTED;
73 mZs.avail_in = aItem->Size();
74 mOutSize = aItem->RealSize();
75 mZs.total_out = 0;
76 return NS_OK;
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
84 mMode = MODE_CLOSED;
86 // Keep the zipReader for getting the actual zipItems
87 mJar = aJar;
88 mJar->mLock.AssertCurrentThreadIn();
89 mozilla::UniquePtr<nsZipFind> find;
90 nsresult rv;
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
101 // works correctly
102 nsAutoCString escDirName;
103 const char* curr = dirName.BeginReading();
104 const char* end = dirName.EndReading();
105 while (curr != end) {
106 switch (*curr) {
107 case '*':
108 case '?':
109 case '$':
110 case '[':
111 case ']':
112 case '^':
113 case '~':
114 case '(':
115 case ')':
116 case '\\':
117 escDirName.Append('\\');
118 [[fallthrough]];
119 default:
120 escDirName.Append(*curr);
122 ++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;
128 const char* name;
129 uint16_t nameLen;
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
139 // Sort it
140 mArray.Sort();
142 mBuffer.AppendLiteral(
143 "200: filename content-length last-modified file-type\n");
145 // Open for reading
146 mMode = MODE_DIRECTORY;
147 mZs.total_out = 0;
148 mArrPos = 0;
149 return NS_OK;
152 NS_IMETHODIMP
153 nsJARInputStream::Available(uint64_t* _retval) {
154 // A lot of callers don't check the error code.
155 // They just use the _retval value.
156 *_retval = 0;
158 uint64_t maxAvailableSize = 0;
160 switch (mMode) {
161 case MODE_NOTINITED:
162 break;
164 case MODE_CLOSED:
165 return NS_BASE_STREAM_CLOSED;
167 case MODE_DIRECTORY:
168 *_retval = mBuffer.Length();
169 break;
171 case MODE_INFLATE:
172 case MODE_COPY:
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);
178 break;
181 return NS_OK;
184 NS_IMETHODIMP
185 nsJARInputStream::StreamStatus() {
186 return mMode == MODE_CLOSED ? NS_BASE_STREAM_CLOSED : NS_OK;
189 NS_IMETHODIMP
190 nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytesRead) {
191 NS_ENSURE_ARG_POINTER(aBuffer);
192 NS_ENSURE_ARG_POINTER(aBytesRead);
194 *aBytesRead = 0;
196 nsresult rv = NS_OK;
197 MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
198 switch (mMode) {
199 case MODE_NOTINITED:
200 return NS_OK;
202 case MODE_CLOSED:
203 return NS_BASE_STREAM_CLOSED;
205 case MODE_DIRECTORY:
206 return ReadDirectory(aBuffer, aCount, aBytesRead);
208 case MODE_INFLATE:
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) {
216 mFd = nullptr;
218 break;
220 case MODE_COPY:
221 if (mFd) {
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));
225 if (count) {
226 std::copy(mZs.next_in + mZs.total_out,
227 mZs.next_in + mZs.total_out + count, aBuffer);
228 mZs.total_out += count;
230 *aBytesRead = 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) {
235 mFd = nullptr;
237 break;
239 MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
240 return rv;
243 NS_IMETHODIMP
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;
250 NS_IMETHODIMP
251 nsJARInputStream::IsNonBlocking(bool* aNonBlocking) {
252 *aNonBlocking = false;
253 return NS_OK;
256 NS_IMETHODIMP
257 nsJARInputStream::Close() {
258 if (mMode == MODE_INFLATE) {
259 inflateEnd(&mZs);
261 mMode = MODE_CLOSED;
262 mFd = nullptr;
263 return NS_OK;
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) {
282 // now 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);
302 if (zerr != Z_OK) {
303 return NS_ERROR_FILE_CORRUPTED;
306 // Stream is finished but has a different size from what
307 // we expected.
308 if (mozilla::StaticPrefs::network_jar_require_size_match() &&
309 mZs.total_out != mOutSize) {
310 return NS_ERROR_FILE_CORRUPTED;
314 // stop returning valid data as soon as we know we have a bad CRC
315 if (mOutCrc != mInCrc) {
316 return NS_ERROR_FILE_CORRUPTED;
320 return NS_OK;
323 nsresult nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount,
324 uint32_t* aBytesRead) {
325 // No need to check the args, ::Read did that, but assert them at least
326 NS_ASSERTION(aBuffer, "aBuffer parameter must not be null");
327 NS_ASSERTION(aBytesRead, "aBytesRead parameter must not be null");
329 // If the buffer contains data, copy what's there up to the desired amount
330 uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
332 if (aCount > 0) {
333 mozilla::RecursiveMutexAutoLock lock(mJar->mLock);
334 // empty the buffer and start writing directory entry lines to it
335 mBuffer.Truncate();
336 mCurPos = 0;
337 const uint32_t arrayLen = mArray.Length();
339 for (; aCount > mBuffer.Length(); mArrPos++) {
340 // have we consumed all the directory contents?
341 if (arrayLen <= mArrPos) break;
343 const char* entryName = mArray[mArrPos].get();
344 uint32_t entryNameLen = mArray[mArrPos].Length();
345 nsZipItem* ze = mJar->mZip->GetItem(entryName);
346 NS_ENSURE_TRUE(ze, NS_ERROR_FILE_NOT_FOUND);
348 // Last Modified Time
349 PRExplodedTime tm;
350 PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
351 char itemLastModTime[65];
352 PR_FormatTimeUSEnglish(itemLastModTime, sizeof(itemLastModTime),
353 " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
355 // write a 201: line to the buffer for this item
356 // 200: filename content-length last-modified file-type
357 mBuffer.AppendLiteral("201: ");
359 // Names must be escaped and relative, so use the pre-calculated length
360 // of the directory name as the offset into the string
361 // NS_EscapeURL adds the escaped URL to the give string buffer
362 NS_EscapeURL(entryName + mNameLen, entryNameLen - mNameLen,
363 esc_Minimal | esc_AlwaysCopy, mBuffer);
365 mBuffer.Append(' ');
366 mBuffer.AppendInt(ze->RealSize(), 10);
367 mBuffer.Append(itemLastModTime); // starts/ends with ' '
368 if (ze->IsDirectory())
369 mBuffer.AppendLiteral("DIRECTORY\n");
370 else
371 mBuffer.AppendLiteral("FILE\n");
374 // Copy up to the desired amount of data to buffer
375 numRead += CopyDataToBuffer(aBuffer, aCount);
378 *aBytesRead = numRead;
379 return NS_OK;
382 uint32_t nsJARInputStream::CopyDataToBuffer(char*& aBuffer, uint32_t& aCount) {
383 const uint32_t writeLength =
384 std::min<uint32_t>(aCount, mBuffer.Length() - mCurPos);
386 if (writeLength > 0) {
387 std::copy(mBuffer.get() + mCurPos, mBuffer.get() + mCurPos + writeLength,
388 aBuffer);
389 mCurPos += writeLength;
390 aCount -= writeLength;
391 aBuffer += writeLength;
394 // return number of bytes copied to the buffer so the
395 // Read method can return the number of bytes copied
396 return writeLength;