netwerk/cache/nsDiskCacheDeviceSQL.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial