michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DOMStorage.h" michael@0: #include "DOMStorageCache.h" michael@0: #include "DOMStorageManager.h" michael@0: michael@0: #include "nsIDOMStorageEvent.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsICookiePermission.h" michael@0: michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "GeneratedEvents.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: DOMCI_DATA(Storage, mozilla::dom::DOMStorage) michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_ADDREF(DOMStorage) michael@0: NS_IMPL_RELEASE(DOMStorage) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(DOMStorage) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMStorage) michael@0: NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: DOMStorage::DOMStorage(DOMStorageManager* aManager, michael@0: DOMStorageCache* aCache, michael@0: const nsAString& aDocumentURI, michael@0: nsIPrincipal* aPrincipal, michael@0: bool aIsPrivate) michael@0: : mManager(aManager) michael@0: , mCache(aCache) michael@0: , mDocumentURI(aDocumentURI) michael@0: , mPrincipal(aPrincipal) michael@0: , mIsPrivate(aIsPrivate) michael@0: , mIsSessionOnly(false) michael@0: { michael@0: mCache->Preload(); michael@0: } michael@0: michael@0: DOMStorage::~DOMStorage() michael@0: { michael@0: mCache->KeepAlive(); michael@0: } michael@0: michael@0: // nsIDOMStorage (web content public API implementation) michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::GetLength(uint32_t* aLength) michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: return mCache->GetLength(this, aLength); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::Key(uint32_t aIndex, nsAString& aRetval) michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: return mCache->GetKey(this, aIndex, aRetval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::GetItem(const nsAString& aKey, nsAString& aRetval) michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: return mCache->GetItem(this, aKey, aRetval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData) michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: Telemetry::Accumulate(GetType() == LocalStorage michael@0: ? Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES michael@0: : Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES, aKey.Length()); michael@0: Telemetry::Accumulate(GetType() == LocalStorage michael@0: ? Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES michael@0: : Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES, aData.Length()); michael@0: michael@0: nsString old; michael@0: nsresult rv = mCache->SetItem(this, aKey, nsString(aData), old); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (rv != NS_SUCCESS_DOM_NO_OPERATION) { michael@0: BroadcastChangeNotification(aKey, old, aData); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::RemoveItem(const nsAString& aKey) michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsAutoString old; michael@0: nsresult rv = mCache->RemoveItem(this, aKey, old); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (rv != NS_SUCCESS_DOM_NO_OPERATION) { michael@0: BroadcastChangeNotification(aKey, old, NullString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DOMStorage::Clear() michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsresult rv = mCache->Clear(this); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (rv != NS_SUCCESS_DOM_NO_OPERATION) { michael@0: BroadcastChangeNotification(NullString(), NullString(), NullString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class StorageNotifierRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aType) michael@0: : mSubject(aSubject), mType(aType) michael@0: { } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: private: michael@0: nsCOMPtr mSubject; michael@0: const char16_t* mType; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: StorageNotifierRunnable::Run() michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (observerService) { michael@0: observerService->NotifyObservers(mSubject, "dom-storage2-changed", mType); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: void michael@0: DOMStorage::BroadcastChangeNotification(const nsSubstring& aKey, michael@0: const nsSubstring& aOldValue, michael@0: const nsSubstring& aNewValue) michael@0: { michael@0: nsCOMPtr domEvent; michael@0: // Note, this DOM event should never reach JS. It is cloned later in michael@0: // nsGlobalWindow. michael@0: NS_NewDOMStorageEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr); michael@0: michael@0: nsCOMPtr event = do_QueryInterface(domEvent); michael@0: nsresult rv = event->InitStorageEvent(NS_LITERAL_STRING("storage"), michael@0: false, michael@0: false, michael@0: aKey, michael@0: aOldValue, michael@0: aNewValue, michael@0: mDocumentURI, michael@0: static_cast(this)); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr r = michael@0: new StorageNotifierRunnable(event, michael@0: GetType() == LocalStorage michael@0: ? MOZ_UTF16("localStorage") michael@0: : MOZ_UTF16("sessionStorage")); michael@0: NS_DispatchToMainThread(r); michael@0: } michael@0: michael@0: static const uint32_t ASK_BEFORE_ACCEPT = 1; michael@0: static const uint32_t ACCEPT_SESSION = 2; michael@0: static const uint32_t BEHAVIOR_REJECT = 2; michael@0: michael@0: static const char kPermissionType[] = "cookie"; michael@0: static const char kStorageEnabled[] = "dom.storage.enabled"; michael@0: static const char kCookiesBehavior[] = "network.cookie.cookieBehavior"; michael@0: static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy"; michael@0: michael@0: // static, public michael@0: bool michael@0: DOMStorage::CanUseStorage(DOMStorage* aStorage) michael@0: { michael@0: // This method is responsible for correct setting of mIsSessionOnly. michael@0: // It doesn't work with mIsPrivate flag at all, since it is checked michael@0: // regardless mIsSessionOnly flag in DOMStorageCache code. michael@0: if (aStorage) { michael@0: aStorage->mIsSessionOnly = false; michael@0: } michael@0: michael@0: if (!mozilla::Preferences::GetBool(kStorageEnabled)) { michael@0: return false; michael@0: } michael@0: michael@0: // chrome can always use aStorage regardless of permission preferences michael@0: if (nsContentUtils::IsCallerChrome()) { michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr subjectPrincipal; michael@0: nsresult rv = nsContentUtils::GetSecurityManager()-> michael@0: GetSubjectPrincipal(getter_AddRefs(subjectPrincipal)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // if subjectPrincipal were null we'd have returned after michael@0: // IsCallerChrome(). michael@0: michael@0: nsCOMPtr permissionManager = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (!permissionManager) { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t perm; michael@0: permissionManager->TestPermissionFromPrincipal(subjectPrincipal, michael@0: kPermissionType, &perm); michael@0: michael@0: if (perm == nsIPermissionManager::DENY_ACTION) { michael@0: return false; michael@0: } michael@0: michael@0: if (perm == nsICookiePermission::ACCESS_SESSION) { michael@0: if (aStorage) { michael@0: aStorage->mIsSessionOnly = true; michael@0: } michael@0: } else if (perm != nsIPermissionManager::ALLOW_ACTION) { michael@0: uint32_t cookieBehavior = Preferences::GetUint(kCookiesBehavior); michael@0: uint32_t lifetimePolicy = Preferences::GetUint(kCookiesLifetimePolicy); michael@0: michael@0: // Treat "ask every time" as "reject always". michael@0: if ((cookieBehavior == BEHAVIOR_REJECT || lifetimePolicy == ASK_BEFORE_ACCEPT)) { michael@0: return false; michael@0: } michael@0: michael@0: if (lifetimePolicy == ACCEPT_SESSION && aStorage) { michael@0: aStorage->mIsSessionOnly = true; michael@0: } michael@0: } michael@0: michael@0: if (aStorage) { michael@0: return aStorage->CanAccess(subjectPrincipal); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // nsPIDOMStorage michael@0: michael@0: nsPIDOMStorage::StorageType michael@0: DOMStorage::GetType() const michael@0: { michael@0: return mManager->Type(); michael@0: } michael@0: michael@0: nsIPrincipal* michael@0: DOMStorage::GetPrincipal() michael@0: { michael@0: return mPrincipal; michael@0: } michael@0: michael@0: // Defined in DOMStorageManager.cpp michael@0: extern bool michael@0: PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal); michael@0: michael@0: bool michael@0: DOMStorage::PrincipalEquals(nsIPrincipal* aPrincipal) michael@0: { michael@0: return PrincipalsEqual(mPrincipal, aPrincipal); michael@0: } michael@0: michael@0: bool michael@0: DOMStorage::CanAccess(nsIPrincipal* aPrincipal) michael@0: { michael@0: return !aPrincipal || aPrincipal->Subsumes(mPrincipal); michael@0: } michael@0: michael@0: nsTArray* michael@0: DOMStorage::GetKeys() michael@0: { michael@0: if (!CanUseStorage(this)) { michael@0: return new nsTArray(); // return just an empty array michael@0: } michael@0: michael@0: return mCache->GetKeys(this); michael@0: } michael@0: michael@0: } // ::dom michael@0: } // ::mozilla