gecko-dev/dom/storage/Storage.cpp

385 строки
10 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Storage.h"
#include "StorageCache.h"
#include "StorageManager.h"
#include "nsIObserverService.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsICookiePermission.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/StorageBinding.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/Services.h"
#include "mozilla/Preferences.h"
#include "mozilla/EnumSet.h"
#include "nsThreadUtils.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
namespace mozilla {
using namespace ipc;
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mManager, mPrincipal, mWindow)
NS_IMPL_CYCLE_COLLECTING_ADDREF(Storage)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Storage)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Storage)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorage)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END
Storage::Storage(nsPIDOMWindowInner* aWindow,
StorageManagerBase* aManager,
StorageCache* aCache,
const nsAString& aDocumentURI,
nsIPrincipal* aPrincipal,
bool aIsPrivate)
: mWindow(aWindow)
, mManager(aManager)
, mCache(aCache)
, mDocumentURI(aDocumentURI)
, mPrincipal(aPrincipal)
, mIsPrivate(aIsPrivate)
, mIsSessionOnly(false)
{
mCache->Preload();
}
Storage::~Storage()
{
}
/* virtual */ JSObject*
Storage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return StorageBinding::Wrap(aCx, this, aGivenProto);
}
uint32_t
Storage::GetLength(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return 0;
}
uint32_t length;
aRv = mCache->GetLength(this, &length);
return length;
}
void
Storage::Key(uint32_t aIndex, nsAString& aResult,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
aRv = mCache->GetKey(this, aIndex, aResult);
}
void
Storage::GetItem(const nsAString& aKey, nsAString& aResult,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
aRv = mCache->GetItem(this, aKey, aResult);
}
void
Storage::SetItem(const nsAString& aKey, const nsAString& aData,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsString data;
bool ok = data.Assign(aData, fallible);
if (!ok) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
nsString old;
aRv = mCache->SetItem(this, aKey, data, old);
if (aRv.Failed()) {
return;
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(aKey, old, aData);
}
}
void
Storage::RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsAutoString old;
aRv = mCache->RemoveItem(this, aKey, old);
if (aRv.Failed()) {
return;
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(aKey, old, NullString());
}
}
void
Storage::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
{
if (!CanUseStorage(aSubjectPrincipal)) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
aRv = mCache->Clear(this);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(NullString(), NullString(), NullString());
}
}
namespace {
class StorageNotifierRunnable : public Runnable
{
public:
StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aType,
bool aPrivateBrowsing)
: Runnable("StorageNotifierRunnable")
, mSubject(aSubject)
, mType(aType)
, mPrivateBrowsing(aPrivateBrowsing)
{ }
NS_DECL_NSIRUNNABLE
private:
nsCOMPtr<nsISupports> mSubject;
const char16_t* mType;
const bool mPrivateBrowsing;
};
NS_IMETHODIMP
StorageNotifierRunnable::Run()
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(mSubject,
mPrivateBrowsing
? "dom-private-storage2-changed"
: "dom-storage2-changed",
mType);
}
return NS_OK;
}
} // namespace
void
Storage::BroadcastChangeNotification(const nsSubstring& aKey,
const nsSubstring& aOldValue,
const nsSubstring& aNewValue)
{
if (!XRE_IsParentProcess() && GetType() == LocalStorage && mPrincipal) {
// If we are in a child process, we want to send a message to the parent in
// order to broadcast the StorageEvent correctly to any child process.
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
Unused << NS_WARN_IF(!cc->SendBroadcastLocalStorageChange(
mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
IPC::Principal(mPrincipal), mIsPrivate));
}
DispatchStorageEvent(GetType(), mDocumentURI, aKey, aOldValue, aNewValue,
mPrincipal, mIsPrivate, this, false);
}
/* static */ void
Storage::DispatchStorageEvent(StorageType aStorageType,
const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage,
bool aImmediateDispatch)
{
StorageEventInit dict;
dict.mBubbles = false;
dict.mCancelable = false;
dict.mKey = aKey;
dict.mNewValue = aNewValue;
dict.mOldValue = aOldValue;
dict.mStorageArea = aStorage;
dict.mUrl = aDocumentURI;
// Note, this DOM event should never reach JS. It is cloned later in
// nsGlobalWindow.
RefPtr<StorageEvent> event =
StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict);
event->SetPrincipal(aPrincipal);
RefPtr<StorageNotifierRunnable> r =
new StorageNotifierRunnable(event,
aStorageType == LocalStorage
? u"localStorage"
: u"sessionStorage",
aIsPrivate);
if (aImmediateDispatch) {
Unused << r->Run();
} else {
NS_DispatchToMainThread(r);
}
// If we are in the parent process and we have the principal, we want to
// broadcast this event to every other process.
if (aStorageType == LocalStorage && XRE_IsParentProcess() && aPrincipal) {
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
Unused << cp->SendDispatchLocalStorageChange(
nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
nsString(aNewValue), IPC::Principal(aPrincipal), aIsPrivate);
}
}
}
void
Storage::ApplyEvent(StorageEvent* aStorageEvent)
{
MOZ_ASSERT(aStorageEvent);
nsAutoString key;
nsAutoString old;
nsAutoString value;
aStorageEvent->GetKey(key);
aStorageEvent->GetNewValue(value);
// No key means clearing the full storage.
if (key.IsVoid()) {
MOZ_ASSERT(value.IsVoid());
mCache->Clear(this, StorageCache::E10sPropagated);
return;
}
// No new value means removing the key.
if (value.IsVoid()) {
mCache->RemoveItem(this, key, old, StorageCache::E10sPropagated);
return;
}
// Otherwise, we set the new value.
mCache->SetItem(this, key, value, old, StorageCache::E10sPropagated);
}
static const char kPermissionType[] = "cookie";
static const char kStorageEnabled[] = "dom.storage.enabled";
bool
Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal)
{
// This method is responsible for correct setting of mIsSessionOnly.
// It doesn't work with mIsPrivate flag at all, since it is checked
// regardless mIsSessionOnly flag in DOMStorageCache code.
if (!mozilla::Preferences::GetBool(kStorageEnabled)) {
return false;
}
nsContentUtils::StorageAccess access =
nsContentUtils::StorageAllowedForPrincipal(mPrincipal);
if (access == nsContentUtils::StorageAccess::eDeny) {
return false;
}
mIsSessionOnly = access <= nsContentUtils::StorageAccess::eSessionScoped;
return CanAccess(&aSubjectPrincipal);
}
Storage::StorageType
Storage::GetType() const
{
return mManager->Type();
}
nsIPrincipal*
Storage::GetPrincipal()
{
return mPrincipal;
}
// Defined in StorageManager.cpp
extern bool
PrincipalsEqual(nsIPrincipal* aObjectPrincipal,
nsIPrincipal* aSubjectPrincipal);
bool
Storage::PrincipalEquals(nsIPrincipal* aPrincipal)
{
return PrincipalsEqual(mPrincipal, aPrincipal);
}
bool
Storage::CanAccess(nsIPrincipal* aPrincipal)
{
return !aPrincipal || aPrincipal->Subsumes(mPrincipal);
}
void
Storage::GetSupportedNames(nsTArray<nsString>& aKeys)
{
if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
// return just an empty array
aKeys.Clear();
return;
}
mCache->GetKeys(this, aKeys);
}
} // namespace dom
} // namespace mozilla