forked from mirrors/gecko-dev
		
	 7937c7c4cc
			
		
	
	
		7937c7c4cc
		
	
	
	
	
		
			
			The patch introduces NS_GetURIWithNewRef and NS_GetURIWithNewRef which perform the same function. Differential Revision: https://phabricator.services.mozilla.com/D2239 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			2903 lines
		
	
	
	
		
			79 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2903 lines
		
	
	
	
		
			79 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "nsMemory.h"
 | |
| 
 | |
| #include "mozIStorageService.h"
 | |
| #include "mozIStorageStatement.h"
 | |
| #include "mozIStorageFunction.h"
 | |
| #include "mozStorageHelper.h"
 | |
| 
 | |
| #include "nsICacheVisitor.h"
 | |
| #include "nsISeekableStream.h"
 | |
| 
 | |
| #include "mozilla/Telemetry.h"
 | |
| 
 | |
| #include "mozilla/storage.h"
 | |
| #include "nsVariant.h"
 | |
| #include "mozilla/BasePrincipal.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::storage;
 | |
| using mozilla::OriginAttributes;
 | |
| 
 | |
| 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)) {
 | |
|     // This code thought it was performing the equivalent of invoking the SQL
 | |
|     // "RAISE(IGNORE)" function.  It was not.  Please see bug 1470961 and any
 | |
|     // follow-ups to understand the plan for correcting this bug.
 | |
|     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=%" PRIx32 "]!\n",
 | |
|          key, generation, static_cast<uint32_t>(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)) {
 | |
|       LOG(("  removing %s\n", items[i]->HumanReadablePath().get()));
 | |
|     }
 | |
| 
 | |
|     items[i]->Remove(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class nsOfflineCacheDiscardCache : public Runnable
 | |
| {
 | |
| public:
 | |
|   nsOfflineCacheDiscardCache(nsOfflineCacheDevice* device,
 | |
|                              nsCString& group,
 | |
|                              nsCString& clientID)
 | |
|     : mozilla::Runnable("nsOfflineCacheDiscardCache")
 | |
|     , 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() = default;
 | |
| 
 | |
|   nsOfflineCacheDevice* mDevice;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsOfflineCacheDeviceInfo::GetDescription(nsACString& aDescription)
 | |
| {
 | |
|   aDescription.AssignLiteral("Offline cache device");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsOfflineCacheDeviceInfo::GetUsageReport(nsACString& aUsageReport)
 | |
| {
 | |
|   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");
 | |
| 
 | |
|   aUsageReport.Assign(buffer);
 | |
|   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() = default;
 | |
| 
 | |
| 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() = default;
 | |
| 
 | |
| public:
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSICACHEENTRYINFO
 | |
| 
 | |
|   nsOfflineCacheRecord *mRec;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsOfflineCacheEntryInfo::GetClientID(nsACString& aClientID)
 | |
| {
 | |
|   aClientID.Assign(mRec->clientID);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsOfflineCacheEntryInfo::GetDeviceID(nsACString& aDeviceID)
 | |
| {
 | |
|   aDeviceID.Assign(OFFLINE_CACHE_DEVICE_ID);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| 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 = NS_GetURIWithNewRef(uri, EmptyCString(), 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)
 | |
|     : mozilla::Runnable("nsCloseDBEvent")
 | |
|   {
 | |
|     mDB = aDB;
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD Run() override
 | |
|   {
 | |
|     mDB->Close();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
| protected:
 | |
|   virtual ~nsCloseDBEvent() = default;
 | |
| 
 | |
| 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)
 | |
| {
 | |
| }
 | |
| 
 | |
| /* 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);
 | |
| 
 | |
|   mInitEventTarget = GetCurrentThreadEventTarget();
 | |
| 
 | |
|   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,        "/* do not warn (bug 1293375) */ 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, "/* do not warn (bug 1293375) */ 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 nsACString& 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 = NS_GetURIWithNewRef(aManifestURL, EmptyCString(), 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 (mInitEventTarget)
 | |
|     isOnCurrentThread = mInitEventTarget->IsOnCurrentThread();
 | |
| 
 | |
|   if (!isOnCurrentThread) {
 | |
|     nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
 | |
| 
 | |
|     if (ev) {
 | |
|       mInitEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     mDB->Close();
 | |
|   }
 | |
| 
 | |
|   mDB = nullptr;
 | |
|   mInitEventTarget = 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 %" PRId64 " %" PRId64 " %" PRId64 "]\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.forget(),
 | |
|                                16 * 1024);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bufferedOut.forget(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("|%016" PRId64 "|%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->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() = default;
 | |
|   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::OriginAttributes 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 nsACString& group,
 | |
|                                     const nsACString& 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 nsACString& group,
 | |
|                                     const nsACString& 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;
 | |
| }
 |