summaryrefslogtreecommitdiff
path: root/netwerk/cache/nsDiskCacheDeviceSQL.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache/nsDiskCacheDeviceSQL.cpp')
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.cpp2906
1 files changed, 2906 insertions, 0 deletions
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
new file mode 100644
index 0000000000..56ece58870
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -0,0 +1,2906 @@
+/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <inttypes.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ThreadLocal.h"
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheService.h"
+#include "nsApplicationCache.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsAutoPtr.h"
+#include "nsEscape.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsCRT.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
+#include "nsIVariant.h"
+#include "nsILoadContextInfo.h"
+#include "nsThreadUtils.h"
+#include "nsISerializable.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsSerializationHelper.h"
+
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageFunction.h"
+#include "mozStorageHelper.h"
+
+#include "nsICacheVisitor.h"
+#include "nsISeekableStream.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "sqlite3.h"
+#include "mozilla/storage.h"
+#include "nsVariant.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::storage;
+using mozilla::NeckoOriginAttributes;
+
+static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
+
+#define LOG(args) CACHE_LOG_DEBUG(args)
+
+static uint32_t gNextTemporaryClientID = 0;
+
+/*****************************************************************************
+ * helpers
+ */
+
+static nsresult
+EnsureDir(nsIFile *dir)
+{
+ bool exists;
+ nsresult rv = dir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ return rv;
+}
+
+static bool
+DecomposeCacheEntryKey(const nsCString *fullKey,
+ const char **cid,
+ const char **key,
+ nsCString &buf)
+{
+ buf = *fullKey;
+
+ int32_t colon = buf.FindChar(':');
+ if (colon == kNotFound)
+ {
+ NS_ERROR("Invalid key");
+ return false;
+ }
+ buf.SetCharAt('\0', colon);
+
+ *cid = buf.get();
+ *key = buf.get() + colon + 1;
+
+ return true;
+}
+
+class AutoResetStatement
+{
+ public:
+ explicit AutoResetStatement(mozIStorageStatement *s)
+ : mStatement(s) {}
+ ~AutoResetStatement() { mStatement->Reset(); }
+ mozIStorageStatement *operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mStatement; }
+ private:
+ mozIStorageStatement *mStatement;
+};
+
+class EvictionObserver
+{
+ public:
+ EvictionObserver(mozIStorageConnection *db,
+ nsOfflineCacheEvictionFunction *evictionFunction)
+ : mDB(db), mEvictionFunction(evictionFunction)
+ {
+ mEvictionFunction->Init();
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
+ " ON moz_cache FOR EACH ROW BEGIN SELECT"
+ " cache_eviction_observer("
+ " OLD.ClientID, OLD.key, OLD.generation);"
+ " END;"));
+ }
+
+ ~EvictionObserver()
+ {
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
+ mEvictionFunction->Reset();
+ }
+
+ void Apply() { return mEvictionFunction->Apply(); }
+
+ private:
+ mozIStorageConnection *mDB;
+ RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+};
+
+#define DCACHE_HASH_MAX INT64_MAX
+#define DCACHE_HASH_BITS 64
+
+/**
+ * nsOfflineCache::Hash(const char * key)
+ *
+ * This algorithm of this method implies nsOfflineCacheRecords will be stored
+ * in a certain order on disk. If the algorithm changes, existing cache
+ * map files may become invalid, and therefore the kCurrentVersion needs
+ * to be revised.
+ */
+static uint64_t
+DCacheHash(const char * key)
+{
+ // initval 0x7416f295 was chosen randomly
+ return (uint64_t(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
+}
+
+/******************************************************************************
+ * nsOfflineCacheEvictionFunction
+ */
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
+
+// helper function for directly exposing the same data file binding
+// path algorithm used in nsOfflineCacheBinding::Create
+static nsresult
+GetCacheDataFile(nsIFile *cacheDir, const char *key,
+ int generation, nsCOMPtr<nsIFile> &file)
+{
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t) (hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ file->AppendNative(nsPrintfCString("%X", dir2));
+
+ char leaf[64];
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ return file->AppendNative(nsDependentCString(leaf));
+}
+
+namespace appcachedetail {
+
+typedef nsCOMArray<nsIFile> FileArray;
+static MOZ_THREAD_LOCAL(FileArray*) tlsEvictionItems;
+
+} // appcachedetail
+
+NS_IMETHODIMP
+nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
+{
+ LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
+
+ *_retval = nullptr;
+
+ uint32_t numEntries;
+ nsresult rv = values->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
+
+ uint32_t valueLen;
+ const char *clientID = values->AsSharedUTF8String(0, &valueLen);
+ const char *key = values->AsSharedUTF8String(1, &valueLen);
+ nsAutoCString fullKey(clientID);
+ fullKey.Append(':');
+ fullKey.Append(key);
+ int generation = values->AsInt32(2);
+
+ // If the key is currently locked, refuse to delete this row.
+ if (mDevice->IsLocked(fullKey)) {
+ NS_ADDREF(*_retval = new IntegerVariant(SQLITE_IGNORE));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
+ generation, file);
+ if (NS_FAILED(rv))
+ {
+ LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
+ key, generation, rv));
+ return rv;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ MOZ_ASSERT(items);
+ if (items) {
+ items->AppendObject(file);
+ }
+
+ return NS_OK;
+}
+
+nsOfflineCacheEvictionFunction::nsOfflineCacheEvictionFunction(nsOfflineCacheDevice * device)
+ : mDevice(device)
+{
+ mTLSInited = appcachedetail::tlsEvictionItems.init();
+}
+
+void nsOfflineCacheEvictionFunction::Init()
+{
+ if (mTLSInited) {
+ appcachedetail::tlsEvictionItems.set(new appcachedetail::FileArray());
+ }
+}
+
+void nsOfflineCacheEvictionFunction::Reset()
+{
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ if (!items) {
+ return;
+ }
+
+ appcachedetail::tlsEvictionItems.set(nullptr);
+ delete items;
+}
+
+void
+nsOfflineCacheEvictionFunction::Apply()
+{
+ LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
+
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* pitems = appcachedetail::tlsEvictionItems.get();
+ if (!pitems) {
+ return;
+ }
+
+ appcachedetail::FileArray items;
+ items.SwapElements(*pitems);
+
+ for (int32_t i = 0; i < items.Count(); i++) {
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ items[i]->GetNativePath(path);
+ LOG((" removing %s\n", path.get()));
+ }
+
+ items[i]->Remove(false);
+ }
+}
+
+class nsOfflineCacheDiscardCache : public Runnable
+{
+public:
+ nsOfflineCacheDiscardCache(nsOfflineCacheDevice *device,
+ nsCString &group,
+ nsCString &clientID)
+ : mDevice(device)
+ , mGroup(group)
+ , mClientID(clientID)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mDevice->IsActiveCache(mGroup, mClientID))
+ {
+ mDevice->DeactivateGroup(mGroup);
+ }
+
+ return mDevice->EvictEntries(mClientID.get());
+ }
+
+private:
+ RefPtr<nsOfflineCacheDevice> mDevice;
+ nsCString mGroup;
+ nsCString mClientID;
+};
+
+/******************************************************************************
+ * nsOfflineCacheDeviceInfo
+ */
+
+class nsOfflineCacheDeviceInfo final : public nsICacheDeviceInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
+ : mDevice(device)
+ {}
+
+private:
+ ~nsOfflineCacheDeviceInfo() {}
+
+ nsOfflineCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
+{
+ *aDescription = NS_strdup("Offline cache device");
+ return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
+{
+ nsAutoCString buffer;
+ buffer.AssignLiteral(" <tr>\n"
+ " <th>Cache Directory:</th>\n"
+ " <td>");
+ nsIFile *cacheDir = mDevice->CacheDirectory();
+ if (!cacheDir)
+ return NS_OK;
+
+ nsAutoString path;
+ nsresult rv = cacheDir->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ AppendUTF16toUTF8(path, buffer);
+ else
+ buffer.AppendLiteral("directory unavailable");
+
+ buffer.AppendLiteral("</td>\n"
+ " </tr>\n");
+
+ *usageReport = ToNewCString(buffer);
+ if (!*usageReport)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t *aEntryCount)
+{
+ *aEntryCount = mDevice->EntryCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t *aTotalSize)
+{
+ *aTotalSize = mDevice->CacheSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t *aMaximumSize)
+{
+ *aMaximumSize = mDevice->CacheCapacity();
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsOfflineCacheBinding
+ */
+
+class nsOfflineCacheBinding final : public nsISupports
+{
+ ~nsOfflineCacheBinding() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static nsOfflineCacheBinding *
+ Create(nsIFile *cacheDir, const nsCString *key, int generation);
+
+ enum { FLAG_NEW_ENTRY = 1 };
+
+ nsCOMPtr<nsIFile> mDataFile;
+ int mGeneration;
+ int mFlags;
+
+ bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
+ void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
+ void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
+};
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
+
+nsOfflineCacheBinding *
+nsOfflineCacheBinding::Create(nsIFile *cacheDir,
+ const nsCString *fullKey,
+ int generation)
+{
+ nsCOMPtr<nsIFile> file;
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file)
+ return nullptr;
+
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
+ return nullptr;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t) (hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ // XXX we might want to create these directories up-front
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ file->AppendNative(nsPrintfCString("%X", dir2));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ nsresult rv;
+ char leaf[64];
+
+ if (generation == -1)
+ {
+ file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
+
+ for (generation = 0; ; ++generation)
+ {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+
+ rv = file->SetNativeLeafName(nsDependentCString(leaf));
+ if (NS_FAILED(rv))
+ return nullptr;
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ return nullptr;
+ if (NS_SUCCEEDED(rv))
+ break;
+ }
+ }
+ else
+ {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ rv = file->AppendNative(nsDependentCString(leaf));
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
+ if (!binding)
+ return nullptr;
+
+ binding->mDataFile.swap(file);
+ binding->mGeneration = generation;
+ binding->mFlags = 0;
+ return binding;
+}
+
+/******************************************************************************
+ * nsOfflineCacheRecord
+ */
+
+struct nsOfflineCacheRecord
+{
+ const char *clientID;
+ const char *key;
+ const uint8_t *metaData;
+ uint32_t metaDataLen;
+ int32_t generation;
+ int32_t dataSize;
+ int32_t fetchCount;
+ int64_t lastFetched;
+ int64_t lastModified;
+ int64_t expirationTime;
+};
+
+static nsCacheEntry *
+CreateCacheEntry(nsOfflineCacheDevice *device,
+ const nsCString *fullKey,
+ const nsOfflineCacheRecord &rec)
+{
+ nsCacheEntry *entry;
+
+ if (device->IsLocked(*fullKey)) {
+ return nullptr;
+ }
+
+ nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
+ nsICache::STREAM_BASED,
+ nsICache::STORE_OFFLINE,
+ device, &entry);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ entry->SetFetchCount((uint32_t) rec.fetchCount);
+ entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
+ entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
+ entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
+ entry->SetDataSize((uint32_t) rec.dataSize);
+
+ entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
+
+ // Restore security info, if present
+ const char* info = entry->GetMetaDataElement("security-info");
+ if (info) {
+ nsCOMPtr<nsISupports> infoObj;
+ rv = NS_DeserializeObject(nsDependentCString(info),
+ getter_AddRefs(infoObj));
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetSecurityInfo(infoObj);
+ }
+
+ // create a binding object for this entry
+ nsOfflineCacheBinding *binding =
+ nsOfflineCacheBinding::Create(device->CacheDirectory(),
+ fullKey,
+ rec.generation);
+ if (!binding)
+ {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetData(binding);
+
+ return entry;
+}
+
+
+/******************************************************************************
+ * nsOfflineCacheEntryInfo
+ */
+
+class nsOfflineCacheEntryInfo final : public nsICacheEntryInfo
+{
+ ~nsOfflineCacheEntryInfo() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsOfflineCacheRecord *mRec;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetClientID(char **result)
+{
+ *result = NS_strdup(mRec->clientID);
+ return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
+{
+ *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
+ return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
+{
+ clientKey.Assign(mRec->key);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetFetchCount(int32_t *aFetchCount)
+{
+ *aFetchCount = mRec->fetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastFetched(uint32_t *aLastFetched)
+{
+ *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastModified(uint32_t *aLastModified)
+{
+ *aLastModified = SecondsFromPRTime(mRec->lastModified);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t *aExpirationTime)
+{
+ *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
+{
+ *aStreamBased = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDataSize(uint32_t *aDataSize)
+{
+ *aDataSize = mRec->dataSize;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsApplicationCacheNamespace
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::Init(uint32_t itemType,
+ const nsACString &namespaceSpec,
+ const nsACString &data)
+{
+ mItemType = itemType;
+ mNamespaceSpec = namespaceSpec;
+ mData = data;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetItemType(uint32_t *out)
+{
+ *out = mItemType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
+{
+ out = mNamespaceSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetData(nsACString &out)
+{
+ out = mData;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsApplicationCache
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCache,
+ nsIApplicationCache,
+ nsISupportsWeakReference)
+
+nsApplicationCache::nsApplicationCache()
+ : mDevice(nullptr)
+ , mValid(true)
+{
+}
+
+nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
+ const nsACString &group,
+ const nsACString &clientID)
+ : mDevice(device)
+ , mGroup(group)
+ , mClientID(clientID)
+ , mValid(true)
+{
+}
+
+nsApplicationCache::~nsApplicationCache()
+{
+ if (!mDevice)
+ return;
+
+ {
+ MutexAutoLock lock(mDevice->mLock);
+ mDevice->mCaches.Remove(mClientID);
+ }
+
+ // If this isn't an active cache anymore, it can be destroyed.
+ if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
+ Discard();
+}
+
+void
+nsApplicationCache::MarkInvalid()
+{
+ mValid = false;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::InitAsHandle(const nsACString &groupId,
+ const nsACString &clientId)
+{
+ NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
+
+ mGroup = groupId;
+ mClientID = clientId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetManifestURI(nsIURI **out)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = uri->CloneIgnoringRef(out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetGroupID(nsACString &out)
+{
+ out = mGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetClientID(nsACString &out)
+{
+ out = mClientID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetProfileDirectory(nsIFile **out)
+{
+ if (mDevice->BaseDirectory())
+ NS_ADDREF(*out = mDevice->BaseDirectory());
+ else
+ *out = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetActive(bool *out)
+{
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ *out = mDevice->IsActiveCache(mGroup, mClientID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Activate()
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mDevice->ActivateCache(mGroup, mClientID);
+
+ if (mDevice->AutoShutdown(this))
+ mDevice = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Discard()
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mValid = false;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
+ nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::MarkEntry(const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->MarkEntry(mClientID, key, typeBits);
+}
+
+
+NS_IMETHODIMP
+nsApplicationCache::UnmarkEntry(const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->UnmarkEntry(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetTypes(const nsACString &key,
+ uint32_t *typeBits)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetTypes(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GatherEntries(uint32_t typeBits,
+ uint32_t * count,
+ char *** keys)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GatherEntries(mClientID, typeBits, count, keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::AddNamespaces(nsIArray *namespaces)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ if (!namespaces)
+ return NS_OK;
+
+ mozStorageTransaction transaction(mDevice->mDB, false);
+
+ uint32_t length;
+ nsresult rv = namespaces->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ do_QueryElementAt(namespaces, i);
+ if (ns) {
+ rv = mDevice->AddNamespace(mClientID, ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetMatchingNamespace(const nsACString &key,
+ nsIApplicationCacheNamespace **out)
+
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetMatchingNamespace(mClientID, key, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetUsage(uint32_t *usage)
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetUsage(mClientID, usage);
+}
+
+/******************************************************************************
+ * nsCloseDBEvent
+ *****************************************************************************/
+
+class nsCloseDBEvent : public Runnable {
+public:
+ explicit nsCloseDBEvent(mozIStorageConnection *aDB)
+ {
+ mDB = aDB;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mDB->Close();
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsCloseDBEvent() {}
+
+private:
+ nsCOMPtr<mozIStorageConnection> mDB;
+};
+
+
+
+/******************************************************************************
+ * nsOfflineCacheDevice
+ */
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
+
+nsOfflineCacheDevice::nsOfflineCacheDevice()
+ : mDB(nullptr)
+ , mCacheCapacity(0)
+ , mDeltaCounter(0)
+ , mAutoShutdown(false)
+ , mLock("nsOfflineCacheDevice.lock")
+ , mActiveCaches(4)
+ , mLockedEntries(32)
+{
+}
+
+nsOfflineCacheDevice::~nsOfflineCacheDevice()
+{}
+
+/* static */
+bool
+nsOfflineCacheDevice::GetStrictFileOriginPolicy()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ bool retval;
+ if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
+ return retval;
+
+ // As default value use true (be more strict)
+ return true;
+}
+
+uint32_t
+nsOfflineCacheDevice::CacheSize()
+{
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_CacheSize);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t) statement->AsInt32(0);
+}
+
+uint32_t
+nsOfflineCacheDevice::EntryCount()
+{
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_EntryCount);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t) statement->AsInt32(0);
+}
+
+nsresult
+nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // Store security info, if it is serializable
+ nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+ if (infoObj && !serializable)
+ return NS_ERROR_UNEXPECTED;
+
+ if (serializable) {
+ nsCString info;
+ nsresult rv = NS_SerializeToString(serializable, info);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = entry->SetMetaDataElement("security-info", info.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString metaDataBuf;
+ uint32_t mdSize = entry->MetaDataSize();
+ if (!metaDataBuf.SetLength(mdSize, fallible))
+ return NS_ERROR_OUT_OF_MEMORY;
+ char *md = metaDataBuf.BeginWriting();
+ entry->FlattenMetaData(md, mdSize);
+
+ nsOfflineCacheRecord rec;
+ rec.metaData = (const uint8_t *) md;
+ rec.metaDataLen = mdSize;
+ rec.dataSize = entry->DataSize();
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_UpdateEntry);
+
+ nsresult rv;
+ rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
+ nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(4, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, uint32_t newSize)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_UpdateEntrySize);
+
+ nsresult rv = statement->BindInt32ByIndex(0, newSize);
+ nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ if (deleteData)
+ {
+ nsresult rv = DeleteData(entry);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_DeleteEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "DELETE should not result in output");
+ return rv;
+}
+
+nsresult
+nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
+{
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ return binding->mDataFile->Remove(false);
+}
+
+/**
+ * nsCacheDevice implementation
+ */
+
+// This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
+// allow a template (mozilla::ArrayLength) to be instantiated based on a local
+// type. Boo-urns!
+struct StatementSql {
+ nsCOMPtr<mozIStorageStatement> &statement;
+ const char *sql;
+ StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
+ statement (aStatement), sql (aSql) {}
+};
+
+nsresult
+nsOfflineCacheDevice::Init()
+{
+ MOZ_ASSERT(false, "Need to be initialized with sqlite");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsOfflineCacheDevice::InitWithSqlite(mozIStorageService * ss)
+{
+ NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
+
+ // SetCacheParentDirectory must have been called
+ NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
+
+ // make sure the cache directory exists
+ nsresult rv = EnsureDir(mCacheDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build path to index file
+ nsCOMPtr<nsIFile> indexFile;
+ rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(ss, "nsOfflineCacheDevice::InitWithSqlite called before nsCacheService::Init() ?");
+ NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
+
+ rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitThread = do_GetCurrentThread();
+
+ mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
+
+ // XXX ... other initialization steps
+
+ // XXX in the future we may wish to verify the schema for moz_cache
+ // perhaps using "PRAGMA table_info" ?
+
+ // build the table
+ //
+ // "Generation" is the data file generation number.
+ //
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
+ " ClientID TEXT,\n"
+ " Key TEXT,\n"
+ " MetaData BLOB,\n"
+ " Generation INTEGER,\n"
+ " DataSize INTEGER,\n"
+ " FetchCount INTEGER,\n"
+ " LastFetched INTEGER,\n"
+ " LastModified INTEGER,\n"
+ " ExpirationTime INTEGER,\n"
+ " ItemType INTEGER DEFAULT 0\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 don't have the ItemType column. Add the column
+ // here, but don't worry about failures (the column probably already exists)
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
+
+ // Create the table for storing cache groups. All actions on
+ // moz_cache_groups use the GroupID, so use it as the primary key.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
+ " GroupID TEXT PRIMARY KEY,\n"
+ " ActiveClientID TEXT\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
+ "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
+
+ // ClientID: clientID joining moz_cache and moz_cache_namespaces
+ // tables.
+ // Data: Data associated with this namespace (e.g. a fallback URI
+ // for fallback entries).
+ // ItemType: the type of namespace.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
+ " moz_cache_namespaces (\n"
+ " ClientID TEXT,\n"
+ " NameSpace TEXT,\n"
+ " Data TEXT,\n"
+ " ItemType INTEGER\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 have a moz_cache_index that should be dropped
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Key/ClientID pairs should be unique in the database. All queries
+ // against moz_cache use the Key (which is also the most unique), so
+ // use it as the primary key for this index.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
+ " moz_cache_key_clientid_index"
+ " ON moz_cache (Key, ClientID);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_clientid_index"
+ " ON moz_cache_namespaces (ClientID, NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for namespace lookups.
+ rv = mDB->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_namespace_index"
+ " ON moz_cache_namespaces (NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+
+ mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
+ if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, mEvictionFunction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create all (most) of our statements up front
+ StatementSql prepared[] = {
+ StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
+ StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
+ StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
+ StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
+
+ StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
+ StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
+ StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
+
+ StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
+ StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
+ StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
+
+ // Search for namespaces that match the URI. Use the <= operator
+ // to ensure that we use the index on moz_cache_namespaces.
+ StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
+ " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
+ " ON ns.ClientID = groups.ActiveClientID"
+ " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
+ " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
+ StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
+ " WHERE ClientID = ?1"
+ " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
+ " ORDER BY NameSpace DESC;"),
+ StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
+ StatementSql ( mStatement_EnumerateApps, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
+ StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
+ StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
+ };
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i)
+ {
+ LOG(("Creating statement: %s\n", prepared[i].sql));
+
+ rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
+ getter_AddRefs(prepared[i].statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitActiveCaches();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+namespace {
+
+nsresult
+GetGroupForCache(const nsCSubstring &clientID, nsCString &group)
+{
+ group.Assign(clientID);
+ group.Truncate(group.FindChar('|'));
+ NS_UnescapeURL(group);
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+nsresult
+nsOfflineCacheDevice::BuildApplicationCacheGroupID(nsIURI *aManifestURL,
+ nsACString const &aOriginSuffix,
+ nsACString &_result)
+{
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = aManifestURL->CloneIgnoringRef(getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString manifestSpec;
+ rv = newURI->GetAsciiSpec(manifestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ _result.Assign(manifestSpec);
+ _result.Append('#');
+ _result.Append(aOriginSuffix);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::InitActiveCaches()
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ MutexAutoLock lock(mLock);
+
+ AutoResetStatement statement(mStatement_EnumerateGroups);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows)
+ {
+ nsAutoCString group;
+ statement->GetUTF8String(0, group);
+ nsCString clientID;
+ statement->GetUTF8String(1, clientID);
+
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Shutdown()
+{
+ NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
+
+ {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(iter.UserData());
+ if (obj) {
+ auto appCache = static_cast<nsApplicationCache*>(obj.get());
+ appCache->MarkInvalid();
+ }
+ }
+ }
+
+ {
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ // Delete all rows whose clientID is not an active clientID.
+ nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cache WHERE rowid IN"
+ " (SELECT moz_cache.rowid FROM"
+ " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to clean up unused application caches.");
+ else
+ evictionObserver.Apply();
+
+ // Delete all namespaces whose clientID is not an active clientID.
+ rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_cache_namespaces WHERE rowid IN"
+ " (SELECT moz_cache_namespaces.rowid FROM"
+ " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to clean up namespaces.");
+
+ mEvictionFunction = nullptr;
+
+ mStatement_CacheSize = nullptr;
+ mStatement_ApplicationCacheSize = nullptr;
+ mStatement_EntryCount = nullptr;
+ mStatement_UpdateEntry = nullptr;
+ mStatement_UpdateEntrySize = nullptr;
+ mStatement_DeleteEntry = nullptr;
+ mStatement_FindEntry = nullptr;
+ mStatement_BindEntry = nullptr;
+ mStatement_ClearDomain = nullptr;
+ mStatement_MarkEntry = nullptr;
+ mStatement_UnmarkEntry = nullptr;
+ mStatement_GetTypes = nullptr;
+ mStatement_FindNamespaceEntry = nullptr;
+ mStatement_InsertNamespaceEntry = nullptr;
+ mStatement_CleanupUnmarked = nullptr;
+ mStatement_GatherEntries = nullptr;
+ mStatement_ActivateClient = nullptr;
+ mStatement_DeactivateGroup = nullptr;
+ mStatement_FindClient = nullptr;
+ mStatement_FindClientByNamespace = nullptr;
+ mStatement_EnumerateApps = nullptr;
+ mStatement_EnumerateGroups = nullptr;
+ mStatement_EnumerateGroupsTimeOrder = nullptr;
+ }
+
+ // Close Database on the correct thread
+ bool isOnCurrentThread = true;
+ if (mInitThread)
+ mInitThread->IsOnCurrentThread(&isOnCurrentThread);
+
+ if (!isOnCurrentThread) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
+
+ if (ev) {
+ mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ }
+ else {
+ mDB->Close();
+ }
+
+ mDB = nullptr;
+ mInitThread = nullptr;
+
+ return NS_OK;
+}
+
+const char *
+nsOfflineCacheDevice::GetDeviceID()
+{
+ return OFFLINE_CACHE_DEVICE_ID;
+}
+
+nsCacheEntry *
+nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
+{
+ NS_ENSURE_TRUE(Initialized(), nullptr);
+
+ mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2> timer;
+ LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
+
+ // SELECT * FROM moz_cache WHERE key = ?
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
+ return nullptr;
+
+ AutoResetStatement statement(mStatement_FindEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ NS_ENSURE_SUCCESS(rv2, nullptr);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows)
+ return nullptr; // entry not found
+
+ nsOfflineCacheRecord rec;
+ statement->GetSharedBlob(0, &rec.metaDataLen,
+ (const uint8_t **) &rec.metaData);
+ rec.generation = statement->AsInt32(1);
+ rec.dataSize = statement->AsInt32(2);
+ rec.fetchCount = statement->AsInt32(3);
+ rec.lastFetched = statement->AsInt64(4);
+ rec.lastModified = statement->AsInt64(5);
+ rec.expirationTime = statement->AsInt64(6);
+
+ LOG(("entry: [%u %d %d %d %lld %lld %lld]\n",
+ rec.metaDataLen,
+ rec.generation,
+ rec.dataSize,
+ rec.fetchCount,
+ rec.lastFetched,
+ rec.lastModified,
+ rec.expirationTime));
+
+ nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
+
+ if (entry)
+ {
+ // make sure that the data file exists
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
+ bool isFile;
+ rv = binding->mDataFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile)
+ {
+ DeleteEntry(entry, false);
+ delete entry;
+ return nullptr;
+ }
+
+ // lock the entry
+ Lock(*fullKey);
+ }
+
+ return entry;
+}
+
+nsresult
+nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
+{
+ LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ // This method is called to inform us that the nsCacheEntry object is going
+ // away. We should persist anything that needs to be persisted, or if the
+ // entry is doomed, we can go ahead and clear its storage.
+
+ if (entry->IsDoomed())
+ {
+ // remove corresponding row and file if they exist
+
+ // the row should have been removed in DoomEntry... we could assert that
+ // that happened. otherwise, all we have to do here is delete the file
+ // on disk.
+ DeleteData(entry);
+ }
+ else if (((nsOfflineCacheBinding *)entry->Data())->IsNewEntry())
+ {
+ // UPDATE the database row
+
+ // Only new entries are updated, since offline cache is updated in
+ // transactions. New entries are those who is returned from
+ // BindEntry().
+
+ LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
+ UpdateEntry(entry);
+ } else {
+ LOG(("nsOfflineCacheDevice::DeactivateEntry "
+ "skipping update since entry is not dirty\n"));
+ }
+
+ // Unlock the entry
+ Unlock(*entry->Key());
+
+ delete entry;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
+
+ NS_ENSURE_STATE(!entry->Data());
+
+ // This method is called to inform us that we have a new entry. The entry
+ // may collide with an existing entry in our DB, but if that happens we can
+ // assume that the entry is not being used.
+
+ // INSERT the database row
+
+ // XXX Assumption: if the row already exists, then FindEntry would have
+ // returned it. if that entry was doomed, then DoomEntry would have removed
+ // it from the table. so, we should always have to insert at this point.
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // create binding, pick best generation number
+ RefPtr<nsOfflineCacheBinding> binding =
+ nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
+ if (!binding)
+ return NS_ERROR_OUT_OF_MEMORY;
+ binding->MarkNewEntry();
+
+ nsOfflineCacheRecord rec;
+ rec.clientID = cid;
+ rec.key = key;
+ rec.metaData = nullptr; // don't write any metadata now.
+ rec.metaDataLen = 0;
+ rec.generation = binding->mGeneration;
+ rec.dataSize = 0;
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_BindEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
+ nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(3, rec.generation);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(4, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(7, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(!hasRows, "INSERT should not result in output");
+
+ entry->SetData(binding);
+
+ // lock the entry
+ Lock(*entry->Key());
+
+ return NS_OK;
+}
+
+void
+nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
+{
+ LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
+
+ // This method is called to inform us that we should mark the entry to be
+ // deleted when it is no longer in use.
+
+ // We can go ahead and delete the corresponding row in our table,
+ // but we must not delete the file on disk until we are deactivated.
+ // In another word, the file should be deleted if the entry had been
+ // deactivated.
+
+ DeleteEntry(entry, !entry->IsActive());
+}
+
+nsresult
+nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream **result)
+{
+ LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
+
+ // return an input stream to the entry's data file. the stream
+ // may be read on a background thread.
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIInputStream> in;
+ NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
+ if (!in)
+ return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ if (offset != 0)
+ {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ }
+
+ in.swap(*result);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream **result)
+{
+ LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
+
+ // return an output stream to the entry's data file. we can assume
+ // that the output stream will only be used on the main thread.
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIOutputStream> out;
+ NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ 00600);
+ if (!out)
+ return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+ if (offset != 0)
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+
+ // truncate the file at the given offset
+ seekable->SetEOF();
+
+ nsCOMPtr<nsIOutputStream> bufferedOut;
+ nsresult rv =
+ NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bufferedOut.swap(*result);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
+{
+ LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ NS_IF_ADDREF(*result = binding->mDataFile);
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, int32_t deltaSize)
+{
+ LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
+ entry->Key()->get(), deltaSize));
+
+ const int32_t DELTA_THRESHOLD = 1<<14; // 16k
+
+ // called to notify us of an impending change in the total size of the
+ // specified entry.
+
+ uint32_t oldSize = entry->DataSize();
+ NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
+ uint32_t newSize = int32_t(oldSize) + deltaSize;
+ UpdateEntrySize(entry, newSize);
+
+ mDeltaCounter += deltaSize; // this may go negative
+
+ if (mDeltaCounter >= DELTA_THRESHOLD)
+ {
+ if (CacheSize() > mCacheCapacity) {
+ // the entry will overrun the cache capacity, doom the entry
+ // and abort
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+
+ mDeltaCounter = 0; // reset counter
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // called to enumerate the offline cache.
+
+ nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
+ new nsOfflineCacheDeviceInfo(this);
+
+ bool keepGoing;
+ nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
+ &keepGoing);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!keepGoing)
+ return NS_OK;
+
+ // SELECT * from moz_cache;
+
+ nsOfflineCacheRecord rec;
+ RefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
+ if (!info)
+ return NS_ERROR_OUT_OF_MEMORY;
+ info->mRec = &rec;
+
+ // XXX may want to list columns explicitly
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ for (;;)
+ {
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows)
+ break;
+
+ statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
+ statement->GetSharedUTF8String(1, nullptr, &rec.key);
+ statement->GetSharedBlob(2, &rec.metaDataLen,
+ (const uint8_t **) &rec.metaData);
+ rec.generation = statement->AsInt32(3);
+ rec.dataSize = statement->AsInt32(4);
+ rec.fetchCount = statement->AsInt32(5);
+ rec.lastFetched = statement->AsInt64(6);
+ rec.lastModified = statement->AsInt64(7);
+ rec.expirationTime = statement->AsInt64(8);
+
+ bool keepGoing;
+ rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
+ if (NS_FAILED(rv) || !keepGoing)
+ break;
+ }
+
+ info->mRec = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::EvictEntries(const char *clientID)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
+ clientID ? clientID : ""));
+
+ // called to evict all entries matching the given clientID.
+
+ // need trigger to fire user defined function after a row is deleted
+ // so we can delete the corresponding data file.
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv;
+ if (clientID)
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO - Should update internal hashtables.
+ // Low priority, since this API is not widely used.
+ }
+ else
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Clear();
+ mActiveCaches.Clear();
+ mActiveCachesByGroup.Clear();
+ }
+
+ evictionObserver.Apply();
+
+ statement = nullptr;
+ // Also evict any namespaces associated with this clientID.
+ if (clientID)
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_MarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_UnmarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove the entry if it is now empty.
+
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
+ rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cleanupStatement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cleanupStatement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ evictionObserver.Apply();
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
+ const nsACString &key,
+ nsIApplicationCacheNamespace **out)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
+ clientID.get(), PromiseFlatCString(key).get()));
+
+ nsresult rv;
+
+ AutoResetStatement statement(mStatement_FindNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *out = nullptr;
+
+ bool found = false;
+ nsCString nsSpec;
+ int32_t nsType = 0;
+ nsCString nsData;
+
+ while (hasRows)
+ {
+ int32_t itemType;
+ rv = statement->GetInt32(2, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!found || itemType > nsType)
+ {
+ nsType = itemType;
+
+ rv = statement->GetUTF8String(0, nsSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->GetUTF8String(1, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ found = true;
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (found) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ new nsApplicationCacheNamespace();
+ if (!ns)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = ns->Init(nsType, nsSpec, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ns.swap(*out);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
+ const nsACString &key)
+{
+ // XXX: We should also be propagating this cache entry to other matching
+ // caches. See bug 444807.
+
+ return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
+}
+
+nsresult
+nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
+ const nsACString &key,
+ uint32_t *typeBits)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
+ clientID.get(), PromiseFlatCString(key).get()));
+
+ AutoResetStatement statement(mStatement_GetTypes);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows)
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+
+ *typeBits = statement->AsInt32(0);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
+ uint32_t typeBits,
+ uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
+ clientID.get(), typeBits));
+
+ AutoResetStatement statement(mStatement_GatherEntries);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(1, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
+}
+
+nsresult
+nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
+ nsIApplicationCacheNamespace *ns)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString namespaceSpec;
+ nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = ns->GetData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t itemType;
+ rv = ns->GetItemType(&itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
+ clientID.get(), namespaceSpec.get(), data.get(), itemType));
+
+ AutoResetStatement statement(mStatement_InsertNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(2, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(3, itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
+ uint32_t *usage)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
+ PromiseFlatCString(clientID).get()));
+
+ *usage = 0;
+
+ AutoResetStatement statement(mStatement_ApplicationCacheSize);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows)
+ return NS_OK;
+
+ *usage = static_cast<uint32_t>(statement->AsInt32(0));
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetGroups(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroups"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
+}
+
+nsresult
+nsOfflineCacheDevice::GetGroupsTimeOrdered(uint32_t *count,
+ char ***keys)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, count, keys);
+}
+
+bool
+nsOfflineCacheDevice::IsLocked(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ return mLockedEntries.GetEntry(key);
+}
+
+void
+nsOfflineCacheDevice::Lock(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ mLockedEntries.PutEntry(key);
+}
+
+void
+nsOfflineCacheDevice::Unlock(const nsACString &key)
+{
+ MutexAutoLock lock(mLock);
+ mLockedEntries.RemoveEntry(key);
+}
+
+nsresult
+nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
+ uint32_t resultIndex,
+ uint32_t * count,
+ char *** values)
+{
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> valArray;
+ while (hasRows)
+ {
+ uint32_t length;
+ valArray.AppendElement(
+ nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *count = valArray.Length();
+ char **ret = static_cast<char **>(moz_xmalloc(*count * sizeof(char*)));
+ if (!ret) return NS_ERROR_OUT_OF_MEMORY;
+
+ for (uint32_t i = 0; i < *count; i++) {
+ ret[i] = NS_strdup(valArray[i].get());
+ if (!ret[i]) {
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ *values = ret;
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ nsCString clientID;
+ // Some characters are special in the clientID. Escape the groupID
+ // before putting it in to the client key.
+ if (!NS_Escape(nsCString(group), clientID, url_Path)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ PRTime now = PR_Now();
+
+ // Include the timestamp to guarantee uniqueness across runs, and
+ // the gNextTemporaryClientID for uniqueness within a second.
+ clientID.Append(nsPrintfCString("|%016lld|%d",
+ now / PR_USEC_PER_SEC,
+ gNextTemporaryClientID++));
+
+ nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
+ group,
+ clientID);
+ if (!cache)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
+ if (!weak)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ MutexAutoLock lock(mLock);
+ mCaches.Put(clientID, weak);
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
+ nsIApplicationCache **out)
+{
+ MutexAutoLock lock(mLock);
+ return GetApplicationCache_Unlocked(clientID, out);
+}
+
+nsresult
+nsOfflineCacheDevice::GetApplicationCache_Unlocked(const nsACString &clientID,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ nsCOMPtr<nsIApplicationCache> cache;
+
+ nsWeakPtr weak;
+ if (mCaches.Get(clientID, getter_AddRefs(weak)))
+ cache = do_QueryReferent(weak);
+
+ if (!cache)
+ {
+ nsCString group;
+ nsresult rv = GetGroupForCache(clientID, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (group.IsEmpty()) {
+ return NS_OK;
+ }
+
+ cache = new nsApplicationCache(this, group, clientID);
+ weak = do_GetWeakReference(cache);
+ if (!weak)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mCaches.Put(clientID, weak);
+ }
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
+ nsIApplicationCache **out)
+{
+ *out = nullptr;
+
+ MutexAutoLock lock(mLock);
+
+ nsCString *clientID;
+ if (mActiveCachesByGroup.Get(group, &clientID))
+ return GetApplicationCache_Unlocked(*clientID, out);
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString *active = nullptr;
+
+ AutoResetStatement statement(mStatement_DeactivateGroup);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mActiveCachesByGroup.Get(group, &active))
+ {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::Evict(nsILoadContextInfo *aInfo)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aInfo);
+
+ nsresult rv;
+
+ mozilla::OriginAttributes const *oa = aInfo->OriginAttributesPtr();
+
+ if (oa->mAppId == NECKO_NO_APP_ID && oa->mInIsolatedMozBrowser == false) {
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsCacheService::GlobalInstance()->EvictEntriesInternal(nsICache::STORE_OFFLINE);
+ }
+
+ nsAutoCString jaridsuffix;
+ jaridsuffix.Append('%');
+
+ nsAutoCString suffix;
+ oa->CreateSuffix(suffix);
+ jaridsuffix.Append('#');
+ jaridsuffix.Append(suffix);
+
+ AutoResetStatement statement(mStatement_EnumerateApps);
+ rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+namespace { // anon
+
+class OriginMatch final : public mozIStorageFunction
+{
+ ~OriginMatch() {}
+ mozilla::OriginAttributesPattern const mPattern;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+ explicit OriginMatch(mozilla::OriginAttributesPattern const &aPattern)
+ : mPattern(aPattern) {}
+};
+
+NS_IMPL_ISUPPORTS(OriginMatch, mozIStorageFunction)
+
+NS_IMETHODIMP
+OriginMatch::OnFunctionCall(mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString groupId;
+ rv = aFunctionArguments->GetUTF8String(0, groupId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t hash = groupId.Find(NS_LITERAL_CSTRING("#"));
+ if (hash == kNotFound) {
+ // Just ignore...
+ return NS_OK;
+ }
+
+ ++hash;
+
+ nsDependentCSubstring suffix(groupId.BeginReading() + hash, groupId.Length() - hash);
+
+ mozilla::NeckoOriginAttributes oa;
+ bool ok = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+
+ bool match = mPattern.Matches(oa);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsUint32(match ? 1 : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // anon
+
+nsresult
+nsOfflineCacheDevice::Evict(mozilla::OriginAttributesPattern const &aPattern)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ nsCOMPtr<mozIStorageFunction> function1(new OriginMatch(aPattern));
+ rv = mDB->CreateFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"), 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ class AutoRemoveFunc {
+ public:
+ mozIStorageConnection* mDB;
+ explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
+ ~AutoRemoveFunc() {
+ mDB->RemoveFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"));
+ }
+ };
+ AutoRemoveFunc autoRemove(mDB);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE ORIGIN_MATCH(GroupID);"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoResetStatement statementScope(statement);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI,
+ const nsACString &clientID,
+ nsILoadContextInfo *loadContextInfo)
+{
+ {
+ MutexAutoLock lock(mLock);
+ if (!mActiveCaches.Contains(clientID))
+ return false;
+ }
+
+ nsAutoCString groupID;
+ nsresult rv = GetGroupForCache(clientID, groupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> groupURI;
+ rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // When we are choosing an initial cache to load the top
+ // level document from, the URL of that document must have
+ // the same origin as the manifest, according to the spec.
+ // The following check is here because explicit, fallback
+ // and dynamic entries might have origin different from the
+ // manifest origin.
+ if (!NS_SecurityCompareURIs(keyURI, groupURI,
+ GetStrictFileOriginPolicy())) {
+ return false;
+ }
+
+ // Check the groupID we found is equal to groupID based
+ // on the load context demanding load from app cache.
+ // This is check of extended origin.
+
+ nsAutoCString originSuffix;
+ loadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
+
+ nsAutoCString demandedGroupID;
+ rv = BuildApplicationCacheGroupID(groupURI, originSuffix, demandedGroupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (groupID != demandedGroupID) {
+ return false;
+ }
+
+ return true;
+}
+
+
+nsresult
+nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
+ nsILoadContextInfo *loadContextInfo,
+ nsIApplicationCache **out)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(loadContextInfo);
+
+ nsresult rv;
+
+ *out = nullptr;
+
+ nsCOMPtr<nsIURI> keyURI;
+ rv = NS_NewURI(getter_AddRefs(keyURI), key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // First try to find a matching cache entry.
+ AutoResetStatement statement(mStatement_FindClient);
+ rv = statement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ int32_t itemType;
+ rv = statement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
+ nsAutoCString clientID;
+ rv = statement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // OK, we didn't find an exact match. Search for a client with a
+ // matching namespace.
+
+ AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
+
+ rv = nsstatement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows)
+ {
+ int32_t itemType;
+ rv = nsstatement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't associate with a cache based solely on a whitelist entry
+ if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
+ nsAutoCString clientID;
+ rv = nsstatement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString &key)
+{
+ NS_ENSURE_ARG_POINTER(cache);
+
+ nsresult rv;
+
+ nsAutoCString clientID;
+ rv = cache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CacheOpportunistically(clientID, key);
+}
+
+nsresult
+nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
+ const nsCSubstring &clientID)
+{
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ AutoResetStatement statement(mStatement_ActivateClient);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ nsCString *active;
+ if (mActiveCachesByGroup.Get(group, &active))
+ {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ if (!clientID.IsEmpty())
+ {
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+ }
+
+ return NS_OK;
+}
+
+bool
+nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
+ const nsCSubstring &clientID)
+{
+ nsCString *active = nullptr;
+ MutexAutoLock lock(mLock);
+ return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
+}
+
+/**
+ * Preference accessors
+ */
+
+void
+nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile *parentDir)
+{
+ if (Initialized())
+ {
+ NS_ERROR("cannot switch cache directory once initialized");
+ return;
+ }
+
+ if (!parentDir)
+ {
+ mCacheDirectory = nullptr;
+ return;
+ }
+
+ // ensure parent directory exists
+ nsresult rv = EnsureDir(parentDir);
+ if (NS_FAILED(rv))
+ {
+ NS_WARNING("unable to create parent directory");
+ return;
+ }
+
+ mBaseDirectory = parentDir;
+
+ // cache dir may not exist, but that's ok
+ nsCOMPtr<nsIFile> dir;
+ rv = parentDir->Clone(getter_AddRefs(dir));
+ if (NS_FAILED(rv))
+ return;
+ rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ mCacheDirectory = do_QueryInterface(dir);
+}
+
+void
+nsOfflineCacheDevice::SetCapacity(uint32_t capacity)
+{
+ mCacheCapacity = capacity * 1024;
+}
+
+bool
+nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache * aAppCache)
+{
+ if (!mAutoShutdown)
+ return false;
+
+ mAutoShutdown = false;
+
+ Shutdown();
+
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
+ RefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
+ cacheService->RemoveCustomOfflineDevice(this);
+
+ nsAutoCString clientID;
+ aAppCache->GetClientID(clientID);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Remove(clientID);
+
+ return true;
+}