1 /* -*- Mode: C++; indent-tabs-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/. */
6 
7 #include <inttypes.h>
8 
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/Sprintf.h"
12 #include "mozilla/ThreadLocal.h"
13 
14 #include "nsCache.h"
15 #include "nsDiskCache.h"
16 #include "nsDiskCacheDeviceSQL.h"
17 #include "nsCacheService.h"
18 #include "nsApplicationCache.h"
19 #include "../cache2/CacheHashUtils.h"
20 
21 #include "nsNetCID.h"
22 #include "nsNetUtil.h"
23 #include "nsIURI.h"
24 #include "nsEscape.h"
25 #include "nsIPrefBranch.h"
26 #include "nsString.h"
27 #include "nsPrintfCString.h"
28 #include "nsCRT.h"
29 #include "nsArrayUtils.h"
30 #include "nsIArray.h"
31 #include "nsIVariant.h"
32 #include "nsILoadContextInfo.h"
33 #include "nsThreadUtils.h"
34 #include "nsISerializable.h"
35 #include "nsIInputStream.h"
36 #include "nsIOutputStream.h"
37 #include "nsSerializationHelper.h"
38 #include "nsMemory.h"
39 
40 #include "mozIStorageService.h"
41 #include "mozIStorageStatement.h"
42 #include "mozIStorageFunction.h"
43 #include "mozStorageHelper.h"
44 
45 #include "nsICacheVisitor.h"
46 #include "nsISeekableStream.h"
47 
48 #include "mozilla/Telemetry.h"
49 
50 #include "mozilla/storage.h"
51 #include "nsVariant.h"
52 #include "mozilla/BasePrincipal.h"
53 
54 using namespace mozilla;
55 using namespace mozilla::storage;
56 using mozilla::OriginAttributes;
57 
58 static const char OFFLINE_CACHE_DEVICE_ID[] = {"offline"};
59 
60 #define LOG(args) CACHE_LOG_DEBUG(args)
61 
62 static uint32_t gNextTemporaryClientID = 0;
63 
64 /*****************************************************************************
65  * helpers
66  */
67 
EnsureDir(nsIFile * dir)68 static nsresult EnsureDir(nsIFile* dir) {
69   bool exists;
70   nsresult rv = dir->Exists(&exists);
71   if (NS_SUCCEEDED(rv) && !exists)
72     rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
73   return rv;
74 }
75 
DecomposeCacheEntryKey(const nsCString * fullKey,const char ** cid,const char ** key,nsCString & buf)76 static bool DecomposeCacheEntryKey(const nsCString* fullKey, const char** cid,
77                                    const char** key, nsCString& buf) {
78   buf = *fullKey;
79 
80   int32_t colon = buf.FindChar(':');
81   if (colon == kNotFound) {
82     NS_ERROR("Invalid key");
83     return false;
84   }
85   buf.SetCharAt('\0', colon);
86 
87   *cid = buf.get();
88   *key = buf.get() + colon + 1;
89 
90   return true;
91 }
92 
93 class AutoResetStatement {
94  public:
AutoResetStatement(mozIStorageStatement * s)95   explicit AutoResetStatement(mozIStorageStatement* s) : mStatement(s) {}
~AutoResetStatement()96   ~AutoResetStatement() { mStatement->Reset(); }
operator ->()97   mozIStorageStatement* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN {
98     return mStatement;
99   }
100 
101  private:
102   mozIStorageStatement* mStatement;
103 };
104 
105 class EvictionObserver {
106  public:
EvictionObserver(mozIStorageConnection * db,nsOfflineCacheEvictionFunction * evictionFunction)107   EvictionObserver(mozIStorageConnection* db,
108                    nsOfflineCacheEvictionFunction* evictionFunction)
109       : mDB(db), mEvictionFunction(evictionFunction) {
110     mEvictionFunction->Init();
111     mDB->ExecuteSimpleSQL(
112         NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
113                            " ON moz_cache FOR EACH ROW BEGIN SELECT"
114                            " cache_eviction_observer("
115                            "  OLD.ClientID, OLD.key, OLD.generation);"
116                            " END;"));
117   }
118 
~EvictionObserver()119   ~EvictionObserver() {
120     mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
121     mEvictionFunction->Reset();
122   }
123 
Apply()124   void Apply() { return mEvictionFunction->Apply(); }
125 
126  private:
127   mozIStorageConnection* mDB;
128   RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
129 };
130 
131 #define DCACHE_HASH_MAX INT64_MAX
132 #define DCACHE_HASH_BITS 64
133 
134 /**
135  *  nsOfflineCache::Hash(const char * key)
136  *
137  *  This algorithm of this method implies nsOfflineCacheRecords will be stored
138  *  in a certain order on disk.  If the algorithm changes, existing cache
139  *  map files may become invalid, and therefore the kCurrentVersion needs
140  *  to be revised.
141  */
DCacheHash(const char * key)142 static uint64_t DCacheHash(const char* key) {
143   // initval 0x7416f295 was chosen randomly
144   return (uint64_t(CacheHash::Hash(key, strlen(key), 0)) << 32) |
145          CacheHash::Hash(key, strlen(key), 0x7416f295);
146 }
147 
148 /******************************************************************************
149  * nsOfflineCacheEvictionFunction
150  */
151 
NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction,mozIStorageFunction)152 NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
153 
154 // helper function for directly exposing the same data file binding
155 // path algorithm used in nsOfflineCacheBinding::Create
156 static nsresult GetCacheDataFile(nsIFile* cacheDir, const char* key,
157                                  int generation, nsCOMPtr<nsIFile>& file) {
158   cacheDir->Clone(getter_AddRefs(file));
159   if (!file) return NS_ERROR_OUT_OF_MEMORY;
160 
161   uint64_t hash = DCacheHash(key);
162 
163   uint32_t dir1 = (uint32_t)(hash & 0x0F);
164   uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
165 
166   hash >>= 8;
167 
168   file->AppendNative(nsPrintfCString("%X", dir1));
169   file->AppendNative(nsPrintfCString("%X", dir2));
170 
171   char leaf[64];
172   SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
173   return file->AppendNative(nsDependentCString(leaf));
174 }
175 
176 namespace appcachedetail {
177 
178 typedef nsCOMArray<nsIFile> FileArray;
179 static MOZ_THREAD_LOCAL(FileArray*) tlsEvictionItems;
180 
181 }  // namespace appcachedetail
182 
183 NS_IMETHODIMP
OnFunctionCall(mozIStorageValueArray * values,nsIVariant ** _retval)184 nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray* values,
185                                                nsIVariant** _retval) {
186   LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
187 
188   *_retval = nullptr;
189 
190   uint32_t numEntries;
191   nsresult rv = values->GetNumEntries(&numEntries);
192   NS_ENSURE_SUCCESS(rv, rv);
193   NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
194 
195   uint32_t valueLen;
196   const char* clientID = values->AsSharedUTF8String(0, &valueLen);
197   const char* key = values->AsSharedUTF8String(1, &valueLen);
198   nsAutoCString fullKey(clientID);
199   fullKey.Append(':');
200   fullKey.Append(key);
201   int generation = values->AsInt32(2);
202 
203   // If the key is currently locked, refuse to delete this row.
204   if (mDevice->IsLocked(fullKey)) {
205     // This code thought it was performing the equivalent of invoking the SQL
206     // "RAISE(IGNORE)" function.  It was not.  Please see bug 1470961 and any
207     // follow-ups to understand the plan for correcting this bug.
208     return NS_OK;
209   }
210 
211   nsCOMPtr<nsIFile> file;
212   rv = GetCacheDataFile(mDevice->CacheDirectory(), key, generation, file);
213   if (NS_FAILED(rv)) {
214     LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%" PRIx32 "]!\n",
215          key, generation, static_cast<uint32_t>(rv)));
216     return rv;
217   }
218 
219   appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
220   MOZ_ASSERT(items);
221   if (items) {
222     items->AppendObject(file);
223   }
224 
225   return NS_OK;
226 }
227 
nsOfflineCacheEvictionFunction(nsOfflineCacheDevice * device)228 nsOfflineCacheEvictionFunction::nsOfflineCacheEvictionFunction(
229     nsOfflineCacheDevice* device)
230     : mDevice(device) {
231   mTLSInited = appcachedetail::tlsEvictionItems.init();
232 }
233 
Init()234 void nsOfflineCacheEvictionFunction::Init() {
235   if (mTLSInited) {
236     appcachedetail::tlsEvictionItems.set(new appcachedetail::FileArray());
237   }
238 }
239 
Reset()240 void nsOfflineCacheEvictionFunction::Reset() {
241   if (!mTLSInited) {
242     return;
243   }
244 
245   appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
246   if (!items) {
247     return;
248   }
249 
250   appcachedetail::tlsEvictionItems.set(nullptr);
251   delete items;
252 }
253 
Apply()254 void nsOfflineCacheEvictionFunction::Apply() {
255   LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
256 
257   if (!mTLSInited) {
258     return;
259   }
260 
261   appcachedetail::FileArray* pitems = appcachedetail::tlsEvictionItems.get();
262   if (!pitems) {
263     return;
264   }
265 
266   appcachedetail::FileArray items;
267   items.SwapElements(*pitems);
268 
269   for (int32_t i = 0; i < items.Count(); i++) {
270     if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
271       LOG(("  removing %s\n", items[i]->HumanReadablePath().get()));
272     }
273 
274     items[i]->Remove(false);
275   }
276 }
277 
278 class nsOfflineCacheDiscardCache : public Runnable {
279  public:
nsOfflineCacheDiscardCache(nsOfflineCacheDevice * device,nsCString & group,nsCString & clientID)280   nsOfflineCacheDiscardCache(nsOfflineCacheDevice* device, nsCString& group,
281                              nsCString& clientID)
282       : mozilla::Runnable("nsOfflineCacheDiscardCache"),
283         mDevice(device),
284         mGroup(group),
285         mClientID(clientID) {}
286 
Run()287   NS_IMETHOD Run() override {
288     if (mDevice->IsActiveCache(mGroup, mClientID)) {
289       mDevice->DeactivateGroup(mGroup);
290     }
291 
292     return mDevice->EvictEntries(mClientID.get());
293   }
294 
295  private:
296   RefPtr<nsOfflineCacheDevice> mDevice;
297   nsCString mGroup;
298   nsCString mClientID;
299 };
300 
301 /******************************************************************************
302  * nsOfflineCacheDeviceInfo
303  */
304 
305 class nsOfflineCacheDeviceInfo final : public nsICacheDeviceInfo {
306  public:
307   NS_DECL_ISUPPORTS
308   NS_DECL_NSICACHEDEVICEINFO
309 
nsOfflineCacheDeviceInfo(nsOfflineCacheDevice * device)310   explicit nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
311       : mDevice(device) {}
312 
313  private:
314   ~nsOfflineCacheDeviceInfo() = default;
315 
316   nsOfflineCacheDevice* mDevice;
317 };
318 
NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo,nsICacheDeviceInfo)319 NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
320 
321 NS_IMETHODIMP
322 nsOfflineCacheDeviceInfo::GetDescription(nsACString& aDescription) {
323   aDescription.AssignLiteral("Offline cache device");
324   return NS_OK;
325 }
326 
327 NS_IMETHODIMP
GetUsageReport(nsACString & aUsageReport)328 nsOfflineCacheDeviceInfo::GetUsageReport(nsACString& aUsageReport) {
329   nsAutoCString buffer;
330   buffer.AssignLiteral(
331       "  <tr>\n"
332       "    <th>Cache Directory:</th>\n"
333       "    <td>");
334   nsIFile* cacheDir = mDevice->CacheDirectory();
335   if (!cacheDir) return NS_OK;
336 
337   nsAutoString path;
338   nsresult rv = cacheDir->GetPath(path);
339   if (NS_SUCCEEDED(rv))
340     AppendUTF16toUTF8(path, buffer);
341   else
342     buffer.AppendLiteral("directory unavailable");
343 
344   buffer.AppendLiteral(
345       "</td>\n"
346       "  </tr>\n");
347 
348   aUsageReport.Assign(buffer);
349   return NS_OK;
350 }
351 
352 NS_IMETHODIMP
GetEntryCount(uint32_t * aEntryCount)353 nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t* aEntryCount) {
354   *aEntryCount = mDevice->EntryCount();
355   return NS_OK;
356 }
357 
358 NS_IMETHODIMP
GetTotalSize(uint32_t * aTotalSize)359 nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t* aTotalSize) {
360   *aTotalSize = mDevice->CacheSize();
361   return NS_OK;
362 }
363 
364 NS_IMETHODIMP
GetMaximumSize(uint32_t * aMaximumSize)365 nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t* aMaximumSize) {
366   *aMaximumSize = mDevice->CacheCapacity();
367   return NS_OK;
368 }
369 
370 /******************************************************************************
371  * nsOfflineCacheBinding
372  */
373 
374 class nsOfflineCacheBinding final : public nsISupports {
375   ~nsOfflineCacheBinding() = default;
376 
377  public:
378   NS_DECL_THREADSAFE_ISUPPORTS
379 
380   static nsOfflineCacheBinding* Create(nsIFile* cacheDir, const nsCString* key,
381                                        int generation);
382 
383   enum { FLAG_NEW_ENTRY = 1 };
384 
385   nsCOMPtr<nsIFile> mDataFile;
386   int mGeneration;
387   int mFlags;
388 
IsNewEntry()389   bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
MarkNewEntry()390   void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
ClearNewEntry()391   void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
392 };
393 
NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)394 NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
395 
396 nsOfflineCacheBinding* nsOfflineCacheBinding::Create(nsIFile* cacheDir,
397                                                      const nsCString* fullKey,
398                                                      int generation) {
399   nsCOMPtr<nsIFile> file;
400   cacheDir->Clone(getter_AddRefs(file));
401   if (!file) return nullptr;
402 
403   nsAutoCString keyBuf;
404   const char *cid, *key;
405   if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) return nullptr;
406 
407   uint64_t hash = DCacheHash(key);
408 
409   uint32_t dir1 = (uint32_t)(hash & 0x0F);
410   uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
411 
412   hash >>= 8;
413 
414   // XXX we might want to create these directories up-front
415 
416   file->AppendNative(nsPrintfCString("%X", dir1));
417   Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
418 
419   file->AppendNative(nsPrintfCString("%X", dir2));
420   Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
421 
422   nsresult rv;
423   char leaf[64];
424 
425   if (generation == -1) {
426     file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
427 
428     for (generation = 0;; ++generation) {
429       SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
430 
431       rv = file->SetNativeLeafName(nsDependentCString(leaf));
432       if (NS_FAILED(rv)) return nullptr;
433       rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
434       if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) return nullptr;
435       if (NS_SUCCEEDED(rv)) break;
436     }
437   } else {
438     SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
439     rv = file->AppendNative(nsDependentCString(leaf));
440     if (NS_FAILED(rv)) return nullptr;
441   }
442 
443   nsOfflineCacheBinding* binding = new nsOfflineCacheBinding;
444   if (!binding) return nullptr;
445 
446   binding->mDataFile.swap(file);
447   binding->mGeneration = generation;
448   binding->mFlags = 0;
449   return binding;
450 }
451 
452 /******************************************************************************
453  * nsOfflineCacheRecord
454  */
455 
456 struct nsOfflineCacheRecord {
457   const char* clientID;
458   const char* key;
459   const uint8_t* metaData;
460   uint32_t metaDataLen;
461   int32_t generation;
462   int32_t dataSize;
463   int32_t fetchCount;
464   int64_t lastFetched;
465   int64_t lastModified;
466   int64_t expirationTime;
467 };
468 
CreateCacheEntry(nsOfflineCacheDevice * device,const nsCString * fullKey,const nsOfflineCacheRecord & rec)469 static nsCacheEntry* CreateCacheEntry(nsOfflineCacheDevice* device,
470                                       const nsCString* fullKey,
471                                       const nsOfflineCacheRecord& rec) {
472   nsCacheEntry* entry;
473 
474   if (device->IsLocked(*fullKey)) {
475     return nullptr;
476   }
477 
478   nsresult rv = nsCacheEntry::Create(fullKey->get(),  // XXX enable sharing
479                                      nsICache::STREAM_BASED,
480                                      nsICache::STORE_OFFLINE, device, &entry);
481   if (NS_FAILED(rv)) return nullptr;
482 
483   entry->SetFetchCount((uint32_t)rec.fetchCount);
484   entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
485   entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
486   entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
487   entry->SetDataSize((uint32_t)rec.dataSize);
488 
489   entry->UnflattenMetaData((const char*)rec.metaData, rec.metaDataLen);
490 
491   // Restore security info, if present
492   const char* info = entry->GetMetaDataElement("security-info");
493   if (info) {
494     nsCOMPtr<nsISupports> infoObj;
495     rv =
496         NS_DeserializeObject(nsDependentCString(info), getter_AddRefs(infoObj));
497     if (NS_FAILED(rv)) {
498       delete entry;
499       return nullptr;
500     }
501     entry->SetSecurityInfo(infoObj);
502   }
503 
504   // create a binding object for this entry
505   nsOfflineCacheBinding* binding = nsOfflineCacheBinding::Create(
506       device->CacheDirectory(), fullKey, rec.generation);
507   if (!binding) {
508     delete entry;
509     return nullptr;
510   }
511   entry->SetData(binding);
512 
513   return entry;
514 }
515 
516 /******************************************************************************
517  * nsOfflineCacheEntryInfo
518  */
519 
520 class nsOfflineCacheEntryInfo final : public nsICacheEntryInfo {
521   ~nsOfflineCacheEntryInfo() = default;
522 
523  public:
524   NS_DECL_ISUPPORTS
525   NS_DECL_NSICACHEENTRYINFO
526 
527   nsOfflineCacheRecord* mRec;
528 };
529 
NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo,nsICacheEntryInfo)530 NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
531 
532 NS_IMETHODIMP
533 nsOfflineCacheEntryInfo::GetClientID(nsACString& aClientID) {
534   aClientID.Assign(mRec->clientID);
535   return NS_OK;
536 }
537 
538 NS_IMETHODIMP
GetDeviceID(nsACString & aDeviceID)539 nsOfflineCacheEntryInfo::GetDeviceID(nsACString& aDeviceID) {
540   aDeviceID.Assign(OFFLINE_CACHE_DEVICE_ID);
541   return NS_OK;
542 }
543 
544 NS_IMETHODIMP
GetKey(nsACString & clientKey)545 nsOfflineCacheEntryInfo::GetKey(nsACString& clientKey) {
546   clientKey.Assign(mRec->key);
547   return NS_OK;
548 }
549 
550 NS_IMETHODIMP
GetFetchCount(int32_t * aFetchCount)551 nsOfflineCacheEntryInfo::GetFetchCount(int32_t* aFetchCount) {
552   *aFetchCount = mRec->fetchCount;
553   return NS_OK;
554 }
555 
556 NS_IMETHODIMP
GetLastFetched(uint32_t * aLastFetched)557 nsOfflineCacheEntryInfo::GetLastFetched(uint32_t* aLastFetched) {
558   *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
559   return NS_OK;
560 }
561 
562 NS_IMETHODIMP
GetLastModified(uint32_t * aLastModified)563 nsOfflineCacheEntryInfo::GetLastModified(uint32_t* aLastModified) {
564   *aLastModified = SecondsFromPRTime(mRec->lastModified);
565   return NS_OK;
566 }
567 
568 NS_IMETHODIMP
GetExpirationTime(uint32_t * aExpirationTime)569 nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t* aExpirationTime) {
570   *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
571   return NS_OK;
572 }
573 
574 NS_IMETHODIMP
IsStreamBased(bool * aStreamBased)575 nsOfflineCacheEntryInfo::IsStreamBased(bool* aStreamBased) {
576   *aStreamBased = true;
577   return NS_OK;
578 }
579 
580 NS_IMETHODIMP
GetDataSize(uint32_t * aDataSize)581 nsOfflineCacheEntryInfo::GetDataSize(uint32_t* aDataSize) {
582   *aDataSize = mRec->dataSize;
583   return NS_OK;
584 }
585 
586 /******************************************************************************
587  * nsApplicationCacheNamespace
588  */
589 
NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace,nsIApplicationCacheNamespace)590 NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
591 
592 NS_IMETHODIMP
593 nsApplicationCacheNamespace::Init(uint32_t itemType,
594                                   const nsACString& namespaceSpec,
595                                   const nsACString& data) {
596   mItemType = itemType;
597   mNamespaceSpec = namespaceSpec;
598   mData = data;
599   return NS_OK;
600 }
601 
602 NS_IMETHODIMP
GetItemType(uint32_t * out)603 nsApplicationCacheNamespace::GetItemType(uint32_t* out) {
604   *out = mItemType;
605   return NS_OK;
606 }
607 
608 NS_IMETHODIMP
GetNamespaceSpec(nsACString & out)609 nsApplicationCacheNamespace::GetNamespaceSpec(nsACString& out) {
610   out = mNamespaceSpec;
611   return NS_OK;
612 }
613 
614 NS_IMETHODIMP
GetData(nsACString & out)615 nsApplicationCacheNamespace::GetData(nsACString& out) {
616   out = mData;
617   return NS_OK;
618 }
619 
620 /******************************************************************************
621  * nsApplicationCache
622  */
623 
NS_IMPL_ISUPPORTS(nsApplicationCache,nsIApplicationCache,nsISupportsWeakReference)624 NS_IMPL_ISUPPORTS(nsApplicationCache, nsIApplicationCache,
625                   nsISupportsWeakReference)
626 
627 nsApplicationCache::nsApplicationCache() : mDevice(nullptr), mValid(true) {}
628 
nsApplicationCache(nsOfflineCacheDevice * device,const nsACString & group,const nsACString & clientID)629 nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice* device,
630                                        const nsACString& group,
631                                        const nsACString& clientID)
632     : mDevice(device), mGroup(group), mClientID(clientID), mValid(true) {}
633 
~nsApplicationCache()634 nsApplicationCache::~nsApplicationCache() {
635   if (!mDevice) return;
636 
637   {
638     MutexAutoLock lock(mDevice->mLock);
639     mDevice->mCaches.Remove(mClientID);
640   }
641 
642   // If this isn't an active cache anymore, it can be destroyed.
643   if (mValid && !mDevice->IsActiveCache(mGroup, mClientID)) Discard();
644 }
645 
MarkInvalid()646 void nsApplicationCache::MarkInvalid() { mValid = false; }
647 
648 NS_IMETHODIMP
InitAsHandle(const nsACString & groupId,const nsACString & clientId)649 nsApplicationCache::InitAsHandle(const nsACString& groupId,
650                                  const nsACString& clientId) {
651   NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
652   NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
653 
654   mGroup = groupId;
655   mClientID = clientId;
656   return NS_OK;
657 }
658 
659 NS_IMETHODIMP
GetManifestURI(nsIURI ** out)660 nsApplicationCache::GetManifestURI(nsIURI** out) {
661   nsCOMPtr<nsIURI> uri;
662   nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
663   NS_ENSURE_SUCCESS(rv, rv);
664 
665   rv = NS_GetURIWithNewRef(uri, EmptyCString(), out);
666   NS_ENSURE_SUCCESS(rv, rv);
667 
668   return NS_OK;
669 }
670 
671 NS_IMETHODIMP
GetGroupID(nsACString & out)672 nsApplicationCache::GetGroupID(nsACString& out) {
673   out = mGroup;
674   return NS_OK;
675 }
676 
677 NS_IMETHODIMP
GetClientID(nsACString & out)678 nsApplicationCache::GetClientID(nsACString& out) {
679   out = mClientID;
680   return NS_OK;
681 }
682 
683 NS_IMETHODIMP
GetProfileDirectory(nsIFile ** out)684 nsApplicationCache::GetProfileDirectory(nsIFile** out) {
685   *out = do_AddRef(mDevice->BaseDirectory()).take();
686   return NS_OK;
687 }
688 
689 NS_IMETHODIMP
GetActive(bool * out)690 nsApplicationCache::GetActive(bool* out) {
691   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
692 
693   *out = mDevice->IsActiveCache(mGroup, mClientID);
694   return NS_OK;
695 }
696 
697 NS_IMETHODIMP
Activate()698 nsApplicationCache::Activate() {
699   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
700   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
701 
702   mDevice->ActivateCache(mGroup, mClientID);
703 
704   if (mDevice->AutoShutdown(this)) mDevice = nullptr;
705 
706   return NS_OK;
707 }
708 
709 NS_IMETHODIMP
Discard()710 nsApplicationCache::Discard() {
711   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
712   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
713 
714   mValid = false;
715 
716   nsCOMPtr<nsIRunnable> ev =
717       new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
718   nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
719   return rv;
720 }
721 
722 NS_IMETHODIMP
MarkEntry(const nsACString & key,uint32_t typeBits)723 nsApplicationCache::MarkEntry(const nsACString& key, uint32_t typeBits) {
724   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
725   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
726 
727   return mDevice->MarkEntry(mClientID, key, typeBits);
728 }
729 
730 NS_IMETHODIMP
UnmarkEntry(const nsACString & key,uint32_t typeBits)731 nsApplicationCache::UnmarkEntry(const nsACString& key, uint32_t typeBits) {
732   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
733   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
734 
735   return mDevice->UnmarkEntry(mClientID, key, typeBits);
736 }
737 
738 NS_IMETHODIMP
GetTypes(const nsACString & key,uint32_t * typeBits)739 nsApplicationCache::GetTypes(const nsACString& key, uint32_t* typeBits) {
740   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
741   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
742 
743   return mDevice->GetTypes(mClientID, key, typeBits);
744 }
745 
746 NS_IMETHODIMP
GatherEntries(uint32_t typeBits,nsTArray<nsCString> & keys)747 nsApplicationCache::GatherEntries(uint32_t typeBits,
748                                   nsTArray<nsCString>& keys) {
749   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
750   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
751 
752   return mDevice->GatherEntries(mClientID, typeBits, keys);
753 }
754 
755 NS_IMETHODIMP
AddNamespaces(nsIArray * namespaces)756 nsApplicationCache::AddNamespaces(nsIArray* namespaces) {
757   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
758   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
759 
760   if (!namespaces) return NS_OK;
761 
762   mozStorageTransaction transaction(mDevice->mDB, false);
763 
764   uint32_t length;
765   nsresult rv = namespaces->GetLength(&length);
766   NS_ENSURE_SUCCESS(rv, rv);
767 
768   for (uint32_t i = 0; i < length; i++) {
769     nsCOMPtr<nsIApplicationCacheNamespace> ns =
770         do_QueryElementAt(namespaces, i);
771     if (ns) {
772       rv = mDevice->AddNamespace(mClientID, ns);
773       NS_ENSURE_SUCCESS(rv, rv);
774     }
775   }
776 
777   rv = transaction.Commit();
778   NS_ENSURE_SUCCESS(rv, rv);
779 
780   return NS_OK;
781 }
782 
783 NS_IMETHODIMP
GetMatchingNamespace(const nsACString & key,nsIApplicationCacheNamespace ** out)784 nsApplicationCache::GetMatchingNamespace(const nsACString& key,
785                                          nsIApplicationCacheNamespace** out)
786 
787 {
788   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
789   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
790 
791   return mDevice->GetMatchingNamespace(mClientID, key, out);
792 }
793 
794 NS_IMETHODIMP
GetUsage(uint32_t * usage)795 nsApplicationCache::GetUsage(uint32_t* usage) {
796   NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
797   NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
798 
799   return mDevice->GetUsage(mClientID, usage);
800 }
801 
802 /******************************************************************************
803  * nsCloseDBEvent
804  *****************************************************************************/
805 
806 class nsCloseDBEvent : public Runnable {
807  public:
nsCloseDBEvent(mozIStorageConnection * aDB)808   explicit nsCloseDBEvent(mozIStorageConnection* aDB)
809       : mozilla::Runnable("nsCloseDBEvent") {
810     mDB = aDB;
811   }
812 
Run()813   NS_IMETHOD Run() override {
814     mDB->Close();
815     return NS_OK;
816   }
817 
818  protected:
819   virtual ~nsCloseDBEvent() = default;
820 
821  private:
822   nsCOMPtr<mozIStorageConnection> mDB;
823 };
824 
825 /******************************************************************************
826  * nsOfflineCacheDevice
827  */
828 
NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)829 NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
830 
831 nsOfflineCacheDevice::nsOfflineCacheDevice()
832     : mDB(nullptr),
833       mCacheCapacity(0),
834       mDeltaCounter(0),
835       mAutoShutdown(false),
836       mLock("nsOfflineCacheDevice.lock"),
837       mActiveCaches(4),
838       mLockedEntries(32) {}
839 
840 /* static */
GetStrictFileOriginPolicy()841 bool nsOfflineCacheDevice::GetStrictFileOriginPolicy() {
842   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
843 
844   bool retval;
845   if (prefs && NS_SUCCEEDED(prefs->GetBoolPref(
846                    "security.fileuri.strict_origin_policy", &retval)))
847     return retval;
848 
849   // As default value use true (be more strict)
850   return true;
851 }
852 
CacheSize()853 uint32_t nsOfflineCacheDevice::CacheSize() {
854   NS_ENSURE_TRUE(Initialized(), 0);
855 
856   AutoResetStatement statement(mStatement_CacheSize);
857 
858   bool hasRows;
859   nsresult rv = statement->ExecuteStep(&hasRows);
860   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
861 
862   return (uint32_t)statement->AsInt32(0);
863 }
864 
EntryCount()865 uint32_t nsOfflineCacheDevice::EntryCount() {
866   NS_ENSURE_TRUE(Initialized(), 0);
867 
868   AutoResetStatement statement(mStatement_EntryCount);
869 
870   bool hasRows;
871   nsresult rv = statement->ExecuteStep(&hasRows);
872   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
873 
874   return (uint32_t)statement->AsInt32(0);
875 }
876 
UpdateEntry(nsCacheEntry * entry)877 nsresult nsOfflineCacheDevice::UpdateEntry(nsCacheEntry* entry) {
878   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
879 
880   // Decompose the key into "ClientID" and "Key"
881   nsAutoCString keyBuf;
882   const char *cid, *key;
883 
884   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
885     return NS_ERROR_UNEXPECTED;
886 
887   // Store security info, if it is serializable
888   nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
889   nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
890   if (infoObj && !serializable) return NS_ERROR_UNEXPECTED;
891 
892   if (serializable) {
893     nsCString info;
894     nsresult rv = NS_SerializeToString(serializable, info);
895     NS_ENSURE_SUCCESS(rv, rv);
896 
897     rv = entry->SetMetaDataElement("security-info", info.get());
898     NS_ENSURE_SUCCESS(rv, rv);
899   }
900 
901   nsCString metaDataBuf;
902   uint32_t mdSize = entry->MetaDataSize();
903   if (!metaDataBuf.SetLength(mdSize, fallible)) return NS_ERROR_OUT_OF_MEMORY;
904   char* md = metaDataBuf.BeginWriting();
905   entry->FlattenMetaData(md, mdSize);
906 
907   nsOfflineCacheRecord rec;
908   rec.metaData = (const uint8_t*)md;
909   rec.metaDataLen = mdSize;
910   rec.dataSize = entry->DataSize();
911   rec.fetchCount = entry->FetchCount();
912   rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
913   rec.lastModified = PRTimeFromSeconds(entry->LastModified());
914   rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
915 
916   AutoResetStatement statement(mStatement_UpdateEntry);
917 
918   nsresult rv;
919   rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
920   nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
921   if (NS_FAILED(tmp)) {
922     rv = tmp;
923   }
924   tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
925   if (NS_FAILED(tmp)) {
926     rv = tmp;
927   }
928   tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
929   if (NS_FAILED(tmp)) {
930     rv = tmp;
931   }
932   tmp = statement->BindInt64ByIndex(4, rec.lastModified);
933   if (NS_FAILED(tmp)) {
934     rv = tmp;
935   }
936   tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
937   if (NS_FAILED(tmp)) {
938     rv = tmp;
939   }
940   tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
941   if (NS_FAILED(tmp)) {
942     rv = tmp;
943   }
944   tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
945   if (NS_FAILED(tmp)) {
946     rv = tmp;
947   }
948   NS_ENSURE_SUCCESS(rv, rv);
949 
950   bool hasRows;
951   rv = statement->ExecuteStep(&hasRows);
952   NS_ENSURE_SUCCESS(rv, rv);
953 
954   NS_ASSERTION(!hasRows, "UPDATE should not result in output");
955   return rv;
956 }
957 
UpdateEntrySize(nsCacheEntry * entry,uint32_t newSize)958 nsresult nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry* entry,
959                                                uint32_t newSize) {
960   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
961 
962   // Decompose the key into "ClientID" and "Key"
963   nsAutoCString keyBuf;
964   const char *cid, *key;
965   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
966     return NS_ERROR_UNEXPECTED;
967 
968   AutoResetStatement statement(mStatement_UpdateEntrySize);
969 
970   nsresult rv = statement->BindInt32ByIndex(0, newSize);
971   nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
972   if (NS_FAILED(tmp)) {
973     rv = tmp;
974   }
975   tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
976   if (NS_FAILED(tmp)) {
977     rv = tmp;
978   }
979   NS_ENSURE_SUCCESS(rv, rv);
980 
981   bool hasRows;
982   rv = statement->ExecuteStep(&hasRows);
983   NS_ENSURE_SUCCESS(rv, rv);
984 
985   NS_ASSERTION(!hasRows, "UPDATE should not result in output");
986   return rv;
987 }
988 
DeleteEntry(nsCacheEntry * entry,bool deleteData)989 nsresult nsOfflineCacheDevice::DeleteEntry(nsCacheEntry* entry,
990                                            bool deleteData) {
991   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
992 
993   if (deleteData) {
994     nsresult rv = DeleteData(entry);
995     if (NS_FAILED(rv)) return rv;
996   }
997 
998   // Decompose the key into "ClientID" and "Key"
999   nsAutoCString keyBuf;
1000   const char *cid, *key;
1001   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1002     return NS_ERROR_UNEXPECTED;
1003 
1004   AutoResetStatement statement(mStatement_DeleteEntry);
1005 
1006   nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1007   nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1008   NS_ENSURE_SUCCESS(rv, rv);
1009   NS_ENSURE_SUCCESS(rv2, rv2);
1010 
1011   bool hasRows;
1012   rv = statement->ExecuteStep(&hasRows);
1013   NS_ENSURE_SUCCESS(rv, rv);
1014 
1015   NS_ASSERTION(!hasRows, "DELETE should not result in output");
1016   return rv;
1017 }
1018 
DeleteData(nsCacheEntry * entry)1019 nsresult nsOfflineCacheDevice::DeleteData(nsCacheEntry* entry) {
1020   nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
1021   NS_ENSURE_STATE(binding);
1022 
1023   return binding->mDataFile->Remove(false);
1024 }
1025 
1026 /**
1027  * nsCacheDevice implementation
1028  */
1029 
1030 // This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
1031 // allow a template (mozilla::ArrayLength) to be instantiated based on a local
1032 // type.  Boo-urns!
1033 struct StatementSql {
1034   nsCOMPtr<mozIStorageStatement>& statement;
1035   const char* sql;
StatementSqlStatementSql1036   StatementSql(nsCOMPtr<mozIStorageStatement>& aStatement, const char* aSql)
1037       : statement(aStatement), sql(aSql) {}
1038 };
1039 
Init()1040 nsresult nsOfflineCacheDevice::Init() {
1041   MOZ_ASSERT(false, "Need to be initialized with sqlite");
1042   return NS_ERROR_NOT_IMPLEMENTED;
1043 }
1044 
InitWithSqlite(mozIStorageService * ss)1045 nsresult nsOfflineCacheDevice::InitWithSqlite(mozIStorageService* ss) {
1046   NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
1047 
1048   // SetCacheParentDirectory must have been called
1049   NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
1050 
1051   // make sure the cache directory exists
1052   nsresult rv = EnsureDir(mCacheDirectory);
1053   NS_ENSURE_SUCCESS(rv, rv);
1054 
1055   // build path to index file
1056   nsCOMPtr<nsIFile> indexFile;
1057   rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
1058   NS_ENSURE_SUCCESS(rv, rv);
1059   rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
1060   NS_ENSURE_SUCCESS(rv, rv);
1061 
1062   MOZ_ASSERT(ss,
1063              "nsOfflineCacheDevice::InitWithSqlite called before "
1064              "nsCacheService::Init() ?");
1065   NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
1066 
1067   rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
1068   NS_ENSURE_SUCCESS(rv, rv);
1069 
1070   mInitEventTarget = GetCurrentThreadEventTarget();
1071 
1072   mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
1073 
1074   // XXX ... other initialization steps
1075 
1076   // XXX in the future we may wish to verify the schema for moz_cache
1077   //     perhaps using "PRAGMA table_info" ?
1078 
1079   // build the table
1080   //
1081   //  "Generation" is the data file generation number.
1082   //
1083   rv = mDB->ExecuteSimpleSQL(
1084       NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1085                          "  ClientID        TEXT,\n"
1086                          "  Key             TEXT,\n"
1087                          "  MetaData        BLOB,\n"
1088                          "  Generation      INTEGER,\n"
1089                          "  DataSize        INTEGER,\n"
1090                          "  FetchCount      INTEGER,\n"
1091                          "  LastFetched     INTEGER,\n"
1092                          "  LastModified    INTEGER,\n"
1093                          "  ExpirationTime  INTEGER,\n"
1094                          "  ItemType        INTEGER DEFAULT 0\n"
1095                          ");\n"));
1096   NS_ENSURE_SUCCESS(rv, rv);
1097 
1098   // Databases from 1.9.0 don't have the ItemType column.  Add the column
1099   // here, but don't worry about failures (the column probably already exists)
1100   mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1101       "ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1102 
1103   // Create the table for storing cache groups.  All actions on
1104   // moz_cache_groups use the GroupID, so use it as the primary key.
1105   rv = mDB->ExecuteSimpleSQL(
1106       NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1107                          " GroupID TEXT PRIMARY KEY,\n"
1108                          " ActiveClientID TEXT\n"
1109                          ");\n"));
1110   NS_ENSURE_SUCCESS(rv, rv);
1111 
1112   mDB->ExecuteSimpleSQL(
1113       NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1114                          "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1115 
1116   // ClientID: clientID joining moz_cache and moz_cache_namespaces
1117   // tables.
1118   // Data: Data associated with this namespace (e.g. a fallback URI
1119   // for fallback entries).
1120   // ItemType: the type of namespace.
1121   rv =
1122       mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1123                                                " moz_cache_namespaces (\n"
1124                                                " ClientID TEXT,\n"
1125                                                " NameSpace TEXT,\n"
1126                                                " Data TEXT,\n"
1127                                                " ItemType INTEGER\n"
1128                                                ");\n"));
1129   NS_ENSURE_SUCCESS(rv, rv);
1130 
1131   // Databases from 1.9.0 have a moz_cache_index that should be dropped
1132   rv = mDB->ExecuteSimpleSQL(
1133       NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1134   NS_ENSURE_SUCCESS(rv, rv);
1135 
1136   // Key/ClientID pairs should be unique in the database.  All queries
1137   // against moz_cache use the Key (which is also the most unique), so
1138   // use it as the primary key for this index.
1139   rv = mDB->ExecuteSimpleSQL(
1140       NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1141                          " moz_cache_key_clientid_index"
1142                          " ON moz_cache (Key, ClientID);"));
1143   NS_ENSURE_SUCCESS(rv, rv);
1144 
1145   // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1146   rv = mDB->ExecuteSimpleSQL(
1147       NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1148                          " moz_cache_namespaces_clientid_index"
1149                          " ON moz_cache_namespaces (ClientID, NameSpace);"));
1150   NS_ENSURE_SUCCESS(rv, rv);
1151 
1152   // Used for namespace lookups.
1153   rv = mDB->ExecuteSimpleSQL(
1154       NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1155                          " moz_cache_namespaces_namespace_index"
1156                          " ON moz_cache_namespaces (NameSpace);"));
1157   NS_ENSURE_SUCCESS(rv, rv);
1158 
1159   mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
1160   if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
1161 
1162   rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3,
1163                            mEvictionFunction);
1164   NS_ENSURE_SUCCESS(rv, rv);
1165 
1166   // create all (most) of our statements up front
1167   StatementSql prepared[] = {
1168       // clang-format off
1169     StatementSql ( mStatement_CacheSize,         "SELECT Sum(DataSize) from moz_cache;" ),
1170     StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
1171     StatementSql ( mStatement_EntryCount,        "SELECT count(*) from moz_cache;" ),
1172     StatementSql ( mStatement_UpdateEntry,       "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1173     StatementSql ( mStatement_UpdateEntrySize,   "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1174     StatementSql ( mStatement_DeleteEntry,       "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1175     StatementSql ( mStatement_FindEntry,         "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1176     StatementSql ( mStatement_BindEntry,         "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
1177 
1178     StatementSql ( mStatement_MarkEntry,         "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1179     StatementSql ( mStatement_UnmarkEntry,       "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1180     StatementSql ( mStatement_GetTypes,          "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1181     StatementSql ( mStatement_CleanupUnmarked,   "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1182     StatementSql ( mStatement_GatherEntries,     "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1183 
1184     StatementSql ( mStatement_ActivateClient,    "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1185     StatementSql ( mStatement_DeactivateGroup,   "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1186     StatementSql ( mStatement_FindClient,        "/* do not warn (bug 1293375) */ SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1187 
1188     // Search for namespaces that match the URI.  Use the <= operator
1189     // to ensure that we use the index on moz_cache_namespaces.
1190     StatementSql ( mStatement_FindClientByNamespace, "/* do not warn (bug 1293375) */ SELECT ns.ClientID, ns.ItemType FROM"
1191                                                      "  moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1192                                                      "  ON ns.ClientID = groups.ActiveClientID"
1193                                                      " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1194                                                      " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1195     StatementSql ( mStatement_FindNamespaceEntry,    "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
1196                                                      " WHERE ClientID = ?1"
1197                                                      " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1198                                                      " ORDER BY NameSpace DESC;"),
1199     StatementSql ( mStatement_InsertNamespaceEntry,  "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1200     StatementSql ( mStatement_EnumerateApps,         "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
1201     StatementSql ( mStatement_EnumerateGroups,       "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
1202     StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
1203       // clang-format on
1204   };
1205   for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i) {
1206     LOG(("Creating statement: %s\n", prepared[i].sql));
1207 
1208     rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
1209                               getter_AddRefs(prepared[i].statement));
1210     NS_ENSURE_SUCCESS(rv, rv);
1211   }
1212 
1213   rv = InitActiveCaches();
1214   NS_ENSURE_SUCCESS(rv, rv);
1215 
1216   return NS_OK;
1217 }
1218 
1219 namespace {
1220 
GetGroupForCache(const nsACString & clientID,nsCString & group)1221 nsresult GetGroupForCache(const nsACString& clientID, nsCString& group) {
1222   group.Assign(clientID);
1223   group.Truncate(group.FindChar('|'));
1224   NS_UnescapeURL(group);
1225 
1226   return NS_OK;
1227 }
1228 
1229 }  // namespace
1230 
1231 // static
BuildApplicationCacheGroupID(nsIURI * aManifestURL,nsACString const & aOriginSuffix,nsACString & _result)1232 nsresult nsOfflineCacheDevice::BuildApplicationCacheGroupID(
1233     nsIURI* aManifestURL, nsACString const& aOriginSuffix,
1234     nsACString& _result) {
1235   nsCOMPtr<nsIURI> newURI;
1236   nsresult rv =
1237       NS_GetURIWithNewRef(aManifestURL, EmptyCString(), getter_AddRefs(newURI));
1238   NS_ENSURE_SUCCESS(rv, rv);
1239 
1240   nsAutoCString manifestSpec;
1241   rv = newURI->GetAsciiSpec(manifestSpec);
1242   NS_ENSURE_SUCCESS(rv, rv);
1243 
1244   _result.Assign(manifestSpec);
1245   _result.Append('#');
1246   _result.Append(aOriginSuffix);
1247 
1248   return NS_OK;
1249 }
1250 
InitActiveCaches()1251 nsresult nsOfflineCacheDevice::InitActiveCaches() {
1252   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1253 
1254   MutexAutoLock lock(mLock);
1255 
1256   AutoResetStatement statement(mStatement_EnumerateGroups);
1257 
1258   bool hasRows;
1259   nsresult rv = statement->ExecuteStep(&hasRows);
1260   NS_ENSURE_SUCCESS(rv, rv);
1261 
1262   while (hasRows) {
1263     nsAutoCString group;
1264     statement->GetUTF8String(0, group);
1265     nsCString clientID;
1266     statement->GetUTF8String(1, clientID);
1267 
1268     mActiveCaches.PutEntry(clientID);
1269     mActiveCachesByGroup.Put(group, new nsCString(clientID));
1270 
1271     rv = statement->ExecuteStep(&hasRows);
1272     NS_ENSURE_SUCCESS(rv, rv);
1273   }
1274 
1275   return NS_OK;
1276 }
1277 
Shutdown()1278 nsresult nsOfflineCacheDevice::Shutdown() {
1279   NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
1280 
1281   {
1282     MutexAutoLock lock(mLock);
1283     for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
1284       nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(iter.UserData());
1285       if (obj) {
1286         auto appCache = static_cast<nsApplicationCache*>(obj.get());
1287         appCache->MarkInvalid();
1288       }
1289     }
1290   }
1291 
1292   {
1293     EvictionObserver evictionObserver(mDB, mEvictionFunction);
1294 
1295     // Delete all rows whose clientID is not an active clientID.
1296     nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1297         "DELETE FROM moz_cache WHERE rowid IN"
1298         "  (SELECT moz_cache.rowid FROM"
1299         "    moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1300         "      (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1301         "   WHERE moz_cache_groups.GroupID ISNULL)"));
1302 
1303     if (NS_FAILED(rv))
1304       NS_WARNING("Failed to clean up unused application caches.");
1305     else
1306       evictionObserver.Apply();
1307 
1308     // Delete all namespaces whose clientID is not an active clientID.
1309     rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1310         "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1311         "  (SELECT moz_cache_namespaces.rowid FROM"
1312         "    moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1313         "      (moz_cache_namespaces.ClientID = "
1314         "moz_cache_groups.ActiveClientID)"
1315         "   WHERE moz_cache_groups.GroupID ISNULL)"));
1316 
1317     if (NS_FAILED(rv)) NS_WARNING("Failed to clean up namespaces.");
1318 
1319     mEvictionFunction = nullptr;
1320 
1321     mStatement_CacheSize = nullptr;
1322     mStatement_ApplicationCacheSize = nullptr;
1323     mStatement_EntryCount = nullptr;
1324     mStatement_UpdateEntry = nullptr;
1325     mStatement_UpdateEntrySize = nullptr;
1326     mStatement_DeleteEntry = nullptr;
1327     mStatement_FindEntry = nullptr;
1328     mStatement_BindEntry = nullptr;
1329     mStatement_ClearDomain = nullptr;
1330     mStatement_MarkEntry = nullptr;
1331     mStatement_UnmarkEntry = nullptr;
1332     mStatement_GetTypes = nullptr;
1333     mStatement_FindNamespaceEntry = nullptr;
1334     mStatement_InsertNamespaceEntry = nullptr;
1335     mStatement_CleanupUnmarked = nullptr;
1336     mStatement_GatherEntries = nullptr;
1337     mStatement_ActivateClient = nullptr;
1338     mStatement_DeactivateGroup = nullptr;
1339     mStatement_FindClient = nullptr;
1340     mStatement_FindClientByNamespace = nullptr;
1341     mStatement_EnumerateApps = nullptr;
1342     mStatement_EnumerateGroups = nullptr;
1343     mStatement_EnumerateGroupsTimeOrder = nullptr;
1344   }
1345 
1346   // Close Database on the correct thread
1347   bool isOnCurrentThread = true;
1348   if (mInitEventTarget)
1349     isOnCurrentThread = mInitEventTarget->IsOnCurrentThread();
1350 
1351   if (!isOnCurrentThread) {
1352     nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
1353 
1354     if (ev) {
1355       mInitEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
1356     }
1357   } else {
1358     mDB->Close();
1359   }
1360 
1361   mDB = nullptr;
1362   mInitEventTarget = nullptr;
1363 
1364   return NS_OK;
1365 }
1366 
GetDeviceID()1367 const char* nsOfflineCacheDevice::GetDeviceID() {
1368   return OFFLINE_CACHE_DEVICE_ID;
1369 }
1370 
FindEntry(nsCString * fullKey,bool * collision)1371 nsCacheEntry* nsOfflineCacheDevice::FindEntry(nsCString* fullKey,
1372                                               bool* collision) {
1373   NS_ENSURE_TRUE(Initialized(), nullptr);
1374 
1375   mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2>
1376       timer;
1377   LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
1378 
1379   // SELECT * FROM moz_cache WHERE key = ?
1380 
1381   // Decompose the key into "ClientID" and "Key"
1382   nsAutoCString keyBuf;
1383   const char *cid, *key;
1384   if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) return nullptr;
1385 
1386   AutoResetStatement statement(mStatement_FindEntry);
1387 
1388   nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1389   nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1390   NS_ENSURE_SUCCESS(rv, nullptr);
1391   NS_ENSURE_SUCCESS(rv2, nullptr);
1392 
1393   bool hasRows;
1394   rv = statement->ExecuteStep(&hasRows);
1395   if (NS_FAILED(rv) || !hasRows) return nullptr;  // entry not found
1396 
1397   nsOfflineCacheRecord rec;
1398   statement->GetSharedBlob(0, &rec.metaDataLen, (const uint8_t**)&rec.metaData);
1399   rec.generation = statement->AsInt32(1);
1400   rec.dataSize = statement->AsInt32(2);
1401   rec.fetchCount = statement->AsInt32(3);
1402   rec.lastFetched = statement->AsInt64(4);
1403   rec.lastModified = statement->AsInt64(5);
1404   rec.expirationTime = statement->AsInt64(6);
1405 
1406   LOG(("entry: [%u %d %d %d %" PRId64 " %" PRId64 " %" PRId64 "]\n",
1407        rec.metaDataLen, rec.generation, rec.dataSize, rec.fetchCount,
1408        rec.lastFetched, rec.lastModified, rec.expirationTime));
1409 
1410   nsCacheEntry* entry = CreateCacheEntry(this, fullKey, rec);
1411 
1412   if (entry) {
1413     // make sure that the data file exists
1414     nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
1415     bool isFile;
1416     rv = binding->mDataFile->IsFile(&isFile);
1417     if (NS_FAILED(rv) || !isFile) {
1418       DeleteEntry(entry, false);
1419       delete entry;
1420       return nullptr;
1421     }
1422 
1423     // lock the entry
1424     Lock(*fullKey);
1425   }
1426 
1427   return entry;
1428 }
1429 
DeactivateEntry(nsCacheEntry * entry)1430 nsresult nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry* entry) {
1431   LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1432        entry->Key()->get()));
1433 
1434   // This method is called to inform us that the nsCacheEntry object is going
1435   // away.  We should persist anything that needs to be persisted, or if the
1436   // entry is doomed, we can go ahead and clear its storage.
1437 
1438   if (entry->IsDoomed()) {
1439     // remove corresponding row and file if they exist
1440 
1441     // the row should have been removed in DoomEntry... we could assert that
1442     // that happened.  otherwise, all we have to do here is delete the file
1443     // on disk.
1444     DeleteData(entry);
1445   } else if (((nsOfflineCacheBinding*)entry->Data())->IsNewEntry()) {
1446     // UPDATE the database row
1447 
1448     // Only new entries are updated, since offline cache is updated in
1449     // transactions.  New entries are those who is returned from
1450     // BindEntry().
1451 
1452     LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
1453     UpdateEntry(entry);
1454   } else {
1455     LOG(
1456         ("nsOfflineCacheDevice::DeactivateEntry "
1457          "skipping update since entry is not dirty\n"));
1458   }
1459 
1460   // Unlock the entry
1461   Unlock(*entry->Key());
1462 
1463   delete entry;
1464 
1465   return NS_OK;
1466 }
1467 
BindEntry(nsCacheEntry * entry)1468 nsresult nsOfflineCacheDevice::BindEntry(nsCacheEntry* entry) {
1469   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1470 
1471   LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
1472 
1473   NS_ENSURE_STATE(!entry->Data());
1474 
1475   // This method is called to inform us that we have a new entry.  The entry
1476   // may collide with an existing entry in our DB, but if that happens we can
1477   // assume that the entry is not being used.
1478 
1479   // INSERT the database row
1480 
1481   // XXX Assumption: if the row already exists, then FindEntry would have
1482   // returned it.  if that entry was doomed, then DoomEntry would have removed
1483   // it from the table.  so, we should always have to insert at this point.
1484 
1485   // Decompose the key into "ClientID" and "Key"
1486   nsAutoCString keyBuf;
1487   const char *cid, *key;
1488   if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1489     return NS_ERROR_UNEXPECTED;
1490 
1491   // create binding, pick best generation number
1492   RefPtr<nsOfflineCacheBinding> binding =
1493       nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
1494   if (!binding) return NS_ERROR_OUT_OF_MEMORY;
1495   binding->MarkNewEntry();
1496 
1497   nsOfflineCacheRecord rec;
1498   rec.clientID = cid;
1499   rec.key = key;
1500   rec.metaData = nullptr;  // don't write any metadata now.
1501   rec.metaDataLen = 0;
1502   rec.generation = binding->mGeneration;
1503   rec.dataSize = 0;
1504   rec.fetchCount = entry->FetchCount();
1505   rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1506   rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1507   rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1508 
1509   AutoResetStatement statement(mStatement_BindEntry);
1510 
1511   nsresult rv =
1512       statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
1513   nsresult tmp =
1514       statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
1515   if (NS_FAILED(tmp)) {
1516     rv = tmp;
1517   }
1518   tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
1519   if (NS_FAILED(tmp)) {
1520     rv = tmp;
1521   }
1522   tmp = statement->BindInt32ByIndex(3, rec.generation);
1523   if (NS_FAILED(tmp)) {
1524     rv = tmp;
1525   }
1526   tmp = statement->BindInt32ByIndex(4, rec.dataSize);
1527   if (NS_FAILED(tmp)) {
1528     rv = tmp;
1529   }
1530   tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
1531   if (NS_FAILED(tmp)) {
1532     rv = tmp;
1533   }
1534   tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
1535   if (NS_FAILED(tmp)) {
1536     rv = tmp;
1537   }
1538   tmp = statement->BindInt64ByIndex(7, rec.lastModified);
1539   if (NS_FAILED(tmp)) {
1540     rv = tmp;
1541   }
1542   tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
1543   if (NS_FAILED(tmp)) {
1544     rv = tmp;
1545   }
1546   NS_ENSURE_SUCCESS(rv, rv);
1547 
1548   bool hasRows;
1549   rv = statement->ExecuteStep(&hasRows);
1550   NS_ENSURE_SUCCESS(rv, rv);
1551   NS_ASSERTION(!hasRows, "INSERT should not result in output");
1552 
1553   entry->SetData(binding);
1554 
1555   // lock the entry
1556   Lock(*entry->Key());
1557 
1558   return NS_OK;
1559 }
1560 
DoomEntry(nsCacheEntry * entry)1561 void nsOfflineCacheDevice::DoomEntry(nsCacheEntry* entry) {
1562   LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
1563 
1564   // This method is called to inform us that we should mark the entry to be
1565   // deleted when it is no longer in use.
1566 
1567   // We can go ahead and delete the corresponding row in our table,
1568   // but we must not delete the file on disk until we are deactivated.
1569   // In another word, the file should be deleted if the entry had been
1570   // deactivated.
1571 
1572   DeleteEntry(entry, !entry->IsActive());
1573 }
1574 
OpenInputStreamForEntry(nsCacheEntry * entry,nsCacheAccessMode mode,uint32_t offset,nsIInputStream ** result)1575 nsresult nsOfflineCacheDevice::OpenInputStreamForEntry(
1576     nsCacheEntry* entry, nsCacheAccessMode mode, uint32_t offset,
1577     nsIInputStream** result) {
1578   LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1579        entry->Key()->get()));
1580 
1581   *result = nullptr;
1582 
1583   NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
1584 
1585   // return an input stream to the entry's data file.  the stream
1586   // may be read on a background thread.
1587 
1588   nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
1589   NS_ENSURE_STATE(binding);
1590 
1591   nsCOMPtr<nsIInputStream> in;
1592   NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
1593   if (!in) return NS_ERROR_UNEXPECTED;
1594 
1595   // respect |offset| param
1596   if (offset != 0) {
1597     nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1598     NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1599 
1600     seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1601   }
1602 
1603   in.swap(*result);
1604   return NS_OK;
1605 }
1606 
OpenOutputStreamForEntry(nsCacheEntry * entry,nsCacheAccessMode mode,uint32_t offset,nsIOutputStream ** result)1607 nsresult nsOfflineCacheDevice::OpenOutputStreamForEntry(
1608     nsCacheEntry* entry, nsCacheAccessMode mode, uint32_t offset,
1609     nsIOutputStream** result) {
1610   LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1611        entry->Key()->get()));
1612 
1613   *result = nullptr;
1614 
1615   NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
1616 
1617   // return an output stream to the entry's data file.  we can assume
1618   // that the output stream will only be used on the main thread.
1619 
1620   nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
1621   NS_ENSURE_STATE(binding);
1622 
1623   nsCOMPtr<nsIOutputStream> out;
1624   NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
1625                               PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00600);
1626   if (!out) return NS_ERROR_UNEXPECTED;
1627 
1628   // respect |offset| param
1629   nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1630   NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1631   if (offset != 0) seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1632 
1633   // truncate the file at the given offset
1634   seekable->SetEOF();
1635 
1636   nsCOMPtr<nsIOutputStream> bufferedOut;
1637   nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut),
1638                                            out.forget(), 16 * 1024);
1639   NS_ENSURE_SUCCESS(rv, rv);
1640 
1641   bufferedOut.forget(result);
1642   return NS_OK;
1643 }
1644 
GetFileForEntry(nsCacheEntry * entry,nsIFile ** result)1645 nsresult nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry* entry,
1646                                                nsIFile** result) {
1647   LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1648        entry->Key()->get()));
1649 
1650   nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
1651   NS_ENSURE_STATE(binding);
1652 
1653   NS_IF_ADDREF(*result = binding->mDataFile);
1654   return NS_OK;
1655 }
1656 
OnDataSizeChange(nsCacheEntry * entry,int32_t deltaSize)1657 nsresult nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry* entry,
1658                                                 int32_t deltaSize) {
1659   LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1660        entry->Key()->get(), deltaSize));
1661 
1662   const int32_t DELTA_THRESHOLD = 1 << 14;  // 16k
1663 
1664   // called to notify us of an impending change in the total size of the
1665   // specified entry.
1666 
1667   uint32_t oldSize = entry->DataSize();
1668   NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
1669   uint32_t newSize = int32_t(oldSize) + deltaSize;
1670   UpdateEntrySize(entry, newSize);
1671 
1672   mDeltaCounter += deltaSize;  // this may go negative
1673 
1674   if (mDeltaCounter >= DELTA_THRESHOLD) {
1675     if (CacheSize() > mCacheCapacity) {
1676       // the entry will overrun the cache capacity, doom the entry
1677       // and abort
1678 #ifdef DEBUG
1679       nsresult rv =
1680 #endif
1681           nsCacheService::DoomEntry(entry);
1682       NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1683       return NS_ERROR_ABORT;
1684     }
1685 
1686     mDeltaCounter = 0;  // reset counter
1687   }
1688 
1689   return NS_OK;
1690 }
1691 
Visit(nsICacheVisitor * visitor)1692 nsresult nsOfflineCacheDevice::Visit(nsICacheVisitor* visitor) {
1693   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1694 
1695   // called to enumerate the offline cache.
1696 
1697   nsCOMPtr<nsICacheDeviceInfo> deviceInfo = new nsOfflineCacheDeviceInfo(this);
1698 
1699   bool keepGoing;
1700   nsresult rv =
1701       visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
1702   if (NS_FAILED(rv)) return rv;
1703 
1704   if (!keepGoing) return NS_OK;
1705 
1706   // SELECT * from moz_cache;
1707 
1708   nsOfflineCacheRecord rec;
1709   RefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1710   if (!info) return NS_ERROR_OUT_OF_MEMORY;
1711   info->mRec = &rec;
1712 
1713   // XXX may want to list columns explicitly
1714   nsCOMPtr<mozIStorageStatement> statement;
1715   rv = mDB->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1716                             getter_AddRefs(statement));
1717   NS_ENSURE_SUCCESS(rv, rv);
1718 
1719   bool hasRows;
1720   for (;;) {
1721     rv = statement->ExecuteStep(&hasRows);
1722     if (NS_FAILED(rv) || !hasRows) break;
1723 
1724     statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
1725     statement->GetSharedUTF8String(1, nullptr, &rec.key);
1726     statement->GetSharedBlob(2, &rec.metaDataLen,
1727                              (const uint8_t**)&rec.metaData);
1728     rec.generation = statement->AsInt32(3);
1729     rec.dataSize = statement->AsInt32(4);
1730     rec.fetchCount = statement->AsInt32(5);
1731     rec.lastFetched = statement->AsInt64(6);
1732     rec.lastModified = statement->AsInt64(7);
1733     rec.expirationTime = statement->AsInt64(8);
1734 
1735     bool keepGoing;
1736     rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1737     if (NS_FAILED(rv) || !keepGoing) break;
1738   }
1739 
1740   info->mRec = nullptr;
1741   return NS_OK;
1742 }
1743 
EvictEntries(const char * clientID)1744 nsresult nsOfflineCacheDevice::EvictEntries(const char* clientID) {
1745   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1746 
1747   LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1748        clientID ? clientID : ""));
1749 
1750   // called to evict all entries matching the given clientID.
1751 
1752   // need trigger to fire user defined function after a row is deleted
1753   // so we can delete the corresponding data file.
1754   EvictionObserver evictionObserver(mDB, mEvictionFunction);
1755 
1756   nsCOMPtr<mozIStorageStatement> statement;
1757   nsresult rv;
1758   if (clientID) {
1759     rv = mDB->CreateStatement(
1760         NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=?;"),
1761         getter_AddRefs(statement));
1762     NS_ENSURE_SUCCESS(rv, rv);
1763 
1764     rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1765     NS_ENSURE_SUCCESS(rv, rv);
1766 
1767     rv = statement->Execute();
1768     NS_ENSURE_SUCCESS(rv, rv);
1769 
1770     rv = mDB->CreateStatement(
1771         NS_LITERAL_CSTRING(
1772             "DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
1773         getter_AddRefs(statement));
1774     NS_ENSURE_SUCCESS(rv, rv);
1775 
1776     rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1777     NS_ENSURE_SUCCESS(rv, rv);
1778 
1779     rv = statement->Execute();
1780     NS_ENSURE_SUCCESS(rv, rv);
1781 
1782     // TODO - Should update internal hashtables.
1783     // Low priority, since this API is not widely used.
1784   } else {
1785     rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache;"),
1786                               getter_AddRefs(statement));
1787     NS_ENSURE_SUCCESS(rv, rv);
1788 
1789     rv = statement->Execute();
1790     NS_ENSURE_SUCCESS(rv, rv);
1791 
1792     rv = mDB->CreateStatement(
1793         NS_LITERAL_CSTRING("DELETE FROM moz_cache_groups;"),
1794         getter_AddRefs(statement));
1795     NS_ENSURE_SUCCESS(rv, rv);
1796 
1797     rv = statement->Execute();
1798     NS_ENSURE_SUCCESS(rv, rv);
1799 
1800     MutexAutoLock lock(mLock);
1801     mCaches.Clear();
1802     mActiveCaches.Clear();
1803     mActiveCachesByGroup.Clear();
1804   }
1805 
1806   evictionObserver.Apply();
1807 
1808   statement = nullptr;
1809   // Also evict any namespaces associated with this clientID.
1810   if (clientID) {
1811     rv = mDB->CreateStatement(
1812         NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
1813         getter_AddRefs(statement));
1814     NS_ENSURE_SUCCESS(rv, rv);
1815 
1816     rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1817     NS_ENSURE_SUCCESS(rv, rv);
1818   } else {
1819     rv = mDB->CreateStatement(
1820         NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
1821         getter_AddRefs(statement));
1822     NS_ENSURE_SUCCESS(rv, rv);
1823   }
1824 
1825   rv = statement->Execute();
1826   NS_ENSURE_SUCCESS(rv, rv);
1827 
1828   return NS_OK;
1829 }
1830 
MarkEntry(const nsCString & clientID,const nsACString & key,uint32_t typeBits)1831 nsresult nsOfflineCacheDevice::MarkEntry(const nsCString& clientID,
1832                                          const nsACString& key,
1833                                          uint32_t typeBits) {
1834   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1835 
1836   LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1837        clientID.get(), PromiseFlatCString(key).get(), typeBits));
1838 
1839   AutoResetStatement statement(mStatement_MarkEntry);
1840   nsresult rv = statement->BindInt32ByIndex(0, typeBits);
1841   NS_ENSURE_SUCCESS(rv, rv);
1842   rv = statement->BindUTF8StringByIndex(1, clientID);
1843   NS_ENSURE_SUCCESS(rv, rv);
1844   rv = statement->BindUTF8StringByIndex(2, key);
1845   NS_ENSURE_SUCCESS(rv, rv);
1846 
1847   rv = statement->Execute();
1848   NS_ENSURE_SUCCESS(rv, rv);
1849 
1850   return NS_OK;
1851 }
1852 
UnmarkEntry(const nsCString & clientID,const nsACString & key,uint32_t typeBits)1853 nsresult nsOfflineCacheDevice::UnmarkEntry(const nsCString& clientID,
1854                                            const nsACString& key,
1855                                            uint32_t typeBits) {
1856   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1857 
1858   LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1859        clientID.get(), PromiseFlatCString(key).get(), typeBits));
1860 
1861   AutoResetStatement statement(mStatement_UnmarkEntry);
1862   nsresult rv = statement->BindInt32ByIndex(0, typeBits);
1863   NS_ENSURE_SUCCESS(rv, rv);
1864   rv = statement->BindUTF8StringByIndex(1, clientID);
1865   NS_ENSURE_SUCCESS(rv, rv);
1866   rv = statement->BindUTF8StringByIndex(2, key);
1867   NS_ENSURE_SUCCESS(rv, rv);
1868 
1869   rv = statement->Execute();
1870   NS_ENSURE_SUCCESS(rv, rv);
1871 
1872   // Remove the entry if it is now empty.
1873 
1874   EvictionObserver evictionObserver(mDB, mEvictionFunction);
1875 
1876   AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
1877   rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
1878   NS_ENSURE_SUCCESS(rv, rv);
1879   rv = cleanupStatement->BindUTF8StringByIndex(1, key);
1880   NS_ENSURE_SUCCESS(rv, rv);
1881 
1882   rv = cleanupStatement->Execute();
1883   NS_ENSURE_SUCCESS(rv, rv);
1884 
1885   evictionObserver.Apply();
1886 
1887   return NS_OK;
1888 }
1889 
GetMatchingNamespace(const nsCString & clientID,const nsACString & key,nsIApplicationCacheNamespace ** out)1890 nsresult nsOfflineCacheDevice::GetMatchingNamespace(
1891     const nsCString& clientID, const nsACString& key,
1892     nsIApplicationCacheNamespace** out) {
1893   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1894 
1895   LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
1896        clientID.get(), PromiseFlatCString(key).get()));
1897 
1898   nsresult rv;
1899 
1900   AutoResetStatement statement(mStatement_FindNamespaceEntry);
1901 
1902   rv = statement->BindUTF8StringByIndex(0, clientID);
1903   NS_ENSURE_SUCCESS(rv, rv);
1904   rv = statement->BindUTF8StringByIndex(1, key);
1905   NS_ENSURE_SUCCESS(rv, rv);
1906 
1907   bool hasRows;
1908   rv = statement->ExecuteStep(&hasRows);
1909   NS_ENSURE_SUCCESS(rv, rv);
1910 
1911   *out = nullptr;
1912 
1913   bool found = false;
1914   nsCString nsSpec;
1915   int32_t nsType = 0;
1916   nsCString nsData;
1917 
1918   while (hasRows) {
1919     int32_t itemType;
1920     rv = statement->GetInt32(2, &itemType);
1921     NS_ENSURE_SUCCESS(rv, rv);
1922 
1923     if (!found || itemType > nsType) {
1924       nsType = itemType;
1925 
1926       rv = statement->GetUTF8String(0, nsSpec);
1927       NS_ENSURE_SUCCESS(rv, rv);
1928 
1929       rv = statement->GetUTF8String(1, nsData);
1930       NS_ENSURE_SUCCESS(rv, rv);
1931 
1932       found = true;
1933     }
1934 
1935     rv = statement->ExecuteStep(&hasRows);
1936     NS_ENSURE_SUCCESS(rv, rv);
1937   }
1938 
1939   if (found) {
1940     nsCOMPtr<nsIApplicationCacheNamespace> ns =
1941         new nsApplicationCacheNamespace();
1942     if (!ns) return NS_ERROR_OUT_OF_MEMORY;
1943     rv = ns->Init(nsType, nsSpec, nsData);
1944     NS_ENSURE_SUCCESS(rv, rv);
1945 
1946     ns.swap(*out);
1947   }
1948 
1949   return NS_OK;
1950 }
1951 
CacheOpportunistically(const nsCString & clientID,const nsACString & key)1952 nsresult nsOfflineCacheDevice::CacheOpportunistically(const nsCString& clientID,
1953                                                       const nsACString& key) {
1954   // XXX: We should also be propagating this cache entry to other matching
1955   // caches.  See bug 444807.
1956 
1957   return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
1958 }
1959 
GetTypes(const nsCString & clientID,const nsACString & key,uint32_t * typeBits)1960 nsresult nsOfflineCacheDevice::GetTypes(const nsCString& clientID,
1961                                         const nsACString& key,
1962                                         uint32_t* typeBits) {
1963   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1964 
1965   LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n", clientID.get(),
1966        PromiseFlatCString(key).get()));
1967 
1968   AutoResetStatement statement(mStatement_GetTypes);
1969   nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
1970   NS_ENSURE_SUCCESS(rv, rv);
1971   rv = statement->BindUTF8StringByIndex(1, key);
1972   NS_ENSURE_SUCCESS(rv, rv);
1973 
1974   bool hasRows;
1975   rv = statement->ExecuteStep(&hasRows);
1976   NS_ENSURE_SUCCESS(rv, rv);
1977 
1978   if (!hasRows) return NS_ERROR_CACHE_KEY_NOT_FOUND;
1979 
1980   *typeBits = statement->AsInt32(0);
1981 
1982   return NS_OK;
1983 }
1984 
GatherEntries(const nsCString & clientID,uint32_t typeBits,nsTArray<nsCString> & keys)1985 nsresult nsOfflineCacheDevice::GatherEntries(const nsCString& clientID,
1986                                              uint32_t typeBits,
1987                                              nsTArray<nsCString>& keys) {
1988   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1989 
1990   LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
1991        clientID.get(), typeBits));
1992 
1993   AutoResetStatement statement(mStatement_GatherEntries);
1994   nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
1995   NS_ENSURE_SUCCESS(rv, rv);
1996 
1997   rv = statement->BindInt32ByIndex(1, typeBits);
1998   NS_ENSURE_SUCCESS(rv, rv);
1999 
2000   return RunSimpleQuery(mStatement_GatherEntries, 0, keys);
2001 }
2002 
AddNamespace(const nsCString & clientID,nsIApplicationCacheNamespace * ns)2003 nsresult nsOfflineCacheDevice::AddNamespace(const nsCString& clientID,
2004                                             nsIApplicationCacheNamespace* ns) {
2005   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2006 
2007   nsCString namespaceSpec;
2008   nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
2009   NS_ENSURE_SUCCESS(rv, rv);
2010 
2011   nsCString data;
2012   rv = ns->GetData(data);
2013   NS_ENSURE_SUCCESS(rv, rv);
2014 
2015   uint32_t itemType;
2016   rv = ns->GetItemType(&itemType);
2017   NS_ENSURE_SUCCESS(rv, rv);
2018 
2019   LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
2020        clientID.get(), namespaceSpec.get(), data.get(), itemType));
2021 
2022   AutoResetStatement statement(mStatement_InsertNamespaceEntry);
2023 
2024   rv = statement->BindUTF8StringByIndex(0, clientID);
2025   NS_ENSURE_SUCCESS(rv, rv);
2026 
2027   rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
2028   NS_ENSURE_SUCCESS(rv, rv);
2029 
2030   rv = statement->BindUTF8StringByIndex(2, data);
2031   NS_ENSURE_SUCCESS(rv, rv);
2032 
2033   rv = statement->BindInt32ByIndex(3, itemType);
2034   NS_ENSURE_SUCCESS(rv, rv);
2035 
2036   rv = statement->Execute();
2037   NS_ENSURE_SUCCESS(rv, rv);
2038 
2039   return NS_OK;
2040 }
2041 
GetUsage(const nsACString & clientID,uint32_t * usage)2042 nsresult nsOfflineCacheDevice::GetUsage(const nsACString& clientID,
2043                                         uint32_t* usage) {
2044   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2045 
2046   LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2047        PromiseFlatCString(clientID).get()));
2048 
2049   *usage = 0;
2050 
2051   AutoResetStatement statement(mStatement_ApplicationCacheSize);
2052 
2053   nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2054   NS_ENSURE_SUCCESS(rv, rv);
2055 
2056   bool hasRows;
2057   rv = statement->ExecuteStep(&hasRows);
2058   NS_ENSURE_SUCCESS(rv, rv);
2059 
2060   if (!hasRows) return NS_OK;
2061 
2062   *usage = static_cast<uint32_t>(statement->AsInt32(0));
2063 
2064   return NS_OK;
2065 }
2066 
GetGroups(nsTArray<nsCString> & keys)2067 nsresult nsOfflineCacheDevice::GetGroups(nsTArray<nsCString>& keys) {
2068   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2069 
2070   LOG(("nsOfflineCacheDevice::GetGroups"));
2071 
2072   return RunSimpleQuery(mStatement_EnumerateGroups, 0, keys);
2073 }
2074 
GetGroupsTimeOrdered(nsTArray<nsCString> & keys)2075 nsresult nsOfflineCacheDevice::GetGroupsTimeOrdered(nsTArray<nsCString>& keys) {
2076   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2077 
2078   LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
2079 
2080   return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, keys);
2081 }
2082 
IsLocked(const nsACString & key)2083 bool nsOfflineCacheDevice::IsLocked(const nsACString& key) {
2084   MutexAutoLock lock(mLock);
2085   return mLockedEntries.GetEntry(key);
2086 }
2087 
Lock(const nsACString & key)2088 void nsOfflineCacheDevice::Lock(const nsACString& key) {
2089   MutexAutoLock lock(mLock);
2090   mLockedEntries.PutEntry(key);
2091 }
2092 
Unlock(const nsACString & key)2093 void nsOfflineCacheDevice::Unlock(const nsACString& key) {
2094   MutexAutoLock lock(mLock);
2095   mLockedEntries.RemoveEntry(key);
2096 }
2097 
RunSimpleQuery(mozIStorageStatement * statement,uint32_t resultIndex,nsTArray<nsCString> & values)2098 nsresult nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement* statement,
2099                                               uint32_t resultIndex,
2100                                               nsTArray<nsCString>& values) {
2101   bool hasRows;
2102   nsresult rv = statement->ExecuteStep(&hasRows);
2103   NS_ENSURE_SUCCESS(rv, rv);
2104 
2105   nsTArray<nsCString> valArray;
2106   while (hasRows) {
2107     uint32_t length;
2108     valArray.AppendElement(nsDependentCString(
2109         statement->AsSharedUTF8String(resultIndex, &length)));
2110 
2111     rv = statement->ExecuteStep(&hasRows);
2112     NS_ENSURE_SUCCESS(rv, rv);
2113   }
2114 
2115   values.SwapElements(valArray);
2116   return NS_OK;
2117 }
2118 
CreateApplicationCache(const nsACString & group,nsIApplicationCache ** out)2119 nsresult nsOfflineCacheDevice::CreateApplicationCache(
2120     const nsACString& group, nsIApplicationCache** out) {
2121   *out = nullptr;
2122 
2123   nsCString clientID;
2124   // Some characters are special in the clientID.  Escape the groupID
2125   // before putting it in to the client key.
2126   if (!NS_Escape(nsCString(group), clientID, url_Path)) {
2127     return NS_ERROR_OUT_OF_MEMORY;
2128   }
2129 
2130   PRTime now = PR_Now();
2131 
2132   // Include the timestamp to guarantee uniqueness across runs, and
2133   // the gNextTemporaryClientID for uniqueness within a second.
2134   clientID.Append(nsPrintfCString("|%016" PRId64 "|%d", now / PR_USEC_PER_SEC,
2135                                   gNextTemporaryClientID++));
2136 
2137   nsCOMPtr<nsIApplicationCache> cache =
2138       new nsApplicationCache(this, group, clientID);
2139   if (!cache) return NS_ERROR_OUT_OF_MEMORY;
2140 
2141   nsWeakPtr weak = do_GetWeakReference(cache);
2142   if (!weak) return NS_ERROR_OUT_OF_MEMORY;
2143 
2144   MutexAutoLock lock(mLock);
2145   mCaches.Put(clientID, weak);
2146 
2147   cache.swap(*out);
2148 
2149   return NS_OK;
2150 }
2151 
GetApplicationCache(const nsACString & clientID,nsIApplicationCache ** out)2152 nsresult nsOfflineCacheDevice::GetApplicationCache(const nsACString& clientID,
2153                                                    nsIApplicationCache** out) {
2154   MutexAutoLock lock(mLock);
2155   return GetApplicationCache_Unlocked(clientID, out);
2156 }
2157 
GetApplicationCache_Unlocked(const nsACString & clientID,nsIApplicationCache ** out)2158 nsresult nsOfflineCacheDevice::GetApplicationCache_Unlocked(
2159     const nsACString& clientID, nsIApplicationCache** out) {
2160   *out = nullptr;
2161 
2162   nsCOMPtr<nsIApplicationCache> cache;
2163 
2164   nsWeakPtr weak;
2165   if (mCaches.Get(clientID, getter_AddRefs(weak)))
2166     cache = do_QueryReferent(weak);
2167 
2168   if (!cache) {
2169     nsCString group;
2170     nsresult rv = GetGroupForCache(clientID, group);
2171     NS_ENSURE_SUCCESS(rv, rv);
2172 
2173     if (group.IsEmpty()) {
2174       return NS_OK;
2175     }
2176 
2177     cache = new nsApplicationCache(this, group, clientID);
2178     weak = do_GetWeakReference(cache);
2179     if (!weak) return NS_ERROR_OUT_OF_MEMORY;
2180 
2181     mCaches.Put(clientID, weak);
2182   }
2183 
2184   cache.swap(*out);
2185 
2186   return NS_OK;
2187 }
2188 
GetActiveCache(const nsACString & group,nsIApplicationCache ** out)2189 nsresult nsOfflineCacheDevice::GetActiveCache(const nsACString& group,
2190                                               nsIApplicationCache** out) {
2191   *out = nullptr;
2192 
2193   MutexAutoLock lock(mLock);
2194 
2195   nsCString* clientID;
2196   if (mActiveCachesByGroup.Get(group, &clientID))
2197     return GetApplicationCache_Unlocked(*clientID, out);
2198 
2199   return NS_OK;
2200 }
2201 
DeactivateGroup(const nsACString & group)2202 nsresult nsOfflineCacheDevice::DeactivateGroup(const nsACString& group) {
2203   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2204 
2205   nsCString* active = nullptr;
2206 
2207   AutoResetStatement statement(mStatement_DeactivateGroup);
2208   nsresult rv = statement->BindUTF8StringByIndex(0, group);
2209   NS_ENSURE_SUCCESS(rv, rv);
2210 
2211   rv = statement->Execute();
2212   NS_ENSURE_SUCCESS(rv, rv);
2213 
2214   MutexAutoLock lock(mLock);
2215 
2216   if (mActiveCachesByGroup.Get(group, &active)) {
2217     mActiveCaches.RemoveEntry(*active);
2218     mActiveCachesByGroup.Remove(group);
2219     active = nullptr;
2220   }
2221 
2222   return NS_OK;
2223 }
2224 
Evict(nsILoadContextInfo * aInfo)2225 nsresult nsOfflineCacheDevice::Evict(nsILoadContextInfo* aInfo) {
2226   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2227 
2228   NS_ENSURE_ARG(aInfo);
2229 
2230   nsresult rv;
2231 
2232   mozilla::OriginAttributes const* oa = aInfo->OriginAttributesPtr();
2233 
2234   if (oa->mInIsolatedMozBrowser == false) {
2235     nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
2236     NS_ENSURE_SUCCESS(rv, rv);
2237 
2238     return nsCacheService::GlobalInstance()->EvictEntriesInternal(
2239         nsICache::STORE_OFFLINE);
2240   }
2241 
2242   nsAutoCString jaridsuffix;
2243   jaridsuffix.Append('%');
2244 
2245   nsAutoCString suffix;
2246   oa->CreateSuffix(suffix);
2247   jaridsuffix.Append('#');
2248   jaridsuffix.Append(suffix);
2249 
2250   AutoResetStatement statement(mStatement_EnumerateApps);
2251   rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
2252   NS_ENSURE_SUCCESS(rv, rv);
2253 
2254   bool hasRows;
2255   rv = statement->ExecuteStep(&hasRows);
2256   NS_ENSURE_SUCCESS(rv, rv);
2257 
2258   while (hasRows) {
2259     nsAutoCString group;
2260     rv = statement->GetUTF8String(0, group);
2261     NS_ENSURE_SUCCESS(rv, rv);
2262 
2263     nsCString clientID;
2264     rv = statement->GetUTF8String(1, clientID);
2265     NS_ENSURE_SUCCESS(rv, rv);
2266 
2267     nsCOMPtr<nsIRunnable> ev =
2268         new nsOfflineCacheDiscardCache(this, group, clientID);
2269 
2270     rv = nsCacheService::DispatchToCacheIOThread(ev);
2271     NS_ENSURE_SUCCESS(rv, rv);
2272 
2273     rv = statement->ExecuteStep(&hasRows);
2274     NS_ENSURE_SUCCESS(rv, rv);
2275   }
2276 
2277   return NS_OK;
2278 }
2279 
2280 namespace {  // anon
2281 
2282 class OriginMatch final : public mozIStorageFunction {
2283   ~OriginMatch() = default;
2284   mozilla::OriginAttributesPattern const mPattern;
2285 
2286   NS_DECL_ISUPPORTS
2287   NS_DECL_MOZISTORAGEFUNCTION
OriginMatch(mozilla::OriginAttributesPattern const & aPattern)2288   explicit OriginMatch(mozilla::OriginAttributesPattern const& aPattern)
2289       : mPattern(aPattern) {}
2290 };
2291 
NS_IMPL_ISUPPORTS(OriginMatch,mozIStorageFunction)2292 NS_IMPL_ISUPPORTS(OriginMatch, mozIStorageFunction)
2293 
2294 NS_IMETHODIMP
2295 OriginMatch::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
2296                             nsIVariant** aResult) {
2297   nsresult rv;
2298 
2299   nsAutoCString groupId;
2300   rv = aFunctionArguments->GetUTF8String(0, groupId);
2301   NS_ENSURE_SUCCESS(rv, rv);
2302 
2303   int32_t hash = groupId.Find(NS_LITERAL_CSTRING("#"));
2304   if (hash == kNotFound) {
2305     // Just ignore...
2306     return NS_OK;
2307   }
2308 
2309   ++hash;
2310 
2311   nsDependentCSubstring suffix(groupId.BeginReading() + hash,
2312                                groupId.Length() - hash);
2313 
2314   mozilla::OriginAttributes oa;
2315   bool ok = oa.PopulateFromSuffix(suffix);
2316   NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
2317 
2318   bool match = mPattern.Matches(oa);
2319 
2320   RefPtr<nsVariant> outVar(new nsVariant());
2321   rv = outVar->SetAsUint32(match ? 1 : 0);
2322   NS_ENSURE_SUCCESS(rv, rv);
2323 
2324   outVar.forget(aResult);
2325   return NS_OK;
2326 }
2327 
2328 }  // namespace
2329 
Evict(mozilla::OriginAttributesPattern const & aPattern)2330 nsresult nsOfflineCacheDevice::Evict(
2331     mozilla::OriginAttributesPattern const& aPattern) {
2332   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2333 
2334   nsresult rv;
2335 
2336   nsCOMPtr<mozIStorageFunction> function1(new OriginMatch(aPattern));
2337   rv = mDB->CreateFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"), 1, function1);
2338   NS_ENSURE_SUCCESS(rv, rv);
2339 
2340   class AutoRemoveFunc {
2341    public:
2342     mozIStorageConnection* mDB;
2343     explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
2344     ~AutoRemoveFunc() {
2345       mDB->RemoveFunction(NS_LITERAL_CSTRING("ORIGIN_MATCH"));
2346     }
2347   };
2348   AutoRemoveFunc autoRemove(mDB);
2349 
2350   nsCOMPtr<mozIStorageStatement> statement;
2351   rv = mDB->CreateStatement(
2352       NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID FROM moz_cache_groups "
2353                          "WHERE ORIGIN_MATCH(GroupID);"),
2354       getter_AddRefs(statement));
2355   NS_ENSURE_SUCCESS(rv, rv);
2356 
2357   AutoResetStatement statementScope(statement);
2358 
2359   bool hasRows;
2360   rv = statement->ExecuteStep(&hasRows);
2361   NS_ENSURE_SUCCESS(rv, rv);
2362 
2363   while (hasRows) {
2364     nsAutoCString group;
2365     rv = statement->GetUTF8String(0, group);
2366     NS_ENSURE_SUCCESS(rv, rv);
2367 
2368     nsCString clientID;
2369     rv = statement->GetUTF8String(1, clientID);
2370     NS_ENSURE_SUCCESS(rv, rv);
2371 
2372     nsCOMPtr<nsIRunnable> ev =
2373         new nsOfflineCacheDiscardCache(this, group, clientID);
2374 
2375     rv = nsCacheService::DispatchToCacheIOThread(ev);
2376     NS_ENSURE_SUCCESS(rv, rv);
2377 
2378     rv = statement->ExecuteStep(&hasRows);
2379     NS_ENSURE_SUCCESS(rv, rv);
2380   }
2381 
2382   return NS_OK;
2383 }
2384 
CanUseCache(nsIURI * keyURI,const nsACString & clientID,nsILoadContextInfo * loadContextInfo)2385 bool nsOfflineCacheDevice::CanUseCache(nsIURI* keyURI,
2386                                        const nsACString& clientID,
2387                                        nsILoadContextInfo* loadContextInfo) {
2388   {
2389     MutexAutoLock lock(mLock);
2390     if (!mActiveCaches.Contains(clientID)) return false;
2391   }
2392 
2393   nsAutoCString groupID;
2394   nsresult rv = GetGroupForCache(clientID, groupID);
2395   NS_ENSURE_SUCCESS(rv, false);
2396 
2397   nsCOMPtr<nsIURI> groupURI;
2398   rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
2399   if (NS_FAILED(rv)) {
2400     return false;
2401   }
2402 
2403   // When we are choosing an initial cache to load the top
2404   // level document from, the URL of that document must have
2405   // the same origin as the manifest, according to the spec.
2406   // The following check is here because explicit, fallback
2407   // and dynamic entries might have origin different from the
2408   // manifest origin.
2409   if (!NS_SecurityCompareURIs(keyURI, groupURI, GetStrictFileOriginPolicy())) {
2410     return false;
2411   }
2412 
2413   // Check the groupID we found is equal to groupID based
2414   // on the load context demanding load from app cache.
2415   // This is check of extended origin.
2416 
2417   nsAutoCString originSuffix;
2418   loadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
2419 
2420   nsAutoCString demandedGroupID;
2421   rv = BuildApplicationCacheGroupID(groupURI, originSuffix, demandedGroupID);
2422   NS_ENSURE_SUCCESS(rv, false);
2423 
2424   if (groupID != demandedGroupID) {
2425     return false;
2426   }
2427 
2428   return true;
2429 }
2430 
ChooseApplicationCache(const nsACString & key,nsILoadContextInfo * loadContextInfo,nsIApplicationCache ** out)2431 nsresult nsOfflineCacheDevice::ChooseApplicationCache(
2432     const nsACString& key, nsILoadContextInfo* loadContextInfo,
2433     nsIApplicationCache** out) {
2434   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2435 
2436   NS_ENSURE_ARG(loadContextInfo);
2437 
2438   nsresult rv;
2439 
2440   *out = nullptr;
2441 
2442   nsCOMPtr<nsIURI> keyURI;
2443   rv = NS_NewURI(getter_AddRefs(keyURI), key);
2444   NS_ENSURE_SUCCESS(rv, rv);
2445 
2446   // First try to find a matching cache entry.
2447   AutoResetStatement statement(mStatement_FindClient);
2448   rv = statement->BindUTF8StringByIndex(0, key);
2449   NS_ENSURE_SUCCESS(rv, rv);
2450 
2451   bool hasRows;
2452   rv = statement->ExecuteStep(&hasRows);
2453   NS_ENSURE_SUCCESS(rv, rv);
2454 
2455   while (hasRows) {
2456     int32_t itemType;
2457     rv = statement->GetInt32(1, &itemType);
2458     NS_ENSURE_SUCCESS(rv, rv);
2459 
2460     if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
2461       nsAutoCString clientID;
2462       rv = statement->GetUTF8String(0, clientID);
2463       NS_ENSURE_SUCCESS(rv, rv);
2464 
2465       if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2466         return GetApplicationCache(clientID, out);
2467       }
2468     }
2469 
2470     rv = statement->ExecuteStep(&hasRows);
2471     NS_ENSURE_SUCCESS(rv, rv);
2472   }
2473 
2474   // OK, we didn't find an exact match.  Search for a client with a
2475   // matching namespace.
2476 
2477   AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
2478 
2479   rv = nsstatement->BindUTF8StringByIndex(0, key);
2480   NS_ENSURE_SUCCESS(rv, rv);
2481 
2482   rv = nsstatement->ExecuteStep(&hasRows);
2483   NS_ENSURE_SUCCESS(rv, rv);
2484 
2485   while (hasRows) {
2486     int32_t itemType;
2487     rv = nsstatement->GetInt32(1, &itemType);
2488     NS_ENSURE_SUCCESS(rv, rv);
2489 
2490     // Don't associate with a cache based solely on a whitelist entry
2491     if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
2492       nsAutoCString clientID;
2493       rv = nsstatement->GetUTF8String(0, clientID);
2494       NS_ENSURE_SUCCESS(rv, rv);
2495 
2496       if (CanUseCache(keyURI, clientID, loadContextInfo)) {
2497         return GetApplicationCache(clientID, out);
2498       }
2499     }
2500 
2501     rv = nsstatement->ExecuteStep(&hasRows);
2502     NS_ENSURE_SUCCESS(rv, rv);
2503   }
2504 
2505   return NS_OK;
2506 }
2507 
CacheOpportunistically(nsIApplicationCache * cache,const nsACString & key)2508 nsresult nsOfflineCacheDevice::CacheOpportunistically(
2509     nsIApplicationCache* cache, const nsACString& key) {
2510   NS_ENSURE_ARG_POINTER(cache);
2511 
2512   nsresult rv;
2513 
2514   nsAutoCString clientID;
2515   rv = cache->GetClientID(clientID);
2516   NS_ENSURE_SUCCESS(rv, rv);
2517 
2518   return CacheOpportunistically(clientID, key);
2519 }
2520 
ActivateCache(const nsACString & group,const nsACString & clientID)2521 nsresult nsOfflineCacheDevice::ActivateCache(const nsACString& group,
2522                                              const nsACString& clientID) {
2523   NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
2524 
2525   AutoResetStatement statement(mStatement_ActivateClient);
2526   nsresult rv = statement->BindUTF8StringByIndex(0, group);
2527   NS_ENSURE_SUCCESS(rv, rv);
2528   rv = statement->BindUTF8StringByIndex(1, clientID);
2529   NS_ENSURE_SUCCESS(rv, rv);
2530   rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
2531   NS_ENSURE_SUCCESS(rv, rv);
2532 
2533   rv = statement->Execute();
2534   NS_ENSURE_SUCCESS(rv, rv);
2535 
2536   MutexAutoLock lock(mLock);
2537 
2538   nsCString* active;
2539   if (mActiveCachesByGroup.Get(group, &active)) {
2540     mActiveCaches.RemoveEntry(*active);
2541     mActiveCachesByGroup.Remove(group);
2542     active = nullptr;
2543   }
2544 
2545   if (!clientID.IsEmpty()) {
2546     mActiveCaches.PutEntry(clientID);
2547     mActiveCachesByGroup.Put(group, new nsCString(clientID));
2548   }
2549 
2550   return NS_OK;
2551 }
2552 
IsActiveCache(const nsACString & group,const nsACString & clientID)2553 bool nsOfflineCacheDevice::IsActiveCache(const nsACString& group,
2554                                          const nsACString& clientID) {
2555   nsCString* active = nullptr;
2556   MutexAutoLock lock(mLock);
2557   return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
2558 }
2559 
2560 /**
2561  * Preference accessors
2562  */
2563 
SetCacheParentDirectory(nsIFile * parentDir)2564 void nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile* parentDir) {
2565   if (Initialized()) {
2566     NS_ERROR("cannot switch cache directory once initialized");
2567     return;
2568   }
2569 
2570   if (!parentDir) {
2571     mCacheDirectory = nullptr;
2572     return;
2573   }
2574 
2575   // ensure parent directory exists
2576   nsresult rv = EnsureDir(parentDir);
2577   if (NS_FAILED(rv)) {
2578     NS_WARNING("unable to create parent directory");
2579     return;
2580   }
2581 
2582   mBaseDirectory = parentDir;
2583 
2584   // cache dir may not exist, but that's ok
2585   nsCOMPtr<nsIFile> dir;
2586   rv = parentDir->Clone(getter_AddRefs(dir));
2587   if (NS_FAILED(rv)) return;
2588   rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2589   if (NS_FAILED(rv)) return;
2590 
2591   mCacheDirectory = dir;
2592 }
2593 
SetCapacity(uint32_t capacity)2594 void nsOfflineCacheDevice::SetCapacity(uint32_t capacity) {
2595   mCacheCapacity = capacity * 1024;
2596 }
2597 
AutoShutdown(nsIApplicationCache * aAppCache)2598 bool nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache* aAppCache) {
2599   if (!mAutoShutdown) return false;
2600 
2601   mAutoShutdown = false;
2602 
2603   Shutdown();
2604 
2605   nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
2606   RefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
2607   cacheService->RemoveCustomOfflineDevice(this);
2608 
2609   nsAutoCString clientID;
2610   aAppCache->GetClientID(clientID);
2611 
2612   MutexAutoLock lock(mLock);
2613   mCaches.Remove(clientID);
2614 
2615   return true;
2616 }
2617