/* -*- 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>
 *   Ehsan Akhgari <ehsan.akhgari@gmail.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 ***** */

#ifndef nsDOMStorage_h___
#define nsDOMStorage_h___

#include "nscore.h"
#include "nsAutoPtr.h"
#include "nsIDOMStorage.h"
#include "nsIDOMStorage2.h"
#include "nsIDOMStorageList.h"
#include "nsIDOMStorageItem.h"
#include "nsInterfaceHashtable.h"
#include "nsVoidArray.h"
#include "nsPIDOMStorage.h"
#include "nsIDOMToString.h"
#include "nsDOMEvent.h"
#include "nsIDOMStorageEvent.h"
#include "nsIDOMStorageManager.h"
#include "nsCycleCollectionParticipant.h"

#ifdef MOZ_STORAGE
#include "nsDOMStorageDBWrapper.h"
#endif

class nsDOMStorage;
class nsDOMStorage2;
class nsDOMStorageItem;

class nsDOMStorageEntry : public nsVoidPtrHashKey
{
public:
  nsDOMStorageEntry(KeyTypePointer aStr);
  nsDOMStorageEntry(const nsDOMStorageEntry& aToCopy);
  ~nsDOMStorageEntry();

  // weak reference so that it can be deleted when no longer used
  nsDOMStorage* mStorage;
};

class nsSessionStorageEntry : public nsStringHashKey
{
public:
  nsSessionStorageEntry(KeyTypePointer aStr);
  nsSessionStorageEntry(const nsSessionStorageEntry& aToCopy);
  ~nsSessionStorageEntry();

  nsRefPtr<nsDOMStorageItem> mItem;
};

class nsDOMStorageManager : public nsIDOMStorageManager
                          , public nsIObserver
{
public:
  // nsISupports
  NS_DECL_ISUPPORTS

  // nsIDOMStorageManager
  NS_DECL_NSIDOMSTORAGEMANAGER

  // nsIObserver
  NS_DECL_NSIOBSERVER

  nsDOMStorageManager();

  void AddToStoragesHash(nsDOMStorage* aStorage);
  void RemoveFromStoragesHash(nsDOMStorage* aStorage);

  nsresult ClearAllStorages();

  PRBool InPrivateBrowsingMode() { return mInPrivateBrowsing; }

  static nsresult Initialize();
  static nsDOMStorageManager* GetInstance();
  static void Shutdown();

  static nsDOMStorageManager* gStorageManager;

protected:

  nsTHashtable<nsDOMStorageEntry> mStorages;
  PRBool mInPrivateBrowsing;
};

class nsDOMStorage : public nsIDOMStorage,
                     public nsPIDOMStorage
{
public:
  nsDOMStorage();
  nsDOMStorage(nsDOMStorage& aThat);
  virtual ~nsDOMStorage();

  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMStorage, nsIDOMStorage)

  // nsIDOMStorage
  NS_DECL_NSIDOMSTORAGE

  // Helpers for implementing nsIDOMStorage2
  nsresult GetItem(const nsAString& key, nsAString& aData);
  nsresult Clear();

  // nsPIDOMStorage
  virtual nsresult InitAsSessionStorage(nsIPrincipal *aPrincipal);
  virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal);
  virtual nsresult InitAsGlobalStorage(const nsACString &aDomainDemanded);
  virtual already_AddRefed<nsIDOMStorage2> Clone();
  virtual nsTArray<nsString> *GetKeys();
  virtual nsIPrincipal* Principal();
  virtual PRBool CanAccess(nsIPrincipal *aPrincipal);

  // If true, the contents of the storage should be stored in the
  // database, otherwise this storage should act like a session
  // storage.
  // This call relies on mSessionOnly, and should only be used
  // after a CacheStoragePermissions() call.  See the comments
  // for mSessionOnly below.
  PRBool UseDB() {
    return mUseDB;
  }

  PRBool SessionOnly() {
    return mSessionOnly;
  }

  // Check whether storage may be used by the caller, and whether it
  // is session only.  Returns true if storage may be used.
  static PRBool
  CanUseStorage(PRPackedBool* aSessionOnly);

  // Check whether storage may be used.  Updates mSessionOnly based on
  // the result of CanUseStorage.
  PRBool
  CacheStoragePermissions();

  // retrieve the value and secure state corresponding to a key out of storage.
  nsresult
  GetDBValue(const nsAString& aKey,
             nsAString& aValue,
             PRBool* aSecure);

  // set the value corresponding to a key in the storage. If
  // aSecure is false, then attempts to modify a secure value
  // throw NS_ERROR_DOM_INVALID_ACCESS_ERR
  nsresult
  SetDBValue(const nsAString& aKey,
             const nsAString& aValue,
             PRBool aSecure);

  // set the value corresponding to a key as secure.
  nsresult
  SetSecure(const nsAString& aKey, PRBool aSecure);

  // clear all values from the store
  void ClearAll();

  nsresult
  CloneFrom(nsDOMStorage* aThat);

  nsIDOMStorageItem* GetNamedItem(const nsAString& aKey, nsresult* aResult);

  static nsDOMStorage* FromSupports(nsISupports* aSupports)
  {
    return static_cast<nsDOMStorage*>(static_cast<nsIDOMStorage*>(aSupports));
  }

