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