/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Neil Deakin <enndeakin@sympatico.ca>
* Johnny Stenback <jst@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsCOMPtr.h"
#include "nsDOMError.h"
#include "nsDOMClassInfo.h"
#include "nsUnicharUtils.h"
#include "nsIDocument.h"
#include "nsDOMStorage.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "nsIPrincipal.h"
#include "nsIURI.h"
#include "nsReadableUtils.h"
#include "nsIObserverService.h"
#include "nsNetUtil.h"
#include "nsIPrefBranch.h"
#include "nsICookiePermission.h"
#include "nsIPermissionManager.h"
static const PRUint32 ASK_BEFORE_ACCEPT = 1;
static const PRUint32 ACCEPT_SESSION = 2;
static const PRUint32 BEHAVIOR_REJECT = 2;
static const PRUint32 DEFAULT_QUOTA = 5 * 1024;
static const char kPermissionType[] = "cookie";
static const char kStorageEnabled[] = "dom.storage.enabled";
static const char kDefaultQuota[] = "dom.storage.default_quota";
static const char kCookiesBehavior[] = "network.cookie.cookieBehavior";
static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
//
// Helper that tells us whether the caller is secure or not.
//
static PRBool
IsCallerSecure()
{
nsCOMPtr<nsIPrincipal> subjectPrincipal;
nsContentUtils::GetSecurityManager()->
GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
if (!subjectPrincipal) {
// No subject principal means no code is running. Default to not
// being secure in that case.
return PR_FALSE;
}
nsCOMPtr<nsIURI> codebase;
subjectPrincipal->GetURI(getter_AddRefs(codebase));
if (!codebase) {
return PR_FALSE;
}
nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(codebase);
if (!innerUri) {
return PR_FALSE;
}
PRBool isHttps = PR_FALSE;
nsresult rv = innerUri->SchemeIs("https", &isHttps);
return NS_SUCCEEDED(rv) && isHttps;
}
static PRInt32
GetQuota(const nsAString &domain)
{
// FIXME: per-domain quotas?
return ((PRInt32)nsContentUtils::GetIntPref(kDefaultQuota, DEFAULT_QUOTA) * 1024);
}
nsSessionStorageEntry::nsSessionStorageEntry(KeyTypePointer aStr)
: nsStringHashKey(aStr), mItem(nsnull)
{
}
nsSessionStorageEntry::nsSessionStorageEntry(const nsSessionStorageEntry& aToCopy)
: nsStringHashKey(aToCopy), mItem(nsnull)
{
NS_ERROR("We're horked.");
}
nsSessionStorageEntry::~nsSessionStorageEntry()
{
}
//
// nsDOMStorageManager
//
nsDOMStorageManager* nsDOMStorageManager::gStorageManager;
NS_IMPL_ISUPPORTS1(nsDOMStorageManager, nsIObserver)
//static
nsresult
nsDOMStorageManager::Initialize()
{
gStorageManager = new nsDOMStorageManager();
if (!gStorageManager)
return NS_ERROR_OUT_OF_MEMORY;
if (!gStorageManager->mStorages.Init()) {
delete gStorageManager;
gStorageManager = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(gStorageManager);
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os)
os->AddObserver(gStorageManager, "cookie-changed", PR_FALSE);
return NS_OK;
}
//static
void
nsDOMStorageManager::Shutdown()
{
NS_IF_RELEASE(gStorageManager);
gStorageManager = nsnull;
}
PR_STATIC_CALLBACK(PLDHashOperator)
ClearStorage(nsDOMStorageEntry* aEntry, void* userArg)
{
aEntry->mStorage->ClearAll();
return PL_DHASH_REMOVE;
}
nsresult
nsDOMStorageManager::Observe(nsISupports *aSubject,
const char *aTopic,
const PRUnichar *aData)
{
if (!nsCRT::strcmp(aData, NS_LITERAL_STRING("cleared").get())) {
mStorages.EnumerateEntries(ClearStorage, nsnull);
#ifdef MOZ_STORAGE
nsresult rv = nsDOMStorage::InitDB();
NS_ENSURE_SUCCESS(rv, rv);
return nsDOMStorage::gStorageDB->RemoveAll();
#endif
}
return NS_OK;
}
void
nsDOMStorageManager::AddToStoragesHash(nsDOMStorage* aStorage)
{
nsDOMStorageEntry* entry = mStorages.PutEntry(aStorage);
if (entry)
entry->mStorage = aStorage;
}
void
nsDOMStorageManager::RemoveFromStoragesHash(nsDOMStorage* aStorage)
{
nsDOMStorageEntry* entry = mStorages.GetEntry(aStorage);
if (entry)
mStorages.RemoveEntry(aStorage);
}
//
// nsDOMStorage
//
#ifdef MOZ_STORAGE
nsDOMStorageDB* nsDOMStorage::gStorageDB = nsnull;
#endif
nsDOMStorageEntry::nsDOMStorageEntry(KeyTypePointer aStr)
: nsVoidPtrHashKey(aStr), mStorage(nsnull)
{
}
nsDOMStorageEntry::nsDOMStorageEntry(const nsDOMStorageEntry& aToCopy)
: nsVoidPtrHashKey(aToCopy), mStorage(nsnull)
{
NS_ERROR("DOMStorage horked.");
}
nsDOMStorageEntry::~nsDOMStorageEntry()
{
}
NS_INTERFACE_MAP_BEGIN(nsDOMStorage)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorage)
NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Storage)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsDOMStorage)
NS_IMPL_RELEASE(nsDOMStorage)
NS_IMETHODIMP
NS_NewDOMStorage(nsISupports* aOuter, REFNSIID aIID, void** aResult)
{
nsDOMStorage* storage = new nsDOMStorage();
if (!storage)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(storage);
*aResult = storage;
return NS_OK;
}
nsDOMStorage::nsDOMStorage()
: mUseDB(PR_FALSE), mSessionOnly(PR_TRUE), mItemsCached(PR_FALSE)
{
mItems.Init(8);
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
}
nsDOMStorage::nsDOMStorage(nsIURI* aURI, const nsAString& aDomain, PRBool aUseDB)
: mUseDB(aUseDB),
mSessionOnly(PR_TRUE),
mItemsCached(PR_FALSE),
mURI(aURI),
mDomain(aDomain)
{
#ifndef MOZ_STORAGE
mUseDB = PR_FALSE;
#endif
mItems.Init(8);
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
}
nsDOMStorage::~nsDOMStorage()
{
if (nsDOMStorageManager::gStorageManager)
nsDOMStorageManager::gStorageManager->RemoveFromStoragesHash(this);
}
void
nsDOMStorage::Init(nsIURI* aURI, const nsAString& aDomain, PRBool aUseDB)
{
mURI = aURI;
mDomain.Assign(aDomain);
#ifdef MOZ_STORAGE
mUseDB = aUseDB;
#else
mUseDB = PR_FALSE;
#endif
}
//static
PRBool
nsDOMStorage::CanUseStorage(nsIURI* aURI, PRPackedBool* aSessionOnly)
{
// check if the domain can use storage. Downgrade to session only if only
// session storage may be used.
NS_ASSERTION(aSessionOnly, "null session flag");
*aSessionOnly = PR_FALSE;
if (!nsContentUtils::GetBoolPref(kStorageEnabled))
return PR_FALSE;
// chrome can always use storage regardless of permission preferences
if (nsContentUtils::IsCallerChrome())
return PR_TRUE;
nsCOMPtr<nsIPermissionManager> permissionManager =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
if (!permissionManager)
return PR_FALSE;
PRUint32 perm;
permissionManager->TestPermission(aURI, kPermissionType, &perm);
if (perm == nsIPermissionManager::DENY_ACTION)
return PR_FALSE;
if (perm == nsICookiePermission::ACCESS_SESSION) {
*aSessionOnly = PR_TRUE;
}
else if (perm != nsIPermissionManager::ALLOW_ACTION) {
PRUint32 cookieBehavior = nsContentUtils::GetIntPref(kCookiesBehavior);
PRUint32 lifetimePolicy = nsContentUtils::GetIntPref(kCookiesLifetimePolicy);
// treat ask as reject always
if (cookieBehavior == BEHAVIOR_REJECT || lifetimePolicy == ASK_BEFORE_ACCEPT)
return PR_FALSE;
if (lifetimePolicy == ACCEPT_SESSION)
*aSessionOnly = PR_TRUE;
}
return PR_TRUE;
}
class ItemCounterState
{
public:
ItemCounterState(PRBool aIsCallerSecure)
: mIsCallerSecure(aIsCallerSecure), mCount(0)
{
}
PRBool mIsCallerSecure;
PRBool mCount;
private:
ItemCounterState(); // Not to be implemented
};
PR_STATIC_CALLBACK(PLDHashOperator)
ItemCounter(nsSessionStorageEntry* aEntry, void* userArg)
{
ItemCounterState *state = (ItemCounterState *)userArg;
if (state->mIsCallerSecure || !aEntry->mItem->IsSecure()) {
++state->mCount;
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
nsDOMStorage::GetLength(PRUint32 *aLength)
{
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (UseDB())
CacheKeysFromDB();
ItemCounterState state(IsCallerSecure());
mItems.EnumerateEntries(ItemCounter, &state);
*aLength = state.mCount;
return NS_OK;
}
class IndexFinderData
{
public:
IndexFinderData(PRBool aIsCallerSecure, PRUint32 aWantedIndex)
: mIsCallerSecure(aIsCallerSecure), mIndex(0), mWantedIndex(aWantedIndex),
mItem(nsnull)
{
}
PRBool mIsCallerSecure;
PRUint32 mIndex;
PRUint32 mWantedIndex;
nsSessionStorageEntry *mItem;
private:
IndexFinderData(); // Not to be implemented
};
PR_STATIC_CALLBACK(PLDHashOperator)
IndexFinder(nsSessionStorageEntry* aEntry, void* userArg)
{
IndexFinderData *data = (IndexFinderData *)userArg;
if (data->mIndex == data->mWantedIndex &&
(data->mIsCallerSecure || !aEntry->mItem->IsSecure())) {
data->mItem = aEntry;
return PL_DHASH_STOP;
}
++data->mIndex;
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
nsDOMStorage::Key(PRUint32 aIndex, nsAString& aKey)
{
// XXXjst: This is as retarded as the DOM spec is, takes an unsigned
// int, but the spec talks about what to do if a negative value is
// passed in.
// XXX: This does a linear search for the key at index, which would
// suck if there's a large numer of indexes. Do we care? If so,
// maybe we need to have a lazily populated key array here or
// something?
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (UseDB())
CacheKeysFromDB();
IndexFinderData data(IsCallerSecure(), aIndex);
mItems.EnumerateEntries(IndexFinder, &data);
if (!data.mItem) {
// aIndex was larger than the number of accessible keys. Throw.
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
aKey = data.mItem->GetKey();
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorage::GetItem(const nsAString& aKey, nsIDOMStorageItem **aItem)
{
*aItem = nsnull;
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (aKey.IsEmpty())
return NS_OK;
nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
if (entry) {
if (!IsCallerSecure() && entry->mItem->IsSecure()) {
return NS_OK;
}
NS_ADDREF(*aItem = entry->mItem);
}
else if (UseDB()) {
PRBool secure;
nsAutoString value;
nsAutoString unused;
nsresult rv = GetDBValue(aKey, value, &secure, unused);
// return null if access isn't allowed or the key wasn't found
if (rv == NS_ERROR_DOM_SECURITY_ERR || rv == NS_ERROR_DOM_NOT_FOUND_ERR)
return NS_OK;
NS_ENSURE_SUCCESS(rv, rv);
nsRefPtr<nsDOMStorageItem> newitem =
new nsDOMStorageItem(this, aKey, value, secure);
if (!newitem)
return NS_ERROR_OUT_OF_MEMORY;
entry = mItems.PutEntry(aKey);
NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
entry->mItem = newitem;
NS_ADDREF(*aItem = newitem);
}
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorage::SetItem(const nsAString& aKey, const nsAString& aData)
{
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (aKey.IsEmpty())
return NS_OK;
nsresult rv;
nsRefPtr<nsDOMStorageItem> newitem = nsnull;
nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
if (entry) {
if (entry->mItem->IsSecure() && !IsCallerSecure()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (!UseDB()) {
entry->mItem->SetValueInternal(aData);
}
}
else {
if (UseDB())
newitem = new nsDOMStorageItem(this, aKey, aData, PR_FALSE);
else
newitem = new nsDOMStorageItem(this, aKey, aData, PR_FALSE);
if (!newitem)
return NS_ERROR_OUT_OF_MEMORY;
}
if (UseDB()) {
rv = SetDBValue(aKey, aData, IsCallerSecure());
NS_ENSURE_SUCCESS(rv, rv);
}
if (newitem) {
entry = mItems.PutEntry(aKey);
NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
entry->mItem = newitem;
}
// SetDBValue already calls BroadcastChangeNotification so don't do it again
if (!UseDB())
BroadcastChangeNotification();
return NS_OK;
}
NS_IMETHODIMP nsDOMStorage::RemoveItem(const nsAString& aKey)
{
if (!CacheStoragePermissions())
return NS_ERROR_DOM_SECURITY_ERR;
if (aKey.IsEmpty())
return NS_OK;
nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
if (entry && entry->mItem->IsSecure() && !IsCallerSecure()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (UseDB()) {
#ifdef MOZ_STORAGE
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString value;
PRBool secureItem;
nsAutoString owner;
rv = GetDBValue(aKey, value, &secureItem, owner);
if (rv == NS_ERROR_DOM_NOT_FOUND_ERR)
return NS_OK;
NS_ENSURE_SUCCESS(rv, rv);
rv = gStorageDB->RemoveKey(mDomain, aKey, owner,
aKey.Length() + value.Length());
NS_ENSURE_SUCCESS(rv, rv);
mItemsCached = PR_FALSE;
BroadcastChangeNotification();
#endif
}
else if (entry) {
// clear string as StorageItems may be referencing this item
entry->mItem->ClearValue();
BroadcastChangeNotification();
}
if (entry) {
mItems.RawRemoveEntry(entry);
}
return NS_OK;
}
nsresult
nsDOMStorage::InitDB()
{
#ifdef MOZ_STORAGE
if (!gStorageDB) {
gStorageDB = new nsDOMStorageDB();
if (!gStorageDB)
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = gStorageDB->Init();
if (NS_FAILED(rv)) {
// Failed to initialize the DB, delete it and null out the
// pointer so we don't end up attempting to use an
// un-initialized DB later on.
delete gStorageDB;
gStorageDB = nsnull;
return rv;
}
}
#endif
return NS_OK;
}
nsresult
nsDOMStorage::CacheKeysFromDB()
{
#ifdef MOZ_STORAGE
// cache all the keys in the hash. This is used by the Length and Key methods
// use this cache for better performance. The disadvantage is that the
// order may break if someone changes the keys in the database directly.
if (!mItemsCached) {
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
rv = gStorageDB->GetAllKeys(mDomain, this, &mItems);
NS_ENSURE_SUCCESS(rv, rv);
mItemsCached = PR_TRUE;
}
#endif
return NS_OK;
}
nsresult
nsDOMStorage::GetDBValue(const nsAString& aKey, nsAString& aValue,
PRBool* aSecure, nsAString& aOwner)
{
aValue.Truncate();
#ifdef MOZ_STORAGE
if (!UseDB())
return NS_OK;
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString value;
rv = gStorageDB->GetKeyValue(mDomain, aKey, value, aSecure, aOwner);
if (NS_FAILED(rv))
return rv;
if (!IsCallerSecure() && *aSecure) {
return NS_ERROR_DOM_SECURITY_ERR;
}
aValue.Assign(value);
#endif
return NS_OK;
}
nsresult
nsDOMStorage::SetDBValue(const nsAString& aKey,
const nsAString& aValue,
PRBool aSecure)
{
#ifdef MOZ_STORAGE
if (!UseDB())
return NS_OK;
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
// Get the current domain for quota enforcement
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (!ssm)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIPrincipal> subjectPrincipal;
ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
nsAutoString currentDomain;
if (subjectPrincipal) {
nsCOMPtr<nsIURI> uri;
rv = subjectPrincipal->GetURI(getter_AddRefs(uri));
if (NS_SUCCEEDED(rv) && uri) {
nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
if (!innerUri)
return NS_ERROR_UNEXPECTED;
nsCAutoString currentDomainAscii;
innerUri->GetAsciiHost(currentDomainAscii);
currentDomain = NS_ConvertUTF8toUTF16(currentDomainAscii);
}
if (currentDomain.IsEmpty()) {
// allow chrome urls and trusted file urls to write using
// the storage's domain
if (nsContentUtils::IsCallerTrustedForWrite())
currentDomain = mDomain;
else
return NS_ERROR_DOM_SECURITY_ERR;
}
} else {
currentDomain = mDomain;
}
rv = gStorageDB->SetKey(mDomain, aKey, aValue, aSecure,
currentDomain, GetQuota(currentDomain));
NS_ENSURE_SUCCESS(rv, rv);
mItemsCached = PR_FALSE;
BroadcastChangeNotification();
#endif
return NS_OK;
}
nsresult
nsDOMStorage::SetSecure(const nsAString& aKey, PRBool aSecure)
{
#ifdef MOZ_STORAGE
if (UseDB()) {
nsresult rv = InitDB();
NS_ENSURE_SUCCESS(rv, rv);
return gStorageDB->SetSecure(mDomain, aKey, aSecure);
}
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
NS_ASSERTION(entry, "Don't use SetSecure() with non-existing keys!");
if (entry) {
entry->mItem->SetSecureInternal(aSecure);
}
return NS_OK;
}
PR_STATIC_CALLBACK(PLDHashOperator)
ClearStorageItem(nsSessionStorageEntry* aEntry, void* userArg)
{
aEntry->mItem->SetValueInternal(EmptyString());
return PL_DHASH_NEXT;
}
void
nsDOMStorage::ClearAll()
{
mItems.EnumerateEntries(ClearStorageItem, nsnull);
}
PR_STATIC_CALLBACK(PLDHashOperator)
CopyStorageItems(nsSessionStorageEntry* aEntry, void* userArg)
{
nsDOMStorage* newstorage = NS_STATIC_CAST(nsDOMStorage*, userArg);
newstorage->SetItem(aEntry->GetKey(), aEntry->mItem->GetValueInternal());
if (aEntry->mItem->IsSecure()) {
newstorage->SetSecure(aEntry->GetKey(), PR_TRUE);
}
return PL_DHASH_NEXT;
}
already_AddRefed<nsIDOMStorage>
nsDOMStorage::Clone(nsIURI* aURI)
{
if (UseDB()) {
NS_ERROR("Uh, don't clone a global storage object.");
return nsnull;
}
nsDOMStorage* storage = new nsDOMStorage(aURI, mDomain, PR_FALSE);
if (!storage)
return nsnull;
mItems.EnumerateEntries(CopyStorageItems, storage);
NS_ADDREF(storage);
return storage;
}
struct KeysArrayBuilderStruct
{
PRBool callerIsSecure;
nsTArray<nsString> *keys;
};
PR_STATIC_CALLBACK(PLDHashOperator)
KeysArrayBuilder(nsSessionStorageEntry* aEntry, void* userArg)
{
KeysArrayBuilderStruct *keystruct = (KeysArrayBuilderStruct *)userArg;
if (keystruct->callerIsSecure || !aEntry->mItem->IsSecure())
keystruct->keys->AppendElement(aEntry->GetKey());
return PL_DHASH_NEXT;
}
nsTArray<nsString> *
nsDOMStorage::GetKeys()
{
if (UseDB())
CacheKeysFromDB();
KeysArrayBuilderStruct keystruct;
keystruct.callerIsSecure = IsCallerSecure();
keystruct.keys = new nsTArray<nsString>();
if (keystruct.keys)
mItems.EnumerateEntries(KeysArrayBuilder, &keystruct);
return keystruct.keys;
}
void
nsDOMStorage::BroadcastChangeNotification()
{
nsresult rv;
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1", &rv);
if (NS_FAILED(rv)) {
return;
}
// Fire off a notification that a storage object changed. If the
// storage object is a session storage object, we don't pass a
// domain, but if it's a global storage object we do.
observerService->NotifyObservers((nsIDOMStorage *)this,
"dom-storage-changed",
UseDB() ? mDomain.get() : nsnull);
}
//
// nsDOMStorageList
//
NS_INTERFACE_MAP_BEGIN(nsDOMStorageList)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageList)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(StorageList)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsDOMStorageList)
NS_IMPL_RELEASE(nsDOMStorageList)
nsresult
nsDOMStorageList::NamedItem(const nsAString& aDomain,
nsIDOMStorage** aStorage)
{
*aStorage = nsnull;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (!ssm)
return NS_ERROR_FAILURE;
nsCOMPtr<nsIPrincipal> subjectPrincipal;
nsresult rv = ssm->GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
nsCAutoString currentDomain;
if (subjectPrincipal) {
rv = subjectPrincipal->GetURI(getter_AddRefs(uri));
if (NS_SUCCEEDED(rv) && uri) {
PRPackedBool sessionOnly;
if (!nsDOMStorage::CanUseStorage(uri, &sessionOnly))
return NS_ERROR_DOM_SECURITY_ERR;
nsCOMPtr<nsIURI> innerUri = NS_GetInnermostURI(uri);
if (!innerUri)
return NS_ERROR_UNEXPECTED;
uri = innerUri;
rv = uri->GetAsciiHost(currentDomain);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
}
}
PRBool isSystem;
rv = ssm->SubjectPrincipalIsSystem(&isSystem);
NS_ENSURE_SUCCESS(rv, rv);
// allow code that has read privileges to get the storage for any domain
if (!isSystem && nsContentUtils::IsCallerTrustedForRead())
isSystem = PR_TRUE;
if (isSystem || !currentDomain.IsEmpty()) {
return GetStorageForDomain(uri, aDomain, NS_ConvertUTF8toUTF16(currentDomain),
isSystem, aStorage);
}
return NS_ERROR_DOM_SECURITY_ERR;
}
// static
PRBool
nsDOMStorageList::CanAccessDomain(const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain)
{
nsStringArray requestedDomainArray, currentDomainArray;
PRBool ok = ConvertDomainToArray(aRequestedDomain, &requestedDomainArray);
if (!ok)
return PR_FALSE;
ok = ConvertDomainToArray(aCurrentDomain, ¤tDomainArray);
if (!ok)
return PR_FALSE;
if (currentDomainArray.Count() == 1)
currentDomainArray.AppendString(NS_LITERAL_STRING("localdomain"));
// need to use the shorter of the two arrays
PRInt32 currentPos = 0;
PRInt32 requestedPos = 0;
PRInt32 length = requestedDomainArray.Count();
if (currentDomainArray.Count() > length)
currentPos = currentDomainArray.Count() - length;
else if (currentDomainArray.Count() < length)
requestedPos = length - currentDomainArray.Count();
// If the current domain is different in any of the parts from the
// requested domain, a security exception is raised
for (; requestedPos < length; requestedPos++, currentPos++) {
if (*requestedDomainArray[requestedPos] != *currentDomainArray[currentPos])
return PR_FALSE;
}
return PR_TRUE;
}
nsresult
nsDOMStorageList::GetStorageForDomain(nsIURI* aURI,
const nsAString& aRequestedDomain,
const nsAString& aCurrentDomain,
PRBool aNoCurrentDomainCheck,
nsIDOMStorage** aStorage)
{
// fail if the domain contains no periods.
// XXXndeakin update this when bug 342314 is fixed so that we can check
// for top-level domain names properly
nsAutoString trimmedDomain(aRequestedDomain);
trimmedDomain.Trim(".");
if (trimmedDomain.FindChar('.') == kNotFound)
return NS_ERROR_DOM_SECURITY_ERR;
if (!aNoCurrentDomainCheck && !CanAccessDomain(aRequestedDomain,
aCurrentDomain)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsStringArray requestedDomainArray;
PRBool ok = ConvertDomainToArray(aRequestedDomain, &requestedDomainArray);
if (!ok)
return NS_ERROR_DOM_SECURITY_ERR;
// now rebuild a string for the domain.
nsAutoString usedDomain;
PRInt32 requestedPos = 0;
for (requestedPos = 0; requestedPos < requestedDomainArray.Count();
requestedPos++) {
if (!usedDomain.IsEmpty())
usedDomain.AppendLiteral(".");
usedDomain.Append(*requestedDomainArray[requestedPos]);
}
// now have a valid domain, so look it up in the storage table
if (!mStorages.Get(usedDomain, aStorage)) {
nsCOMPtr<nsIDOMStorage> newstorage = new nsDOMStorage(aURI, usedDomain, PR_TRUE);
if (!newstorage)
return NS_ERROR_OUT_OF_MEMORY;
if (!mStorages.Put(usedDomain, newstorage))
return NS_ERROR_OUT_OF_MEMORY;
newstorage.swap(*aStorage);
}
return NS_OK;
}
// static
PRBool
nsDOMStorageList::ConvertDomainToArray(const nsAString& aDomain,
nsStringArray* aArray)
{
PRInt32 length = aDomain.Length();
PRInt32 n = 0;
while (n < length) {
PRInt32 dotpos = aDomain.FindChar('.', n);
nsAutoString domain;
if (dotpos == -1) // no more dots
domain.Assign(Substring(aDomain, n));
else if (dotpos - n == 0) // no point continuing in this case
return false;
else if (dotpos >= 0)
domain.Assign(Substring(aDomain, n, dotpos - n));
ToLowerCase(domain);
aArray->AppendString(domain);
if (dotpos == -1)
break;
n = dotpos + 1;
}
// if n equals the length, there is a dot at the end, so treat it as invalid
return (n != length);
}
nsresult
NS_NewDOMStorageList(nsIDOMStorageList** aResult)
{
*aResult = new nsDOMStorageList();
if (!*aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}
//
// nsDOMStorageItem
//
NS_INTERFACE_MAP_BEGIN(nsDOMStorageItem)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorageItem)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageItem)
NS_INTERFACE_MAP_ENTRY(nsIDOMToString)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(StorageItem)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsDOMStorageItem)
NS_IMPL_RELEASE(nsDOMStorageItem)
nsDOMStorageItem::nsDOMStorageItem(nsDOMStorage* aStorage,
const nsAString& aKey,
const nsAString& aValue,
PRBool aSecure)
: mSecure(aSecure),
mKey(aKey),
mValue(aValue),
mStorage(aStorage)
{
}
nsDOMStorageItem::~nsDOMStorageItem()
{
}
NS_IMETHODIMP
nsDOMStorageItem::GetSecure(PRBool* aSecure)
{
if (!mStorage->CacheStoragePermissions() || !IsCallerSecure()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
if (mStorage->UseDB()) {
nsAutoString value;
nsAutoString owner;
return mStorage->GetDBValue(mKey, value, aSecure, owner);
}
*aSecure = IsSecure();
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageItem::SetSecure(PRBool aSecure)
{
if (!mStorage->CacheStoragePermissions() || !IsCallerSecure()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
if (mStorage->UseDB()) {
nsresult rv = mStorage->SetSecure(mKey, aSecure);
NS_ENSURE_SUCCESS(rv, rv);
}
mSecure = aSecure;
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageItem::GetValue(nsAString& aValue)
{
if (!mStorage->CacheStoragePermissions())
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
if (mStorage->UseDB()) {
// GetDBValue checks the secure state so no need to do it here
PRBool secure;
nsAutoString unused;
nsresult rv = mStorage->GetDBValue(mKey, aValue, &secure, unused);
return (rv == NS_ERROR_DOM_NOT_FOUND_ERR) ? NS_OK : rv;
}
if (IsSecure() && !IsCallerSecure()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
aValue = mValue;
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageItem::SetValue(const nsAString& aValue)
{
if (!mStorage->CacheStoragePermissions())
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
PRBool secureCaller = IsCallerSecure();
if (mStorage->UseDB()) {
// SetDBValue() does the security checks for us.
return mStorage->SetDBValue(mKey, aValue, secureCaller);
}
PRBool secureItem = IsSecure();
if (!secureCaller && secureItem) {
// The item is secure, but the caller isn't. Throw.
return NS_ERROR_DOM_SECURITY_ERR;
}
mValue = aValue;
mSecure = secureCaller;
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageItem::ToString(nsAString& aStr)
{
return GetValue(aStr);
}
// QueryInterface implementation for nsDOMStorageEvent
NS_INTERFACE_MAP_BEGIN(nsDOMStorageEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMStorageEvent)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(StorageEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
NS_IMPL_ADDREF_INHERITED(nsDOMStorageEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMStorageEvent, nsDOMEvent)
NS_IMETHODIMP
nsDOMStorageEvent::GetDomain(nsAString& aDomain)
{
// mDomain will be #session for session storage for events that fire
// due to a change in a session storage object.
aDomain = mDomain;
return NS_OK;
}
nsresult
nsDOMStorageEvent::Init()
{
nsresult rv = InitEvent(NS_LITERAL_STRING("storage"), PR_TRUE, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
// This init method is only called by native code, so set the
// trusted flag here.
SetTrusted(PR_TRUE);
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageEvent::InitStorageEvent(const nsAString& aTypeArg,
PRBool aCanBubbleArg,
PRBool aCancelableArg,
const nsAString& aDomainArg)
{
nsresult rv = InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
NS_ENSURE_SUCCESS(rv, rv);
mDomain = aDomainArg;
return NS_OK;
}
NS_IMETHODIMP
nsDOMStorageEvent::InitStorageEventNS(const nsAString& aNamespaceURIArg,
const nsAString& aTypeArg,
PRBool aCanBubbleArg,
PRBool aCancelableArg,
const nsAString& aDomainArg)
{
// XXXjst: Figure out what to do with aNamespaceURIArg here!
nsresult rv = InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
NS_ENSURE_SUCCESS(rv, rv);
mDomain = aDomainArg;
return NS_OK;
}