/* 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/. */

#ifndef mozilla_net_CookieStorage_h
#define mozilla_net_CookieStorage_h

#include "CookieKey.h"

#include "nsICookieNotification.h"
#include "nsIObserver.h"
#include "nsTHashtable.h"
#include "nsWeakReference.h"
#include <functional>
#include "CookieCommons.h"

class nsIArray;
class nsICookie;
class nsICookieTransactionCallback;
class nsIPrefBranch;

namespace mozilla {
namespace net {

class Cookie;
class CookieParser;

// Inherit from CookieKey so this can be stored in nsTHashTable
// TODO: why aren't we using nsClassHashTable<CookieKey, ArrayType>?
class CookieEntry : public CookieKey {
 public:
  // Hash methods
  using ArrayType = nsTArray<RefPtr<Cookie>>;
  using IndexType = ArrayType::index_type;

  explicit CookieEntry(KeyTypePointer aKey) : CookieKey(aKey) {}

  CookieEntry(const CookieEntry& toCopy) {
    // if we end up here, things will break. nsTHashtable shouldn't
    // allow this, since we set ALLOW_MEMMOVE to true.
    MOZ_ASSERT_UNREACHABLE("CookieEntry copy constructor is forbidden!");
  }

  ~CookieEntry() = default;

  inline ArrayType& GetCookies() { return mCookies; }
  inline const ArrayType& GetCookies() const { return mCookies; }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;

  bool IsPartitioned() const;

 private:
  ArrayType mCookies;
};

class CookieStorage : public nsIObserver, public nsSupportsWeakReference {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIOBSERVER

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;

  void GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const;

  void GetSessionCookies(nsTArray<RefPtr<nsICookie>>& aCookies) const;

  already_AddRefed<Cookie> FindCookie(const nsACString& aBaseDomain,
                                      const OriginAttributes& aOriginAttributes,
                                      const nsACString& aHost,
                                      const nsACString& aName,
                                      const nsACString& aPath);

  uint32_t CountCookiesFromHost(const nsACString& aBaseDomain,
                                uint32_t aPrivateBrowsingId);

  bool HasCookiesForSite(const nsACString& aBaseDomain,
                         const OriginAttributesPattern& aPattern);

  uint32_t CountCookieBytesNotMatchingCookie(const Cookie& cookie,
                                             const nsACString& baseDomain);

  void GetAll(nsTArray<RefPtr<nsICookie>>& aResult) const;

  void GetCookiesFromHost(const nsACString& aBaseDomain,
                          const OriginAttributes& aOriginAttributes,
                          nsTArray<RefPtr<Cookie>>& aCookies);

  void GetCookiesWithOriginAttributes(const OriginAttributesPattern& aPattern,
                                      const nsACString& aBaseDomain,
                                      bool aSorted,
                                      nsTArray<RefPtr<nsICookie>>& aResult);

  void RemoveCookie(const nsACString& aBaseDomain,
                    const OriginAttributes& aOriginAttributes,
                    const nsACString& aHost, const nsACString& aName,
                    const nsACString& aPath, bool aFromHttp,
                    const nsID* aOperationID);

  virtual void RemoveCookiesWithOriginAttributes(
      const OriginAttributesPattern& aPattern, const nsACString& aBaseDomain);

  virtual void RemoveCookiesFromExactHost(
      const nsACString& aHost, const nsACString& aBaseDomain,
      const OriginAttributesPattern& aPattern);

  void RemoveAll();

  void NotifyChanged(nsISupports* aSubject,
                     nsICookieNotification::Action aAction,
                     const nsACString& aBaseDomain, bool aIsThirdParty = false,
                     dom::BrowsingContext* aBrowsingContext = nullptr,
                     bool aOldCookieIsSession = false,
                     const nsID* aOperationID = nullptr);

  void AddCookie(CookieParser* aCookieParser, const nsACString& aBaseDomain,
                 const OriginAttributes& aOriginAttributes, Cookie* aCookie,
                 int64_t aCurrentTimeInUsec, nsIURI* aHostURI,
                 const nsACString& aCookieHeader, bool aFromHttp,
                 bool aIsThirdParty, dom::BrowsingContext* aBrowsingContext,
                 const nsID* aOperationID = nullptr);

  uint32_t RemoveOldestCookies(CookieEntry* aEntry, bool aSecure,
                               uint32_t aBytesToRemove,
                               nsCOMPtr<nsIArray>& aPurgedList);

  void RemoveOlderCookiesByBytes(CookieEntry* aEntry, uint32_t removeBytes,
                                 nsCOMPtr<nsIArray>& aPurgedList);

  // tracks how far over the hard and soft CHIPS limits
  // we use the hard and soft limit to prevent excessive purging.
  // the soft limit (aka quota) is derived directly from partitionLimitCapacity
  // pref while the hard limit is the softLimit * a factor
  // (kChipsHardLimitFactor). the hard limit is used to trigger purging and when
  // we do purge, we purge down to the soft limit (quota)
  struct ChipsLimitExcess {
    uint32_t hard;
    uint32_t soft;  // aka quota
  };

  ChipsLimitExcess PartitionLimitExceededBytes(Cookie* aCookie,
                                               const nsACString& aBaseDomain);

