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