Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; indent-tab-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/Attributes.h"
10 #include "nsCache.h"
11 #include "nsDiskCache.h"
12 #include "nsDiskCacheDeviceSQL.h"
13 #include "nsCacheService.h"
14 #include "nsApplicationCache.h"
16 #include "nsNetCID.h"
17 #include "nsNetUtil.h"
18 #include "nsAutoPtr.h"
19 #include "nsEscape.h"
20 #include "nsIPrefBranch.h"
21 #include "nsIPrefService.h"
22 #include "nsString.h"
23 #include "nsPrintfCString.h"
24 #include "nsCRT.h"
25 #include "nsArrayUtils.h"
26 #include "nsIArray.h"
27 #include "nsIVariant.h"
28 #include "nsILoadContextInfo.h"
29 #include "nsThreadUtils.h"
30 #include "nsISerializable.h"
31 #include "nsSerializationHelper.h"
33 #include "mozIStorageService.h"
34 #include "mozIStorageStatement.h"
35 #include "mozIStorageFunction.h"
36 #include "mozStorageHelper.h"
38 #include "nsICacheVisitor.h"
39 #include "nsISeekableStream.h"
41 #include "mozilla/Telemetry.h"
43 #include "sqlite3.h"
44 #include "mozilla/storage.h"
46 using namespace mozilla;
47 using namespace mozilla::storage;
49 static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
51 #define LOG(args) CACHE_LOG_DEBUG(args)
53 static uint32_t gNextTemporaryClientID = 0;
55 /*****************************************************************************
56 * helpers
57 */
59 static nsresult
60 EnsureDir(nsIFile *dir)
61 {
62 bool exists;
63 nsresult rv = dir->Exists(&exists);
64 if (NS_SUCCEEDED(rv) && !exists)
65 rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
66 return rv;
67 }
69 static bool
70 DecomposeCacheEntryKey(const nsCString *fullKey,
71 const char **cid,
72 const char **key,
73 nsCString &buf)
74 {
75 buf = *fullKey;
77 int32_t colon = buf.FindChar(':');
78 if (colon == kNotFound)
79 {
80 NS_ERROR("Invalid key");
81 return false;
82 }
83 buf.SetCharAt('\0', colon);
85 *cid = buf.get();
86 *key = buf.get() + colon + 1;
88 return true;
89 }
91 class AutoResetStatement
92 {
93 public:
94 AutoResetStatement(mozIStorageStatement *s)
95 : mStatement(s) {}
96 ~AutoResetStatement() { mStatement->Reset(); }
97 mozIStorageStatement *operator->() { return mStatement; }
98 private:
99 mozIStorageStatement *mStatement;
100 };
102 class EvictionObserver
103 {
104 public:
105 EvictionObserver(mozIStorageConnection *db,
106 nsOfflineCacheEvictionFunction *evictionFunction)
107 : mDB(db), mEvictionFunction(evictionFunction)
108 {
109 mDB->ExecuteSimpleSQL(
110 NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
111 " ON moz_cache FOR EACH ROW BEGIN SELECT"
112 " cache_eviction_observer("
113 " OLD.ClientID, OLD.key, OLD.generation);"
114 " END;"));
115 mEvictionFunction->Reset();
116 }
118 ~EvictionObserver()
119 {
120 mDB->ExecuteSimpleSQL(
121 NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
122 mEvictionFunction->Reset();
123 }
125 void Apply() { return mEvictionFunction->Apply(); }
127 private:
128 mozIStorageConnection *mDB;
129 nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
130 };
132 #define DCACHE_HASH_MAX INT64_MAX
133 #define DCACHE_HASH_BITS 64
135 /**
136 * nsOfflineCache::Hash(const char * key)
137 *
138 * This algorithm of this method implies nsOfflineCacheRecords will be stored
139 * in a certain order on disk. If the algorithm changes, existing cache
140 * map files may become invalid, and therefore the kCurrentVersion needs
141 * to be revised.
142 */
143 static uint64_t
144 DCacheHash(const char * key)
145 {
146 // initval 0x7416f295 was chosen randomly
147 return (uint64_t(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
148 }
150 /******************************************************************************
151 * nsOfflineCacheEvictionFunction
152 */
154 NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
156 // helper function for directly exposing the same data file binding
157 // path algorithm used in nsOfflineCacheBinding::Create
158 static nsresult
159 GetCacheDataFile(nsIFile *cacheDir, const char *key,
160 int generation, nsCOMPtr<nsIFile> &file)
161 {
162 cacheDir->Clone(getter_AddRefs(file));
163 if (!file)
164 return NS_ERROR_OUT_OF_MEMORY;
166 uint64_t hash = DCacheHash(key);
168 uint32_t dir1 = (uint32_t) (hash & 0x0F);
169 uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
171 hash >>= 8;
173 file->AppendNative(nsPrintfCString("%X", dir1));
174 file->AppendNative(nsPrintfCString("%X", dir2));
176 char leaf[64];
177 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
178 return file->AppendNative(nsDependentCString(leaf));
179 }
181 NS_IMETHODIMP
182 nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
183 {
184 LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
186 *_retval = nullptr;
188 uint32_t numEntries;
189 nsresult rv = values->GetNumEntries(&numEntries);
190 NS_ENSURE_SUCCESS(rv, rv);
191 NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
193 uint32_t valueLen;
194 const char *clientID = values->AsSharedUTF8String(0, &valueLen);
195 const char *key = values->AsSharedUTF8String(1, &valueLen);
196 nsAutoCString fullKey(clientID);
197 fullKey.AppendLiteral(":");
198 fullKey.Append(key);
199 int generation = values->AsInt32(2);
201 // If the key is currently locked, refuse to delete this row.
202 if (mDevice->IsLocked(fullKey)) {
203 NS_ADDREF(*_retval = new IntegerVariant(SQLITE_IGNORE));
204 return NS_OK;
205 }
207 nsCOMPtr<nsIFile> file;
208 rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
209 generation, file);
210 if (NS_FAILED(rv))
211 {
212 LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
213 key, generation, rv));
214 return rv;
215 }
217 mItems.AppendObject(file);
219 return NS_OK;
220 }
222 void
223 nsOfflineCacheEvictionFunction::Apply()
224 {
225 LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
227 for (int32_t i = 0; i < mItems.Count(); i++) {
228 #if defined(PR_LOGGING)
229 nsAutoCString path;
230 mItems[i]->GetNativePath(path);
231 LOG((" removing %s\n", path.get()));
232 #endif
234 mItems[i]->Remove(false);
235 }
237 Reset();
238 }
240 class nsOfflineCacheDiscardCache : public nsRunnable
241 {
242 public:
243 nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device,
244 nsCString &group,
245 nsCString &clientID)
246 : mDevice(device)
247 , mGroup(group)
248 , mClientID(clientID)
249 {
250 }
252 NS_IMETHOD Run()
253 {
254 if (mDevice->IsActiveCache(mGroup, mClientID))
255 {
256 mDevice->DeactivateGroup(mGroup);
257 }
259 return mDevice->EvictEntries(mClientID.get());
260 }
262 private:
263 nsRefPtr<nsOfflineCacheDevice> mDevice;
264 nsCString mGroup;
265 nsCString mClientID;
266 };
268 /******************************************************************************
269 * nsOfflineCacheDeviceInfo
270 */
272 class nsOfflineCacheDeviceInfo MOZ_FINAL : public nsICacheDeviceInfo
273 {
274 public:
275 NS_DECL_ISUPPORTS
276 NS_DECL_NSICACHEDEVICEINFO
278 nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
279 : mDevice(device)
280 {}
282 private:
283 nsOfflineCacheDevice* mDevice;
284 };
286 NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
288 NS_IMETHODIMP
289 nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
290 {
291 *aDescription = NS_strdup("Offline cache device");
292 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
293 }
295 NS_IMETHODIMP
296 nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
297 {
298 nsAutoCString buffer;
299 buffer.AssignLiteral(" <tr>\n"
300 " <th>Cache Directory:</th>\n"
301 " <td>");
302 nsIFile *cacheDir = mDevice->CacheDirectory();
303 if (!cacheDir)
304 return NS_OK;
306 nsAutoString path;
307 nsresult rv = cacheDir->GetPath(path);
308 if (NS_SUCCEEDED(rv))
309 AppendUTF16toUTF8(path, buffer);
310 else
311 buffer.AppendLiteral("directory unavailable");
313 buffer.AppendLiteral("</td>\n"
314 " </tr>\n");
316 *usageReport = ToNewCString(buffer);
317 if (!*usageReport)
318 return NS_ERROR_OUT_OF_MEMORY;
320 return NS_OK;
321 }
323 NS_IMETHODIMP
324 nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
325 {
326 *aEntryCount = mDevice->EntryCount();
327 return NS_OK;
328 }
330 NS_IMETHODIMP
331 nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
332 {
333 *aTotalSize = mDevice->CacheSize();
334 return NS_OK;
335 }
337 NS_IMETHODIMP
338 nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
339 {
340 *aMaximumSize = mDevice->CacheCapacity();
341 return NS_OK;
342 }
344 /******************************************************************************
345 * nsOfflineCacheBinding
346 */
348 class nsOfflineCacheBinding MOZ_FINAL : public nsISupports
349 {
350 public:
351 NS_DECL_THREADSAFE_ISUPPORTS
353 static nsOfflineCacheBinding *
354 Create(nsIFile *cacheDir, const nsCString *key, int generation);
356 enum { FLAG_NEW_ENTRY = 1 };
358 nsCOMPtr<nsIFile> mDataFile;
359 int mGeneration;
360 int mFlags;
362 bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
363 void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
364 void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
365 };
367 NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
369 nsOfflineCacheBinding *
370 nsOfflineCacheBinding::Create(nsIFile *cacheDir,
371 const nsCString *fullKey,
372 int generation)
373 {
374 nsCOMPtr<nsIFile> file;
375 cacheDir->Clone(getter_AddRefs(file));
376 if (!file)
377 return nullptr;
379 nsAutoCString keyBuf;
380 const char *cid, *key;
381 if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
382 return nullptr;
384 uint64_t hash = DCacheHash(key);
386 uint32_t dir1 = (uint32_t) (hash & 0x0F);
387 uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
389 hash >>= 8;
391 // XXX we might want to create these directories up-front
393 file->AppendNative(nsPrintfCString("%X", dir1));
394 file->Create(nsIFile::DIRECTORY_TYPE, 00700);
396 file->AppendNative(nsPrintfCString("%X", dir2));
397 file->Create(nsIFile::DIRECTORY_TYPE, 00700);
399 nsresult rv;
400 char leaf[64];
402 if (generation == -1)
403 {
404 file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
406 for (generation = 0; ; ++generation)
407 {
408 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
410 rv = file->SetNativeLeafName(nsDependentCString(leaf));
411 if (NS_FAILED(rv))
412 return nullptr;
413 rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
414 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
415 return nullptr;
416 if (NS_SUCCEEDED(rv))
417 break;
418 }
419 }
420 else
421 {
422 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
423 rv = file->AppendNative(nsDependentCString(leaf));
424 if (NS_FAILED(rv))
425 return nullptr;
426 }
428 nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
429 if (!binding)
430 return nullptr;
432 binding->mDataFile.swap(file);
433 binding->mGeneration = generation;
434 binding->mFlags = 0;
435 return binding;
436 }
438 /******************************************************************************
439 * nsOfflineCacheRecord
440 */
442 struct nsOfflineCacheRecord
443 {
444 const char *clientID;
445 const char *key;
446 const uint8_t *metaData;
447 uint32_t metaDataLen;
448 int32_t generation;
449 int32_t dataSize;
450 int32_t fetchCount;
451 int64_t lastFetched;
452 int64_t lastModified;
453 int64_t expirationTime;
454 };
456 static nsCacheEntry *
457 CreateCacheEntry(nsOfflineCacheDevice *device,
458 const nsCString *fullKey,
459 const nsOfflineCacheRecord &rec)
460 {
461 nsCacheEntry *entry;
463 if (device->IsLocked(*fullKey)) {
464 return nullptr;
465 }
467 nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
468 nsICache::STREAM_BASED,
469 nsICache::STORE_OFFLINE,
470 device, &entry);
471 if (NS_FAILED(rv))
472 return nullptr;
474 entry->SetFetchCount((uint32_t) rec.fetchCount);
475 entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
476 entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
477 entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
478 entry->SetDataSize((uint32_t) rec.dataSize);
480 entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
482 // Restore security info, if present
483 const char* info = entry->GetMetaDataElement("security-info");
484 if (info) {
485 nsCOMPtr<nsISupports> infoObj;
486 rv = NS_DeserializeObject(nsDependentCString(info),
487 getter_AddRefs(infoObj));
488 if (NS_FAILED(rv)) {
489 delete entry;
490 return nullptr;
491 }
492 entry->SetSecurityInfo(infoObj);
493 }
495 // create a binding object for this entry
496 nsOfflineCacheBinding *binding =
497 nsOfflineCacheBinding::Create(device->CacheDirectory(),
498 fullKey,
499 rec.generation);
500 if (!binding)
501 {
502 delete entry;
503 return nullptr;
504 }
505 entry->SetData(binding);
507 return entry;
508 }
511 /******************************************************************************
512 * nsOfflineCacheEntryInfo
513 */
515 class nsOfflineCacheEntryInfo MOZ_FINAL : public nsICacheEntryInfo
516 {
517 public:
518 NS_DECL_ISUPPORTS
519 NS_DECL_NSICACHEENTRYINFO
521 nsOfflineCacheRecord *mRec;
522 };
524 NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
526 NS_IMETHODIMP
527 nsOfflineCacheEntryInfo::GetClientID(char **result)
528 {
529 *result = NS_strdup(mRec->clientID);
530 return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
531 }
533 NS_IMETHODIMP
534 nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
535 {
536 *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
537 return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
538 }
540 NS_IMETHODIMP
541 nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
542 {
543 clientKey.Assign(mRec->key);
544 return NS_OK;
545 }
547 NS_IMETHODIMP
548 nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
549 {
550 *aFetchCount = mRec->fetchCount;
551 return NS_OK;
552 }
554 NS_IMETHODIMP
555 nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
556 {
557 *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
558 return NS_OK;
559 }
561 NS_IMETHODIMP
562 nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
563 {
564 *aLastModified = SecondsFromPRTime(mRec->lastModified);
565 return NS_OK;
566 }
568 NS_IMETHODIMP
569 nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
570 {
571 *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
572 return NS_OK;
573 }
575 NS_IMETHODIMP
576 nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
577 {
578 *aStreamBased = true;
579 return NS_OK;
580 }
582 NS_IMETHODIMP
583 nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
584 {
585 *aDataSize = mRec->dataSize;
586 return NS_OK;
587 }
590 /******************************************************************************
591 * nsApplicationCacheNamespace
592 */
594 NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
596 NS_IMETHODIMP
597 nsApplicationCacheNamespace::Init(uint32_t itemType,
598 const nsACString &namespaceSpec,
599 const nsACString &data)
600 {
601 mItemType = itemType;
602 mNamespaceSpec = namespaceSpec;
603 mData = data;
604 return NS_OK;
605 }
607 NS_IMETHODIMP
608 nsApplicationCacheNamespace::GetItemType(uint32_t *out)
609 {
610 *out = mItemType;
611 return NS_OK;
612 }
614 NS_IMETHODIMP
615 nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
616 {
617 out = mNamespaceSpec;
618 return NS_OK;
619 }
621 NS_IMETHODIMP
622 nsApplicationCacheNamespace::GetData(nsACString &out)
623 {
624 out = mData;
625 return NS_OK;
626 }
628 /******************************************************************************
629 * nsApplicationCache
630 */
632 NS_IMPL_ISUPPORTS(nsApplicationCache,
633 nsIApplicationCache,
634 nsISupportsWeakReference)
636 nsApplicationCache::nsApplicationCache()
637 : mDevice(nullptr)
638 , mValid(true)
639 {
640 }
642 nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
643 const nsACString &group,
644 const nsACString &clientID)
645 : mDevice(device)
646 , mGroup(group)
647 , mClientID(clientID)
648 , mValid(true)
649 {
650 }
652 nsApplicationCache::~nsApplicationCache()
653 {
654 if (!mDevice)
655 return;
657 {
658 MutexAutoLock lock(mDevice->mLock);
659 mDevice->mCaches.Remove(mClientID);
660 }
662 // If this isn't an active cache anymore, it can be destroyed.
663 if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
664 Discard();
665 }
667 void
668 nsApplicationCache::MarkInvalid()
669 {
670 mValid = false;
671 }
673 NS_IMETHODIMP
674 nsApplicationCache::InitAsHandle(const nsACString &groupId,
675 const nsACString &clientId)
676 {
677 NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
678 NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
680 mGroup = groupId;
681 mClientID = clientId;
682 return NS_OK;
683 }
685 NS_IMETHODIMP
686 nsApplicationCache::GetManifestURI(nsIURI **out)
687 {
688 nsCOMPtr<nsIURI> uri;
689 nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
690 NS_ENSURE_SUCCESS(rv, rv);
692 rv = uri->CloneIgnoringRef(out);
693 NS_ENSURE_SUCCESS(rv, rv);
695 return NS_OK;
696 }
698 NS_IMETHODIMP
699 nsApplicationCache::GetGroupID(nsACString &out)
700 {
701 out = mGroup;
702 return NS_OK;
703 }
705 NS_IMETHODIMP
706 nsApplicationCache::GetClientID(nsACString &out)
707 {
708 out = mClientID;
709 return NS_OK;
710 }
712 NS_IMETHODIMP
713 nsApplicationCache::GetProfileDirectory(nsIFile **out)
714 {
715 if (mDevice->BaseDirectory())
716 NS_ADDREF(*out = mDevice->BaseDirectory());
717 else
718 *out = nullptr;
720 return NS_OK;
721 }
723 NS_IMETHODIMP
724 nsApplicationCache::GetActive(bool *out)
725 {
726 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
728 *out = mDevice->IsActiveCache(mGroup, mClientID);
729 return NS_OK;
730 }
732 NS_IMETHODIMP
733 nsApplicationCache::Activate()
734 {
735 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
736 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
738 mDevice->ActivateCache(mGroup, mClientID);
740 if (mDevice->AutoShutdown(this))
741 mDevice = nullptr;
743 return NS_OK;
744 }
746 NS_IMETHODIMP
747 nsApplicationCache::Discard()
748 {
749 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
750 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
752 mValid = false;
754 nsRefPtr<nsIRunnable> ev =
755 new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
756 nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
757 return rv;
758 }
760 NS_IMETHODIMP
761 nsApplicationCache::MarkEntry(const nsACString &key,
762 uint32_t typeBits)
763 {
764 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
765 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
767 return mDevice->MarkEntry(mClientID, key, typeBits);
768 }
771 NS_IMETHODIMP
772 nsApplicationCache::UnmarkEntry(const nsACString &key,
773 uint32_t typeBits)
774 {
775 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
776 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
778 return mDevice->UnmarkEntry(mClientID, key, typeBits);
779 }
781 NS_IMETHODIMP
782 nsApplicationCache::GetTypes(const nsACString &key,
783 uint32_t *typeBits)
784 {
785 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
786 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
788 return mDevice->GetTypes(mClientID, key, typeBits);
789 }
791 NS_IMETHODIMP
792 nsApplicationCache::GatherEntries(uint32_t typeBits,
793 uint32_t * count,
794 char *** keys)
795 {
796 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
797 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
799 return mDevice->GatherEntries(mClientID, typeBits, count, keys);
800 }
802 NS_IMETHODIMP
803 nsApplicationCache::AddNamespaces(nsIArray *namespaces)
804 {
805 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
806 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
808 if (!namespaces)
809 return NS_OK;
811 mozStorageTransaction transaction(mDevice->mDB, false);
813 uint32_t length;
814 nsresult rv = namespaces->GetLength(&length);
815 NS_ENSURE_SUCCESS(rv, rv);
817 for (uint32_t i = 0; i < length; i++) {
818 nsCOMPtr<nsIApplicationCacheNamespace> ns =
819 do_QueryElementAt(namespaces, i);
820 if (ns) {
821 rv = mDevice->AddNamespace(mClientID, ns);
822 NS_ENSURE_SUCCESS(rv, rv);
823 }
824 }
826 rv = transaction.Commit();
827 NS_ENSURE_SUCCESS(rv, rv);
829 return NS_OK;
830 }
832 NS_IMETHODIMP
833 nsApplicationCache::GetMatchingNamespace(const nsACString &key,
834 nsIApplicationCacheNamespace **out)
836 {
837 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
838 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
840 return mDevice->GetMatchingNamespace(mClientID, key, out);
841 }
843 NS_IMETHODIMP
844 nsApplicationCache::GetUsage(uint32_t *usage)
845 {
846 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
847 NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
849 return mDevice->GetUsage(mClientID, usage);
850 }
852 /******************************************************************************
853 * nsCloseDBEvent
854 *****************************************************************************/
856 class nsCloseDBEvent : public nsRunnable {
857 public:
858 nsCloseDBEvent(mozIStorageConnection *aDB)
859 {
860 mDB = aDB;
861 }
863 NS_IMETHOD Run()
864 {
865 mDB->Close();
866 return NS_OK;
867 }
869 protected:
870 virtual ~nsCloseDBEvent() {}
872 private:
873 nsCOMPtr<mozIStorageConnection> mDB;
874 };
878 /******************************************************************************
879 * nsOfflineCacheDevice
880 */
882 NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
884 nsOfflineCacheDevice::nsOfflineCacheDevice()
885 : mDB(nullptr)
886 , mCacheCapacity(0)
887 , mDeltaCounter(0)
888 , mAutoShutdown(false)
889 , mLock("nsOfflineCacheDevice.lock")
890 , mActiveCaches(5)
891 , mLockedEntries(64)
892 {
893 }
895 /* static */
896 bool
897 nsOfflineCacheDevice::GetStrictFileOriginPolicy()
898 {
899 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
901 bool retval;
902 if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
903 return retval;
905 // As default value use true (be more strict)
906 return true;
907 }
909 uint32_t
910 nsOfflineCacheDevice::CacheSize()
911 {
912 AutoResetStatement statement(mStatement_CacheSize);
914 bool hasRows;
915 nsresult rv = statement->ExecuteStep(&hasRows);
916 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
918 return (uint32_t) statement->AsInt32(0);
919 }
921 uint32_t
922 nsOfflineCacheDevice::EntryCount()
923 {
924 AutoResetStatement statement(mStatement_EntryCount);
926 bool hasRows;
927 nsresult rv = statement->ExecuteStep(&hasRows);
928 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
930 return (uint32_t) statement->AsInt32(0);
931 }
933 nsresult
934 nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
935 {
936 // Decompose the key into "ClientID" and "Key"
937 nsAutoCString keyBuf;
938 const char *cid, *key;
940 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
941 return NS_ERROR_UNEXPECTED;
943 // Store security info, if it is serializable
944 nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
945 nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
946 if (infoObj && !serializable)
947 return NS_ERROR_UNEXPECTED;
949 if (serializable) {
950 nsCString info;
951 nsresult rv = NS_SerializeToString(serializable, info);
952 NS_ENSURE_SUCCESS(rv, rv);
954 rv = entry->SetMetaDataElement("security-info", info.get());
955 NS_ENSURE_SUCCESS(rv, rv);
956 }
958 nsCString metaDataBuf;
959 uint32_t mdSize = entry->MetaDataSize();
960 if (!metaDataBuf.SetLength(mdSize, fallible_t()))
961 return NS_ERROR_OUT_OF_MEMORY;
962 char *md = metaDataBuf.BeginWriting();
963 entry->FlattenMetaData(md, mdSize);
965 nsOfflineCacheRecord rec;
966 rec.metaData = (const uint8_t *) md;
967 rec.metaDataLen = mdSize;
968 rec.dataSize = entry->DataSize();
969 rec.fetchCount = entry->FetchCount();
970 rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
971 rec.lastModified = PRTimeFromSeconds(entry->LastModified());
972 rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
974 AutoResetStatement statement(mStatement_UpdateEntry);
976 nsresult rv;
977 rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
978 nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
979 if (NS_FAILED(tmp)) {
980 rv = tmp;
981 }
982 tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
983 if (NS_FAILED(tmp)) {
984 rv = tmp;
985 }
986 tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
987 if (NS_FAILED(tmp)) {
988 rv = tmp;
989 }
990 tmp = statement->BindInt64ByIndex(4, rec.lastModified);
991 if (NS_FAILED(tmp)) {
992 rv = tmp;
993 }
994 tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
995 if (NS_FAILED(tmp)) {
996 rv = tmp;
997 }
998 tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
999 if (NS_FAILED(tmp)) {
1000 rv = tmp;
1001 }
1002 tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
1003 if (NS_FAILED(tmp)) {
1004 rv = tmp;
1005 }
1006 NS_ENSURE_SUCCESS(rv, rv);
1008 bool hasRows;
1009 rv = statement->ExecuteStep(&hasRows);
1010 NS_ENSURE_SUCCESS(rv, rv);
1012 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1013 return rv;
1014 }
1016 nsresult
1017 nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize)
1018 {
1019 // Decompose the key into "ClientID" and "Key"
1020 nsAutoCString keyBuf;
1021 const char *cid, *key;
1022 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1023 return NS_ERROR_UNEXPECTED;
1025 AutoResetStatement statement(mStatement_UpdateEntrySize);
1027 nsresult rv = statement->BindInt32ByIndex(0, newSize);
1028 nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
1029 if (NS_FAILED(tmp)) {
1030 rv = tmp;
1031 }
1032 tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
1033 if (NS_FAILED(tmp)) {
1034 rv = tmp;
1035 }
1036 NS_ENSURE_SUCCESS(rv, rv);
1038 bool hasRows;
1039 rv = statement->ExecuteStep(&hasRows);
1040 NS_ENSURE_SUCCESS(rv, rv);
1042 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1043 return rv;
1044 }
1046 nsresult
1047 nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
1048 {
1049 if (deleteData)
1050 {
1051 nsresult rv = DeleteData(entry);
1052 if (NS_FAILED(rv))
1053 return rv;
1054 }
1056 // Decompose the key into "ClientID" and "Key"
1057 nsAutoCString keyBuf;
1058 const char *cid, *key;
1059 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1060 return NS_ERROR_UNEXPECTED;
1062 AutoResetStatement statement(mStatement_DeleteEntry);
1064 nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1065 nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1066 NS_ENSURE_SUCCESS(rv, rv);
1067 NS_ENSURE_SUCCESS(rv2, rv2);
1069 bool hasRows;
1070 rv = statement->ExecuteStep(&hasRows);
1071 NS_ENSURE_SUCCESS(rv, rv);
1073 NS_ASSERTION(!hasRows, "DELETE should not result in output");
1074 return rv;
1075 }
1077 nsresult
1078 nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
1079 {
1080 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1081 NS_ENSURE_STATE(binding);
1083 return binding->mDataFile->Remove(false);
1084 }
1086 /**
1087 * nsCacheDevice implementation
1088 */
1090 // This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
1091 // allow a template (mozilla::ArrayLength) to be instantiated based on a local
1092 // type. Boo-urns!
1093 struct StatementSql {
1094 nsCOMPtr<mozIStorageStatement> &statement;
1095 const char *sql;
1096 StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
1097 statement (aStatement), sql (aSql) {}
1098 };
1100 nsresult
1101 nsOfflineCacheDevice::Init()
1102 {
1103 MOZ_ASSERT(false, "Need to be initialized with sqlite");
1104 return NS_ERROR_NOT_IMPLEMENTED;
1105 }
1107 nsresult
1108 nsOfflineCacheDevice::InitWithSqlite(mozIStorageService * ss)
1109 {
1110 NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
1112 // SetCacheParentDirectory must have been called
1113 NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
1115 // make sure the cache directory exists
1116 nsresult rv = EnsureDir(mCacheDirectory);
1117 NS_ENSURE_SUCCESS(rv, rv);
1119 // build path to index file
1120 nsCOMPtr<nsIFile> indexFile;
1121 rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
1122 NS_ENSURE_SUCCESS(rv, rv);
1123 rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
1124 NS_ENSURE_SUCCESS(rv, rv);
1126 MOZ_ASSERT(ss, "nsOfflineCacheDevice::InitWithSqlite called before nsCacheService::Init() ?");
1127 NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
1129 rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
1130 NS_ENSURE_SUCCESS(rv, rv);
1132 mInitThread = do_GetCurrentThread();
1134 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
1136 // XXX ... other initialization steps
1138 // XXX in the future we may wish to verify the schema for moz_cache
1139 // perhaps using "PRAGMA table_info" ?
1141 // build the table
1142 //
1143 // "Generation" is the data file generation number.
1144 //
1145 rv = mDB->ExecuteSimpleSQL(
1146 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1147 " ClientID TEXT,\n"
1148 " Key TEXT,\n"
1149 " MetaData BLOB,\n"
1150 " Generation INTEGER,\n"
1151 " DataSize INTEGER,\n"
1152 " FetchCount INTEGER,\n"
1153 " LastFetched INTEGER,\n"
1154 " LastModified INTEGER,\n"
1155 " ExpirationTime INTEGER,\n"
1156 " ItemType INTEGER DEFAULT 0\n"
1157 ");\n"));
1158 NS_ENSURE_SUCCESS(rv, rv);
1160 // Databases from 1.9.0 don't have the ItemType column. Add the column
1161 // here, but don't worry about failures (the column probably already exists)
1162 mDB->ExecuteSimpleSQL(
1163 NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1165 // Create the table for storing cache groups. All actions on
1166 // moz_cache_groups use the GroupID, so use it as the primary key.
1167 rv = mDB->ExecuteSimpleSQL(
1168 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1169 " GroupID TEXT PRIMARY KEY,\n"
1170 " ActiveClientID TEXT\n"
1171 ");\n"));
1172 NS_ENSURE_SUCCESS(rv, rv);
1174 mDB->ExecuteSimpleSQL(
1175 NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1176 "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1178 // ClientID: clientID joining moz_cache and moz_cache_namespaces
1179 // tables.
1180 // Data: Data associated with this namespace (e.g. a fallback URI
1181 // for fallback entries).
1182 // ItemType: the type of namespace.
1183 rv = mDB->ExecuteSimpleSQL(
1184 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1185 " moz_cache_namespaces (\n"
1186 " ClientID TEXT,\n"
1187 " NameSpace TEXT,\n"
1188 " Data TEXT,\n"
1189 " ItemType INTEGER\n"
1190 ");\n"));
1191 NS_ENSURE_SUCCESS(rv, rv);
1193 // Databases from 1.9.0 have a moz_cache_index that should be dropped
1194 rv = mDB->ExecuteSimpleSQL(
1195 NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1196 NS_ENSURE_SUCCESS(rv, rv);
1198 // Key/ClientID pairs should be unique in the database. All queries
1199 // against moz_cache use the Key (which is also the most unique), so
1200 // use it as the primary key for this index.
1201 rv = mDB->ExecuteSimpleSQL(
1202 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1203 " moz_cache_key_clientid_index"
1204 " ON moz_cache (Key, ClientID);"));
1205 NS_ENSURE_SUCCESS(rv, rv);
1207 // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1208 rv = mDB->ExecuteSimpleSQL(
1209 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1210 " moz_cache_namespaces_clientid_index"
1211 " ON moz_cache_namespaces (ClientID, NameSpace);"));
1212 NS_ENSURE_SUCCESS(rv, rv);
1214 // Used for namespace lookups.
1215 rv = mDB->ExecuteSimpleSQL(
1216 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1217 " moz_cache_namespaces_namespace_index"
1218 " ON moz_cache_namespaces (NameSpace);"));
1219 NS_ENSURE_SUCCESS(rv, rv);
1222 mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
1223 if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
1225 rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction);
1226 NS_ENSURE_SUCCESS(rv, rv);
1228 // create all (most) of our statements up front
1229 StatementSql prepared[] = {
1230 StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
1231 StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
1232 StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
1233 StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1234 StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1235 StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1236 StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1237 StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
1239 StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1240 StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1241 StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1242 StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1243 StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1245 StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1246 StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1247 StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1249 // Search for namespaces that match the URI. Use the <= operator
1250 // to ensure that we use the index on moz_cache_namespaces.
1251 StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
1252 " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1253 " ON ns.ClientID = groups.ActiveClientID"
1254 " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1255 " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1256 StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
1257 " WHERE ClientID = ?1"
1258 " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1259 " ORDER BY NameSpace DESC;"),
1260 StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1261 StatementSql ( mStatement_EnumerateApps, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
1262 StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
1263 StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
1264 };
1265 for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i)
1266 {
1267 LOG(("Creating statement: %s\n", prepared[i].sql));
1269 rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
1270 getter_AddRefs(prepared[i].statement));
1271 NS_ENSURE_SUCCESS(rv, rv);
1272 }
1274 rv = InitActiveCaches();
1275 NS_ENSURE_SUCCESS(rv, rv);
1277 return NS_OK;
1278 }
1280 namespace {
1282 nsresult
1283 GetGroupForCache(const nsCSubstring &clientID, nsCString &group)
1284 {
1285 group.Assign(clientID);
1286 group.Truncate(group.FindChar('|'));
1287 NS_UnescapeURL(group);
1289 return NS_OK;
1290 }
1292 nsresult
1293 AppendJARIdentifier(nsACString &_result, int32_t appId, bool isInBrowserElement)
1294 {
1295 _result.Append('#');
1296 _result.AppendInt(appId);
1297 _result.Append('+');
1298 _result.Append(isInBrowserElement ? 't' : 'f');
1300 return NS_OK;
1301 }
1303 nsresult
1304 GetJARIdentifier(nsIURI *aURI,
1305 uint32_t appId, bool isInBrowserElement,
1306 nsACString &_result)
1307 {
1308 _result.Truncate();
1310 // These lines are here for compatibility only. We must not fill the
1311 // JAR identifier when this is no-app context, otherwise web content
1312 // offline application cache loads would not be satisfied (cache would
1313 // not be found).
1314 if (!isInBrowserElement && appId == NECKO_NO_APP_ID)
1315 return NS_OK;
1317 // This load context has some special attributes, create a jar identifier
1318 return AppendJARIdentifier(_result, appId, isInBrowserElement);
1319 }
1321 } // anon namespace
1323 // static
1324 nsresult
1325 nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL,
1326 uint32_t appId, bool isInBrowserElement,
1327 nsACString &_result)
1328 {
1329 nsCOMPtr<nsIURI> newURI;
1330 nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI));
1331 NS_ENSURE_SUCCESS(rv, rv);
1333 nsAutoCString manifestSpec;
1334 rv = newURI->GetAsciiSpec(manifestSpec);
1335 NS_ENSURE_SUCCESS(rv, rv);
1337 _result.Assign(manifestSpec);
1339 nsAutoCString jarid;
1340 rv = GetJARIdentifier(aManifestURL, appId, isInBrowserElement, jarid);
1341 NS_ENSURE_SUCCESS(rv, rv);
1343 // Include JAR ID, i.e. the extended origin if present.
1344 if (!jarid.IsEmpty())
1345 _result.Append(jarid);
1347 return NS_OK;
1348 }
1350 nsresult
1351 nsOfflineCacheDevice::InitActiveCaches()
1352 {
1353 MutexAutoLock lock(mLock);
1355 AutoResetStatement statement(mStatement_EnumerateGroups);
1357 bool hasRows;
1358 nsresult rv = statement->ExecuteStep(&hasRows);
1359 NS_ENSURE_SUCCESS(rv, rv);
1361 while (hasRows)
1362 {
1363 nsAutoCString group;
1364 statement->GetUTF8String(0, group);
1365 nsCString clientID;
1366 statement->GetUTF8String(1, clientID);
1368 mActiveCaches.PutEntry(clientID);
1369 mActiveCachesByGroup.Put(group, new nsCString(clientID));
1371 rv = statement->ExecuteStep(&hasRows);
1372 NS_ENSURE_SUCCESS(rv, rv);
1373 }
1375 return NS_OK;
1376 }
1378 /* static */
1379 PLDHashOperator
1380 nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key,
1381 nsIWeakReference *weakRef,
1382 void *ctx)
1383 {
1384 nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(weakRef);
1385 if (obj)
1386 {
1387 nsApplicationCache *appCache = static_cast<nsApplicationCache*>(obj.get());
1388 appCache->MarkInvalid();
1389 }
1391 return PL_DHASH_NEXT;
1392 }
1394 nsresult
1395 nsOfflineCacheDevice::Shutdown()
1396 {
1397 NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
1399 {
1400 MutexAutoLock lock(mLock);
1401 mCaches.EnumerateRead(ShutdownApplicationCache, this);
1402 }
1404 {
1405 EvictionObserver evictionObserver(mDB, mEvictionFunction);
1407 // Delete all rows whose clientID is not an active clientID.
1408 nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1409 "DELETE FROM moz_cache WHERE rowid IN"
1410 " (SELECT moz_cache.rowid FROM"
1411 " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1412 " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1413 " WHERE moz_cache_groups.GroupID ISNULL)"));
1415 if (NS_FAILED(rv))
1416 NS_WARNING("Failed to clean up unused application caches.");
1417 else
1418 evictionObserver.Apply();
1420 // Delete all namespaces whose clientID is not an active clientID.
1421 rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1422 "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1423 " (SELECT moz_cache_namespaces.rowid FROM"
1424 " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1425 " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
1426 " WHERE moz_cache_groups.GroupID ISNULL)"));
1428 if (NS_FAILED(rv))
1429 NS_WARNING("Failed to clean up namespaces.");
1431 mEvictionFunction = 0;
1433 mStatement_CacheSize = nullptr;
1434 mStatement_ApplicationCacheSize = nullptr;
1435 mStatement_EntryCount = nullptr;
1436 mStatement_UpdateEntry = nullptr;
1437 mStatement_UpdateEntrySize = nullptr;
1438 mStatement_DeleteEntry = nullptr;
1439 mStatement_FindEntry = nullptr;
1440 mStatement_BindEntry = nullptr;
1441 mStatement_ClearDomain = nullptr;
1442 mStatement_MarkEntry = nullptr;
1443 mStatement_UnmarkEntry = nullptr;
1444 mStatement_GetTypes = nullptr;
1445 mStatement_FindNamespaceEntry = nullptr;
1446 mStatement_InsertNamespaceEntry = nullptr;
1447 mStatement_CleanupUnmarked = nullptr;
1448 mStatement_GatherEntries = nullptr;
1449 mStatement_ActivateClient = nullptr;
1450 mStatement_DeactivateGroup = nullptr;
1451 mStatement_FindClient = nullptr;
1452 mStatement_FindClientByNamespace = nullptr;
1453 mStatement_EnumerateApps = nullptr;
1454 mStatement_EnumerateGroups = nullptr;
1455 mStatement_EnumerateGroupsTimeOrder = nullptr;
1456 }
1458 // Close Database on the correct thread
1459 bool isOnCurrentThread = true;
1460 if (mInitThread)
1461 mInitThread->IsOnCurrentThread(&isOnCurrentThread);
1463 if (!isOnCurrentThread) {
1464 nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
1466 if (ev) {
1467 mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1468 }
1469 }
1470 else {
1471 mDB->Close();
1472 }
1474 mDB = nullptr;
1475 mInitThread = nullptr;
1477 return NS_OK;
1478 }
1480 const char *
1481 nsOfflineCacheDevice::GetDeviceID()
1482 {
1483 return OFFLINE_CACHE_DEVICE_ID;
1484 }
1486 nsCacheEntry *
1487 nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
1488 {
1489 mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2> timer;
1490 LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
1492 // SELECT * FROM moz_cache WHERE key = ?
1494 // Decompose the key into "ClientID" and "Key"
1495 nsAutoCString keyBuf;
1496 const char *cid, *key;
1497 if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
1498 return nullptr;
1500 AutoResetStatement statement(mStatement_FindEntry);
1502 nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1503 nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1504 NS_ENSURE_SUCCESS(rv, nullptr);
1505 NS_ENSURE_SUCCESS(rv2, nullptr);
1507 bool hasRows;
1508 rv = statement->ExecuteStep(&hasRows);
1509 if (NS_FAILED(rv) || !hasRows)
1510 return nullptr; // entry not found
1512 nsOfflineCacheRecord rec;
1513 statement->GetSharedBlob(0, &rec.metaDataLen,
1514 (const uint8_t **) &rec.metaData);
1515 rec.generation = statement->AsInt32(1);
1516 rec.dataSize = statement->AsInt32(2);
1517 rec.fetchCount = statement->AsInt32(3);
1518 rec.lastFetched = statement->AsInt64(4);
1519 rec.lastModified = statement->AsInt64(5);
1520 rec.expirationTime = statement->AsInt64(6);
1522 LOG(("entry: [%u %d %d %d %lld %lld %lld]\n",
1523 rec.metaDataLen,
1524 rec.generation,
1525 rec.dataSize,
1526 rec.fetchCount,
1527 rec.lastFetched,
1528 rec.lastModified,
1529 rec.expirationTime));
1531 nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
1533 if (entry)
1534 {
1535 // make sure that the data file exists
1536 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
1537 bool isFile;
1538 rv = binding->mDataFile->IsFile(&isFile);
1539 if (NS_FAILED(rv) || !isFile)
1540 {
1541 DeleteEntry(entry, false);
1542 delete entry;
1543 return nullptr;
1544 }
1546 // lock the entry
1547 Lock(*fullKey);
1548 }
1550 return entry;
1551 }
1553 nsresult
1554 nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
1555 {
1556 LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1557 entry->Key()->get()));
1559 // This method is called to inform us that the nsCacheEntry object is going
1560 // away. We should persist anything that needs to be persisted, or if the
1561 // entry is doomed, we can go ahead and clear its storage.
1563 if (entry->IsDoomed())
1564 {
1565 // remove corresponding row and file if they exist
1567 // the row should have been removed in DoomEntry... we could assert that
1568 // that happened. otherwise, all we have to do here is delete the file
1569 // on disk.
1570 DeleteData(entry);
1571 }
1572 else if (((nsOfflineCacheBinding *)entry->Data())->IsNewEntry())
1573 {
1574 // UPDATE the database row
1576 // Only new entries are updated, since offline cache is updated in
1577 // transactions. New entries are those who is returned from
1578 // BindEntry().
1580 LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
1581 UpdateEntry(entry);
1582 } else {
1583 LOG(("nsOfflineCacheDevice::DeactivateEntry "
1584 "skipping update since entry is not dirty\n"));
1585 }
1587 // Unlock the entry
1588 Unlock(*entry->Key());
1590 delete entry;
1592 return NS_OK;
1593 }
1595 nsresult
1596 nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
1597 {
1598 LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
1600 NS_ENSURE_STATE(!entry->Data());
1602 // This method is called to inform us that we have a new entry. The entry
1603 // may collide with an existing entry in our DB, but if that happens we can
1604 // assume that the entry is not being used.
1606 // INSERT the database row
1608 // XXX Assumption: if the row already exists, then FindEntry would have
1609 // returned it. if that entry was doomed, then DoomEntry would have removed
1610 // it from the table. so, we should always have to insert at this point.
1612 // Decompose the key into "ClientID" and "Key"
1613 nsAutoCString keyBuf;
1614 const char *cid, *key;
1615 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1616 return NS_ERROR_UNEXPECTED;
1618 // create binding, pick best generation number
1619 nsRefPtr<nsOfflineCacheBinding> binding =
1620 nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
1621 if (!binding)
1622 return NS_ERROR_OUT_OF_MEMORY;
1623 binding->MarkNewEntry();
1625 nsOfflineCacheRecord rec;
1626 rec.clientID = cid;
1627 rec.key = key;
1628 rec.metaData = nullptr; // don't write any metadata now.
1629 rec.metaDataLen = 0;
1630 rec.generation = binding->mGeneration;
1631 rec.dataSize = 0;
1632 rec.fetchCount = entry->FetchCount();
1633 rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1634 rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1635 rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1637 AutoResetStatement statement(mStatement_BindEntry);
1639 nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
1640 nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
1641 if (NS_FAILED(tmp)) {
1642 rv = tmp;
1643 }
1644 tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
1645 if (NS_FAILED(tmp)) {
1646 rv = tmp;
1647 }
1648 tmp = statement->BindInt32ByIndex(3, rec.generation);
1649 if (NS_FAILED(tmp)) {
1650 rv = tmp;
1651 }
1652 tmp = statement->BindInt32ByIndex(4, rec.dataSize);
1653 if (NS_FAILED(tmp)) {
1654 rv = tmp;
1655 }
1656 tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
1657 if (NS_FAILED(tmp)) {
1658 rv = tmp;
1659 }
1660 tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
1661 if (NS_FAILED(tmp)) {
1662 rv = tmp;
1663 }
1664 tmp = statement->BindInt64ByIndex(7, rec.lastModified);
1665 if (NS_FAILED(tmp)) {
1666 rv = tmp;
1667 }
1668 tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
1669 if (NS_FAILED(tmp)) {
1670 rv = tmp;
1671 }
1672 NS_ENSURE_SUCCESS(rv, rv);
1674 bool hasRows;
1675 rv = statement->ExecuteStep(&hasRows);
1676 NS_ENSURE_SUCCESS(rv, rv);
1677 NS_ASSERTION(!hasRows, "INSERT should not result in output");
1679 entry->SetData(binding);
1681 // lock the entry
1682 Lock(*entry->Key());
1684 return NS_OK;
1685 }
1687 void
1688 nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
1689 {
1690 LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
1692 // This method is called to inform us that we should mark the entry to be
1693 // deleted when it is no longer in use.
1695 // We can go ahead and delete the corresponding row in our table,
1696 // but we must not delete the file on disk until we are deactivated.
1697 // In another word, the file should be deleted if the entry had been
1698 // deactivated.
1700 DeleteEntry(entry, !entry->IsActive());
1701 }
1703 nsresult
1704 nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
1705 nsCacheAccessMode mode,
1706 uint32_t offset,
1707 nsIInputStream **result)
1708 {
1709 LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1710 entry->Key()->get()));
1712 *result = nullptr;
1714 NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
1716 // return an input stream to the entry's data file. the stream
1717 // may be read on a background thread.
1719 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1720 NS_ENSURE_STATE(binding);
1722 nsCOMPtr<nsIInputStream> in;
1723 NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
1724 if (!in)
1725 return NS_ERROR_UNEXPECTED;
1727 // respect |offset| param
1728 if (offset != 0)
1729 {
1730 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1731 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1733 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1734 }
1736 in.swap(*result);
1737 return NS_OK;
1738 }
1740 nsresult
1741 nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
1742 nsCacheAccessMode mode,
1743 uint32_t offset,
1744 nsIOutputStream **result)
1745 {
1746 LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1747 entry->Key()->get()));
1749 *result = nullptr;
1751 NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
1753 // return an output stream to the entry's data file. we can assume
1754 // that the output stream will only be used on the main thread.
1756 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1757 NS_ENSURE_STATE(binding);
1759 nsCOMPtr<nsIOutputStream> out;
1760 NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
1761 PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1762 00600);
1763 if (!out)
1764 return NS_ERROR_UNEXPECTED;
1766 // respect |offset| param
1767 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1768 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1769 if (offset != 0)
1770 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1772 // truncate the file at the given offset
1773 seekable->SetEOF();
1775 nsCOMPtr<nsIOutputStream> bufferedOut;
1776 nsresult rv =
1777 NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
1778 NS_ENSURE_SUCCESS(rv, rv);
1780 bufferedOut.swap(*result);
1781 return NS_OK;
1782 }
1784 nsresult
1785 nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
1786 {
1787 LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1788 entry->Key()->get()));
1790 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1791 NS_ENSURE_STATE(binding);
1793 NS_IF_ADDREF(*result = binding->mDataFile);
1794 return NS_OK;
1795 }
1797 nsresult
1798 nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, int32_t deltaSize)
1799 {
1800 LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1801 entry->Key()->get(), deltaSize));
1803 const int32_t DELTA_THRESHOLD = 1<<14; // 16k
1805 // called to notify us of an impending change in the total size of the
1806 // specified entry.
1808 uint32_t oldSize = entry->DataSize();
1809 NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
1810 uint32_t newSize = int32_t(oldSize) + deltaSize;
1811 UpdateEntrySize(entry, newSize);
1813 mDeltaCounter += deltaSize; // this may go negative
1815 if (mDeltaCounter >= DELTA_THRESHOLD)
1816 {
1817 if (CacheSize() > mCacheCapacity) {
1818 // the entry will overrun the cache capacity, doom the entry
1819 // and abort
1820 #ifdef DEBUG
1821 nsresult rv =
1822 #endif
1823 nsCacheService::DoomEntry(entry);
1824 NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1825 return NS_ERROR_ABORT;
1826 }
1828 mDeltaCounter = 0; // reset counter
1829 }
1831 return NS_OK;
1832 }
1834 nsresult
1835 nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
1836 {
1837 NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1839 // called to enumerate the offline cache.
1841 nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
1842 new nsOfflineCacheDeviceInfo(this);
1844 bool keepGoing;
1845 nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
1846 &keepGoing);
1847 if (NS_FAILED(rv))
1848 return rv;
1850 if (!keepGoing)
1851 return NS_OK;
1853 // SELECT * from moz_cache;
1855 nsOfflineCacheRecord rec;
1856 nsRefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1857 if (!info)
1858 return NS_ERROR_OUT_OF_MEMORY;
1859 info->mRec = &rec;
1861 // XXX may want to list columns explicitly
1862 nsCOMPtr<mozIStorageStatement> statement;
1863 rv = mDB->CreateStatement(
1864 NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1865 getter_AddRefs(statement));
1866 NS_ENSURE_SUCCESS(rv, rv);
1868 bool hasRows;
1869 for (;;)
1870 {
1871 rv = statement->ExecuteStep(&hasRows);
1872 if (NS_FAILED(rv) || !hasRows)
1873 break;
1875 statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
1876 statement->GetSharedUTF8String(1, nullptr, &rec.key);
1877 statement->GetSharedBlob(2, &rec.metaDataLen,
1878 (const uint8_t **) &rec.metaData);
1879 rec.generation = statement->AsInt32(3);
1880 rec.dataSize = statement->AsInt32(4);
1881 rec.fetchCount = statement->AsInt32(5);
1882 rec.lastFetched = statement->AsInt64(6);
1883 rec.lastModified = statement->AsInt64(7);
1884 rec.expirationTime = statement->AsInt64(8);
1886 bool keepGoing;
1887 rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1888 if (NS_FAILED(rv) || !keepGoing)
1889 break;
1890 }
1892 info->mRec = nullptr;
1893 return NS_OK;
1894 }
1896 nsresult
1897 nsOfflineCacheDevice::EvictEntries(const char *clientID)
1898 {
1899 LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1900 clientID ? clientID : ""));
1902 // called to evict all entries matching the given clientID.
1904 // need trigger to fire user defined function after a row is deleted
1905 // so we can delete the corresponding data file.
1906 EvictionObserver evictionObserver(mDB, mEvictionFunction);
1908 nsCOMPtr<mozIStorageStatement> statement;
1909 nsresult rv;
1910 if (clientID)
1911 {
1912 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
1913 getter_AddRefs(statement));
1914 NS_ENSURE_SUCCESS(rv, rv);
1916 rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1917 NS_ENSURE_SUCCESS(rv, rv);
1919 rv = statement->Execute();
1920 NS_ENSURE_SUCCESS(rv, rv);
1922 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
1923 getter_AddRefs(statement));
1924 NS_ENSURE_SUCCESS(rv, rv);
1926 rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1927 NS_ENSURE_SUCCESS(rv, rv);
1929 rv = statement->Execute();
1930 NS_ENSURE_SUCCESS(rv, rv);
1931 }
1932 else
1933 {
1934 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
1935 getter_AddRefs(statement));
1936 NS_ENSURE_SUCCESS(rv, rv);
1938 rv = statement->Execute();
1939 NS_ENSURE_SUCCESS(rv, rv);
1941 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
1942 getter_AddRefs(statement));
1943 NS_ENSURE_SUCCESS(rv, rv);
1945 rv = statement->Execute();
1946 NS_ENSURE_SUCCESS(rv, rv);
1947 }
1949 evictionObserver.Apply();
1951 statement = nullptr;
1952 // Also evict any namespaces associated with this clientID.
1953 if (clientID)
1954 {
1955 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
1956 getter_AddRefs(statement));
1957 NS_ENSURE_SUCCESS(rv, rv);
1959 rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1960 NS_ENSURE_SUCCESS(rv, rv);
1961 }
1962 else
1963 {
1964 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
1965 getter_AddRefs(statement));
1966 NS_ENSURE_SUCCESS(rv, rv);
1967 }
1969 rv = statement->Execute();
1970 NS_ENSURE_SUCCESS(rv, rv);
1972 return NS_OK;
1973 }
1975 nsresult
1976 nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
1977 const nsACString &key,
1978 uint32_t typeBits)
1979 {
1980 LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1981 clientID.get(), PromiseFlatCString(key).get(), typeBits));
1983 AutoResetStatement statement(mStatement_MarkEntry);
1984 nsresult rv = statement->BindInt32ByIndex(0, typeBits);
1985 NS_ENSURE_SUCCESS(rv, rv);
1986 rv = statement->BindUTF8StringByIndex(1, clientID);
1987 NS_ENSURE_SUCCESS(rv, rv);
1988 rv = statement->BindUTF8StringByIndex(2, key);
1989 NS_ENSURE_SUCCESS(rv, rv);
1991 rv = statement->Execute();
1992 NS_ENSURE_SUCCESS(rv, rv);
1994 return NS_OK;
1995 }
1997 nsresult
1998 nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
1999 const nsACString &key,
2000 uint32_t typeBits)
2001 {
2002 LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
2003 clientID.get(), PromiseFlatCString(key).get(), typeBits));
2005 AutoResetStatement statement(mStatement_UnmarkEntry);
2006 nsresult rv = statement->BindInt32ByIndex(0, typeBits);
2007 NS_ENSURE_SUCCESS(rv, rv);
2008 rv = statement->BindUTF8StringByIndex(1, clientID);
2009 NS_ENSURE_SUCCESS(rv, rv);
2010 rv = statement->BindUTF8StringByIndex(2, key);
2011 NS_ENSURE_SUCCESS(rv, rv);
2013 rv = statement->Execute();
2014 NS_ENSURE_SUCCESS(rv, rv);
2016 // Remove the entry if it is now empty.
2018 EvictionObserver evictionObserver(mDB, mEvictionFunction);
2020 AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
2021 rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
2022 NS_ENSURE_SUCCESS(rv, rv);
2023 rv = cleanupStatement->BindUTF8StringByIndex(1, key);
2024 NS_ENSURE_SUCCESS(rv, rv);
2026 rv = cleanupStatement->Execute();
2027 NS_ENSURE_SUCCESS(rv, rv);
2029 evictionObserver.Apply();
2031 return NS_OK;
2032 }
2034 nsresult
2035 nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
2036 const nsACString &key,
2037 nsIApplicationCacheNamespace **out)
2038 {
2039 LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
2040 clientID.get(), PromiseFlatCString(key).get()));
2042 nsresult rv;
2044 AutoResetStatement statement(mStatement_FindNamespaceEntry);
2046 rv = statement->BindUTF8StringByIndex(0, clientID);
2047 NS_ENSURE_SUCCESS(rv, rv);
2048 rv = statement->BindUTF8StringByIndex(1, key);
2049 NS_ENSURE_SUCCESS(rv, rv);
2051 bool hasRows;
2052 rv = statement->ExecuteStep(&hasRows);
2053 NS_ENSURE_SUCCESS(rv, rv);
2055 *out = nullptr;
2057 bool found = false;
2058 nsCString nsSpec;
2059 int32_t nsType = 0;
2060 nsCString nsData;
2062 while (hasRows)
2063 {
2064 int32_t itemType;
2065 rv = statement->GetInt32(2, &itemType);
2066 NS_ENSURE_SUCCESS(rv, rv);
2068 if (!found || itemType > nsType)
2069 {
2070 nsType = itemType;
2072 rv = statement->GetUTF8String(0, nsSpec);
2073 NS_ENSURE_SUCCESS(rv, rv);
2075 rv = statement->GetUTF8String(1, nsData);
2076 NS_ENSURE_SUCCESS(rv, rv);
2078 found = true;
2079 }
2081 rv = statement->ExecuteStep(&hasRows);
2082 NS_ENSURE_SUCCESS(rv, rv);
2083 }
2085 if (found) {
2086 nsCOMPtr<nsIApplicationCacheNamespace> ns =
2087 new nsApplicationCacheNamespace();
2088 if (!ns)
2089 return NS_ERROR_OUT_OF_MEMORY;
2090 rv = ns->Init(nsType, nsSpec, nsData);
2091 NS_ENSURE_SUCCESS(rv, rv);
2093 ns.swap(*out);
2094 }
2096 return NS_OK;
2097 }
2099 nsresult
2100 nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
2101 const nsACString &key)
2102 {
2103 // XXX: We should also be propagating this cache entry to other matching
2104 // caches. See bug 444807.
2106 return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
2107 }
2109 nsresult
2110 nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
2111 const nsACString &key,
2112 uint32_t *typeBits)
2113 {
2114 LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
2115 clientID.get(), PromiseFlatCString(key).get()));
2117 AutoResetStatement statement(mStatement_GetTypes);
2118 nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2119 NS_ENSURE_SUCCESS(rv, rv);
2120 rv = statement->BindUTF8StringByIndex(1, key);
2121 NS_ENSURE_SUCCESS(rv, rv);
2123 bool hasRows;
2124 rv = statement->ExecuteStep(&hasRows);
2125 NS_ENSURE_SUCCESS(rv, rv);
2127 if (!hasRows)
2128 return NS_ERROR_CACHE_KEY_NOT_FOUND;
2130 *typeBits = statement->AsInt32(0);
2132 return NS_OK;
2133 }
2135 nsresult
2136 nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
2137 uint32_t typeBits,
2138 uint32_t *count,
2139 char ***keys)
2140 {
2141 LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
2142 clientID.get(), typeBits));
2144 AutoResetStatement statement(mStatement_GatherEntries);
2145 nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2146 NS_ENSURE_SUCCESS(rv, rv);
2148 rv = statement->BindInt32ByIndex(1, typeBits);
2149 NS_ENSURE_SUCCESS(rv, rv);
2151 return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
2152 }
2154 nsresult
2155 nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
2156 nsIApplicationCacheNamespace *ns)
2157 {
2158 nsCString namespaceSpec;
2159 nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
2160 NS_ENSURE_SUCCESS(rv, rv);
2162 nsCString data;
2163 rv = ns->GetData(data);
2164 NS_ENSURE_SUCCESS(rv, rv);
2166 uint32_t itemType;
2167 rv = ns->GetItemType(&itemType);
2168 NS_ENSURE_SUCCESS(rv, rv);
2170 LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
2171 clientID.get(), namespaceSpec.get(), data.get(), itemType));
2173 AutoResetStatement statement(mStatement_InsertNamespaceEntry);
2175 rv = statement->BindUTF8StringByIndex(0, clientID);
2176 NS_ENSURE_SUCCESS(rv, rv);
2178 rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
2179 NS_ENSURE_SUCCESS(rv, rv);
2181 rv = statement->BindUTF8StringByIndex(2, data);
2182 NS_ENSURE_SUCCESS(rv, rv);
2184 rv = statement->BindInt32ByIndex(3, itemType);
2185 NS_ENSURE_SUCCESS(rv, rv);
2187 rv = statement->Execute();
2188 NS_ENSURE_SUCCESS(rv, rv);
2190 return NS_OK;
2191 }
2193 nsresult
2194 nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
2195 uint32_t *usage)
2196 {
2197 LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2198 PromiseFlatCString(clientID).get()));
2200 *usage = 0;
2202 AutoResetStatement statement(mStatement_ApplicationCacheSize);
2204 nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2205 NS_ENSURE_SUCCESS(rv, rv);
2207 bool hasRows;
2208 rv = statement->ExecuteStep(&hasRows);
2209 NS_ENSURE_SUCCESS(rv, rv);
2211 if (!hasRows)
2212 return NS_OK;
2214 *usage = static_cast<uint32_t>(statement->AsInt32(0));
2216 return NS_OK;
2217 }
2219 nsresult
2220 nsOfflineCacheDevice::GetGroups(uint32_t *count,
2221 char ***keys)
2222 {
2223 LOG(("nsOfflineCacheDevice::GetGroups"));
2225 return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
2226 }
2228 nsresult
2229 nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
2230 char ***keys)
2231 {
2232 LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
2234 return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
2235 }
2237 bool
2238 nsOfflineCacheDevice::IsLocked(const nsACString &key)
2239 {
2240 MutexAutoLock lock(mLock);
2241 return mLockedEntries.GetEntry(key);
2242 }
2244 void
2245 nsOfflineCacheDevice::Lock(const nsACString &key)
2246 {
2247 MutexAutoLock lock(mLock);
2248 mLockedEntries.PutEntry(key);
2249 }
2251 void
2252 nsOfflineCacheDevice::Unlock(const nsACString &key)
2253 {
2254 MutexAutoLock lock(mLock);
2255 mLockedEntries.RemoveEntry(key);
2256 }
2258 nsresult
2259 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
2260 uint32_t resultIndex,
2261 uint32_t * count,
2262 char *** values)
2263 {
2264 bool hasRows;
2265 nsresult rv = statement->ExecuteStep(&hasRows);
2266 NS_ENSURE_SUCCESS(rv, rv);
2268 nsTArray<nsCString> valArray;
2269 while (hasRows)
2270 {
2271 uint32_t length;
2272 valArray.AppendElement(
2273 nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
2275 rv = statement->ExecuteStep(&hasRows);
2276 NS_ENSURE_SUCCESS(rv, rv);
2277 }
2279 *count = valArray.Length();
2280 char **ret = static_cast<char **>(NS_Alloc(*count * sizeof(char*)));
2281 if (!ret) return NS_ERROR_OUT_OF_MEMORY;
2283 for (uint32_t i = 0; i < *count; i++) {
2284 ret[i] = NS_strdup(valArray[i].get());
2285 if (!ret[i]) {
2286 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
2287 return NS_ERROR_OUT_OF_MEMORY;
2288 }
2289 }
2291 *values = ret;
2293 return NS_OK;
2294 }
2296 nsresult
2297 nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
2298 nsIApplicationCache **out)
2299 {
2300 *out = nullptr;
2302 nsCString clientID;
2303 // Some characters are special in the clientID. Escape the groupID
2304 // before putting it in to the client key.
2305 if (!NS_Escape(nsCString(group), clientID, url_Path)) {
2306 return NS_ERROR_OUT_OF_MEMORY;
2307 }
2309 PRTime now = PR_Now();
2311 // Include the timestamp to guarantee uniqueness across runs, and
2312 // the gNextTemporaryClientID for uniqueness within a second.
2313 clientID.Append(nsPrintfCString("|%016lld|%d",
2314 now / PR_USEC_PER_SEC,
2315 gNextTemporaryClientID++));
2317 nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
2318 group,
2319 clientID);
2320 if (!cache)
2321 return NS_ERROR_OUT_OF_MEMORY;
2323 nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
2324 if (!weak)
2325 return NS_ERROR_OUT_OF_MEMORY;
2327 MutexAutoLock lock(mLock);
2328 mCaches.Put(clientID, weak);
2330 cache.swap(*out);
2332 return NS_OK;
2333 }
2335 nsresult
2336 nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
2337 nsIApplicationCache **out)
2338 {
2339 MutexAutoLock lock(mLock);
2340 return GetApplicationCache_Unlocked(clientID, out);
2341 }
2343 nsresult
2344 nsOfflineCacheDevice::GetApplicationCache_Unlocked(const nsACString &clientID,
2345 nsIApplicationCache **out)
2346 {
2347 *out = nullptr;
2349 nsCOMPtr<nsIApplicationCache> cache;
2351 nsWeakPtr weak;
2352 if (mCaches.Get(clientID, getter_AddRefs(weak)))
2353 cache = do_QueryReferent(weak);
2355 if (!cache)
2356 {
2357 nsCString group;
2358 nsresult rv = GetGroupForCache(clientID, group);
2359 NS_ENSURE_SUCCESS(rv, rv);
2361 if (group.IsEmpty()) {
2362 return NS_OK;
2363 }
2365 cache = new nsApplicationCache(this, group, clientID);
2366 weak = do_GetWeakReference(cache);
2367 if (!weak)
2368 return NS_ERROR_OUT_OF_MEMORY;
2370 mCaches.Put(clientID, weak);
2371 }
2373 cache.swap(*out);
2375 return NS_OK;
2376 }
2378 nsresult
2379 nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
2380 nsIApplicationCache **out)
2381 {
2382 *out = nullptr;
2384 MutexAutoLock lock(mLock);
2386 nsCString *clientID;
2387 if (mActiveCachesByGroup.Get(group, &clientID))
2388 return GetApplicationCache_Unlocked(*clientID, out);
2390 return NS_OK;
2391 }
2393 nsresult
2394 nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
2395 {
2396 nsCString *active = nullptr;
2398 AutoResetStatement statement(mStatement_DeactivateGroup);
2399 nsresult rv = statement->BindUTF8StringByIndex(0, group);
2400 NS_ENSURE_SUCCESS(rv, rv);
2402 rv = statement->Execute();
2403 NS_ENSURE_SUCCESS(rv, rv);
2405 MutexAutoLock lock(mLock);
2407 if (mActiveCachesByGroup.Get(group, &active))
2408 {
2409 mActiveCaches.RemoveEntry(*active);
2410 mActiveCachesByGroup.Remove(group);
2411 active = nullptr;
2412 }
2414 return NS_OK;
2415 }
2417 nsresult
2418 nsOfflineCacheDevice::DiscardByAppId(int32_t appID, bool browserEntriesOnly)
2419 {
2420 nsresult rv;
2422 nsAutoCString jaridsuffix;
2423 jaridsuffix.Append('%');
2424 rv = AppendJARIdentifier(jaridsuffix, appID, browserEntriesOnly);
2425 NS_ENSURE_SUCCESS(rv, rv);
2427 {
2428 AutoResetStatement statement(mStatement_EnumerateApps);
2429 rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
2430 NS_ENSURE_SUCCESS(rv, rv);
2432 bool hasRows;
2433 rv = statement->ExecuteStep(&hasRows);
2434 NS_ENSURE_SUCCESS(rv, rv);
2436 while (hasRows) {
2437 nsAutoCString group;
2438 rv = statement->GetUTF8String(0, group);
2439 NS_ENSURE_SUCCESS(rv, rv);
2441 nsCString clientID;
2442 rv = statement->GetUTF8String(1, clientID);
2443 NS_ENSURE_SUCCESS(rv, rv);
2445 nsCOMPtr<nsIRunnable> ev =
2446 new nsOfflineCacheDiscardCache(this, group, clientID);
2448 rv = nsCacheService::DispatchToCacheIOThread(ev);
2449 NS_ENSURE_SUCCESS(rv, rv);
2451 rv = statement->ExecuteStep(&hasRows);
2452 NS_ENSURE_SUCCESS(rv, rv);
2453 }
2454 }
2456 if (!browserEntriesOnly) {
2457 // If deleting app, delete any 'inBrowserElement' entries too
2458 rv = DiscardByAppId(appID, true);
2459 NS_ENSURE_SUCCESS(rv, rv);
2460 }
2462 return NS_OK;
2463 }
2465 bool
2466 nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
2467 const nsACString &clientID,
2468 nsILoadContextInfo *loadContextInfo)
2469 {
2470 {
2471 MutexAutoLock lock(mLock);
2472 if (!mActiveCaches.Contains(clientID))
2473 return false;
2474 }
2476 nsAutoCString groupID;
2477 nsresult rv = GetGroupForCache(clientID, groupID);
2478 NS_ENSURE_SUCCESS(rv, false);
2480 nsCOMPtr<nsIURI> groupURI;
2481 rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
2482 if (NS_FAILED(rv))
2483 return false;
2485 // When we are choosing an initial cache to load the top
2486 // level document from, the URL of that document must have
2487 // the same origin as the manifest, according to the spec.
2488 // The following check is here because explicit, fallback
2489 // and dynamic entries might have origin different from the
2490 // manifest origin.
2491 if (!NS_SecurityCompareURIs(keyURI, groupURI,
2492 GetStrictFileOriginPolicy()))
2493 return false;
2495 // Get extended origin attributes
2496 uint32_t appId = NECKO_NO_APP_ID;
2497 bool isInBrowserElement = false;
2499 if (loadContextInfo) {
2500 appId = loadContextInfo->AppId();
2501 isInBrowserElement = loadContextInfo->IsInBrowserElement();
2502 }
2504 // Check the groupID we found is equal to groupID based
2505 // on the load context demanding load from app cache.
2506 // This is check of extended origin.
2507 nsAutoCString demandedGroupID;
2508 rv = BuildApplicationCacheGroupID(groupURI, appId, isInBrowserElement,
2509 demandedGroupID);
2510 NS_ENSURE_SUCCESS(rv, false);
2512 if (groupID != demandedGroupID)
2513 return false;
2515 return true;
2516 }
2519 nsresult
2520 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
2521 nsILoadContextInfo *loadContextInfo,
2522 nsIApplicationCache **out)
2523 {
2524 *out = nullptr;
2526 nsCOMPtr<nsIURI> keyURI;
2527 nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key);
2528 NS_ENSURE_SUCCESS(rv, rv);
2530 // First try to find a matching cache entry.
2531 AutoResetStatement statement(mStatement_FindClient);
2532 rv = statement->BindUTF8StringByIndex(0, key);
2533 NS_ENSURE_SUCCESS(rv, rv);
2535 bool hasRows;
2536 rv = statement->ExecuteStep(&hasRows);
2537 NS_ENSURE_SUCCESS(rv, rv);
2539 while (hasRows) {
2540 int32_t itemType;
2541 rv = statement->GetInt32(1, &itemType);
2542 NS_ENSURE_SUCCESS(rv, rv);
2544 if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
2545 nsAutoCString clientID;
2546 rv = statement->GetUTF8String(0, clientID);
2547 NS_ENSURE_SUCCESS(rv, rv);
2549 if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2550 return GetApplicationCache(clientID, out);
2551 }
2552 }
2554 rv = statement->ExecuteStep(&hasRows);
2555 NS_ENSURE_SUCCESS(rv, rv);
2556 }
2558 // OK, we didn't find an exact match. Search for a client with a
2559 // matching namespace.
2561 AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
2563 rv = nsstatement->BindUTF8StringByIndex(0, key);
2564 NS_ENSURE_SUCCESS(rv, rv);
2566 rv = nsstatement->ExecuteStep(&hasRows);
2567 NS_ENSURE_SUCCESS(rv, rv);
2569 while (hasRows)
2570 {
2571 int32_t itemType;
2572 rv = nsstatement->GetInt32(1, &itemType);
2573 NS_ENSURE_SUCCESS(rv, rv);
2575 // Don't associate with a cache based solely on a whitelist entry
2576 if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
2577 nsAutoCString clientID;
2578 rv = nsstatement->GetUTF8String(0, clientID);
2579 NS_ENSURE_SUCCESS(rv, rv);
2581 if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2582 return GetApplicationCache(clientID, out);
2583 }
2584 }
2586 rv = nsstatement->ExecuteStep(&hasRows);
2587 NS_ENSURE_SUCCESS(rv, rv);
2588 }
2590 return NS_OK;
2591 }
2593 nsresult
2594 nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
2595 const nsACString &key)
2596 {
2597 NS_ENSURE_ARG_POINTER(cache);
2599 nsresult rv;
2601 nsAutoCString clientID;
2602 rv = cache->GetClientID(clientID);
2603 NS_ENSURE_SUCCESS(rv, rv);
2605 return CacheOpportunistically(clientID, key);
2606 }
2608 nsresult
2609 nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
2610 const nsCSubstring &clientID)
2611 {
2612 AutoResetStatement statement(mStatement_ActivateClient);
2613 nsresult rv = statement->BindUTF8StringByIndex(0, group);
2614 NS_ENSURE_SUCCESS(rv, rv);
2615 rv = statement->BindUTF8StringByIndex(1, clientID);
2616 NS_ENSURE_SUCCESS(rv, rv);
2617 rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
2618 NS_ENSURE_SUCCESS(rv, rv);
2620 rv = statement->Execute();
2621 NS_ENSURE_SUCCESS(rv, rv);
2623 MutexAutoLock lock(mLock);
2625 nsCString *active;
2626 if (mActiveCachesByGroup.Get(group, &active))
2627 {
2628 mActiveCaches.RemoveEntry(*active);
2629 mActiveCachesByGroup.Remove(group);
2630 active = nullptr;
2631 }
2633 if (!clientID.IsEmpty())
2634 {
2635 mActiveCaches.PutEntry(clientID);
2636 mActiveCachesByGroup.Put(group, new nsCString(clientID));
2637 }
2639 return NS_OK;
2640 }
2642 bool
2643 nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
2644 const nsCSubstring &clientID)
2645 {
2646 nsCString *active = nullptr;
2647 MutexAutoLock lock(mLock);
2648 return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
2649 }
2651 /**
2652 * Preference accessors
2653 */
2655 void
2656 nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
2657 {
2658 if (Initialized())
2659 {
2660 NS_ERROR("cannot switch cache directory once initialized");
2661 return;
2662 }
2664 if (!parentDir)
2665 {
2666 mCacheDirectory = nullptr;
2667 return;
2668 }
2670 // ensure parent directory exists
2671 nsresult rv = EnsureDir(parentDir);
2672 if (NS_FAILED(rv))
2673 {
2674 NS_WARNING("unable to create parent directory");
2675 return;
2676 }
2678 mBaseDirectory = parentDir;
2680 // cache dir may not exist, but that's ok
2681 nsCOMPtr<nsIFile> dir;
2682 rv = parentDir->Clone(getter_AddRefs(dir));
2683 if (NS_FAILED(rv))
2684 return;
2685 rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2686 if (NS_FAILED(rv))
2687 return;
2689 mCacheDirectory = do_QueryInterface(dir);
2690 }
2692 void
2693 nsOfflineCacheDevice::SetCapacity(uint32_t capacity)
2694 {
2695 mCacheCapacity = capacity * 1024;
2696 }
2698 bool
2699 nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache)
2700 {
2701 if (!mAutoShutdown)
2702 return false;
2704 mAutoShutdown = false;
2706 Shutdown();
2708 nsRefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
2709 cacheService->RemoveCustomOfflineDevice(this);
2711 nsAutoCString clientID;
2712 aAppCache->GetClientID(clientID);
2714 MutexAutoLock lock(mLock);
2715 mCaches.Remove(clientID);
2717 return true;
2718 }