protected:

  friend class nsDOMStorageManager;
  friend class nsDOMStorage2;

  static nsresult InitDB();

  // cache the keys from the database for faster lookup
  nsresult CacheKeysFromDB();

  void BroadcastChangeNotification();

  PRBool CanAccessSystem(nsIPrincipal *aPrincipal);

  // true if the storage database should be used for values
  PRPackedBool mUseDB;

  // true if the preferences indicates that this storage should be
  // session only. This member is updated by
  // CacheStoragePermissions(), using the current principal.
  // CacheStoragePermissions() must be called at each entry point to
  // make sure this stays up to date.
  PRPackedBool mSessionOnly;

  // true if this storage was initialized as a localStorage object.  localStorage
  // objects are scoped to scheme/host/port in the database, while globalStorage
  // objects are scoped just to host.  this flag also tells the manager to map
  // this storage also in mLocalStorages hash table.
  PRPackedBool mLocalStorage;

  // true if items from the database are cached
  PRPackedBool mItemsCached;

  // domain this store is associated with
  nsCString mDomain;

  // the key->value item pairs
  nsTHashtable<nsSessionStorageEntry> mItems;

  // keys are used for database queries.
  // see comments of the getters bellow.
  nsCString mScopeDBKey;
  nsCString mQuotaDomainDBKey;

  friend class nsIDOMStorage2;
  nsPIDOMStorage* mSecurityChecker;

public:
  // e.g. "moc.rab.oof.:" or "moc.rab.oof.:http:80" depending
  // on association with a domain (globalStorage) or
  // an origin (localStorage).
  nsCString& GetScopeDBKey() {return mScopeDBKey;}

  // e.g. "moc.rab.%" - reversed eTLD+1 subpart of the domain or
  // (in future) reversed offline application allowed domain.
  nsCString& GetQuotaDomainDBKey() {return mQuotaDomainDBKey;}

 #ifdef MOZ_STORAGE
   static nsDOMStorageDBWrapper* gStorageDB;
 #endif
};

class nsDOMStorage2 : public nsIDOMStorage2,
                      public nsPIDOMStorage
{
public:
  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMStorage2, nsIDOMStorage2)

  nsDOMStorage2(nsDOMStorage2& aThat);
  nsDOMStorage2();

  NS_DECL_NSIDOMSTORAGE2

  // nsPIDOMStorage
  virtual nsresult InitAsSessionStorage(nsIPrincipal *aPrincipal);
  virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal);
  virtual nsresult InitAsGlobalStorage(const nsACString &aDomainDemanded);
  virtual already_AddRefed<nsIDOMStorage2> Clone();
  virtual nsTArray<nsString> *GetKeys();
  virtual nsIPrincipal* Principal();
  virtual PRBool CanAccess(nsIPrincipal *aPrincipal);

