|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 #include <string.h> |
|
6 #include "nsJARInputStream.h" |
|
7 #include "nsJAR.h" |
|
8 #include "nsIFile.h" |
|
9 #include "nsIConsoleService.h" |
|
10 #include "nsICryptoHash.h" |
|
11 #include "prprf.h" |
|
12 #include "mozilla/Omnijar.h" |
|
13 |
|
14 #ifdef XP_UNIX |
|
15 #include <sys/stat.h> |
|
16 #elif defined (XP_WIN) |
|
17 #include <io.h> |
|
18 #endif |
|
19 |
|
20 using namespace mozilla; |
|
21 |
|
22 //---------------------------------------------- |
|
23 // nsJARManifestItem declaration |
|
24 //---------------------------------------------- |
|
25 /* |
|
26 * nsJARManifestItem contains meta-information pertaining |
|
27 * to an individual JAR entry, taken from the |
|
28 * META-INF/MANIFEST.MF and META-INF/ *.SF files. |
|
29 * This is security-critical information, defined here so it is not |
|
30 * accessible from anywhere else. |
|
31 */ |
|
32 typedef enum |
|
33 { |
|
34 JAR_INVALID = 1, |
|
35 JAR_INTERNAL = 2, |
|
36 JAR_EXTERNAL = 3 |
|
37 } JARManifestItemType; |
|
38 |
|
39 class nsJARManifestItem |
|
40 { |
|
41 public: |
|
42 JARManifestItemType mType; |
|
43 |
|
44 // True if the second step of verification (VerifyEntry) |
|
45 // has taken place: |
|
46 bool entryVerified; |
|
47 |
|
48 // Not signed, valid, or failure code |
|
49 int16_t status; |
|
50 |
|
51 // Internal storage of digests |
|
52 nsCString calculatedSectionDigest; |
|
53 nsCString storedEntryDigest; |
|
54 |
|
55 nsJARManifestItem(); |
|
56 virtual ~nsJARManifestItem(); |
|
57 }; |
|
58 |
|
59 //------------------------------------------------- |
|
60 // nsJARManifestItem constructors and destructor |
|
61 //------------------------------------------------- |
|
62 nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL), |
|
63 entryVerified(false), |
|
64 status(JAR_NOT_SIGNED) |
|
65 { |
|
66 } |
|
67 |
|
68 nsJARManifestItem::~nsJARManifestItem() |
|
69 { |
|
70 } |
|
71 |
|
72 //---------------------------------------------- |
|
73 // nsJAR constructor/destructor |
|
74 //---------------------------------------------- |
|
75 |
|
76 // The following initialization makes a guess of 10 entries per jarfile. |
|
77 nsJAR::nsJAR(): mZip(new nsZipArchive()), |
|
78 mManifestData(10), |
|
79 mParsedManifest(false), |
|
80 mGlobalStatus(JAR_MANIFEST_NOT_PARSED), |
|
81 mReleaseTime(PR_INTERVAL_NO_TIMEOUT), |
|
82 mCache(nullptr), |
|
83 mLock("nsJAR::mLock"), |
|
84 mTotalItemsInManifest(0), |
|
85 mOpened(false) |
|
86 { |
|
87 } |
|
88 |
|
89 nsJAR::~nsJAR() |
|
90 { |
|
91 Close(); |
|
92 } |
|
93 |
|
94 NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader) |
|
95 NS_IMPL_ADDREF(nsJAR) |
|
96 |
|
97 // Custom Release method works with nsZipReaderCache... |
|
98 MozExternalRefCountType nsJAR::Release(void) |
|
99 { |
|
100 nsrefcnt count; |
|
101 NS_PRECONDITION(0 != mRefCnt, "dup release"); |
|
102 count = --mRefCnt; |
|
103 NS_LOG_RELEASE(this, count, "nsJAR"); |
|
104 if (0 == count) { |
|
105 mRefCnt = 1; /* stabilize */ |
|
106 /* enable this to find non-threadsafe destructors: */ |
|
107 /* NS_ASSERT_OWNINGTHREAD(nsJAR); */ |
|
108 delete this; |
|
109 return 0; |
|
110 } |
|
111 else if (1 == count && mCache) { |
|
112 #ifdef DEBUG |
|
113 nsresult rv = |
|
114 #endif |
|
115 mCache->ReleaseZip(this); |
|
116 NS_ASSERTION(NS_SUCCEEDED(rv), "failed to release zip file"); |
|
117 } |
|
118 return count; |
|
119 } |
|
120 |
|
121 //---------------------------------------------- |
|
122 // nsIZipReader implementation |
|
123 //---------------------------------------------- |
|
124 |
|
125 NS_IMETHODIMP |
|
126 nsJAR::Open(nsIFile* zipFile) |
|
127 { |
|
128 NS_ENSURE_ARG_POINTER(zipFile); |
|
129 if (mOpened) return NS_ERROR_FAILURE; // Already open! |
|
130 |
|
131 mZipFile = zipFile; |
|
132 mOuterZipEntry.Truncate(); |
|
133 mOpened = true; |
|
134 |
|
135 // The omnijar is special, it is opened early on and closed late |
|
136 // this avoids reopening it |
|
137 nsRefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile); |
|
138 if (zip) { |
|
139 mZip = zip; |
|
140 return NS_OK; |
|
141 } |
|
142 return mZip->OpenArchive(zipFile); |
|
143 } |
|
144 |
|
145 NS_IMETHODIMP |
|
146 nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry) |
|
147 { |
|
148 NS_ENSURE_ARG_POINTER(aZipReader); |
|
149 if (mOpened) return NS_ERROR_FAILURE; // Already open! |
|
150 |
|
151 bool exist; |
|
152 nsresult rv = aZipReader->HasEntry(aZipEntry, &exist); |
|
153 NS_ENSURE_SUCCESS(rv, rv); |
|
154 NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND); |
|
155 |
|
156 rv = aZipReader->GetFile(getter_AddRefs(mZipFile)); |
|
157 NS_ENSURE_SUCCESS(rv, rv); |
|
158 |
|
159 mOpened = true; |
|
160 |
|
161 mOuterZipEntry.Assign(aZipEntry); |
|
162 |
|
163 nsRefPtr<nsZipHandle> handle; |
|
164 rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(), |
|
165 getter_AddRefs(handle)); |
|
166 if (NS_FAILED(rv)) |
|
167 return rv; |
|
168 |
|
169 return mZip->OpenArchive(handle); |
|
170 } |
|
171 |
|
172 NS_IMETHODIMP |
|
173 nsJAR::GetFile(nsIFile* *result) |
|
174 { |
|
175 *result = mZipFile; |
|
176 NS_IF_ADDREF(*result); |
|
177 return NS_OK; |
|
178 } |
|
179 |
|
180 NS_IMETHODIMP |
|
181 nsJAR::Close() |
|
182 { |
|
183 mOpened = false; |
|
184 mParsedManifest = false; |
|
185 mManifestData.Clear(); |
|
186 mGlobalStatus = JAR_MANIFEST_NOT_PARSED; |
|
187 mTotalItemsInManifest = 0; |
|
188 |
|
189 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); |
|
190 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); |
|
191 |
|
192 if (mZip == greOmni || mZip == appOmni) { |
|
193 mZip = new nsZipArchive(); |
|
194 return NS_OK; |
|
195 } |
|
196 return mZip->CloseArchive(); |
|
197 } |
|
198 |
|
199 NS_IMETHODIMP |
|
200 nsJAR::Test(const nsACString &aEntryName) |
|
201 { |
|
202 return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get()); |
|
203 } |
|
204 |
|
205 NS_IMETHODIMP |
|
206 nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile) |
|
207 { |
|
208 // nsZipArchive and zlib are not thread safe |
|
209 // we need to use a lock to prevent bug #51267 |
|
210 MutexAutoLock lock(mLock); |
|
211 |
|
212 nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get()); |
|
213 NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); |
|
214 |
|
215 // Remove existing file or directory so we set permissions correctly. |
|
216 // If it's a directory that already exists and contains files, throw |
|
217 // an exception and return. |
|
218 |
|
219 nsresult rv = outFile->Remove(false); |
|
220 if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || |
|
221 rv == NS_ERROR_FAILURE) |
|
222 return rv; |
|
223 |
|
224 if (item->IsDirectory()) |
|
225 { |
|
226 rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode()); |
|
227 //XXX Do this in nsZipArchive? It would be nice to keep extraction |
|
228 //XXX code completely there, but that would require a way to get a |
|
229 //XXX PRDir from outFile. |
|
230 } |
|
231 else |
|
232 { |
|
233 PRFileDesc* fd; |
|
234 rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd); |
|
235 if (NS_FAILED(rv)) return rv; |
|
236 |
|
237 // ExtractFile also closes the fd handle and resolves the symlink if needed |
|
238 nsAutoCString path; |
|
239 rv = outFile->GetNativePath(path); |
|
240 if (NS_FAILED(rv)) return rv; |
|
241 |
|
242 rv = mZip->ExtractFile(item, path.get(), fd); |
|
243 } |
|
244 if (NS_FAILED(rv)) return rv; |
|
245 |
|
246 // nsIFile needs milliseconds, while prtime is in microseconds. |
|
247 // non-fatal if this fails, ignore errors |
|
248 outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC); |
|
249 |
|
250 return NS_OK; |
|
251 } |
|
252 |
|
253 NS_IMETHODIMP |
|
254 nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result) |
|
255 { |
|
256 nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get()); |
|
257 NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); |
|
258 |
|
259 nsJARItem* jarItem = new nsJARItem(zipItem); |
|
260 |
|
261 NS_ADDREF(*result = jarItem); |
|
262 return NS_OK; |
|
263 } |
|
264 |
|
265 NS_IMETHODIMP |
|
266 nsJAR::HasEntry(const nsACString &aEntryName, bool *result) |
|
267 { |
|
268 *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr; |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 NS_IMETHODIMP |
|
273 nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result) |
|
274 { |
|
275 NS_ENSURE_ARG_POINTER(result); |
|
276 |
|
277 nsZipFind *find; |
|
278 nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find); |
|
279 NS_ENSURE_SUCCESS(rv, rv); |
|
280 |
|
281 nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find); |
|
282 |
|
283 NS_ADDREF(*result = zipEnum); |
|
284 return NS_OK; |
|
285 } |
|
286 |
|
287 NS_IMETHODIMP |
|
288 nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result) |
|
289 { |
|
290 return GetInputStreamWithSpec(EmptyCString(), aFilename, result); |
|
291 } |
|
292 |
|
293 NS_IMETHODIMP |
|
294 nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec, |
|
295 const nsACString &aEntryName, nsIInputStream** result) |
|
296 { |
|
297 NS_ENSURE_ARG_POINTER(result); |
|
298 |
|
299 // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case! |
|
300 nsZipItem *item = nullptr; |
|
301 const char *entry = PromiseFlatCString(aEntryName).get(); |
|
302 if (*entry) { |
|
303 // First check if item exists in jar |
|
304 item = mZip->GetItem(entry); |
|
305 if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; |
|
306 } |
|
307 nsJARInputStream* jis = new nsJARInputStream(); |
|
308 // addref now so we can call InitFile/InitDirectory() |
|
309 NS_ADDREF(*result = jis); |
|
310 |
|
311 nsresult rv = NS_OK; |
|
312 if (!item || item->IsDirectory()) { |
|
313 rv = jis->InitDirectory(this, aJarDirSpec, entry); |
|
314 } else { |
|
315 rv = jis->InitFile(this, item); |
|
316 } |
|
317 if (NS_FAILED(rv)) { |
|
318 NS_RELEASE(*result); |
|
319 } |
|
320 return rv; |
|
321 } |
|
322 |
|
323 NS_IMETHODIMP |
|
324 nsJAR::GetCertificatePrincipal(const nsACString &aFilename, nsICertificatePrincipal** aPrincipal) |
|
325 { |
|
326 //-- Parameter check |
|
327 if (!aPrincipal) |
|
328 return NS_ERROR_NULL_POINTER; |
|
329 *aPrincipal = nullptr; |
|
330 |
|
331 // Don't check signatures in the omnijar - this is only |
|
332 // interesting for extensions/XPIs. |
|
333 nsRefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); |
|
334 nsRefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); |
|
335 |
|
336 if (mZip == greOmni || mZip == appOmni) |
|
337 return NS_OK; |
|
338 |
|
339 //-- Parse the manifest |
|
340 nsresult rv = ParseManifest(); |
|
341 if (NS_FAILED(rv)) return rv; |
|
342 if (mGlobalStatus == JAR_NO_MANIFEST) |
|
343 return NS_OK; |
|
344 |
|
345 int16_t requestedStatus; |
|
346 if (!aFilename.IsEmpty()) |
|
347 { |
|
348 //-- Find the item |
|
349 nsJARManifestItem* manItem = mManifestData.Get(aFilename); |
|
350 if (!manItem) |
|
351 return NS_OK; |
|
352 //-- Verify the item against the manifest |
|
353 if (!manItem->entryVerified) |
|
354 { |
|
355 nsXPIDLCString entryData; |
|
356 uint32_t entryDataLen; |
|
357 rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen); |
|
358 if (NS_FAILED(rv)) return rv; |
|
359 rv = VerifyEntry(manItem, entryData, entryDataLen); |
|
360 if (NS_FAILED(rv)) return rv; |
|
361 } |
|
362 requestedStatus = manItem->status; |
|
363 } |
|
364 else // User wants identity of signer w/o verifying any entries |
|
365 requestedStatus = mGlobalStatus; |
|
366 |
|
367 if (requestedStatus != JAR_VALID_MANIFEST) |
|
368 ReportError(aFilename, requestedStatus); |
|
369 else // Valid signature |
|
370 { |
|
371 *aPrincipal = mPrincipal; |
|
372 NS_IF_ADDREF(*aPrincipal); |
|
373 } |
|
374 return NS_OK; |
|
375 } |
|
376 |
|
377 NS_IMETHODIMP |
|
378 nsJAR::GetManifestEntriesCount(uint32_t* count) |
|
379 { |
|
380 *count = mTotalItemsInManifest; |
|
381 return NS_OK; |
|
382 } |
|
383 |
|
384 nsresult |
|
385 nsJAR::GetJarPath(nsACString& aResult) |
|
386 { |
|
387 NS_ENSURE_ARG_POINTER(mZipFile); |
|
388 |
|
389 return mZipFile->GetNativePath(aResult); |
|
390 } |
|
391 |
|
392 //---------------------------------------------- |
|
393 // nsJAR private implementation |
|
394 //---------------------------------------------- |
|
395 nsresult |
|
396 nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen) |
|
397 { |
|
398 //-- Get a stream for reading the file |
|
399 nsresult rv; |
|
400 nsCOMPtr<nsIInputStream> manifestStream; |
|
401 rv = GetInputStream(aFilename, getter_AddRefs(manifestStream)); |
|
402 if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; |
|
403 |
|
404 //-- Read the manifest file into memory |
|
405 char* buf; |
|
406 uint64_t len64; |
|
407 rv = manifestStream->Available(&len64); |
|
408 if (NS_FAILED(rv)) return rv; |
|
409 NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_CORRUPTED); // bug 164695 |
|
410 uint32_t len = (uint32_t)len64; |
|
411 buf = (char*)malloc(len+1); |
|
412 if (!buf) return NS_ERROR_OUT_OF_MEMORY; |
|
413 uint32_t bytesRead; |
|
414 rv = manifestStream->Read(buf, len, &bytesRead); |
|
415 if (bytesRead != len) |
|
416 rv = NS_ERROR_FILE_CORRUPTED; |
|
417 if (NS_FAILED(rv)) { |
|
418 free(buf); |
|
419 return rv; |
|
420 } |
|
421 buf[len] = '\0'; //Null-terminate the buffer |
|
422 *aBuf = buf; |
|
423 if (aBufLen) |
|
424 *aBufLen = len; |
|
425 return NS_OK; |
|
426 } |
|
427 |
|
428 |
|
429 int32_t |
|
430 nsJAR::ReadLine(const char** src) |
|
431 { |
|
432 if (!*src) { |
|
433 return 0; |
|
434 } |
|
435 |
|
436 //--Moves pointer to beginning of next line and returns line length |
|
437 // not including CR/LF. |
|
438 int32_t length; |
|
439 char* eol = PL_strpbrk(*src, "\r\n"); |
|
440 |
|
441 if (eol == nullptr) // Probably reached end of file before newline |
|
442 { |
|
443 length = strlen(*src); |
|
444 if (length == 0) // immediate end-of-file |
|
445 *src = nullptr; |
|
446 else // some data left on this line |
|
447 *src += length; |
|
448 } |
|
449 else |
|
450 { |
|
451 length = eol - *src; |
|
452 if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2 |
|
453 *src = eol+2; |
|
454 else // Either CR or LF, so skip 1 |
|
455 *src = eol+1; |
|
456 } |
|
457 return length; |
|
458 } |
|
459 |
|
460 //-- The following #defines are used by ParseManifest() |
|
461 // and ParseOneFile(). The header strings are defined in the JAR specification. |
|
462 #define JAR_MF 1 |
|
463 #define JAR_SF 2 |
|
464 #define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$" |
|
465 #define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$" |
|
466 #define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0" |
|
467 #define JAR_SF_HEADER (const char*)"Signature-Version: 1.0" |
|
468 |
|
469 nsresult |
|
470 nsJAR::ParseManifest() |
|
471 { |
|
472 //-- Verification Step 1 |
|
473 if (mParsedManifest) |
|
474 return NS_OK; |
|
475 //-- (1)Manifest (MF) file |
|
476 nsCOMPtr<nsIUTF8StringEnumerator> files; |
|
477 nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files)); |
|
478 if (!files) rv = NS_ERROR_FAILURE; |
|
479 if (NS_FAILED(rv)) return rv; |
|
480 |
|
481 //-- Load the file into memory |
|
482 bool more; |
|
483 rv = files->HasMore(&more); |
|
484 NS_ENSURE_SUCCESS(rv, rv); |
|
485 if (!more) |
|
486 { |
|
487 mGlobalStatus = JAR_NO_MANIFEST; |
|
488 mParsedManifest = true; |
|
489 return NS_OK; |
|
490 } |
|
491 |
|
492 nsAutoCString manifestFilename; |
|
493 rv = files->GetNext(manifestFilename); |
|
494 NS_ENSURE_SUCCESS(rv, rv); |
|
495 |
|
496 // Check if there is more than one manifest, if so then error! |
|
497 rv = files->HasMore(&more); |
|
498 if (NS_FAILED(rv)) return rv; |
|
499 if (more) |
|
500 { |
|
501 mParsedManifest = true; |
|
502 return NS_ERROR_FILE_CORRUPTED; // More than one MF file |
|
503 } |
|
504 |
|
505 nsXPIDLCString manifestBuffer; |
|
506 uint32_t manifestLen; |
|
507 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); |
|
508 if (NS_FAILED(rv)) return rv; |
|
509 |
|
510 //-- Parse it |
|
511 rv = ParseOneFile(manifestBuffer, JAR_MF); |
|
512 if (NS_FAILED(rv)) return rv; |
|
513 |
|
514 //-- (2)Signature (SF) file |
|
515 // If there are multiple signatures, we select one. |
|
516 rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files)); |
|
517 if (!files) rv = NS_ERROR_FAILURE; |
|
518 if (NS_FAILED(rv)) return rv; |
|
519 //-- Get an SF file |
|
520 rv = files->HasMore(&more); |
|
521 if (NS_FAILED(rv)) return rv; |
|
522 if (!more) |
|
523 { |
|
524 mGlobalStatus = JAR_NO_MANIFEST; |
|
525 mParsedManifest = true; |
|
526 return NS_OK; |
|
527 } |
|
528 rv = files->GetNext(manifestFilename); |
|
529 if (NS_FAILED(rv)) return rv; |
|
530 |
|
531 rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen); |
|
532 if (NS_FAILED(rv)) return rv; |
|
533 |
|
534 //-- Get its corresponding signature file |
|
535 nsAutoCString sigFilename(manifestFilename); |
|
536 int32_t extension = sigFilename.RFindChar('.') + 1; |
|
537 NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension."); |
|
538 (void)sigFilename.Cut(extension, 2); |
|
539 nsXPIDLCString sigBuffer; |
|
540 uint32_t sigLen; |
|
541 { |
|
542 nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3); |
|
543 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); |
|
544 } |
|
545 if (NS_FAILED(rv)) |
|
546 { |
|
547 nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3); |
|
548 rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen); |
|
549 } |
|
550 if (NS_FAILED(rv)) |
|
551 { |
|
552 mGlobalStatus = JAR_NO_MANIFEST; |
|
553 mParsedManifest = true; |
|
554 return NS_OK; |
|
555 } |
|
556 |
|
557 //-- Get the signature verifier service |
|
558 nsCOMPtr<nsISignatureVerifier> verifier = |
|
559 do_GetService(SIGNATURE_VERIFIER_CONTRACTID, &rv); |
|
560 if (NS_FAILED(rv)) // No signature verifier available |
|
561 { |
|
562 mGlobalStatus = JAR_NO_MANIFEST; |
|
563 mParsedManifest = true; |
|
564 return NS_OK; |
|
565 } |
|
566 |
|
567 //-- Verify that the signature file is a valid signature of the SF file |
|
568 int32_t verifyError; |
|
569 rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen, |
|
570 &verifyError, getter_AddRefs(mPrincipal)); |
|
571 if (NS_FAILED(rv)) return rv; |
|
572 if (mPrincipal && verifyError == 0) |
|
573 mGlobalStatus = JAR_VALID_MANIFEST; |
|
574 else if (verifyError == nsISignatureVerifier::VERIFY_ERROR_UNKNOWN_CA) |
|
575 mGlobalStatus = JAR_INVALID_UNKNOWN_CA; |
|
576 else |
|
577 mGlobalStatus = JAR_INVALID_SIG; |
|
578 |
|
579 //-- Parse the SF file. If the verification above failed, principal |
|
580 // is null, and ParseOneFile will mark the relevant entries as invalid. |
|
581 // if ParseOneFile fails, then it has no effect, and we can safely |
|
582 // continue to the next SF file, or return. |
|
583 ParseOneFile(manifestBuffer, JAR_SF); |
|
584 mParsedManifest = true; |
|
585 |
|
586 return NS_OK; |
|
587 } |
|
588 |
|
589 nsresult |
|
590 nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType) |
|
591 { |
|
592 //-- Check file header |
|
593 const char* nextLineStart = filebuf; |
|
594 nsAutoCString curLine; |
|
595 int32_t linelen; |
|
596 linelen = ReadLine(&nextLineStart); |
|
597 curLine.Assign(filebuf, linelen); |
|
598 |
|
599 if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) || |
|
600 ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) |
|
601 return NS_ERROR_FILE_CORRUPTED; |
|
602 |
|
603 //-- Skip header section |
|
604 do { |
|
605 linelen = ReadLine(&nextLineStart); |
|
606 } while (linelen > 0); |
|
607 |
|
608 //-- Set up parsing variables |
|
609 const char* curPos; |
|
610 const char* sectionStart = nextLineStart; |
|
611 |
|
612 nsJARManifestItem* curItemMF = nullptr; |
|
613 bool foundName = false; |
|
614 if (aFileType == JAR_MF) { |
|
615 curItemMF = new nsJARManifestItem(); |
|
616 } |
|
617 |
|
618 nsAutoCString curItemName; |
|
619 nsAutoCString storedSectionDigest; |
|
620 |
|
621 for(;;) |
|
622 { |
|
623 curPos = nextLineStart; |
|
624 linelen = ReadLine(&nextLineStart); |
|
625 curLine.Assign(curPos, linelen); |
|
626 if (linelen == 0) |
|
627 // end of section (blank line or end-of-file) |
|
628 { |
|
629 if (aFileType == JAR_MF) |
|
630 { |
|
631 mTotalItemsInManifest++; |
|
632 if (curItemMF->mType != JAR_INVALID) |
|
633 { |
|
634 //-- Did this section have a name: line? |
|
635 if(!foundName) |
|
636 curItemMF->mType = JAR_INVALID; |
|
637 else |
|
638 { |
|
639 //-- If it's an internal item, it must correspond |
|
640 // to a valid jar entry |
|
641 if (curItemMF->mType == JAR_INTERNAL) |
|
642 { |
|
643 bool exists; |
|
644 nsresult rv = HasEntry(curItemName, &exists); |
|
645 if (NS_FAILED(rv) || !exists) |
|
646 curItemMF->mType = JAR_INVALID; |
|
647 } |
|
648 //-- Check for duplicates |
|
649 if (mManifestData.Contains(curItemName)) { |
|
650 curItemMF->mType = JAR_INVALID; |
|
651 } |
|
652 } |
|
653 } |
|
654 |
|
655 if (curItemMF->mType == JAR_INVALID) |
|
656 delete curItemMF; |
|
657 else //-- calculate section digest |
|
658 { |
|
659 uint32_t sectionLength = curPos - sectionStart; |
|
660 CalculateDigest(sectionStart, sectionLength, |
|
661 curItemMF->calculatedSectionDigest); |
|
662 //-- Save item in the hashtable |
|
663 mManifestData.Put(curItemName, curItemMF); |
|
664 } |
|
665 if (nextLineStart == nullptr) // end-of-file |
|
666 break; |
|
667 |
|
668 sectionStart = nextLineStart; |
|
669 curItemMF = new nsJARManifestItem(); |
|
670 } // (aFileType == JAR_MF) |
|
671 else |
|
672 //-- file type is SF, compare digest with calculated |
|
673 // section digests from MF file. |
|
674 { |
|
675 if (foundName) |
|
676 { |
|
677 nsJARManifestItem* curItemSF = mManifestData.Get(curItemName); |
|
678 if(curItemSF) |
|
679 { |
|
680 NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED, |
|
681 "SECURITY ERROR: nsJARManifestItem not correctly initialized"); |
|
682 curItemSF->status = mGlobalStatus; |
|
683 if (curItemSF->status == JAR_VALID_MANIFEST) |
|
684 { // Compare digests |
|
685 if (storedSectionDigest.IsEmpty()) |
|
686 curItemSF->status = JAR_NOT_SIGNED; |
|
687 else |
|
688 { |
|
689 if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest)) |
|
690 curItemSF->status = JAR_INVALID_MANIFEST; |
|
691 curItemSF->calculatedSectionDigest.Truncate(); |
|
692 storedSectionDigest.Truncate(); |
|
693 } |
|
694 } // (aPrincipal != nullptr) |
|
695 } // if(curItemSF) |
|
696 } // if(foundName) |
|
697 |
|
698 if(nextLineStart == nullptr) // end-of-file |
|
699 break; |
|
700 } // aFileType == JAR_SF |
|
701 foundName = false; |
|
702 continue; |
|
703 } // if(linelen == 0) |
|
704 |
|
705 //-- Look for continuations (beginning with a space) on subsequent lines |
|
706 // and append them to the current line. |
|
707 while(*nextLineStart == ' ') |
|
708 { |
|
709 curPos = nextLineStart; |
|
710 int32_t continuationLen = ReadLine(&nextLineStart) - 1; |
|
711 nsAutoCString continuation(curPos+1, continuationLen); |
|
712 curLine += continuation; |
|
713 linelen += continuationLen; |
|
714 } |
|
715 |
|
716 //-- Find colon in current line, this separates name from value |
|
717 int32_t colonPos = curLine.FindChar(':'); |
|
718 if (colonPos == -1) // No colon on line, ignore line |
|
719 continue; |
|
720 //-- Break down the line |
|
721 nsAutoCString lineName; |
|
722 curLine.Left(lineName, colonPos); |
|
723 nsAutoCString lineData; |
|
724 curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2)); |
|
725 |
|
726 //-- Lines to look for: |
|
727 // (1) Digest: |
|
728 if (lineName.LowerCaseEqualsLiteral("sha1-digest")) |
|
729 //-- This is a digest line, save the data in the appropriate place |
|
730 { |
|
731 if(aFileType == JAR_MF) |
|
732 curItemMF->storedEntryDigest = lineData; |
|
733 else |
|
734 storedSectionDigest = lineData; |
|
735 continue; |
|
736 } |
|
737 |
|
738 // (2) Name: associates this manifest section with a file in the jar. |
|
739 if (!foundName && lineName.LowerCaseEqualsLiteral("name")) |
|
740 { |
|
741 curItemName = lineData; |
|
742 foundName = true; |
|
743 continue; |
|
744 } |
|
745 |
|
746 // (3) Magic: this may be an inline Javascript. |
|
747 // We can't do any other kind of magic. |
|
748 if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic")) |
|
749 { |
|
750 if (lineData.LowerCaseEqualsLiteral("javascript")) |
|
751 curItemMF->mType = JAR_EXTERNAL; |
|
752 else |
|
753 curItemMF->mType = JAR_INVALID; |
|
754 continue; |
|
755 } |
|
756 |
|
757 } // for (;;) |
|
758 return NS_OK; |
|
759 } //ParseOneFile() |
|
760 |
|
761 nsresult |
|
762 nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData, |
|
763 uint32_t aLen) |
|
764 { |
|
765 if (aManItem->status == JAR_VALID_MANIFEST) |
|
766 { |
|
767 if (aManItem->storedEntryDigest.IsEmpty()) |
|
768 // No entry digests in manifest file. Entry is unsigned. |
|
769 aManItem->status = JAR_NOT_SIGNED; |
|
770 else |
|
771 { //-- Calculate and compare digests |
|
772 nsCString calculatedEntryDigest; |
|
773 nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest); |
|
774 if (NS_FAILED(rv)) return NS_ERROR_FAILURE; |
|
775 if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest)) |
|
776 aManItem->status = JAR_INVALID_ENTRY; |
|
777 aManItem->storedEntryDigest.Truncate(); |
|
778 } |
|
779 } |
|
780 aManItem->entryVerified = true; |
|
781 return NS_OK; |
|
782 } |
|
783 |
|
784 void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode) |
|
785 { |
|
786 //-- Generate error message |
|
787 nsAutoString message; |
|
788 message.AssignLiteral("Signature Verification Error: the signature on "); |
|
789 if (!aFilename.IsEmpty()) |
|
790 AppendASCIItoUTF16(aFilename, message); |
|
791 else |
|
792 message.AppendLiteral("this .jar archive"); |
|
793 message.AppendLiteral(" is invalid because "); |
|
794 switch(errorCode) |
|
795 { |
|
796 case JAR_NOT_SIGNED: |
|
797 message.AppendLiteral("the archive did not contain a valid PKCS7 signature."); |
|
798 break; |
|
799 case JAR_INVALID_SIG: |
|
800 message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF)."); |
|
801 break; |
|
802 case JAR_INVALID_UNKNOWN_CA: |
|
803 message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer."); |
|
804 break; |
|
805 case JAR_INVALID_MANIFEST: |
|
806 message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file."); |
|
807 break; |
|
808 case JAR_INVALID_ENTRY: |
|
809 message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified."); |
|
810 break; |
|
811 case JAR_NO_MANIFEST: |
|
812 message.AppendLiteral("the archive did not contain a manifest."); |
|
813 break; |
|
814 default: |
|
815 message.AppendLiteral("of an unknown problem."); |
|
816 } |
|
817 |
|
818 // Report error in JS console |
|
819 nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1")); |
|
820 if (console) |
|
821 { |
|
822 console->LogStringMessage(message.get()); |
|
823 } |
|
824 #ifdef DEBUG |
|
825 char* messageCstr = ToNewCString(message); |
|
826 if (!messageCstr) return; |
|
827 fprintf(stderr, "%s\n", messageCstr); |
|
828 nsMemory::Free(messageCstr); |
|
829 #endif |
|
830 } |
|
831 |
|
832 |
|
833 nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen, |
|
834 nsCString& digest) |
|
835 { |
|
836 nsresult rv; |
|
837 |
|
838 nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv); |
|
839 if (NS_FAILED(rv)) return rv; |
|
840 |
|
841 rv = hasher->Init(nsICryptoHash::SHA1); |
|
842 if (NS_FAILED(rv)) return rv; |
|
843 |
|
844 rv = hasher->Update((const uint8_t*) aInBuf, aLen); |
|
845 if (NS_FAILED(rv)) return rv; |
|
846 |
|
847 return hasher->Finish(true, digest); |
|
848 } |
|
849 |
|
850 NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator) |
|
851 |
|
852 //---------------------------------------------- |
|
853 // nsJAREnumerator::HasMore |
|
854 //---------------------------------------------- |
|
855 NS_IMETHODIMP |
|
856 nsJAREnumerator::HasMore(bool* aResult) |
|
857 { |
|
858 // try to get the next element |
|
859 if (!mName) { |
|
860 NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind."); |
|
861 nsresult rv = mFind->FindNext( &mName, &mNameLen ); |
|
862 if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { |
|
863 *aResult = false; // No more matches available |
|
864 return NS_OK; |
|
865 } |
|
866 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation |
|
867 } |
|
868 |
|
869 *aResult = true; |
|
870 return NS_OK; |
|
871 } |
|
872 |
|
873 //---------------------------------------------- |
|
874 // nsJAREnumerator::GetNext |
|
875 //---------------------------------------------- |
|
876 NS_IMETHODIMP |
|
877 nsJAREnumerator::GetNext(nsACString& aResult) |
|
878 { |
|
879 // check if the current item is "stale" |
|
880 if (!mName) { |
|
881 bool bMore; |
|
882 nsresult rv = HasMore(&bMore); |
|
883 if (NS_FAILED(rv) || !bMore) |
|
884 return NS_ERROR_FAILURE; // no error translation |
|
885 } |
|
886 aResult.Assign(mName, mNameLen); |
|
887 mName = 0; // we just gave this one away |
|
888 return NS_OK; |
|
889 } |
|
890 |
|
891 |
|
892 NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry) |
|
893 |
|
894 nsJARItem::nsJARItem(nsZipItem* aZipItem) |
|
895 : mSize(aZipItem->Size()), |
|
896 mRealsize(aZipItem->RealSize()), |
|
897 mCrc32(aZipItem->CRC32()), |
|
898 mLastModTime(aZipItem->LastModTime()), |
|
899 mCompression(aZipItem->Compression()), |
|
900 mPermissions(aZipItem->Mode()), |
|
901 mIsDirectory(aZipItem->IsDirectory()), |
|
902 mIsSynthetic(aZipItem->isSynthetic) |
|
903 { |
|
904 } |
|
905 |
|
906 //------------------------------------------ |
|
907 // nsJARItem::GetCompression |
|
908 //------------------------------------------ |
|
909 NS_IMETHODIMP |
|
910 nsJARItem::GetCompression(uint16_t *aCompression) |
|
911 { |
|
912 NS_ENSURE_ARG_POINTER(aCompression); |
|
913 |
|
914 *aCompression = mCompression; |
|
915 return NS_OK; |
|
916 } |
|
917 |
|
918 //------------------------------------------ |
|
919 // nsJARItem::GetSize |
|
920 //------------------------------------------ |
|
921 NS_IMETHODIMP |
|
922 nsJARItem::GetSize(uint32_t *aSize) |
|
923 { |
|
924 NS_ENSURE_ARG_POINTER(aSize); |
|
925 |
|
926 *aSize = mSize; |
|
927 return NS_OK; |
|
928 } |
|
929 |
|
930 //------------------------------------------ |
|
931 // nsJARItem::GetRealSize |
|
932 //------------------------------------------ |
|
933 NS_IMETHODIMP |
|
934 nsJARItem::GetRealSize(uint32_t *aRealsize) |
|
935 { |
|
936 NS_ENSURE_ARG_POINTER(aRealsize); |
|
937 |
|
938 *aRealsize = mRealsize; |
|
939 return NS_OK; |
|
940 } |
|
941 |
|
942 //------------------------------------------ |
|
943 // nsJARItem::GetCrc32 |
|
944 //------------------------------------------ |
|
945 NS_IMETHODIMP |
|
946 nsJARItem::GetCRC32(uint32_t *aCrc32) |
|
947 { |
|
948 NS_ENSURE_ARG_POINTER(aCrc32); |
|
949 |
|
950 *aCrc32 = mCrc32; |
|
951 return NS_OK; |
|
952 } |
|
953 |
|
954 //------------------------------------------ |
|
955 // nsJARItem::GetIsDirectory |
|
956 //------------------------------------------ |
|
957 NS_IMETHODIMP |
|
958 nsJARItem::GetIsDirectory(bool *aIsDirectory) |
|
959 { |
|
960 NS_ENSURE_ARG_POINTER(aIsDirectory); |
|
961 |
|
962 *aIsDirectory = mIsDirectory; |
|
963 return NS_OK; |
|
964 } |
|
965 |
|
966 //------------------------------------------ |
|
967 // nsJARItem::GetIsSynthetic |
|
968 //------------------------------------------ |
|
969 NS_IMETHODIMP |
|
970 nsJARItem::GetIsSynthetic(bool *aIsSynthetic) |
|
971 { |
|
972 NS_ENSURE_ARG_POINTER(aIsSynthetic); |
|
973 |
|
974 *aIsSynthetic = mIsSynthetic; |
|
975 return NS_OK; |
|
976 } |
|
977 |
|
978 //------------------------------------------ |
|
979 // nsJARItem::GetLastModifiedTime |
|
980 //------------------------------------------ |
|
981 NS_IMETHODIMP |
|
982 nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) |
|
983 { |
|
984 NS_ENSURE_ARG_POINTER(aLastModTime); |
|
985 |
|
986 *aLastModTime = mLastModTime; |
|
987 return NS_OK; |
|
988 } |
|
989 |
|
990 //------------------------------------------ |
|
991 // nsJARItem::GetPermissions |
|
992 //------------------------------------------ |
|
993 NS_IMETHODIMP |
|
994 nsJARItem::GetPermissions(uint32_t* aPermissions) |
|
995 { |
|
996 NS_ENSURE_ARG_POINTER(aPermissions); |
|
997 |
|
998 *aPermissions = mPermissions; |
|
999 return NS_OK; |
|
1000 } |
|
1001 |
|
1002 //////////////////////////////////////////////////////////////////////////////// |
|
1003 // nsIZipReaderCache |
|
1004 |
|
1005 NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference) |
|
1006 |
|
1007 nsZipReaderCache::nsZipReaderCache() |
|
1008 : mLock("nsZipReaderCache.mLock") |
|
1009 , mZips(16) |
|
1010 #ifdef ZIP_CACHE_HIT_RATE |
|
1011 , |
|
1012 mZipCacheLookups(0), |
|
1013 mZipCacheHits(0), |
|
1014 mZipCacheFlushes(0), |
|
1015 mZipSyncMisses(0) |
|
1016 #endif |
|
1017 { |
|
1018 } |
|
1019 |
|
1020 NS_IMETHODIMP |
|
1021 nsZipReaderCache::Init(uint32_t cacheSize) |
|
1022 { |
|
1023 mCacheSize = cacheSize; |
|
1024 |
|
1025 // Register as a memory pressure observer |
|
1026 nsCOMPtr<nsIObserverService> os = |
|
1027 do_GetService("@mozilla.org/observer-service;1"); |
|
1028 if (os) |
|
1029 { |
|
1030 os->AddObserver(this, "memory-pressure", true); |
|
1031 os->AddObserver(this, "chrome-flush-caches", true); |
|
1032 os->AddObserver(this, "flush-cache-entry", true); |
|
1033 } |
|
1034 // ignore failure of the observer registration. |
|
1035 |
|
1036 return NS_OK; |
|
1037 } |
|
1038 |
|
1039 static PLDHashOperator |
|
1040 DropZipReaderCache(const nsACString &aKey, nsJAR* aZip, void*) |
|
1041 { |
|
1042 aZip->SetZipReaderCache(nullptr); |
|
1043 return PL_DHASH_NEXT; |
|
1044 } |
|
1045 |
|
1046 nsZipReaderCache::~nsZipReaderCache() |
|
1047 { |
|
1048 mZips.EnumerateRead(DropZipReaderCache, nullptr); |
|
1049 |
|
1050 #ifdef ZIP_CACHE_HIT_RATE |
|
1051 printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n", |
|
1052 mCacheSize, mZipCacheHits, mZipCacheLookups, |
|
1053 (float)mZipCacheHits / mZipCacheLookups, |
|
1054 mZipCacheFlushes, mZipSyncMisses); |
|
1055 #endif |
|
1056 } |
|
1057 |
|
1058 NS_IMETHODIMP |
|
1059 nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) |
|
1060 { |
|
1061 NS_ENSURE_ARG_POINTER(zipFile); |
|
1062 nsresult rv; |
|
1063 nsCOMPtr<nsIZipReader> antiLockZipGrip; |
|
1064 MutexAutoLock lock(mLock); |
|
1065 |
|
1066 nsAutoCString uri; |
|
1067 rv = zipFile->GetNativePath(uri); |
|
1068 if (NS_FAILED(rv)) |
|
1069 return rv; |
|
1070 |
|
1071 uri.Insert(NS_LITERAL_CSTRING("file:"), 0); |
|
1072 |
|
1073 *aResult = mZips.Contains(uri); |
|
1074 return NS_OK; |
|
1075 } |
|
1076 |
|
1077 NS_IMETHODIMP |
|
1078 nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result) |
|
1079 { |
|
1080 NS_ENSURE_ARG_POINTER(zipFile); |
|
1081 nsresult rv; |
|
1082 nsCOMPtr<nsIZipReader> antiLockZipGrip; |
|
1083 MutexAutoLock lock(mLock); |
|
1084 |
|
1085 #ifdef ZIP_CACHE_HIT_RATE |
|
1086 mZipCacheLookups++; |
|
1087 #endif |
|
1088 |
|
1089 nsAutoCString uri; |
|
1090 rv = zipFile->GetNativePath(uri); |
|
1091 if (NS_FAILED(rv)) return rv; |
|
1092 |
|
1093 uri.Insert(NS_LITERAL_CSTRING("file:"), 0); |
|
1094 |
|
1095 nsRefPtr<nsJAR> zip; |
|
1096 mZips.Get(uri, getter_AddRefs(zip)); |
|
1097 if (zip) { |
|
1098 #ifdef ZIP_CACHE_HIT_RATE |
|
1099 mZipCacheHits++; |
|
1100 #endif |
|
1101 zip->ClearReleaseTime(); |
|
1102 } else { |
|
1103 zip = new nsJAR(); |
|
1104 zip->SetZipReaderCache(this); |
|
1105 |
|
1106 rv = zip->Open(zipFile); |
|
1107 if (NS_FAILED(rv)) { |
|
1108 return rv; |
|
1109 } |
|
1110 |
|
1111 MOZ_ASSERT(!mZips.Contains(uri)); |
|
1112 mZips.Put(uri, zip); |
|
1113 } |
|
1114 zip.forget(result); |
|
1115 return rv; |
|
1116 } |
|
1117 |
|
1118 NS_IMETHODIMP |
|
1119 nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry, |
|
1120 nsIZipReader* *result) |
|
1121 { |
|
1122 NS_ENSURE_ARG_POINTER(zipFile); |
|
1123 |
|
1124 nsCOMPtr<nsIZipReader> outerZipReader; |
|
1125 nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader)); |
|
1126 NS_ENSURE_SUCCESS(rv, rv); |
|
1127 |
|
1128 #ifdef ZIP_CACHE_HIT_RATE |
|
1129 mZipCacheLookups++; |
|
1130 #endif |
|
1131 |
|
1132 nsAutoCString uri; |
|
1133 rv = zipFile->GetNativePath(uri); |
|
1134 if (NS_FAILED(rv)) return rv; |
|
1135 |
|
1136 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); |
|
1137 uri.AppendLiteral("!/"); |
|
1138 uri.Append(entry); |
|
1139 |
|
1140 nsRefPtr<nsJAR> zip; |
|
1141 mZips.Get(uri, getter_AddRefs(zip)); |
|
1142 if (zip) { |
|
1143 #ifdef ZIP_CACHE_HIT_RATE |
|
1144 mZipCacheHits++; |
|
1145 #endif |
|
1146 zip->ClearReleaseTime(); |
|
1147 } else { |
|
1148 zip = new nsJAR(); |
|
1149 zip->SetZipReaderCache(this); |
|
1150 |
|
1151 rv = zip->OpenInner(outerZipReader, entry); |
|
1152 if (NS_FAILED(rv)) { |
|
1153 return rv; |
|
1154 } |
|
1155 |
|
1156 MOZ_ASSERT(!mZips.Contains(uri)); |
|
1157 mZips.Put(uri, zip); |
|
1158 } |
|
1159 zip.forget(result); |
|
1160 return rv; |
|
1161 } |
|
1162 |
|
1163 static PLDHashOperator |
|
1164 FindOldestZip(const nsACString &aKey, nsJAR* aZip, void* aClosure) |
|
1165 { |
|
1166 nsJAR** oldestPtr = static_cast<nsJAR**>(aClosure); |
|
1167 nsJAR* oldest = *oldestPtr; |
|
1168 nsJAR* current = aZip; |
|
1169 PRIntervalTime currentReleaseTime = current->GetReleaseTime(); |
|
1170 if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) { |
|
1171 if (oldest == nullptr || |
|
1172 currentReleaseTime < oldest->GetReleaseTime()) { |
|
1173 *oldestPtr = current; |
|
1174 } |
|
1175 } |
|
1176 return PL_DHASH_NEXT; |
|
1177 } |
|
1178 |
|
1179 struct ZipFindData {nsJAR* zip; bool found;}; |
|
1180 |
|
1181 static PLDHashOperator |
|
1182 FindZip(const nsACString &aKey, nsJAR* aZip, void* aClosure) |
|
1183 { |
|
1184 ZipFindData* find_data = static_cast<ZipFindData*>(aClosure); |
|
1185 |
|
1186 if (find_data->zip == aZip) { |
|
1187 find_data->found = true; |
|
1188 return PL_DHASH_STOP; |
|
1189 } |
|
1190 return PL_DHASH_NEXT; |
|
1191 } |
|
1192 |
|
1193 nsresult |
|
1194 nsZipReaderCache::ReleaseZip(nsJAR* zip) |
|
1195 { |
|
1196 nsresult rv; |
|
1197 MutexAutoLock lock(mLock); |
|
1198 |
|
1199 // It is possible that two thread compete for this zip. The dangerous |
|
1200 // case is where one thread Releases the zip and discovers that the ref |
|
1201 // count has gone to one. Before it can call this ReleaseZip method |
|
1202 // another thread calls our GetZip method. The ref count goes to two. That |
|
1203 // second thread then Releases the zip and the ref count goes to one. It |
|
1204 // then tries to enter this ReleaseZip method and blocks while the first |
|
1205 // thread is still here. The first thread continues and remove the zip from |
|
1206 // the cache and calls its Release method sending the ref count to 0 and |
|
1207 // deleting the zip. However, the second thread is still blocked at the |
|
1208 // start of ReleaseZip, but the 'zip' param now hold a reference to a |
|
1209 // deleted zip! |
|
1210 // |
|
1211 // So, we are going to try safeguarding here by searching our hashtable while |
|
1212 // locked here for the zip. We return fast if it is not found. |
|
1213 |
|
1214 ZipFindData find_data = {zip, false}; |
|
1215 mZips.EnumerateRead(FindZip, &find_data); |
|
1216 if (!find_data.found) { |
|
1217 #ifdef ZIP_CACHE_HIT_RATE |
|
1218 mZipSyncMisses++; |
|
1219 #endif |
|
1220 return NS_OK; |
|
1221 } |
|
1222 |
|
1223 zip->SetReleaseTime(); |
|
1224 |
|
1225 if (mZips.Count() <= mCacheSize) |
|
1226 return NS_OK; |
|
1227 |
|
1228 nsJAR* oldest = nullptr; |
|
1229 mZips.EnumerateRead(FindOldestZip, &oldest); |
|
1230 |
|
1231 // Because of the craziness above it is possible that there is no zip that |
|
1232 // needs removing. |
|
1233 if (!oldest) |
|
1234 return NS_OK; |
|
1235 |
|
1236 #ifdef ZIP_CACHE_HIT_RATE |
|
1237 mZipCacheFlushes++; |
|
1238 #endif |
|
1239 |
|
1240 // remove from hashtable |
|
1241 nsAutoCString uri; |
|
1242 rv = oldest->GetJarPath(uri); |
|
1243 if (NS_FAILED(rv)) |
|
1244 return rv; |
|
1245 |
|
1246 if (oldest->mOuterZipEntry.IsEmpty()) { |
|
1247 uri.Insert(NS_LITERAL_CSTRING("file:"), 0); |
|
1248 } else { |
|
1249 uri.Insert(NS_LITERAL_CSTRING("jar:"), 0); |
|
1250 uri.AppendLiteral("!/"); |
|
1251 uri.Append(oldest->mOuterZipEntry); |
|
1252 } |
|
1253 |
|
1254 // Retrieving and removing the JAR must be done without an extra AddRef |
|
1255 // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case |
|
1256 // an extra time and trigger a deadlock. |
|
1257 nsRefPtr<nsJAR> removed; |
|
1258 mZips.Remove(uri, getter_AddRefs(removed)); |
|
1259 NS_ASSERTION(removed, "botched"); |
|
1260 NS_ASSERTION(oldest == removed, "removed wrong entry"); |
|
1261 |
|
1262 if (removed) |
|
1263 removed->SetZipReaderCache(nullptr); |
|
1264 |
|
1265 return NS_OK; |
|
1266 } |
|
1267 |
|
1268 static PLDHashOperator |
|
1269 FindFlushableZip(const nsACString &aKey, nsRefPtr<nsJAR>& aCurrent, void*) |
|
1270 { |
|
1271 if (aCurrent->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) { |
|
1272 aCurrent->SetZipReaderCache(nullptr); |
|
1273 return PL_DHASH_REMOVE; |
|
1274 } |
|
1275 return PL_DHASH_NEXT; |
|
1276 } |
|
1277 |
|
1278 NS_IMETHODIMP |
|
1279 nsZipReaderCache::Observe(nsISupports *aSubject, |
|
1280 const char *aTopic, |
|
1281 const char16_t *aSomeData) |
|
1282 { |
|
1283 if (strcmp(aTopic, "memory-pressure") == 0) { |
|
1284 MutexAutoLock lock(mLock); |
|
1285 mZips.Enumerate(FindFlushableZip, nullptr); |
|
1286 } |
|
1287 else if (strcmp(aTopic, "chrome-flush-caches") == 0) { |
|
1288 mZips.EnumerateRead(DropZipReaderCache, nullptr); |
|
1289 mZips.Clear(); |
|
1290 } |
|
1291 else if (strcmp(aTopic, "flush-cache-entry") == 0) { |
|
1292 nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject); |
|
1293 if (!file) |
|
1294 return NS_OK; |
|
1295 |
|
1296 nsAutoCString uri; |
|
1297 if (NS_FAILED(file->GetNativePath(uri))) |
|
1298 return NS_OK; |
|
1299 |
|
1300 uri.Insert(NS_LITERAL_CSTRING("file:"), 0); |
|
1301 |
|
1302 MutexAutoLock lock(mLock); |
|
1303 |
|
1304 nsRefPtr<nsJAR> zip; |
|
1305 mZips.Get(uri, getter_AddRefs(zip)); |
|
1306 if (!zip) |
|
1307 return NS_OK; |
|
1308 |
|
1309 #ifdef ZIP_CACHE_HIT_RATE |
|
1310 mZipCacheFlushes++; |
|
1311 #endif |
|
1312 |
|
1313 zip->SetZipReaderCache(nullptr); |
|
1314 |
|
1315 mZips.Remove(uri); |
|
1316 } |
|
1317 return NS_OK; |
|
1318 } |
|
1319 |
|
1320 //////////////////////////////////////////////////////////////////////////////// |