/* 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_OriginAttributes_h
#define mozilla_OriginAttributes_h

#include "mozilla/HashFunctions.h"
#include "mozilla/dom/OriginAttributesBinding.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "nsIScriptSecurityManager.h"

namespace mozilla {

class OriginAttributes : public dom::OriginAttributesDictionary {
 public:
  OriginAttributes() = default;

  explicit OriginAttributes(const OriginAttributesDictionary& aOther)
      : OriginAttributesDictionary(aOther) {}

  void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI,
                           bool aForced = false);
  void SetFirstPartyDomain(const bool aIsTopLevelDocument,
                           const nsACString& aDomain);
  void SetFirstPartyDomain(const bool aIsTopLevelDocument,
                           const nsAString& aDomain, bool aForced = false);

  void SetPartitionKey(nsIURI* aURI, bool aForeignByAncestorContext);
  void SetPartitionKey(const nsACString& aOther);
  void SetPartitionKey(const nsAString& aOther);

  enum {
    STRIP_FIRST_PARTY_DOMAIN = 0x01,
    STRIP_USER_CONTEXT_ID = 0x02,
    STRIP_PRIVATE_BROWSING_ID = 0x04,
    STRIP_PARITION_KEY = 0x08,
  };

  inline void StripAttributes(uint32_t aFlags) {
    if (aFlags & STRIP_FIRST_PARTY_DOMAIN) {
      mFirstPartyDomain.Truncate();
    }

    if (aFlags & STRIP_USER_CONTEXT_ID) {
      mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
    }

    if (aFlags & STRIP_PRIVATE_BROWSING_ID) {
      mPrivateBrowsingId =
          nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
    }

    if (aFlags & STRIP_PARITION_KEY) {
      mPartitionKey.Truncate();
    }
  }

  bool operator==(const OriginAttributes& aOther) const {
    return EqualsIgnoringFPD(aOther) &&
           mFirstPartyDomain == aOther.mFirstPartyDomain &&
           // FIXME(emilio, bug 1667440): Should this be part of
           // EqualsIgnoringFPD instead?
           mPartitionKey == aOther.mPartitionKey;
  }

  bool operator!=(const OriginAttributes& aOther) const {
    return !(*this == aOther);
  }

  [[nodiscard]] bool EqualsIgnoringFPD(const OriginAttributes& aOther) const {
    return mUserContextId == aOther.mUserContextId &&
           mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
           mGeckoViewSessionContextId == aOther.mGeckoViewSessionContextId;
  }

  [[nodiscard]] bool EqualsIgnoringPartitionKey(
      const OriginAttributes& aOther) const {
    return EqualsIgnoringFPD(aOther) &&
           mFirstPartyDomain == aOther.mFirstPartyDomain;
  }

  [[nodiscard]] inline bool IsPrivateBrowsing() const {
    return mPrivateBrowsingId !=
           nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
  }

  // Keep field order in sync with CreateSuffix.
  [[nodiscard]] HashNumber Hash() const {
    return AddToHash(
        mUserContextId, mPrivateBrowsingId,
        HashString(mFirstPartyDomain.BeginReading(),
                   mFirstPartyDomain.Length()),
        HashString(mGeckoViewSessionContextId.BeginReading(),
                   mGeckoViewSessionContextId.Length()),
        HashString(mPartitionKey.BeginReading(), mPartitionKey.Length()));
  }

  // Serializes/Deserializes non-default values into the suffix format, i.e.
  // |^key1=value1&key2=value2|. If there are no non-default attributes, this
  // returns an empty string.
  void CreateSuffix(nsACString& aStr) const;

  // Like CreateSuffix, but returns an atom instead of producing a string.
  already_AddRefed<nsAtom> CreateSuffixAtom() const;

  // Don't use this method for anything else than debugging!
  void CreateAnonymizedSuffix(nsACString& aStr) const;

  [[nodiscard]] bool PopulateFromSuffix(const nsACString& aStr);

  // Populates the attributes from a string like
  // |uri^key1=value1&key2=value2| and returns the uri without the suffix.
  [[nodiscard]] bool PopulateFromOrigin(const nsACString& aOrigin,
                                        nsACString& aOriginNoSuffix);

  // Helper function to match mIsPrivateBrowsing to existing private browsing
  // flags. Once all other flags are removed, this can be removed too.
  void SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing);

  // check if "privacy.firstparty.isolate" is enabled.
  static inline bool IsFirstPartyEnabled() {
    return StaticPrefs::privacy_firstparty_isolate();
  }

  // check if the access of window.opener across different FPDs is restricted.
  // We only restrict the access of window.opener when first party isolation
  // is enabled and "privacy.firstparty.isolate.restrict_opener_access" is on.
  static inline bool IsRestrictOpenerAccessForFPI() {
    // We always want to restrict window.opener if first party isolation is
    // disabled.
    return !StaticPrefs::privacy_firstparty_isolate() ||
           StaticPrefs::privacy_firstparty_isolate_restrict_opener_access();
  }

  // Check whether we block the postMessage across different FPDs when the
  // targetOrigin is '*'.
  [[nodiscard]] static inline bool IsBlockPostMessageForFPI() {
    return StaticPrefs::privacy_firstparty_isolate() &&
           StaticPrefs::privacy_firstparty_isolate_block_post_message();
  }

  // returns true if the originAttributes suffix has mPrivateBrowsingId value
  // different than 0.
  static bool IsPrivateBrowsing(const nsACString& aOrigin);

  // Parse a partitionKey of the format
  // "(<scheme>,<baseDomain>,[port],[ancestorbit])" into its components. Returns
  // false if the partitionKey cannot be parsed because the format is invalid.
  static bool ParsePartitionKey(const nsAString& aPartitionKey,
                                nsAString& outScheme, nsAString& outBaseDomain,
                                int32_t& outPort,
                                bool& outForeignByAncestorContext);

  // Parse a partitionKey and extract the site from it. Returns false if the
  // partitionKey cannot be parsed because the format is invalid.
  static bool ExtractSiteFromPartitionKey(const nsAString& aPartitionKey,
                                          nsAString& aOutSite);
};

class OriginAttributesPattern : public dom::OriginAttributesPatternDictionary {
 public:
  // To convert a JSON string to an OriginAttributesPattern, do the following:
  //
  // OriginAttributesPattern pattern;
  // if (!pattern.Init(aJSONString)) {
  //   ... // handle failure.
  // }
  OriginAttributesPattern() = default;

  explicit OriginAttributesPattern(
      const OriginAttributesPatternDictionary& aOther)
      : OriginAttributesPatternDictionary(aOther) {}

  // Performs a match of |aAttrs| against this pattern.
  bool Matches(const OriginAttributes& aAttrs) const {
    if (mUserContextId.WasPassed() &&
        mUserContextId.Value() != aAttrs.mUserContextId) {
      return false;
    }

    if (mPrivateBrowsingId.WasPassed() &&
        mPrivateBrowsingId.Value() != aAttrs.mPrivateBrowsingId) {
      return false;
    }

    if (mFirstPartyDomain.WasPassed() &&
        mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) {
      return false;
    }

    if (mGeckoViewSessionContextId.WasPassed() &&
        mGeckoViewSessionContextId.Value() !=
            aAttrs.mGeckoViewSessionContextId) {
      return false;
    }

    // If both mPartitionKey and mPartitionKeyPattern are passed, mPartitionKey
    // takes precedence.
    if (mPartitionKey.WasPassed()) {
      if (mPartitionKey.Value() != aAttrs.mPartitionKey) {
        return false;
      }
    } else if (mPartitionKeyPattern.WasPassed()) {
      auto& pkPattern = mPartitionKeyPattern.Value();

      if (pkPattern.mScheme.WasPassed() || pkPattern.mBaseDomain.WasPassed() ||
          pkPattern.mPort.WasPassed()) {
        if (aAttrs.mPartitionKey.IsEmpty()) {
          return false;
        }

        nsString scheme;
        nsString baseDomain;
        int32_t port;
        bool ancestor;
        bool success = OriginAttributes::ParsePartitionKey(
            aAttrs.mPartitionKey, scheme, baseDomain, port, ancestor);
        if (!success) {
          return false;
        }

        if (pkPattern.mScheme.WasPassed() &&
            pkPattern.mScheme.Value() != scheme) {
          return false;
        }
        if (pkPattern.mBaseDomain.WasPassed() &&
            pkPattern.mBaseDomain.Value() != baseDomain) {
          return false;
        }
        if (pkPattern.mPort.WasPassed() && pkPattern.mPort.Value() != port) {
          return false;
        }
        if (pkPattern.mForeignByAncestorContext.WasPassed() &&
            pkPattern.mForeignByAncestorContext.Value() != ancestor) {
          return false;
        }
      }
    }

    return true;
  }

  bool Overlaps(const OriginAttributesPattern& aOther) const {
    if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
        mUserContextId.Value() != aOther.mUserContextId.Value()) {
      return false;
    }

    if (mPrivateBrowsingId.WasPassed() &&
        aOther.mPrivateBrowsingId.WasPassed() &&
        mPrivateBrowsingId.Value() != aOther.mPrivateBrowsingId.Value()) {
      return false;
    }

    if (mFirstPartyDomain.WasPassed() && aOther.mFirstPartyDomain.WasPassed() &&
        mFirstPartyDomain.Value() != aOther.mFirstPartyDomain.Value()) {
      return false;
    }

    if (mGeckoViewSessionContextId.WasPassed() &&
        aOther.mGeckoViewSessionContextId.WasPassed() &&
        mGeckoViewSessionContextId.Value() !=
            aOther.mGeckoViewSessionContextId.Value()) {
      return false;
    }

    if (mPartitionKey.WasPassed() && aOther.mPartitionKey.WasPassed() &&
        mPartitionKey.Value() != aOther.mPartitionKey.Value()) {
      return false;
    }

    if (mPartitionKeyPattern.WasPassed() &&
        aOther.mPartitionKeyPattern.WasPassed()) {
      auto& self = mPartitionKeyPattern.Value();
      auto& other = aOther.mPartitionKeyPattern.Value();

      if (self.mScheme.WasPassed() && other.mScheme.WasPassed() &&
          self.mScheme.Value() != other.mScheme.Value()) {
        return false;
      }
      if (self.mBaseDomain.WasPassed() && other.mBaseDomain.WasPassed() &&
          self.mBaseDomain.Value() != other.mBaseDomain.Value()) {
        return false;
      }
      if (self.mPort.WasPassed() && other.mPort.WasPassed() &&
          self.mPort.Value() != other.mPort.Value()) {
        return false;
      }
      if (self.mForeignByAncestorContext.WasPassed() &&
          other.mForeignByAncestorContext.WasPassed() &&
          self.mForeignByAncestorContext.Value() !=
              other.mForeignByAncestorContext.Value()) {
        return false;
      }
    }

    return true;
  }
};

}  // namespace mozilla

#endif /* mozilla_OriginAttributes_h */
