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