private:
  // storages bound to an origin hold the principal to
  // make security checks against it
  nsCOMPtr<nsIPrincipal> mPrincipal;

  nsRefPtr<nsDOMStorage> mStorage;
};

class nsDOMStorageList : public nsIDOMStorageList
{
public:
  nsDOMStorageList()
  {
    mStorages.Init();
  }

  virtual ~nsDOMStorageList() {}

  // nsISupports
  NS_DECL_ISUPPORTS

  // nsIDOMStorageList
  NS_DECL_NSIDOMSTORAGELIST

  nsIDOMStorage* GetNamedItem(const nsAString& aDomain, nsresult* aResult);

  /**
   * Check whether aCurrentDomain has access to aRequestedDomain
   */
  static PRBool
  CanAccessDomain(const nsACString& aRequestedDomain,
                  const nsACString& aCurrentDomain);

protected:

  /**
   * Return the global nsIDOMStorage for a particular domain.
   * aNoCurrentDomainCheck may be true to skip the domain comparison;
   * this is used for chrome code so that it may retrieve data from
   * any domain.
   *
   * @param aRequestedDomain domain to return
   * @param aCurrentDomain domain of current caller
   * @param aNoCurrentDomainCheck true to skip domain comparison
   */
  nsIDOMStorage*
  GetStorageForDomain(const nsACString& aRequestedDomain,
                      const nsACString& aCurrentDomain,
                      PRBool aNoCurrentDomainCheck,
                      nsresult* aResult);

  /**
   * Convert the domain into an array of its component parts.
   */
  static PRBool
  ConvertDomainToArray(const nsACString& aDomain,
                       nsCStringArray* aArray);

  nsInterfaceHashtable<nsCStringHashKey, nsIDOMStorage> mStorages;
};

class nsDOMStorageItem : public nsIDOMStorageItem,
                         public nsIDOMToString
{
public:
  nsDOMStorageItem(nsDOMStorage* aStorage,
                   const nsAString& aKey,
                   const nsAString& aValue,
                   PRBool aSecure);
  virtual ~nsDOMStorageItem();

  // nsISupports
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMStorageItem, nsIDOMStorageItem)

  // nsIDOMStorage
  NS_DECL_NSIDOMSTORAGEITEM

  // nsIDOMToString
  NS_DECL_NSIDOMTOSTRING

  PRBool IsSecure()
  {
    return mSecure;
  }

  void SetSecureInternal(PRBool aSecure)
  {
    mSecure = aSecure;
  }

  const nsAString& GetValueInternal()
  {
    return mValue;
  }

  const void SetValueInternal(const nsAString& aValue)
  {
    mValue = aValue;
  }

  void ClearValue()
  {
    mValue.Truncate();
  }

protected:

  // true if this value is for secure sites only
  PRBool mSecure;

  // key for the item
  nsString mKey;

  // value of the item
  nsString mValue;

  // If this item came from the db, mStorage points to the storage
  // object where this item came from.
  nsRefPtr<nsDOMStorage> mStorage;
};

class nsDOMStorageEvent : public nsDOMEvent,
                          public nsIDOMStorageEvent
{
public:
  nsDOMStorageEvent(const nsAString& aDomain)
    : nsDOMEvent(nsnull, nsnull), mDomain(aDomain)
  {
    if (aDomain.IsEmpty()) {
      // An empty domain means this event is for a session sotrage
      // object change. Store #session as the domain.

      mDomain = NS_LITERAL_STRING("#session");
    }
  }

  virtual ~nsDOMStorageEvent()
  {
  }

  nsresult Init();

  NS_DECL_ISUPPORTS
  NS_DECL_NSIDOMSTORAGEEVENT
  NS_FORWARD_NSIDOMEVENT(nsDOMEvent::)

protected:
  nsString mDomain;
};

NS_IMETHODIMP
NS_NewDOMStorage(nsISupports* aOuter, REFNSIID aIID, void** aResult);

NS_IMETHODIMP
NS_NewDOMStorage2(nsISupports* aOuter, REFNSIID aIID, void** aResult);

nsresult
NS_NewDOMStorageList(nsIDOMStorageList** aResult);

#endif /* nsDOMStorage_h___ */
