Line data Source code
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 "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 0 : NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
28 :
29 : /*----------------------------------------------------------
30 : * nsJARInputStream implementation
31 : *--------------------------------------------------------*/
32 :
33 : nsresult
34 0 : nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
35 : {
36 0 : nsresult rv = NS_OK;
37 0 : MOZ_ASSERT(aJar, "Argument may not be null");
38 0 : MOZ_ASSERT(item, "Argument may not be null");
39 :
40 : // Mark it as closed, in case something fails in initialisation
41 0 : mMode = MODE_CLOSED;
42 : //-- prepare for the compression type
43 0 : switch (item->Compression()) {
44 : case STORED:
45 0 : mMode = MODE_COPY;
46 0 : break;
47 :
48 : case DEFLATED:
49 0 : rv = gZlibInit(&mZs);
50 0 : NS_ENSURE_SUCCESS(rv, rv);
51 :
52 0 : mMode = MODE_INFLATE;
53 0 : mInCrc = item->CRC32();
54 0 : mOutCrc = crc32(0L, Z_NULL, 0);
55 0 : break;
56 :
57 : #ifdef MOZ_JAR_BROTLI
58 : case MOZ_JAR_BROTLI:
59 0 : mBrotliState = BrotliCreateState(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 : #endif
65 :
66 : default:
67 0 : return NS_ERROR_NOT_IMPLEMENTED;
68 : }
69 :
70 : // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
71 0 : mFd = aJar->mZip->GetFD();
72 0 : mZs.next_in = (Bytef *)aJar->mZip->GetData(item);
73 0 : if (!mZs.next_in) {
74 0 : nsZipArchive::sFileCorruptedReason = "nsJARInputStream: !mZs.next_in";
75 0 : return NS_ERROR_FILE_CORRUPTED;
76 : }
77 0 : mZs.avail_in = item->Size();
78 0 : mOutSize = item->RealSize();
79 0 : mZs.total_out = 0;
80 0 : return NS_OK;
81 : }
82 :
83 : nsresult
84 0 : nsJARInputStream::InitDirectory(nsJAR* aJar,
85 : const nsACString& aJarDirSpec,
86 : const char* aDir)
87 : {
88 0 : MOZ_ASSERT(aJar, "Argument may not be null");
89 0 : MOZ_ASSERT(aDir, "Argument may not be null");
90 :
91 : // Mark it as closed, in case something fails in initialisation
92 0 : mMode = MODE_CLOSED;
93 :
94 : // Keep the zipReader for getting the actual zipItems
95 0 : 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:
101 : //
102 : // dirName + "?*~" + dirName + "?*/?*"
103 0 : nsDependentCString dirName(aDir);
104 0 : mNameLen = dirName.Length();
105 :
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 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 : case '*':
115 : case '?':
116 : case '$':
117 : case '[':
118 : case ']':
119 : case '^':
120 : case '~':
121 : case '(':
122 : case ')':
123 : case '\\':
124 0 : escDirName.Append('\\');
125 : MOZ_FALLTHROUGH;
126 : default:
127 0 : escDirName.Append(*curr);
128 : }
129 0 : ++curr;
130 : }
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 :
136 : const char *name;
137 : uint16_t nameLen;
138 0 : while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
139 : // Must copy, to make it zero-terminated
140 0 : mArray.AppendElement(nsCString(name,nameLen));
141 : }
142 0 : delete find;
143 :
144 0 : if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
145 0 : return NS_ERROR_FAILURE; // no error translation
146 : }
147 :
148 : // Sort it
149 0 : mArray.Sort();
150 :
151 0 : mBuffer.AssignLiteral("300: ");
152 0 : mBuffer.Append(aJarDirSpec);
153 0 : mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
154 :
155 : // Open for reading
156 0 : mMode = MODE_DIRECTORY;
157 0 : mZs.total_out = 0;
158 0 : mArrPos = 0;
159 0 : return NS_OK;
160 : }
161 :
162 : NS_IMETHODIMP
163 0 : nsJARInputStream::Available(uint64_t *_retval)
164 : {
165 : // A lot of callers don't check the error code.
166 : // They just use the _retval value.
167 0 : *_retval = 0;
168 :
169 0 : switch (mMode) {
170 : case MODE_NOTINITED:
171 0 : break;
172 :
173 : case MODE_CLOSED:
174 0 : return NS_BASE_STREAM_CLOSED;
175 :
176 : case MODE_DIRECTORY:
177 0 : *_retval = mBuffer.Length();
178 0 : break;
179 :
180 : case MODE_INFLATE:
181 : #ifdef MOZ_JAR_BROTLI
182 : case MODE_BROTLI:
183 : #endif
184 : case MODE_COPY:
185 0 : *_retval = mOutSize - mZs.total_out;
186 0 : break;
187 : }
188 :
189 0 : return NS_OK;
190 : }
191 :
192 : NS_IMETHODIMP
193 0 : nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
194 : {
195 0 : NS_ENSURE_ARG_POINTER(aBuffer);
196 0 : NS_ENSURE_ARG_POINTER(aBytesRead);
197 :
198 0 : *aBytesRead = 0;
199 :
200 0 : nsresult rv = NS_OK;
201 : MOZ_WIN_MEM_TRY_BEGIN
202 0 : switch (mMode) {
203 : case MODE_NOTINITED:
204 0 : return NS_OK;
205 :
206 : case MODE_CLOSED:
207 0 : return NS_BASE_STREAM_CLOSED;
208 :
209 : case MODE_DIRECTORY:
210 0 : return ReadDirectory(aBuffer, aCount, aBytesRead);
211 :
212 : case MODE_INFLATE:
213 : #ifdef MOZ_JAR_BROTLI
214 : case MODE_BROTLI:
215 : #endif
216 0 : if (mZs.total_out < mOutSize) {
217 0 : rv = ContinueInflate(aBuffer, aCount, aBytesRead);
218 : }
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 0 : if (mZs.avail_in == 0) {
223 0 : mFd = nullptr;
224 : }
225 0 : break;
226 :
227 : case MODE_COPY:
228 0 : if (mFd) {
229 0 : uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
230 0 : if (count) {
231 0 : memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
232 0 : mZs.total_out += count;
233 : }
234 0 : *aBytesRead = count;
235 : }
236 : // be aggressive about releasing the file!
237 : // note that sometimes, we will release mFd before we've finished copying.
238 0 : if (mZs.total_out >= mOutSize) {
239 0 : mFd = nullptr;
240 : }
241 0 : break;
242 : }
243 : MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
244 0 : return rv;
245 : }
246 :
247 : NS_IMETHODIMP
248 0 : nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
249 : {
250 : // don't have a buffer to read from, so this better not be called!
251 0 : return NS_ERROR_NOT_IMPLEMENTED;
252 : }
253 :
254 : NS_IMETHODIMP
255 0 : nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
256 : {
257 0 : *aNonBlocking = false;
258 0 : return NS_OK;
259 : }
260 :
261 : NS_IMETHODIMP
262 0 : nsJARInputStream::Close()
263 : {
264 0 : if (mMode == MODE_INFLATE) {
265 0 : inflateEnd(&mZs);
266 : }
267 : #ifdef MOZ_JAR_BROTLI
268 0 : if (mMode == MODE_BROTLI) {
269 0 : BrotliDestroyState(mBrotliState);
270 : }
271 : #endif
272 0 : mMode = MODE_CLOSED;
273 0 : mFd = nullptr;
274 0 : return NS_OK;
275 : }
276 :
277 : nsresult
278 0 : nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount,
279 : uint32_t* aBytesRead)
280 : {
281 0 : bool finished = false;
282 :
283 : // 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 :
287 : // Keep old total_out count
288 0 : const uint32_t oldTotalOut = mZs.total_out;
289 :
290 : // 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 :
294 : #ifndef MOZ_JAR_BROTLI
295 : MOZ_ASSERT(mMode == MODE_INFLATE);
296 : #endif
297 0 : if (mMode == MODE_INFLATE) {
298 : // 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 : }
304 0 : finished = (zerr == Z_STREAM_END);
305 : #ifdef MOZ_JAR_BROTLI
306 : } else {
307 0 : 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 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 : BrotliResult result = BrotliDecompressStream(
315 0 : &avail_in, const_cast<const unsigned char**>(&mZs.next_in),
316 0 : &avail_out, &mZs.next_out, &total_out, mBrotliState);
317 : /* We don't need to update avail_out, it's not used outside this
318 : * function. */
319 0 : mZs.total_out = total_out;
320 0 : mZs.avail_in = avail_in;
321 0 : if (result == BROTLI_RESULT_ERROR) {
322 0 : nsZipArchive::sFileCorruptedReason = "nsJARInputStream: brotli decompression error";
323 0 : return NS_ERROR_FILE_CORRUPTED;
324 : }
325 0 : finished = (result == BROTLI_RESULT_SUCCESS);
326 : #endif
327 : }
328 :
329 0 : *aBytesRead = (mZs.total_out - oldTotalOut);
330 :
331 : // Calculate the CRC on the output
332 0 : mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
333 :
334 : // be aggressive about ending the inflation
335 : // for some reason we don't always get Z_STREAM_END
336 0 : if (finished || mZs.total_out == mOutSize) {
337 0 : if (mMode == MODE_INFLATE) {
338 0 : inflateEnd(&mZs);
339 : }
340 :
341 : // stop returning valid data as soon as we know we have a bad CRC
342 0 : if (mOutCrc != mInCrc) {
343 0 : nsZipArchive::sFileCorruptedReason = "nsJARInputStream: crc mismatch";
344 0 : return NS_ERROR_FILE_CORRUPTED;
345 : }
346 : }
347 :
348 0 : return NS_OK;
349 : }
350 :
351 : nsresult
352 0 : nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
353 : {
354 : // No need to check the args, ::Read did that, but assert them at least
355 0 : NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
356 0 : NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
357 :
358 : // If the buffer contains data, copy what's there up to the desired amount
359 0 : uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
360 :
361 0 : if (aCount > 0) {
362 : // empty the buffer and start writing directory entry lines to it
363 0 : mBuffer.Truncate();
364 0 : mCurPos = 0;
365 0 : const uint32_t arrayLen = mArray.Length();
366 :
367 0 : for ( ;aCount > mBuffer.Length(); mArrPos++) {
368 : // have we consumed all the directory contents?
369 0 : if (arrayLen <= mArrPos)
370 0 : break;
371 :
372 0 : const char * entryName = mArray[mArrPos].get();
373 0 : uint32_t entryNameLen = mArray[mArrPos].Length();
374 0 : nsZipItem* ze = mJar->mZip->GetItem(entryName);
375 0 : NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
376 :
377 : // Last Modified Time
378 : PRExplodedTime tm;
379 0 : PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
380 : char itemLastModTime[65];
381 : PR_FormatTimeUSEnglish(itemLastModTime,
382 : sizeof(itemLastModTime),
383 : " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
384 0 : &tm);
385 :
386 : // write a 201: line to the buffer for this item
387 : // 200: filename content-length last-modified file-type
388 0 : mBuffer.AppendLiteral("201: ");
389 :
390 : // Names must be escaped and relative, so use the pre-calculated length
391 : // of the directory name as the offset into the string
392 : // NS_EscapeURL adds the escaped URL to the give string buffer
393 0 : NS_EscapeURL(entryName + mNameLen,
394 0 : entryNameLen - mNameLen,
395 : esc_Minimal | esc_AlwaysCopy,
396 0 : mBuffer);
397 :
398 0 : mBuffer.Append(' ');
399 0 : mBuffer.AppendInt(ze->RealSize(), 10);
400 0 : mBuffer.Append(itemLastModTime); // starts/ends with ' '
401 0 : if (ze->IsDirectory())
402 0 : mBuffer.AppendLiteral("DIRECTORY\n");
403 : else
404 0 : mBuffer.AppendLiteral("FILE\n");
405 : }
406 :
407 : // Copy up to the desired amount of data to buffer
408 0 : numRead += CopyDataToBuffer(aBuffer, aCount);
409 : }
410 :
411 0 : *aBytesRead = numRead;
412 0 : return NS_OK;
413 : }
414 :
415 : uint32_t
416 0 : nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount)
417 : {
418 0 : const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos);
419 :
420 0 : if (writeLength > 0) {
421 0 : memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength);
422 0 : mCurPos += writeLength;
423 0 : aCount -= writeLength;
424 0 : aBuffer += writeLength;
425 : }
426 :
427 : // return number of bytes copied to the buffer so the
428 : // Read method can return the number of bytes copied
429 0 : return writeLength;
430 : }
|