1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "LocalStorageManager.h"
8 #include "LocalStorage.h"
9 #include "StorageDBThread.h"
10 #include "StorageIPC.h"
11 #include "StorageUtils.h"
12 
13 #include "nsIEffectiveTLDService.h"
14 
15 #include "nsPIDOMWindow.h"
16 #include "nsNetUtil.h"
17 #include "nsNetCID.h"
18 #include "nsPrintfCString.h"
19 #include "nsXULAppAPI.h"
20 #include "nsThreadUtils.h"
21 #include "nsIObserverService.h"
22 #include "mozilla/ipc/BackgroundChild.h"
23 #include "mozilla/ipc/PBackgroundChild.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/StaticPrefs_dom.h"
26 #include "mozilla/dom/LocalStorageCommon.h"
27 
28 namespace mozilla {
29 namespace dom {
30 
31 using namespace StorageUtils;
32 
33 LocalStorageManager* LocalStorageManager::sSelf = nullptr;
34 
35 // static
GetOriginQuota()36 uint32_t LocalStorageManager::GetOriginQuota() {
37   return StaticPrefs::dom_storage_default_quota() * 1024;  // pref is in kBs
38 }
39 
40 // static
GetSiteQuota()41 uint32_t LocalStorageManager::GetSiteQuota() {
42   return std::max(StaticPrefs::dom_storage_default_quota(),
43                   StaticPrefs::dom_storage_default_site_quota()) *
44          1024;  // pref is in kBs
45 }
46 
NS_IMPL_ISUPPORTS(LocalStorageManager,nsIDOMStorageManager,nsILocalStorageManager)47 NS_IMPL_ISUPPORTS(LocalStorageManager, nsIDOMStorageManager,
48                   nsILocalStorageManager)
49 
50 LocalStorageManager::LocalStorageManager() : mCaches(8) {
51   MOZ_ASSERT(!NextGenLocalStorageEnabled());
52 
53   StorageObserver* observer = StorageObserver::Self();
54   NS_ASSERTION(
55       observer,
56       "No StorageObserver, cannot observe private data delete notifications!");
57 
58   if (observer) {
59     observer->AddSink(this);
60   }
61 
62   NS_ASSERTION(!sSelf,
63                "Somebody is trying to "
64                "do_CreateInstance(\"@mozilla/dom/localStorage-manager;1\"");
65   sSelf = this;
66 
67   if (!XRE_IsParentProcess()) {
68     // Do this only on the child process.  The thread IPC bridge
69     // is also used to communicate chrome observer notifications.
70     // Note: must be called after we set sSelf
71     for (const uint32_t id : {0, 1}) {
72       StorageDBChild::GetOrCreate(id);
73     }
74   }
75 }
76 
~LocalStorageManager()77 LocalStorageManager::~LocalStorageManager() {
78   StorageObserver* observer = StorageObserver::Self();
79   if (observer) {
80     observer->RemoveSink(this);
81   }
82 
83   sSelf = nullptr;
84 }
85 
86 // static
CreateOrigin(const nsACString & aOriginSuffix,const nsACString & aOriginNoSuffix)87 nsAutoCString LocalStorageManager::CreateOrigin(
88     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) {
89   // Note: some hard-coded sqlite statements are dependent on the format this
90   // method returns.  Changing this without updating those sqlite statements
91   // will cause malfunction.
92 
93   nsAutoCString scope;
94   scope.Append(aOriginSuffix);
95   scope.Append(':');
96   scope.Append(aOriginNoSuffix);
97   return scope;
98 }
99 
GetCache(const nsACString & aOriginSuffix,const nsACString & aOriginNoSuffix)100 LocalStorageCache* LocalStorageManager::GetCache(
101     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) {
102   CacheOriginHashtable* table = mCaches.GetOrInsertNew(aOriginSuffix);
103   LocalStorageCacheHashKey* entry = table->GetEntry(aOriginNoSuffix);
104   if (!entry) {
105     return nullptr;
106   }
107 
108   return entry->cache();
109 }
110 
GetOriginUsage(const nsACString & aOriginNoSuffix,const uint32_t aPrivateBrowsingId)111 already_AddRefed<StorageUsage> LocalStorageManager::GetOriginUsage(
112     const nsACString& aOriginNoSuffix, const uint32_t aPrivateBrowsingId) {
113   return do_AddRef(mUsages.LookupOrInsertWith(aOriginNoSuffix, [&] {
114     auto usage = MakeRefPtr<StorageUsage>(aOriginNoSuffix);
115 
116     StorageDBChild* storageChild =
117         StorageDBChild::GetOrCreate(aPrivateBrowsingId);
118     if (storageChild) {
119       storageChild->AsyncGetUsage(usage);
120     }
121 
122     return usage;
123   }));
124 }
125 
PutCache(const nsACString & aOriginSuffix,const nsACString & aOriginNoSuffix,const nsACString & aQuotaKey,nsIPrincipal * aPrincipal)126 already_AddRefed<LocalStorageCache> LocalStorageManager::PutCache(
127     const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
128     const nsACString& aQuotaKey, nsIPrincipal* aPrincipal) {
129   CacheOriginHashtable* table = mCaches.GetOrInsertNew(aOriginSuffix);
130   LocalStorageCacheHashKey* entry = table->PutEntry(aOriginNoSuffix);
131   RefPtr<LocalStorageCache> cache = entry->cache();
132 
133   // Lifetime handled by the cache, do persist
134   cache->Init(this, true, aPrincipal, aQuotaKey);
135   return cache.forget();
136 }
137 
DropCache(LocalStorageCache * aCache)138 void LocalStorageManager::DropCache(LocalStorageCache* aCache) {
139   if (!NS_IsMainThread()) {
140     NS_WARNING(
141         "StorageManager::DropCache called on a non-main thread, shutting "
142         "down?");
143   }
144 
145   CacheOriginHashtable* table = mCaches.GetOrInsertNew(aCache->OriginSuffix());
146   table->RemoveEntry(aCache->OriginNoSuffix());
147 }
148 
GetStorageInternal(CreateMode aCreateMode,mozIDOMWindow * aWindow,nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,const nsAString & aDocumentURI,bool aPrivate,Storage ** aRetval)149 nsresult LocalStorageManager::GetStorageInternal(
150     CreateMode aCreateMode, mozIDOMWindow* aWindow, nsIPrincipal* aPrincipal,
151     nsIPrincipal* aStoragePrincipal, const nsAString& aDocumentURI,
152     bool aPrivate, Storage** aRetval) {
153   nsAutoCString originAttrSuffix;
154   nsAutoCString originKey;
155   nsAutoCString quotaKey;
156 
157   aStoragePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
158 
159   nsresult rv = aStoragePrincipal->GetStorageOriginKey(originKey);
160   if (NS_WARN_IF(NS_FAILED(rv))) {
161     return NS_ERROR_NOT_AVAILABLE;
162   }
163 
164   rv = aStoragePrincipal->GetLocalStorageQuotaKey(quotaKey);
165   if (NS_WARN_IF(NS_FAILED(rv))) {
166     return NS_ERROR_NOT_AVAILABLE;
167   }
168 
169   RefPtr<LocalStorageCache> cache = GetCache(originAttrSuffix, originKey);
170 
171   // Get or create a cache for the given scope
172   if (!cache) {
173     if (aCreateMode == CreateMode::UseIfExistsNeverCreate) {
174       *aRetval = nullptr;
175       return NS_OK;
176     }
177 
178     if (aCreateMode == CreateMode::CreateIfShouldPreload) {
179       const uint32_t privateBrowsingId =
180           aStoragePrincipal->GetPrivateBrowsingId();
181 
182       // This is a demand to just preload the cache, if the scope has
183       // no data stored, bypass creation and preload of the cache.
184       StorageDBChild* db = StorageDBChild::Get(privateBrowsingId);
185       if (db) {
186         if (!db->ShouldPreloadOrigin(LocalStorageManager::CreateOrigin(
187                 originAttrSuffix, originKey))) {
188           return NS_OK;
189         }
190       } else {
191         if (originKey.EqualsLiteral("knalb.:about")) {
192           return NS_OK;
193         }
194       }
195     }
196 
197 #if !defined(MOZ_WIDGET_ANDROID)
198     ::mozilla::ipc::PBackgroundChild* backgroundActor =
199         ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
200     if (NS_WARN_IF(!backgroundActor)) {
201       return NS_ERROR_FAILURE;
202     }
203 
204     ::mozilla::ipc::PrincipalInfo principalInfo;
205     rv = mozilla::ipc::PrincipalToPrincipalInfo(aStoragePrincipal,
206                                                 &principalInfo);
207     if (NS_WARN_IF(NS_FAILED(rv))) {
208       return rv;
209     }
210 
211     uint32_t privateBrowsingId;
212     rv = aStoragePrincipal->GetPrivateBrowsingId(&privateBrowsingId);
213     if (NS_WARN_IF(NS_FAILED(rv))) {
214       return rv;
215     }
216 #endif
217 
218     // There is always a single instance of a cache per scope
219     // in a single instance of a DOM storage manager.
220     cache = PutCache(originAttrSuffix, originKey, quotaKey, aStoragePrincipal);
221 
222 #if !defined(MOZ_WIDGET_ANDROID)
223     LocalStorageCacheChild* actor = new LocalStorageCacheChild(cache);
224 
225     MOZ_ALWAYS_TRUE(
226         backgroundActor->SendPBackgroundLocalStorageCacheConstructor(
227             actor, principalInfo, originKey, privateBrowsingId));
228 
229     cache->SetActor(actor);
230 #endif
231   }
232 
233   if (aRetval) {
234     nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
235 
236     RefPtr<Storage> storage =
237         new LocalStorage(inner, this, cache, aDocumentURI, aPrincipal,
238                          aStoragePrincipal, aPrivate);
239     storage.forget(aRetval);
240   }
241 
242   return NS_OK;
243 }
244 
245 NS_IMETHODIMP
PrecacheStorage(nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,Storage ** aRetval)246 LocalStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal,
247                                      nsIPrincipal* aStoragePrincipal,
248                                      Storage** aRetval) {
249   return GetStorageInternal(CreateMode::CreateIfShouldPreload, nullptr,
250                             aPrincipal, aStoragePrincipal, u""_ns, false,
251                             aRetval);
252 }
253 
254 NS_IMETHODIMP
CreateStorage(mozIDOMWindow * aWindow,nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,const nsAString & aDocumentURI,bool aPrivate,Storage ** aRetval)255 LocalStorageManager::CreateStorage(mozIDOMWindow* aWindow,
256                                    nsIPrincipal* aPrincipal,
257                                    nsIPrincipal* aStoragePrincipal,
258                                    const nsAString& aDocumentURI, bool aPrivate,
259                                    Storage** aRetval) {
260   return GetStorageInternal(CreateMode::CreateAlways, aWindow, aPrincipal,
261                             aStoragePrincipal, aDocumentURI, aPrivate, aRetval);
262 }
263 
264 NS_IMETHODIMP
GetStorage(mozIDOMWindow * aWindow,nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,bool aPrivate,Storage ** aRetval)265 LocalStorageManager::GetStorage(mozIDOMWindow* aWindow,
266                                 nsIPrincipal* aPrincipal,
267                                 nsIPrincipal* aStoragePrincipal, bool aPrivate,
268                                 Storage** aRetval) {
269   return GetStorageInternal(CreateMode::UseIfExistsNeverCreate, aWindow,
270                             aPrincipal, aStoragePrincipal, u""_ns, aPrivate,
271                             aRetval);
272 }
273 
274 NS_IMETHODIMP
CloneStorage(Storage * aStorage)275 LocalStorageManager::CloneStorage(Storage* aStorage) {
276   // Cloning is supported only for sessionStorage
277   return NS_ERROR_NOT_IMPLEMENTED;
278 }
279 
280 NS_IMETHODIMP
CheckStorage(nsIPrincipal * aPrincipal,Storage * aStorage,bool * aRetval)281 LocalStorageManager::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage,
282                                   bool* aRetval) {
283   MOZ_ASSERT(NS_IsMainThread());
284   MOZ_ASSERT(aPrincipal);
285   MOZ_ASSERT(aStorage);
286   MOZ_ASSERT(aRetval);
287 
288   // Only used by sessionStorage.
289   return NS_ERROR_NOT_IMPLEMENTED;
290 }
291 
292 NS_IMETHODIMP
GetNextGenLocalStorageEnabled(bool * aResult)293 LocalStorageManager::GetNextGenLocalStorageEnabled(bool* aResult) {
294   MOZ_ASSERT(NS_IsMainThread());
295   MOZ_ASSERT(aResult);
296 
297   *aResult = NextGenLocalStorageEnabled();
298   return NS_OK;
299 }
300 
301 NS_IMETHODIMP
Preload(nsIPrincipal * aPrincipal,JSContext * aContext,Promise ** _retval)302 LocalStorageManager::Preload(nsIPrincipal* aPrincipal, JSContext* aContext,
303                              Promise** _retval) {
304   MOZ_ASSERT(NS_IsMainThread());
305   MOZ_ASSERT(aPrincipal);
306   MOZ_ASSERT(_retval);
307 
308   return NS_ERROR_NOT_IMPLEMENTED;
309 }
310 
311 NS_IMETHODIMP
IsPreloaded(nsIPrincipal * aPrincipal,JSContext * aContext,Promise ** _retval)312 LocalStorageManager::IsPreloaded(nsIPrincipal* aPrincipal, JSContext* aContext,
313                                  Promise** _retval) {
314   MOZ_ASSERT(NS_IsMainThread());
315   MOZ_ASSERT(aPrincipal);
316   MOZ_ASSERT(_retval);
317 
318   return NS_ERROR_NOT_IMPLEMENTED;
319 }
320 
ClearCaches(uint32_t aUnloadFlags,const OriginAttributesPattern & aPattern,const nsACString & aOriginScope)321 void LocalStorageManager::ClearCaches(uint32_t aUnloadFlags,
322                                       const OriginAttributesPattern& aPattern,
323                                       const nsACString& aOriginScope) {
324   for (const auto& cacheEntry : mCaches) {
325     OriginAttributes oa;
326     DebugOnly<bool> rv = oa.PopulateFromSuffix(cacheEntry.GetKey());
327     MOZ_ASSERT(rv);
328     if (!aPattern.Matches(oa)) {
329       // This table doesn't match the given origin attributes pattern
330       continue;
331     }
332 
333     CacheOriginHashtable* table = cacheEntry.GetWeak();
334 
335     for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
336       LocalStorageCache* cache = iter2.Get()->cache();
337 
338       if (aOriginScope.IsEmpty() ||
339           StringBeginsWith(cache->OriginNoSuffix(), aOriginScope)) {
340         cache->UnloadItems(aUnloadFlags);
341       }
342     }
343   }
344 }
345 
Observe(const char * aTopic,const nsAString & aOriginAttributesPattern,const nsACString & aOriginScope)346 nsresult LocalStorageManager::Observe(const char* aTopic,
347                                       const nsAString& aOriginAttributesPattern,
348                                       const nsACString& aOriginScope) {
349   OriginAttributesPattern pattern;
350   if (!pattern.Init(aOriginAttributesPattern)) {
351     NS_ERROR("Cannot parse origin attributes pattern");
352     return NS_ERROR_FAILURE;
353   }
354 
355   // Clear everything, caches + database
356   if (!strcmp(aTopic, "cookie-cleared")) {
357     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
358     return NS_OK;
359   }
360 
361   // Clear everything, caches + database
362   if (!strcmp(aTopic, "extension:purge-localStorage-caches")) {
363     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, aOriginScope);
364     return NS_OK;
365   }
366 
367   if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
368     // This is only meant for SessionStorageManager.
369     return NS_OK;
370   }
371 
372   // Clear from caches everything that has been stored
373   // while in session-only mode
374   if (!strcmp(aTopic, "session-only-cleared")) {
375     ClearCaches(LocalStorageCache::kUnloadSession, pattern, aOriginScope);
376     return NS_OK;
377   }
378 
379   // Clear all private-browsing caches
380   if (!strcmp(aTopic, "private-browsing-data-cleared")) {
381     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
382     return NS_OK;
383   }
384 
385   // Clear localStorage data belonging to an origin pattern
386   if (!strcmp(aTopic, "clear-origin-attributes-data") ||
387       !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
388     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
389     return NS_OK;
390   }
391 
392   if (!strcmp(aTopic, "profile-change")) {
393     // For case caches are still referenced - clear them completely
394     ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
395     mCaches.Clear();
396     return NS_OK;
397   }
398 
399 #ifdef DOM_STORAGE_TESTS
400   if (!strcmp(aTopic, "test-reload")) {
401     // This immediately completely reloads all caches from the database.
402     ClearCaches(LocalStorageCache::kTestReload, pattern, ""_ns);
403     return NS_OK;
404   }
405 
406   if (!strcmp(aTopic, "test-flushed")) {
407     if (!XRE_IsParentProcess()) {
408       nsCOMPtr<nsIObserverService> obs =
409           mozilla::services::GetObserverService();
410       if (obs) {
411         obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
412       }
413     }
414 
415     return NS_OK;
416   }
417 #endif
418 
419   NS_ERROR("Unexpected topic");
420   return NS_ERROR_UNEXPECTED;
421 }
422 
423 // static
Self()424 LocalStorageManager* LocalStorageManager::Self() {
425   MOZ_ASSERT(!NextGenLocalStorageEnabled());
426 
427   return sSelf;
428 }
429 
Ensure()430 LocalStorageManager* LocalStorageManager::Ensure() {
431   MOZ_ASSERT(!NextGenLocalStorageEnabled());
432 
433   if (sSelf) {
434     return sSelf;
435   }
436 
437   // Cause sSelf to be populated.
438   nsCOMPtr<nsIDOMStorageManager> initializer =
439       do_GetService("@mozilla.org/dom/localStorage-manager;1");
440   MOZ_ASSERT(sSelf, "Didn't initialize?");
441 
442   return sSelf;
443 }
444 
445 }  // namespace dom
446 }  // namespace mozilla
447