Bug 1492992 [wpt PR 11769] - Various test fixes for python3 support., a=testonly
[gecko.git] / modules / libjar / nsJARInputStream.cpp
blobe41e7d2d332172f6e85241119950d52b27393639
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
10 #ifdef MOZ_JAR_BROTLI
11 #include "brotli/decode.h" // brotli
12 #endif
13 #include "nsZipArchive.h"
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
23 /*---------------------------------------------
24 * nsISupports implementation
25 *--------------------------------------------*/
27 NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
29 /*----------------------------------------------------------
30 * nsJARInputStream implementation
31 *--------------------------------------------------------*/
33 nsresult
34 nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
36 nsresult rv = NS_OK;
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
41 mMode = MODE_CLOSED;
42 //-- prepare for the compression type
43 switch (item->Compression()) {
44 case STORED:
45 mMode = MODE_COPY;
46 break;
48 case DEFLATED:
49 rv = gZlibInit(&mZs);
50 NS_ENSURE_SUCCESS(rv, rv);
52 mMode = MODE_INFLATE;
53 mInCrc = item->CRC32();
54 mOutCrc = crc32(0L, Z_NULL, 0);
55 break;
57 #ifdef MOZ_JAR_BROTLI
58 case MOZ_JAR_BROTLI:
59 mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
60 mMode = MODE_BROTLI;
61 mInCrc = item->CRC32();
62 mOutCrc = crc32(0L, Z_NULL, 0);
63 break;
64 #endif
66 default:
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);
73 if (!mZs.next_in) {
74 nsZipArchive::sFileCorruptedReason = "nsJARInputStream: !mZs.next_in";
75 return NS_ERROR_FILE_CORRUPTED;
77 mZs.avail_in = item->Size();
78 mOutSize = item->RealSize();
79 mZs.total_out = 0;
80 return NS_OK;
83 nsresult
84 nsJARInputStream::InitDirectory(nsJAR* aJar,
85 const nsACString& aJarDirSpec,
86 const char* aDir)
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
92 mMode = MODE_CLOSED;
94 // Keep the zipReader for getting the actual zipItems
95 mJar = aJar;
96 nsZipFind *find;
97 nsresult rv;
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
108 // works correctly
109 nsAutoCString escDirName;
110 const char* curr = dirName.BeginReading();
111 const char* end = dirName.EndReading();
112 while (curr != end) {
113 switch (*curr) {
114 case '*':
115 case '?':
116 case '$':
117 case '[':
118 case ']':
119 case '^':
120 case '~':
121 case '(':
122 case ')':
123 case '\\':
124 escDirName.Append('\\');
125 MOZ_FALLTHROUGH;
126 default:
127 escDirName.Append(*curr);
129 ++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;
136 const char *name;
137 uint16_t nameLen;
138 while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
139 // Must copy, to make it zero-terminated
140 mArray.AppendElement(nsCString(name,nameLen));
142 delete find;
144 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
145 return NS_ERROR_FAILURE; // no error translation
148 // Sort it
149 mArray.Sort();
151 mBuffer.AssignLiteral("300: ");
152 mBuffer.Append(aJarDirSpec);
153 mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
155 // Open for reading
156 mMode = MODE_DIRECTORY;
157 mZs.total_out = 0;
158 mArrPos = 0;
159 return NS_OK;
162 NS_IMETHODIMP
163 nsJARInputStream::Available(uint64_t *_retval)
165 // A lot of callers don't check the error code.
166 // They just use the _retval value.
167 *_retval = 0;
169 switch (mMode) {
170 case MODE_NOTINITED:
171 break;
173 case MODE_CLOSED:
174 return NS_BASE_STREAM_CLOSED;
176 case MODE_DIRECTORY:
177 *_retval = mBuffer.Length();
178 break;
180 case MODE_INFLATE:
181 #ifdef MOZ_JAR_BROTLI
182 case MODE_BROTLI:
183 #endif
184 case MODE_COPY:
185 *_retval = mOutSize - mZs.total_out;
186 break;
189 return NS_OK;
192 NS_IMETHODIMP
193 nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
195 NS_ENSURE_ARG_POINTER(aBuffer);
196 NS_ENSURE_ARG_POINTER(aBytesRead);
198 *aBytesRead = 0;
200 nsresult rv = NS_OK;
201 MOZ_WIN_MEM_TRY_BEGIN
202 switch (mMode) {
203 case MODE_NOTINITED:
204 return NS_OK;
206 case MODE_CLOSED:
207 return NS_BASE_STREAM_CLOSED;
209 case MODE_DIRECTORY:
210 return ReadDirectory(aBuffer, aCount, aBytesRead);
212 case MODE_INFLATE:
213 #ifdef MOZ_JAR_BROTLI
214 case MODE_BROTLI:
215 #endif
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) {
223 mFd = nullptr;
225 break;
227 case MODE_COPY:
228 if (mFd) {
229 uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
230 if (count) {
231 memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
232 mZs.total_out += count;
234 *aBytesRead = 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) {
239 mFd = nullptr;
241 break;
243 MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
244 return rv;
247 NS_IMETHODIMP
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;
254 NS_IMETHODIMP
255 nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
257 *aNonBlocking = false;
258 return NS_OK;
261 NS_IMETHODIMP
262 nsJARInputStream::Close()
264 if (mMode == MODE_INFLATE) {
265 inflateEnd(&mZs);
267 #ifdef MOZ_JAR_BROTLI
268 if (mMode == MODE_BROTLI) {
269 BrotliDecoderDestroyInstance(mBrotliState);
271 #endif
272 mMode = MODE_CLOSED;
273 mFd = nullptr;
274 return NS_OK;
277 nsresult
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);
296 #endif
297 if (mMode == MODE_INFLATE) {
298 // now 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
306 } else {
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(
315 mBrotliState,
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
319 * function. */
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);
327 #endif
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) {
339 inflateEnd(&mZs);
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;
349 return NS_OK;
352 nsresult
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);
362 if (aCount > 0) {
363 // empty the buffer and start writing directory entry lines to it
364 mBuffer.Truncate();
365 mCurPos = 0;
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)
371 break;
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
379 PRExplodedTime tm;
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 ",
385 &tm);
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,
397 mBuffer);
399 mBuffer.Append(' ');
400 mBuffer.AppendInt(ze->RealSize(), 10);
401 mBuffer.Append(itemLastModTime); // starts/ends with ' '
402 if (ze->IsDirectory())
403 mBuffer.AppendLiteral("DIRECTORY\n");
404 else
405 mBuffer.AppendLiteral("FILE\n");
408 // Copy up to the desired amount of data to buffer
409 numRead += CopyDataToBuffer(aBuffer, aCount);
412 *aBytesRead = numRead;
413 return NS_OK;
416 uint32_t
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
430 return writeLength;