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