|
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 #include "nsZipArchive.h" |
|
11 |
|
12 #include "nsNetUtil.h" |
|
13 #include "nsEscape.h" |
|
14 #include "nsIFile.h" |
|
15 #include "nsDebug.h" |
|
16 #include <algorithm> |
|
17 #if defined(XP_WIN) |
|
18 #include <windows.h> |
|
19 #endif |
|
20 |
|
21 /*--------------------------------------------- |
|
22 * nsISupports implementation |
|
23 *--------------------------------------------*/ |
|
24 |
|
25 NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream) |
|
26 |
|
27 /*---------------------------------------------------------- |
|
28 * nsJARInputStream implementation |
|
29 *--------------------------------------------------------*/ |
|
30 |
|
31 nsresult |
|
32 nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item) |
|
33 { |
|
34 nsresult rv = NS_OK; |
|
35 NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); |
|
36 NS_ABORT_IF_FALSE(item, "Argument may not be null"); |
|
37 |
|
38 // Mark it as closed, in case something fails in initialisation |
|
39 mMode = MODE_CLOSED; |
|
40 //-- prepare for the compression type |
|
41 switch (item->Compression()) { |
|
42 case STORED: |
|
43 mMode = MODE_COPY; |
|
44 break; |
|
45 |
|
46 case DEFLATED: |
|
47 rv = gZlibInit(&mZs); |
|
48 NS_ENSURE_SUCCESS(rv, rv); |
|
49 |
|
50 mMode = MODE_INFLATE; |
|
51 mInCrc = item->CRC32(); |
|
52 mOutCrc = crc32(0L, Z_NULL, 0); |
|
53 break; |
|
54 |
|
55 default: |
|
56 return NS_ERROR_NOT_IMPLEMENTED; |
|
57 } |
|
58 |
|
59 // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data |
|
60 mFd = aJar->mZip->GetFD(); |
|
61 mZs.next_in = (Bytef *)aJar->mZip->GetData(item); |
|
62 if (!mZs.next_in) |
|
63 return NS_ERROR_FILE_CORRUPTED; |
|
64 mZs.avail_in = item->Size(); |
|
65 mOutSize = item->RealSize(); |
|
66 mZs.total_out = 0; |
|
67 return NS_OK; |
|
68 } |
|
69 |
|
70 nsresult |
|
71 nsJARInputStream::InitDirectory(nsJAR* aJar, |
|
72 const nsACString& aJarDirSpec, |
|
73 const char* aDir) |
|
74 { |
|
75 NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); |
|
76 NS_ABORT_IF_FALSE(aDir, "Argument may not be null"); |
|
77 |
|
78 // Mark it as closed, in case something fails in initialisation |
|
79 mMode = MODE_CLOSED; |
|
80 |
|
81 // Keep the zipReader for getting the actual zipItems |
|
82 mJar = aJar; |
|
83 nsZipFind *find; |
|
84 nsresult rv; |
|
85 // We can get aDir's contents as strings via FindEntries |
|
86 // with the following pattern (see nsIZipReader.findEntries docs) |
|
87 // assuming dirName is properly escaped: |
|
88 // |
|
89 // dirName + "?*~" + dirName + "?*/?*" |
|
90 nsDependentCString dirName(aDir); |
|
91 mNameLen = dirName.Length(); |
|
92 |
|
93 // iterate through dirName and copy it to escDirName, escaping chars |
|
94 // which are special at the "top" level of the regexp so FindEntries |
|
95 // works correctly |
|
96 nsAutoCString escDirName; |
|
97 const char* curr = dirName.BeginReading(); |
|
98 const char* end = dirName.EndReading(); |
|
99 while (curr != end) { |
|
100 switch (*curr) { |
|
101 case '*': |
|
102 case '?': |
|
103 case '$': |
|
104 case '[': |
|
105 case ']': |
|
106 case '^': |
|
107 case '~': |
|
108 case '(': |
|
109 case ')': |
|
110 case '\\': |
|
111 escDirName.Append('\\'); |
|
112 // fall through |
|
113 default: |
|
114 escDirName.Append(*curr); |
|
115 } |
|
116 ++curr; |
|
117 } |
|
118 nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") + |
|
119 escDirName + NS_LITERAL_CSTRING("?*/?*"); |
|
120 rv = mJar->mZip->FindInit(pattern.get(), &find); |
|
121 if (NS_FAILED(rv)) return rv; |
|
122 |
|
123 const char *name; |
|
124 uint16_t nameLen; |
|
125 while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) { |
|
126 // Must copy, to make it zero-terminated |
|
127 mArray.AppendElement(nsCString(name,nameLen)); |
|
128 } |
|
129 delete find; |
|
130 |
|
131 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) { |
|
132 return NS_ERROR_FAILURE; // no error translation |
|
133 } |
|
134 |
|
135 // Sort it |
|
136 mArray.Sort(); |
|
137 |
|
138 mBuffer.AssignLiteral("300: "); |
|
139 mBuffer.Append(aJarDirSpec); |
|
140 mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n"); |
|
141 |
|
142 // Open for reading |
|
143 mMode = MODE_DIRECTORY; |
|
144 mZs.total_out = 0; |
|
145 mArrPos = 0; |
|
146 return NS_OK; |
|
147 } |
|
148 |
|
149 NS_IMETHODIMP |
|
150 nsJARInputStream::Available(uint64_t *_retval) |
|
151 { |
|
152 // A lot of callers don't check the error code. |
|
153 // They just use the _retval value. |
|
154 *_retval = 0; |
|
155 |
|
156 switch (mMode) { |
|
157 case MODE_NOTINITED: |
|
158 break; |
|
159 |
|
160 case MODE_CLOSED: |
|
161 return NS_BASE_STREAM_CLOSED; |
|
162 |
|
163 case MODE_DIRECTORY: |
|
164 *_retval = mBuffer.Length(); |
|
165 break; |
|
166 |
|
167 case MODE_INFLATE: |
|
168 case MODE_COPY: |
|
169 *_retval = mOutSize - mZs.total_out; |
|
170 break; |
|
171 } |
|
172 |
|
173 return NS_OK; |
|
174 } |
|
175 |
|
176 NS_IMETHODIMP |
|
177 nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) |
|
178 { |
|
179 NS_ENSURE_ARG_POINTER(aBuffer); |
|
180 NS_ENSURE_ARG_POINTER(aBytesRead); |
|
181 |
|
182 *aBytesRead = 0; |
|
183 |
|
184 nsresult rv = NS_OK; |
|
185 MOZ_WIN_MEM_TRY_BEGIN |
|
186 switch (mMode) { |
|
187 case MODE_NOTINITED: |
|
188 return NS_OK; |
|
189 |
|
190 case MODE_CLOSED: |
|
191 return NS_BASE_STREAM_CLOSED; |
|
192 |
|
193 case MODE_DIRECTORY: |
|
194 return ReadDirectory(aBuffer, aCount, aBytesRead); |
|
195 |
|
196 case MODE_INFLATE: |
|
197 if (mFd) { |
|
198 rv = ContinueInflate(aBuffer, aCount, aBytesRead); |
|
199 } |
|
200 // be aggressive about releasing the file! |
|
201 // note that sometimes, we will release mFd before we've finished |
|
202 // deflating - this is because zlib buffers the input |
|
203 if (mZs.avail_in == 0) { |
|
204 mFd = nullptr; |
|
205 } |
|
206 break; |
|
207 |
|
208 case MODE_COPY: |
|
209 if (mFd) { |
|
210 uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out)); |
|
211 if (count) { |
|
212 memcpy(aBuffer, mZs.next_in + mZs.total_out, count); |
|
213 mZs.total_out += count; |
|
214 } |
|
215 *aBytesRead = count; |
|
216 } |
|
217 // be aggressive about releasing the file! |
|
218 // note that sometimes, we will release mFd before we've finished copying. |
|
219 if (mZs.total_out >= mOutSize) { |
|
220 mFd = nullptr; |
|
221 } |
|
222 break; |
|
223 } |
|
224 MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE) |
|
225 return rv; |
|
226 } |
|
227 |
|
228 NS_IMETHODIMP |
|
229 nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) |
|
230 { |
|
231 // don't have a buffer to read from, so this better not be called! |
|
232 return NS_ERROR_NOT_IMPLEMENTED; |
|
233 } |
|
234 |
|
235 NS_IMETHODIMP |
|
236 nsJARInputStream::IsNonBlocking(bool *aNonBlocking) |
|
237 { |
|
238 *aNonBlocking = false; |
|
239 return NS_OK; |
|
240 } |
|
241 |
|
242 NS_IMETHODIMP |
|
243 nsJARInputStream::Close() |
|
244 { |
|
245 if (mMode == MODE_INFLATE) { |
|
246 inflateEnd(&mZs); |
|
247 } |
|
248 mMode = MODE_CLOSED; |
|
249 mFd = nullptr; |
|
250 return NS_OK; |
|
251 } |
|
252 |
|
253 nsresult |
|
254 nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount, |
|
255 uint32_t* aBytesRead) |
|
256 { |
|
257 // No need to check the args, ::Read did that, but assert them at least |
|
258 NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); |
|
259 NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); |
|
260 |
|
261 // Keep old total_out count |
|
262 const uint32_t oldTotalOut = mZs.total_out; |
|
263 |
|
264 // make sure we aren't reading too much |
|
265 mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut)); |
|
266 mZs.next_out = (unsigned char*)aBuffer; |
|
267 |
|
268 // now inflate |
|
269 int zerr = inflate(&mZs, Z_SYNC_FLUSH); |
|
270 if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) |
|
271 return NS_ERROR_FILE_CORRUPTED; |
|
272 |
|
273 *aBytesRead = (mZs.total_out - oldTotalOut); |
|
274 |
|
275 // Calculate the CRC on the output |
|
276 mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead); |
|
277 |
|
278 // be aggressive about ending the inflation |
|
279 // for some reason we don't always get Z_STREAM_END |
|
280 if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) { |
|
281 inflateEnd(&mZs); |
|
282 |
|
283 // stop returning valid data as soon as we know we have a bad CRC |
|
284 if (mOutCrc != mInCrc) |
|
285 return NS_ERROR_FILE_CORRUPTED; |
|
286 } |
|
287 |
|
288 return NS_OK; |
|
289 } |
|
290 |
|
291 nsresult |
|
292 nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) |
|
293 { |
|
294 // No need to check the args, ::Read did that, but assert them at least |
|
295 NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); |
|
296 NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); |
|
297 |
|
298 // If the buffer contains data, copy what's there up to the desired amount |
|
299 uint32_t numRead = CopyDataToBuffer(aBuffer, aCount); |
|
300 |
|
301 if (aCount > 0) { |
|
302 // empty the buffer and start writing directory entry lines to it |
|
303 mBuffer.Truncate(); |
|
304 mCurPos = 0; |
|
305 const uint32_t arrayLen = mArray.Length(); |
|
306 |
|
307 for ( ;aCount > mBuffer.Length(); mArrPos++) { |
|
308 // have we consumed all the directory contents? |
|
309 if (arrayLen <= mArrPos) |
|
310 break; |
|
311 |
|
312 const char * entryName = mArray[mArrPos].get(); |
|
313 uint32_t entryNameLen = mArray[mArrPos].Length(); |
|
314 nsZipItem* ze = mJar->mZip->GetItem(entryName); |
|
315 NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); |
|
316 |
|
317 // Last Modified Time |
|
318 PRExplodedTime tm; |
|
319 PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm); |
|
320 char itemLastModTime[65]; |
|
321 PR_FormatTimeUSEnglish(itemLastModTime, |
|
322 sizeof(itemLastModTime), |
|
323 " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", |
|
324 &tm); |
|
325 |
|
326 // write a 201: line to the buffer for this item |
|
327 // 200: filename content-length last-modified file-type |
|
328 mBuffer.AppendLiteral("201: "); |
|
329 |
|
330 // Names must be escaped and relative, so use the pre-calculated length |
|
331 // of the directory name as the offset into the string |
|
332 // NS_EscapeURL adds the escaped URL to the give string buffer |
|
333 NS_EscapeURL(entryName + mNameLen, |
|
334 entryNameLen - mNameLen, |
|
335 esc_Minimal | esc_AlwaysCopy, |
|
336 mBuffer); |
|
337 |
|
338 mBuffer.Append(' '); |
|
339 mBuffer.AppendInt(ze->RealSize(), 10); |
|
340 mBuffer.Append(itemLastModTime); // starts/ends with ' ' |
|
341 if (ze->IsDirectory()) |
|
342 mBuffer.AppendLiteral("DIRECTORY\n"); |
|
343 else |
|
344 mBuffer.AppendLiteral("FILE\n"); |
|
345 } |
|
346 |
|
347 // Copy up to the desired amount of data to buffer |
|
348 numRead += CopyDataToBuffer(aBuffer, aCount); |
|
349 } |
|
350 |
|
351 *aBytesRead = numRead; |
|
352 return NS_OK; |
|
353 } |
|
354 |
|
355 uint32_t |
|
356 nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount) |
|
357 { |
|
358 const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos); |
|
359 |
|
360 if (writeLength > 0) { |
|
361 memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength); |
|
362 mCurPos += writeLength; |
|
363 aCount -= writeLength; |
|
364 aBuffer += writeLength; |
|
365 } |
|
366 |
|
367 // return number of bytes copied to the buffer so the |
|
368 // Read method can return the number of bytes copied |
|
369 return writeLength; |
|
370 } |