nsresult nsDOMStorageDBWrapper::CreateQuotaDBKey(const nsACString& aAsciiDomain, nsACString& aKey) { nsresult rv; nsAutoCString subdomainsDBKey; nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService( NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIURI> uri; rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aAsciiDomain); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString eTLDplusOne; rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne); if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) { // XXX bug 357323 - what to do for localhost/file exactly? eTLDplusOne = aAsciiDomain; rv = NS_OK; } NS_ENSURE_SUCCESS(rv, rv); CreateReversedDomain(eTLDplusOne, subdomainsDBKey); aKey.Assign(subdomainsDBKey); return NS_OK; }
nsresult nsDOMStorageDBWrapper::CreateReversedDomain(nsIURI* aUri, nsACString& aKey) { nsresult rv; nsAutoCString domainScope; rv = aUri->GetAsciiHost(domainScope); NS_ENSURE_SUCCESS(rv, rv); if (domainScope.IsEmpty()) { // About pages have an empty host but a valid path. Since they are handled // internally by our own redirector, we can trust them and use path as key. // if file:/// protocol, let's make the exact directory the domain bool isScheme = false; if ((NS_SUCCEEDED(aUri->SchemeIs("about", &isScheme)) && isScheme) || (NS_SUCCEEDED(aUri->SchemeIs("moz-safe-about", &isScheme)) && isScheme)) { rv = aUri->GetPath(domainScope); NS_ENSURE_SUCCESS(rv, rv); // While the host is always canonicalized to lowercase, the path is not, // thus need to force the casing. ToLowerCase(domainScope); } else if (NS_SUCCEEDED(aUri->SchemeIs("file", &isScheme)) && isScheme) { nsCOMPtr<nsIURL> url = do_QueryInterface(aUri, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = url->GetDirectory(domainScope); NS_ENSURE_SUCCESS(rv, rv); } } rv = CreateReversedDomain(domainScope, aKey); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; }
nsresult GenerateOriginKey(nsIPrincipal* aPrincipal, nsACString& aOriginAttrSuffix, nsACString& aOriginKey) { if (NS_WARN_IF(!aPrincipal)) { return NS_ERROR_UNEXPECTED; } aPrincipal->OriginAttributesRef().CreateSuffix(aOriginAttrSuffix); nsCOMPtr<nsIURI> uri; nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); if (!uri) { return NS_ERROR_UNEXPECTED; } nsAutoCString domainOrigin; rv = uri->GetAsciiHost(domainOrigin); NS_ENSURE_SUCCESS(rv, rv); if (domainOrigin.IsEmpty()) { // For the file:/// protocol use the exact directory as domain. bool isScheme = false; if (NS_SUCCEEDED(uri->SchemeIs("file", &isScheme)) && isScheme) { nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = url->GetDirectory(domainOrigin); NS_ENSURE_SUCCESS(rv, rv); } } // Append reversed domain nsAutoCString reverseDomain; rv = CreateReversedDomain(domainOrigin, reverseDomain); if (NS_FAILED(rv)) { return rv; } aOriginKey.Append(reverseDomain); // Append scheme nsAutoCString scheme; rv = uri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); aOriginKey.Append(':'); aOriginKey.Append(scheme); // Append port if any int32_t port = NS_GetRealPort(uri); if (port != -1) { aOriginKey.Append(nsPrintfCString(":%d", port)); } return NS_OK; }
nsresult nsDOMStorageDBWrapper::CreateQuotaDBKey(nsIPrincipal* aPrincipal, nsACString& aKey) { nsresult rv; nsAutoCString subdomainsDBKey; nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService( NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIURI> uri; rv = aPrincipal->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); nsAutoCString eTLDplusOne; rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne); if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) { // XXX bug 357323 - what to do for localhost/file exactly? rv = uri->GetAsciiHost(eTLDplusOne); } NS_ENSURE_SUCCESS(rv, rv); CreateReversedDomain(eTLDplusOne, subdomainsDBKey); uint32_t appId; rv = aPrincipal->GetAppId(&appId); NS_ENSURE_SUCCESS(rv, rv); bool isInBrowserElement; rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement); NS_ENSURE_SUCCESS(rv, rv); if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) { aKey.Assign(subdomainsDBKey); return NS_OK; } aKey.Truncate(); aKey.AppendInt(appId); aKey.Append(NS_LITERAL_CSTRING(":") + (isInBrowserElement ? NS_LITERAL_CSTRING("t") : NS_LITERAL_CSTRING("f")) + NS_LITERAL_CSTRING(":") + subdomainsDBKey); return NS_OK; }
nsresult nsDOMStorageDBWrapper::CreateScopeDBKey(nsIURI* aUri, nsACString& aKey) { nsresult rv; rv = CreateReversedDomain(aUri, aKey); if (NS_FAILED(rv)) return rv; nsAutoCString scheme; rv = aUri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); aKey.AppendLiteral(":"); aKey.Append(scheme); int32_t port = NS_GetRealPort(aUri); if (port != -1) { aKey.AppendLiteral(":"); aKey.Append(nsPrintfCString("%d", port)); } return NS_OK; }
NS_IMETHODIMP DOMStorageObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { nsresult rv; // Start the thread that opens the database. if (!strcmp(aTopic, kStartupTopic)) { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); obs->RemoveObserver(this, kStartupTopic); mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID); if (!mDBThreadStartDelayTimer) { return NS_ERROR_UNEXPECTED; } mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay); return NS_OK; } // Timer callback used to start the database a short timer after startup if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject); if (!timer) { return NS_ERROR_UNEXPECTED; } if (timer == mDBThreadStartDelayTimer) { mDBThreadStartDelayTimer = nullptr; DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); } return NS_OK; } // Clear everything, caches + database if (!strcmp(aTopic, "cookie-changed")) { if (!NS_LITERAL_STRING("cleared").Equals(aData)) { return NS_OK; } DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); db->AsyncClearAll(); Notify("cookie-cleared"); return NS_OK; } // Clear from caches everything that has been stored // while in session-only mode if (!strcmp(aTopic, "perm-changed")) { // Check for cookie permission change nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject)); if (!perm) { return NS_OK; } nsAutoCString type; perm->GetType(type); if (type != NS_LITERAL_CSTRING("cookie")) { return NS_OK; } uint32_t cap = 0; perm->GetCapability(&cap); if (!(cap & nsICookiePermission::ACCESS_SESSION) || !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) { return NS_OK; } nsAutoCString host; perm->GetHost(host); if (host.IsEmpty()) { return NS_OK; } nsAutoCString scope; rv = CreateReversedDomain(host, scope); NS_ENSURE_SUCCESS(rv, rv); Notify("session-only-cleared", scope); return NS_OK; } // Clear everything (including so and pb data) from caches and database // for the gived domain and subdomains. if (!strcmp(aTopic, "browser:purge-domain-data")) { // Convert the domain name to the ACE format nsAutoCString aceDomain; nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID); if (converter) { rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain); NS_ENSURE_SUCCESS(rv, rv); } else { // In case the IDN service is not available, this is the best we can come up with! NS_EscapeURL(NS_ConvertUTF16toUTF8(aData), esc_OnlyNonASCII | esc_AlwaysCopy, aceDomain); } nsAutoCString scopePrefix; rv = CreateReversedDomain(aceDomain, scopePrefix); NS_ENSURE_SUCCESS(rv, rv); DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); db->AsyncClearMatchingScope(scopePrefix); Notify("domain-data-cleared", scopePrefix); return NS_OK; } // Clear all private-browsing caches if (!strcmp(aTopic, "last-pb-context-exited")) { Notify("private-browsing-data-cleared"); return NS_OK; } // Clear data beloging to an app. if (!strcmp(aTopic, "webapps-clear-data")) { nsCOMPtr<mozIApplicationClearPrivateDataParams> params = do_QueryInterface(aSubject); if (!params) { NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); return NS_ERROR_UNEXPECTED; } uint32_t appId; bool browserOnly; rv = params->GetAppId(&appId); NS_ENSURE_SUCCESS(rv, rv); rv = params->GetBrowserOnly(&browserOnly); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID); DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); nsAutoCString scope; scope.AppendInt(appId); scope.Append(NS_LITERAL_CSTRING(":t:")); db->AsyncClearMatchingScope(scope); Notify("app-data-cleared", scope); if (!browserOnly) { scope.Truncate(); scope.AppendInt(appId); scope.Append(NS_LITERAL_CSTRING(":f:")); db->AsyncClearMatchingScope(scope); Notify("app-data-cleared", scope); } return NS_OK; } if (!strcmp(aTopic, "profile-after-change")) { Notify("profile-change"); return NS_OK; } if (!strcmp(aTopic, "profile-before-change") || !strcmp(aTopic, "xpcom-shutdown")) { rv = DOMStorageCache::StopDatabase(); if (NS_FAILED(rv)) { NS_WARNING("Error while stopping DOMStorage DB background thread"); } return NS_OK; } if (!strcmp(aTopic, "disk-space-watcher")) { if (NS_LITERAL_STRING("full").Equals(aData)) { Notify("low-disk-space"); } else if (NS_LITERAL_STRING("free").Equals(aData)) { Notify("no-low-disk-space"); } return NS_OK; } #ifdef DOM_STORAGE_TESTS if (!strcmp(aTopic, "domstorage-test-flush-force")) { DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); if (db) { db->AsyncFlush(); } return NS_OK; } if (!strcmp(aTopic, "domstorage-test-flushed")) { // Only used to propagate to IPC children Notify("test-flushed"); return NS_OK; } if (!strcmp(aTopic, "domstorage-test-reload")) { Notify("test-reload"); return NS_OK; } #endif NS_ERROR("Unexpected topic"); return NS_ERROR_UNEXPECTED; }
NS_IMETHODIMP DOMStorageObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { nsresult rv; // Start the thread that opens the database. if (!strcmp(aTopic, kStartupTopic)) { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); obs->RemoveObserver(this, kStartupTopic); mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID); if (!mDBThreadStartDelayTimer) { return NS_ERROR_UNEXPECTED; } mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay); return NS_OK; } // Timer callback used to start the database a short timer after startup if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) { nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject); if (!timer) { return NS_ERROR_UNEXPECTED; } if (timer == mDBThreadStartDelayTimer) { mDBThreadStartDelayTimer = nullptr; DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); } return NS_OK; } // Clear everything, caches + database if (!strcmp(aTopic, "cookie-changed")) { if (!NS_LITERAL_STRING("cleared").Equals(aData)) { return NS_OK; } DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); db->AsyncClearAll(); Notify("cookie-cleared"); return NS_OK; } // Clear from caches everything that has been stored // while in session-only mode if (!strcmp(aTopic, "perm-changed")) { // Check for cookie permission change nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject)); if (!perm) { return NS_OK; } nsAutoCString type; perm->GetType(type); if (type != NS_LITERAL_CSTRING("cookie")) { return NS_OK; } uint32_t cap = 0; perm->GetCapability(&cap); if (!(cap & nsICookiePermission::ACCESS_SESSION) || !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) { return NS_OK; } nsCOMPtr<nsIPrincipal> principal; perm->GetPrincipal(getter_AddRefs(principal)); if (!principal) { return NS_OK; } nsAutoCString originSuffix; BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(originSuffix); nsCOMPtr<nsIURI> origin; principal->GetURI(getter_AddRefs(origin)); if (!origin) { return NS_OK; } nsAutoCString host; origin->GetHost(host); if (host.IsEmpty()) { return NS_OK; } nsAutoCString originScope; rv = CreateReversedDomain(host, originScope); NS_ENSURE_SUCCESS(rv, rv); Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix), originScope); return NS_OK; } // Clear everything (including so and pb data) from caches and database // for the gived domain and subdomains. if (!strcmp(aTopic, "browser:purge-domain-data")) { // Convert the domain name to the ACE format nsAutoCString aceDomain; nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID); if (converter) { rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain); NS_ENSURE_SUCCESS(rv, rv); } else { // In case the IDN service is not available, this is the best we can come up with! rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(aData), esc_OnlyNonASCII | esc_AlwaysCopy, aceDomain, fallible); NS_ENSURE_SUCCESS(rv, rv); } nsAutoCString originScope; rv = CreateReversedDomain(aceDomain, originScope); NS_ENSURE_SUCCESS(rv, rv); DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); db->AsyncClearMatchingOrigin(originScope); Notify("domain-data-cleared", EmptyString(), originScope); return NS_OK; } // Clear all private-browsing caches if (!strcmp(aTopic, "last-pb-context-exited")) { Notify("private-browsing-data-cleared"); return NS_OK; } // Clear data of the origins whose prefixes will match the suffix. if (!strcmp(aTopic, "clear-origin-attributes-data")) { OriginAttributesPattern pattern; if (!pattern.Init(nsDependentString(aData))) { NS_ERROR("Cannot parse origin attributes pattern"); return NS_ERROR_FAILURE; } DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); NS_ENSURE_TRUE(db, NS_ERROR_FAILURE); db->AsyncClearMatchingOriginAttributes(pattern); Notify("origin-attr-pattern-cleared", nsDependentString(aData)); return NS_OK; } if (!strcmp(aTopic, "profile-after-change")) { Notify("profile-change"); return NS_OK; } if (!strcmp(aTopic, "profile-before-change") || !strcmp(aTopic, "xpcom-shutdown")) { rv = DOMStorageCache::StopDatabase(); if (NS_FAILED(rv)) { NS_WARNING("Error while stopping DOMStorage DB background thread"); } return NS_OK; } if (!strcmp(aTopic, "disk-space-watcher")) { if (NS_LITERAL_STRING("full").Equals(aData)) { Notify("low-disk-space"); } else if (NS_LITERAL_STRING("free").Equals(aData)) { Notify("no-low-disk-space"); } return NS_OK; } #ifdef DOM_STORAGE_TESTS if (!strcmp(aTopic, "domstorage-test-flush-force")) { DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); if (db) { db->AsyncFlush(); } return NS_OK; } if (!strcmp(aTopic, "domstorage-test-flushed")) { // Only used to propagate to IPC children Notify("test-flushed"); return NS_OK; } if (!strcmp(aTopic, "domstorage-test-reload")) { Notify("test-reload"); return NS_OK; } #endif NS_ERROR("Unexpected topic"); return NS_ERROR_UNEXPECTED; }
nsresult nsDOMStorageDBWrapper::CreateScopeDBKey(nsIPrincipal* aPrincipal, nsACString& aKey) { nsCOMPtr<nsIURI> uri; nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); nsAutoCString domainScope; rv = uri->GetAsciiHost(domainScope); NS_ENSURE_SUCCESS(rv, rv); if (domainScope.IsEmpty()) { // About pages have an empty host but a valid path. Since they are handled // internally by our own redirector, we can trust them and use path as key. // if file:/// protocol, let's make the exact directory the domain bool isScheme = false; if ((NS_SUCCEEDED(uri->SchemeIs("about", &isScheme)) && isScheme) || (NS_SUCCEEDED(uri->SchemeIs("moz-safe-about", &isScheme)) && isScheme)) { rv = uri->GetPath(domainScope); NS_ENSURE_SUCCESS(rv, rv); // While the host is always canonicalized to lowercase, the path is not, // thus need to force the casing. ToLowerCase(domainScope); } else if (NS_SUCCEEDED(uri->SchemeIs("file", &isScheme)) && isScheme) { nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = url->GetDirectory(domainScope); NS_ENSURE_SUCCESS(rv, rv); } } nsAutoCString key; rv = CreateReversedDomain(domainScope, key); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString scheme; rv = uri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); key.Append(NS_LITERAL_CSTRING(":") + scheme); int32_t port = NS_GetRealPort(uri); if (port != -1) { key.Append(nsPrintfCString(":%d", port)); } uint32_t appId; rv = aPrincipal->GetAppId(&appId); NS_ENSURE_SUCCESS(rv, rv); bool isInBrowserElement; rv = aPrincipal->GetIsInBrowserElement(&isInBrowserElement); NS_ENSURE_SUCCESS(rv, rv); if (appId == nsIScriptSecurityManager::NO_APP_ID && !isInBrowserElement) { aKey.Assign(key); return NS_OK; } aKey.Truncate(); aKey.AppendInt(appId); aKey.Append(NS_LITERAL_CSTRING(":") + (isInBrowserElement ? NS_LITERAL_CSTRING("t") : NS_LITERAL_CSTRING("f")) + NS_LITERAL_CSTRING(":") + key); return NS_OK; }