  static void CreateOrUpdatePurgeList(nsCOMPtr<nsIArray>& aPurgedList,
                                      nsICookie* aCookie);

  virtual void StaleCookies(const nsTArray<RefPtr<Cookie>>& aCookieList,
                            int64_t aCurrentTimeInUsec) = 0;

  virtual void Close() = 0;

  virtual void EnsureInitialized() = 0;

  virtual nsresult RunInTransaction(
      nsICookieTransactionCallback* aCallback) = 0;

 protected:
  // stores the CookieEntry entryclass and an index into the cookie array within
  // that entryclass, for purposes of storing an iteration state that points to
  // a certain cookie.
  struct MOZ_STACK_CLASS CookieListIter {
    // default (non-initializing) constructor.
    CookieListIter() = default;

    // explicit constructor to a given iterator state with entryclass 'aEntry'
    // and index 'aIndex'.
    explicit CookieListIter(CookieEntry* aEntry, CookieEntry::IndexType aIndex)
        : entry(aEntry), index(aIndex) {}

    // get the Cookie * the iterator currently points to.
    mozilla::net::Cookie* Cookie() const { return entry->GetCookies()[index]; }

    CookieEntry* entry;
    CookieEntry::IndexType index;
  };

  // comparator class for lastaccessed times of cookies.
  class CompareCookiesByAge;

  // Cookie comparator for the priority queue used in FindStaleCookies.
  // Note that the expired cookie has the highest priority.
  // Other non-expired cookies are sorted by their age.
  class CookieIterComparator;

  // comparator class for sorting cookies by entry and index.
  class CompareCookiesByIndex;

  CookieStorage() = default;
  virtual ~CookieStorage() = default;

  void Init();

  bool FindCookie(const nsACString& aBaseDomain,
                  const OriginAttributes& aOriginAttributes,
                  const nsACString& aHost, const nsACString& aName,
                  const nsACString& aPath, CookieListIter& aIter);

  void AddCookieToList(const nsACString& aBaseDomain,
                       const OriginAttributes& aOriginAttributes,
                       Cookie* aCookie);

  virtual void StoreCookie(const nsACString& aBaseDomain,
                           const OriginAttributes& aOriginAttributes,
                           Cookie* aCookie) = 0;

  virtual const char* NotificationTopic() const = 0;

  virtual void NotifyChangedInternal(nsICookieNotification* aSubject,
                                     bool aOldCookieIsSession) = 0;

  virtual void RemoveAllInternal() = 0;

  // This method calls RemoveCookieFromDB + RemoveCookieFromListInternal.
  void RemoveCookieFromList(const CookieListIter& aIter);

  void RemoveCookieFromListInternal(const CookieListIter& aIter);

  virtual void RemoveCookieFromDB(const Cookie& aCookie) = 0;

  already_AddRefed<nsIArray> PurgeCookiesWithCallbacks(
      int64_t aCurrentTimeInUsec, uint16_t aMaxNumberOfCookies,
      int64_t aCookiePurgeAge,
      std::function<void(const CookieListIter&)>&& aRemoveCookieCallback,
      std::function<void()>&& aFinalizeCallback);

  nsTHashtable<CookieEntry> mHostTable;

  uint32_t mCookieCount{0};

 private:
  void PrefChanged(nsIPrefBranch* aPrefBranch);

  bool FindSecureCookie(const nsACString& aBaseDomain,
                        const OriginAttributes& aOriginAttributes,
                        Cookie* aCookie);

  static void FindStaleCookies(CookieEntry* aEntry, int64_t aCurrentTimeInMSec,
                               bool aIsSecure,
                               nsTArray<CookieListIter>& aOutput,
                               uint32_t aLimit);

  void UpdateCookieOldestTime(Cookie* aCookie);

  void MergeCookieSchemeMap(Cookie* aOldCookie, Cookie* aNewCookie);

  static already_AddRefed<nsIArray> CreatePurgeList(nsICookie* aCookie);

  virtual already_AddRefed<nsIArray> PurgeCookies(int64_t aCurrentTimeInUsec,
                                                  uint16_t aMaxNumberOfCookies,
                                                  int64_t aCookiePurgeAge) = 0;

  void RemoveCookiesFromBack(nsTArray<CookieListIter>& aCookieIters,
                             nsCOMPtr<nsIArray>& aPurgedList);

  // Serialize aBaseDomain e.g. apply "zero abbreveation" (::), use single
  // zeros and remove brackets to match principal base domain representation.
  static bool SerializeIPv6BaseDomain(nsACString& aBaseDomain);

  virtual void CollectCookieJarSizeData() = 0;

  int64_t mCookieOldestTime{INT64_MAX};

  uint16_t mMaxNumberOfCookies{kMaxNumberOfCookies};
  uint16_t mMaxCookiesPerHost{kMaxCookiesPerHost};
  uint16_t mCookieQuotaPerHost{kCookieQuotaPerHost};
  int64_t mCookiePurgeAge{kCookiePurgeAge};
};

}  // namespace net
}  // namespace mozilla

#endif  // mozilla_net_CookieStorage_h
