1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* nsJARInputStream.cpp
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is Netscape Communicator source code.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Netscape Communications Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 1999
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Mitch Stoltz <mstoltz@netscape.com>
26 : * Taras Glek <tglek@mozilla.com>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either the GNU General Public License Version 2 or later (the "GPL"), or
30 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : #include "nsJARInputStream.h"
43 : #include "zipstruct.h" // defines ZIP compression codes
44 : #include "nsZipArchive.h"
45 :
46 : #include "nsNetUtil.h"
47 : #include "nsEscape.h"
48 : #include "nsIFile.h"
49 : #include "nsDebug.h"
50 : #if defined(XP_WIN)
51 : #include <windows.h>
52 : #endif
53 :
54 : /*---------------------------------------------
55 : * nsISupports implementation
56 : *--------------------------------------------*/
57 :
58 24419 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARInputStream, nsIInputStream)
59 :
60 : /*----------------------------------------------------------
61 : * nsJARInputStream implementation
62 : *--------------------------------------------------------*/
63 :
64 : nsresult
65 1288 : nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
66 : {
67 1288 : nsresult rv = NS_OK;
68 1288 : NS_ABORT_IF_FALSE(aJar, "Argument may not be null");
69 1288 : NS_ABORT_IF_FALSE(item, "Argument may not be null");
70 :
71 : // Mark it as closed, in case something fails in initialisation
72 1288 : mMode = MODE_CLOSED;
73 : //-- prepare for the compression type
74 1288 : switch (item->Compression()) {
75 : case STORED:
76 454 : mMode = MODE_COPY;
77 454 : break;
78 :
79 : case DEFLATED:
80 834 : rv = gZlibInit(&mZs);
81 834 : NS_ENSURE_SUCCESS(rv, rv);
82 :
83 834 : mMode = MODE_INFLATE;
84 834 : mInCrc = item->CRC32();
85 834 : mOutCrc = crc32(0L, Z_NULL, 0);
86 834 : break;
87 :
88 : default:
89 0 : return NS_ERROR_NOT_IMPLEMENTED;
90 : }
91 :
92 : // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
93 1288 : mFd = aJar->mZip->GetFD();
94 1288 : mZs.next_in = (Bytef *)aJar->mZip->GetData(item);
95 1288 : if (!mZs.next_in)
96 1 : return NS_ERROR_FILE_CORRUPTED;
97 1287 : mZs.avail_in = item->Size();
98 1287 : mOutSize = item->RealSize();
99 1287 : mZs.total_out = 0;
100 1287 : return NS_OK;
101 : }
102 :
103 : nsresult
104 1 : nsJARInputStream::InitDirectory(nsJAR* aJar,
105 : const nsACString& aJarDirSpec,
106 : const char* aDir)
107 : {
108 1 : NS_ABORT_IF_FALSE(aJar, "Argument may not be null");
109 1 : NS_ABORT_IF_FALSE(aDir, "Argument may not be null");
110 :
111 : // Mark it as closed, in case something fails in initialisation
112 1 : mMode = MODE_CLOSED;
113 :
114 : // Keep the zipReader for getting the actual zipItems
115 1 : mJar = aJar;
116 : nsZipFind *find;
117 : nsresult rv;
118 : // We can get aDir's contents as strings via FindEntries
119 : // with the following pattern (see nsIZipReader.findEntries docs)
120 : // assuming dirName is properly escaped:
121 : //
122 : // dirName + "?*~" + dirName + "?*/?*"
123 2 : nsDependentCString dirName(aDir);
124 1 : mNameLen = dirName.Length();
125 :
126 : // iterate through dirName and copy it to escDirName, escaping chars
127 : // which are special at the "top" level of the regexp so FindEntries
128 : // works correctly
129 2 : nsCAutoString escDirName;
130 1 : const char* curr = dirName.BeginReading();
131 1 : const char* end = dirName.EndReading();
132 22 : while (curr != end) {
133 20 : switch (*curr) {
134 : case '*':
135 : case '?':
136 : case '$':
137 : case '[':
138 : case ']':
139 : case '^':
140 : case '~':
141 : case '(':
142 : case ')':
143 : case '\\':
144 0 : escDirName.Append('\\');
145 : // fall through
146 : default:
147 20 : escDirName.Append(*curr);
148 : }
149 20 : ++curr;
150 : }
151 2 : nsCAutoString pattern = escDirName + NS_LITERAL_CSTRING("?*~") +
152 4 : escDirName + NS_LITERAL_CSTRING("?*/?*");
153 1 : rv = mJar->mZip->FindInit(pattern.get(), &find);
154 1 : if (NS_FAILED(rv)) return rv;
155 :
156 : const char *name;
157 : PRUint16 nameLen;
158 3 : while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
159 : // Must copy, to make it zero-terminated
160 1 : mArray.AppendElement(nsCString(name,nameLen));
161 : }
162 1 : delete find;
163 :
164 1 : if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
165 0 : return NS_ERROR_FAILURE; // no error translation
166 : }
167 :
168 : // Sort it
169 1 : mArray.Sort();
170 :
171 1 : mBuffer.AssignLiteral("300: ");
172 1 : mBuffer.Append(aJarDirSpec);
173 1 : mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
174 :
175 : // Open for reading
176 1 : mMode = MODE_DIRECTORY;
177 1 : mZs.total_out = 0;
178 1 : mArrPos = 0;
179 1 : return NS_OK;
180 : }
181 :
182 : NS_IMETHODIMP
183 2439 : nsJARInputStream::Available(PRUint32 *_retval)
184 : {
185 : // A lot of callers don't check the error code.
186 : // They just use the _retval value.
187 2439 : *_retval = 0;
188 :
189 2439 : switch (mMode) {
190 : case MODE_NOTINITED:
191 0 : break;
192 :
193 : case MODE_CLOSED:
194 0 : return NS_BASE_STREAM_CLOSED;
195 :
196 : case MODE_DIRECTORY:
197 1 : *_retval = mBuffer.Length();
198 1 : break;
199 :
200 : case MODE_INFLATE:
201 : case MODE_COPY:
202 2438 : *_retval = mOutSize - mZs.total_out;
203 2438 : break;
204 : }
205 :
206 2439 : return NS_OK;
207 : }
208 :
209 : NS_IMETHODIMP
210 1246 : nsJARInputStream::Read(char* aBuffer, PRUint32 aCount, PRUint32 *aBytesRead)
211 : {
212 1246 : NS_ENSURE_ARG_POINTER(aBuffer);
213 1246 : NS_ENSURE_ARG_POINTER(aBytesRead);
214 :
215 1246 : *aBytesRead = 0;
216 :
217 1246 : nsresult rv = NS_OK;
218 : MOZ_WIN_MEM_TRY_BEGIN
219 1246 : switch (mMode) {
220 : case MODE_NOTINITED:
221 0 : return NS_OK;
222 :
223 : case MODE_CLOSED:
224 0 : return NS_BASE_STREAM_CLOSED;
225 :
226 : case MODE_DIRECTORY:
227 1 : return ReadDirectory(aBuffer, aCount, aBytesRead);
228 :
229 : case MODE_INFLATE:
230 834 : if (mFd) {
231 834 : rv = ContinueInflate(aBuffer, aCount, aBytesRead);
232 : }
233 : // be aggressive about releasing the file!
234 : // note that sometimes, we will release mFd before we've finished
235 : // deflating - this is because zlib buffers the input
236 834 : if (mZs.avail_in == 0) {
237 833 : mFd = nsnull;
238 : }
239 834 : break;
240 :
241 : case MODE_COPY:
242 411 : if (mFd) {
243 411 : PRUint32 count = NS_MIN(aCount, mOutSize - PRUint32(mZs.total_out));
244 411 : if (count) {
245 411 : memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
246 411 : mZs.total_out += count;
247 : }
248 411 : *aBytesRead = count;
249 : }
250 : // be aggressive about releasing the file!
251 : // note that sometimes, we will release mFd before we've finished copying.
252 411 : if (mZs.total_out >= mOutSize) {
253 411 : mFd = nsnull;
254 : }
255 411 : break;
256 : }
257 : MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
258 1245 : return rv;
259 : }
260 :
261 : NS_IMETHODIMP
262 0 : nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 count, PRUint32 *_retval)
263 : {
264 : // don't have a buffer to read from, so this better not be called!
265 0 : NS_NOTREACHED("Consumers should be using Read()!");
266 0 : return NS_ERROR_NOT_IMPLEMENTED;
267 : }
268 :
269 : NS_IMETHODIMP
270 0 : nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
271 : {
272 0 : *aNonBlocking = false;
273 0 : return NS_OK;
274 : }
275 :
276 : NS_IMETHODIMP
277 3487 : nsJARInputStream::Close()
278 : {
279 3487 : mMode = MODE_CLOSED;
280 3487 : mFd = nsnull;
281 3487 : return NS_OK;
282 : }
283 :
284 : nsresult
285 834 : nsJARInputStream::ContinueInflate(char* aBuffer, PRUint32 aCount,
286 : PRUint32* aBytesRead)
287 : {
288 : // No need to check the args, ::Read did that, but assert them at least
289 834 : NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
290 834 : NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
291 :
292 : // Keep old total_out count
293 834 : const PRUint32 oldTotalOut = mZs.total_out;
294 :
295 : // make sure we aren't reading too much
296 834 : mZs.avail_out = NS_MIN(aCount, (mOutSize-oldTotalOut));
297 834 : mZs.next_out = (unsigned char*)aBuffer;
298 :
299 : // now inflate
300 834 : int zerr = inflate(&mZs, Z_SYNC_FLUSH);
301 834 : if ((zerr != Z_OK) && (zerr != Z_STREAM_END))
302 0 : return NS_ERROR_FILE_CORRUPTED;
303 :
304 834 : *aBytesRead = (mZs.total_out - oldTotalOut);
305 :
306 : // Calculate the CRC on the output
307 834 : mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
308 :
309 : // be aggressive about ending the inflation
310 : // for some reason we don't always get Z_STREAM_END
311 834 : if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) {
312 833 : inflateEnd(&mZs);
313 :
314 : // stop returning valid data as soon as we know we have a bad CRC
315 833 : if (mOutCrc != mInCrc) {
316 : // asserting because while this rarely happens, you definitely
317 : // want to catch it in debug builds!
318 0 : NS_NOTREACHED(0);
319 0 : return NS_ERROR_FILE_CORRUPTED;
320 : }
321 : }
322 :
323 834 : return NS_OK;
324 : }
325 :
326 : nsresult
327 1 : nsJARInputStream::ReadDirectory(char* aBuffer, PRUint32 aCount, PRUint32 *aBytesRead)
328 : {
329 : // No need to check the args, ::Read did that, but assert them at least
330 1 : NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
331 1 : NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
332 :
333 : // If the buffer contains data, copy what's there up to the desired amount
334 1 : PRUint32 numRead = CopyDataToBuffer(aBuffer, aCount);
335 :
336 1 : if (aCount > 0) {
337 : // empty the buffer and start writing directory entry lines to it
338 0 : mBuffer.Truncate();
339 0 : mCurPos = 0;
340 0 : const PRUint32 arrayLen = mArray.Length();
341 :
342 0 : for ( ;aCount > mBuffer.Length(); mArrPos++) {
343 : // have we consumed all the directory contents?
344 0 : if (arrayLen <= mArrPos)
345 0 : break;
346 :
347 0 : const char * entryName = mArray[mArrPos].get();
348 0 : PRUint32 entryNameLen = mArray[mArrPos].Length();
349 0 : nsZipItem* ze = mJar->mZip->GetItem(entryName);
350 0 : NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
351 :
352 : // Last Modified Time
353 : PRExplodedTime tm;
354 0 : PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
355 : char itemLastModTime[65];
356 : PR_FormatTimeUSEnglish(itemLastModTime,
357 : sizeof(itemLastModTime),
358 : " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
359 0 : &tm);
360 :
361 : // write a 201: line to the buffer for this item
362 : // 200: filename content-length last-modified file-type
363 0 : mBuffer.AppendLiteral("201: ");
364 :
365 : // Names must be escaped and relative, so use the pre-calculated length
366 : // of the directory name as the offset into the string
367 : // NS_EscapeURL adds the escaped URL to the give string buffer
368 : NS_EscapeURL(entryName + mNameLen,
369 : entryNameLen - mNameLen,
370 : esc_Minimal | esc_AlwaysCopy,
371 0 : mBuffer);
372 :
373 0 : mBuffer.Append(' ');
374 0 : mBuffer.AppendInt(ze->RealSize(), 10);
375 0 : mBuffer.Append(itemLastModTime); // starts/ends with ' '
376 0 : if (ze->IsDirectory())
377 0 : mBuffer.AppendLiteral("DIRECTORY\n");
378 : else
379 0 : mBuffer.AppendLiteral("FILE\n");
380 : }
381 :
382 : // Copy up to the desired amount of data to buffer
383 0 : numRead += CopyDataToBuffer(aBuffer, aCount);
384 : }
385 :
386 1 : *aBytesRead = numRead;
387 1 : return NS_OK;
388 : }
389 :
390 : PRUint32
391 1 : nsJARInputStream::CopyDataToBuffer(char* &aBuffer, PRUint32 &aCount)
392 : {
393 1 : const PRUint32 writeLength = NS_MIN(aCount, mBuffer.Length() - mCurPos);
394 :
395 1 : if (writeLength > 0) {
396 1 : memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength);
397 1 : mCurPos += writeLength;
398 1 : aCount -= writeLength;
399 1 : aBuffer += writeLength;
400 : }
401 :
402 : // return number of bytes copied to the buffer so the
403 : // Read method can return the number of bytes copied
404 1 : return writeLength;
405 : }
|