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

#ifndef mozilla_BasePrincipal_h
#define mozilla_BasePrincipal_h

#include <stdint.h>
#include "ErrorList.h"
#include "js/TypeDecls.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/OriginAttributes.h"
#include "mozilla/RefPtr.h"
#include "nsAtom.h"
#include "nsIObjectOutputStream.h"
#include "nsIPrincipal.h"
#include "nsJSPrincipals.h"
#include "nsStringFwd.h"
#include "nscore.h"

class ExpandedPrincipal;
class mozIDOMWindow;
class nsIChannel;
class nsIReferrerInfo;
class nsISupports;
class nsIURI;
namespace Json {
class Value;
}

namespace mozilla {

namespace dom {
enum class ReferrerPolicy : uint8_t;
}

namespace extensions {
class WebExtensionPolicy;
class WebExtensionPolicyCore;
}  // namespace extensions

class BasePrincipal;

// Content principals (and content principals embedded within expanded
// principals) stored in SiteIdentifier are guaranteed to contain only the
// eTLD+1 part of the original domain. This is used to determine whether two
// origins are same-site: if it's possible for two origins to access each other
// (maybe after mutating document.domain), then they must have the same site
// identifier.
class SiteIdentifier {
 public:
  void Init(BasePrincipal* aPrincipal) {
    MOZ_ASSERT(aPrincipal);
    mPrincipal = aPrincipal;
  }

  bool IsInitialized() const { return !!mPrincipal; }

  bool Equals(const SiteIdentifier& aOther) const;

 private:
  friend class ::ExpandedPrincipal;

  BasePrincipal* GetPrincipal() const {
    MOZ_ASSERT(IsInitialized());
    return mPrincipal;
  }

  RefPtr<BasePrincipal> mPrincipal;
};

/*
 * Base class from which all nsIPrincipal implementations inherit. Use this for
 * default implementations and other commonalities between principal
 * implementations.
 *
 * We should merge nsJSPrincipals into this class at some point.
 */
class BasePrincipal : public nsJSPrincipals {
 public:
  // Warning: this enum impacts Principal serialization into JSON format.
  // Only update if you know exactly what you are doing
  enum PrincipalKind {
    eNullPrincipal = 0,
    eContentPrincipal,
    eExpandedPrincipal,
    eSystemPrincipal,
    eKindMax = eSystemPrincipal
  };

  template <typename T>
  bool Is() const {
    return mKind == T::Kind();
  }

  template <typename T>
  T* As() {
    MOZ_ASSERT(Is<T>());
    return static_cast<T*>(this);
  }

  enum DocumentDomainConsideration {
    DontConsiderDocumentDomain,
    ConsiderDocumentDomain
  };
  bool Subsumes(nsIPrincipal* aOther,
                DocumentDomainConsideration aConsideration);

  NS_IMETHOD GetOrigin(nsACString& aOrigin) final;
  NS_IMETHOD GetAsciiOrigin(nsACString& aOrigin) override;
  NS_IMETHOD GetOriginNoSuffix(nsACString& aOrigin) final;
  NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final;
  NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final;
  NS_IMETHOD EqualsURI(nsIURI* aOtherURI, bool* _retval) override;
  NS_IMETHOD EqualsForPermission(nsIPrincipal* other, bool aExactHost,
                                 bool* _retval) final;
  NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final;
  NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other,
                                       bool* _retval) final;
  NS_IMETHOD SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* other,
                                                  bool* _retval) final;
  NS_IMETHOD CheckMayLoad(nsIURI* uri, bool allowIfInheritsPrincipal) final;
  NS_IMETHOD CheckMayLoadWithReporting(nsIURI* uri,
                                       bool allowIfInheritsPrincipal,
                                       uint64_t innerWindowID) final;
  NS_IMETHOD GetAddonPolicy(extensions::WebExtensionPolicy** aResult) final;
  NS_IMETHOD GetContentScriptAddonPolicy(
      extensions::WebExtensionPolicy** aResult) final;
  NS_IMETHOD GetIsNullPrincipal(bool* aResult) override;
  NS_IMETHOD GetIsContentPrincipal(bool* aResult) override;
  NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
  NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
  NS_IMETHOD GetScheme(nsACString& aScheme) override;
  NS_IMETHOD SchemeIs(const char* aScheme, bool* aResult) override;
  NS_IMETHOD IsURIInPrefList(const char* aPref, bool* aResult) override;
  NS_IMETHOD IsURIInList(const nsACString& aList, bool* aResult) override;
  NS_IMETHOD IsContentAccessibleAboutURI(bool* aResult) override;
  NS_IMETHOD IsL10nAllowed(nsIURI* aURI, bool* aResult) override;
  NS_IMETHOD GetAboutModuleFlags(uint32_t* flags) override;
  NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;
  NS_IMETHOD GetOriginAttributes(JSContext* aCx,
                                 JS::MutableHandle<JS::Value> aVal) final;
  NS_IMETHOD GetAsciiSpec(nsACString& aSpec) override;
  NS_IMETHOD GetSpec(nsACString& aSpec) override;
  NS_IMETHOD GetExposablePrePath(nsACString& aResult) override;
  NS_IMETHOD GetExposableSpec(nsACString& aSpec) override;
  NS_IMETHOD GetHostPort(nsACString& aRes) override;
  NS_IMETHOD GetHost(nsACString& aRes) override;
  NS_IMETHOD GetPrePath(nsACString& aResult) override;
  NS_IMETHOD GetFilePath(nsACString& aResult) override;
  NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
  NS_IMETHOD GetIsIpAddress(bool* aIsIpAddress) override;
  NS_IMETHOD GetIsLocalIpAddress(bool* aIsIpAddress) override;
  NS_IMETHOD GetIsOnion(bool* aIsOnion) override;
  NS_IMETHOD GetIsInIsolatedMozBrowserElement(
      bool* aIsInIsolatedMozBrowserElement) final;
  NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
  NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
  NS_IMETHOD GetSiteOrigin(nsACString& aSiteOrigin) final;
  NS_IMETHOD GetSiteOriginNoSuffix(nsACString& aSiteOrigin) override;
  NS_IMETHOD IsThirdPartyURI(nsIURI* uri, bool* aRes) override;
  NS_IMETHOD IsThirdPartyPrincipal(nsIPrincipal* uri, bool* aRes) override;
  NS_IMETHOD IsThirdPartyChannel(nsIChannel* aChannel, bool* aRes) override;
  NS_IMETHOD GetIsOriginPotentiallyTrustworthy(bool* aResult) override;
  NS_IMETHOD GetIsLoopbackHost(bool* aResult) override;
  NS_IMETHOD IsSameOrigin(nsIURI* aURI, bool* aRes) override;
  NS_IMETHOD GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials,
                                  const OriginAttributes& aOriginAttributes,
                                  nsACString& _retval) override;
  NS_IMETHOD HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow,
                                        uint32_t* aRejectedReason,
                                        bool* aOutAllowed) override;
  NS_IMETHOD GetAsciiHost(nsACString& aAsciiHost) override;
  NS_IMETHOD GetLocalStorageQuotaKey(nsACString& aRes) override;
  NS_IMETHOD AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI,
                                               bool* aRes) override;
  NS_IMETHOD CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy,
                                nsIReferrerInfo** _retval) override;
  NS_IMETHOD GetIsScriptAllowedByPolicy(
      bool* aIsScriptAllowedByPolicy) override;
  NS_IMETHOD GetStorageOriginKey(nsACString& aOriginKey) override;

  NS_IMETHOD GetNextSubDomainPrincipal(
      nsIPrincipal** aNextSubDomainPrincipal) override;

  NS_IMETHOD GetPrecursorPrincipal(nsIPrincipal** aPrecursor) override;

  nsresult ToJSON(nsACString& aJSON);
  nsresult ToJSON(Json::Value& aObject);

  static already_AddRefed<BasePrincipal> FromJSON(const nsACString& aJSON);
  static already_AddRefed<BasePrincipal> FromJSON(const Json::Value& aJSON);
  // Method populates a passed Json::Value with serializable fields
  // which represent all of the fields to deserialize the principal
  virtual nsresult PopulateJSONObject(Json::Value& aObject);

  virtual bool AddonHasPermission(const nsAtom* aPerm);

  virtual bool IsContentPrincipal() const { return false; };

  static BasePrincipal* Cast(nsIPrincipal* aPrin) {
    return static_cast<BasePrincipal*>(aPrin);
  }

  static BasePrincipal& Cast(nsIPrincipal& aPrin) {
    return *static_cast<BasePrincipal*>(&aPrin);
  }

  static const BasePrincipal* Cast(const nsIPrincipal* aPrin) {
    return static_cast<const BasePrincipal*>(aPrin);
  }

  static const BasePrincipal& Cast(const nsIPrincipal& aPrin) {
    return *static_cast<const BasePrincipal*>(&aPrin);
  }

  static already_AddRefed<BasePrincipal> CreateContentPrincipal(
      const nsACString& aOrigin);

  // This method may not create a content principal in case it's not possible to
  // generate a correct origin from the passed URI. If this happens, a
  // NullPrincipal is returned.
  //
  // If `aInitialDomain` is specified, and a ContentPrincipal is set, it will
  // initially have its domain set to the given value, without re-computing js
  // wrappers. Unlike `SetDomain()` this is safe to do off-main-thread.

  static already_AddRefed<BasePrincipal> CreateContentPrincipal(
      nsIURI* aURI, const OriginAttributes& aAttrs,
      nsIURI* aInitialDomain = nullptr);

  const OriginAttributes& OriginAttributesRef() final {
    return mOriginAttributes;
  }
  extensions::WebExtensionPolicy* AddonPolicy();
  RefPtr<extensions::WebExtensionPolicyCore> AddonPolicyCore();
  uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
  uint32_t PrivateBrowsingId() const {
    return mOriginAttributes.mPrivateBrowsingId;
  }
  bool IsInIsolatedMozBrowserElement() const {
    return mOriginAttributes.mInIsolatedMozBrowser;
  }

  PrincipalKind Kind() const { return mKind; }

  already_AddRefed<BasePrincipal> CloneForcingOriginAttributes(
      const OriginAttributes& aOriginAttributes);

  // If this is an add-on content script principal, returns its AddonPolicy.
  // Otherwise returns null.
  extensions::WebExtensionPolicy* ContentScriptAddonPolicy();
  RefPtr<extensions::WebExtensionPolicyCore> ContentScriptAddonPolicyCore();

  // Helper to check whether this principal is associated with an addon that
  // allows unprivileged code to load aURI.  aExplicit == true will prevent
  // use of all_urls permission, requiring the domain in its permissions.
  bool AddonAllowsLoad(nsIURI* aURI, bool aExplicit = false);

  // Call these to avoid the cost of virtual dispatch.
  inline bool FastEquals(nsIPrincipal* aOther);
  inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther);
  inline bool FastSubsumes(nsIPrincipal* aOther);
  inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther);
  inline bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther);
  inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther);

  // Fast way to check whether we have a system principal.
  inline bool IsSystemPrincipal() const;

  // Returns the principal to inherit when a caller with this principal loads
  // the given URI.
  //
  // For most principal types, this returns the principal itself. For expanded
  // principals, it returns the first sub-principal which subsumes the given URI
  // (or, if no URI is given, the last allowlist principal).
  nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);

  /* Returns true if this principal's CSP should override a document's CSP for
   * loads that it triggers. Currently true for expanded principals which
   * subsume the document principal, and add-on content principals regardless
   * of whether they subsume the document principal.
   */
  bool OverridesCSP(nsIPrincipal* aDocumentPrincipal);

  uint32_t GetOriginNoSuffixHash() const { return mOriginNoSuffix->hash(); }
  uint32_t GetOriginSuffixHash() const { return mOriginSuffix->hash(); }

  virtual nsresult GetSiteIdentifier(SiteIdentifier& aSite) = 0;

 protected:
  BasePrincipal(PrincipalKind aKind, const nsACString& aOriginNoSuffix,
                const OriginAttributes& aOriginAttributes);
  BasePrincipal(BasePrincipal* aOther,
                const OriginAttributes& aOriginAttributes);

  virtual ~BasePrincipal();

  // Note that this does not check OriginAttributes. Callers that depend on
  // those must call Subsumes instead.
  virtual bool SubsumesInternal(nsIPrincipal* aOther,
                                DocumentDomainConsideration aConsider) = 0;

  // Internal, side-effect-free check to determine whether the concrete
  // principal would allow the load ignoring any common behavior implemented in
  // BasePrincipal::CheckMayLoad.
  //
  // Safe to call from any thread, unlike CheckMayLoad.
  virtual bool MayLoadInternal(nsIURI* aURI) = 0;
  friend class ::ExpandedPrincipal;

  // Helper for implementing CheckMayLoad and CheckMayLoadWithReporting.
  nsresult CheckMayLoadHelper(nsIURI* aURI, bool aAllowIfInheritsPrincipal,
                              bool aReport, uint64_t aInnerWindowID);

  void SetHasExplicitDomain() { mHasExplicitDomain = true; }
  bool GetHasExplicitDomain() { return mHasExplicitDomain; }

  // KeyValT holds a principal subtype-specific key value and the associated
  // parsed value after JSON parsing.
  template <typename SerializedKey>
  struct KeyValT {
    static_assert(sizeof(SerializedKey) == 1,
                  "SerializedKey should be a uint8_t");
    SerializedKey key;
    bool valueWasSerialized;
    nsCString value;
  };

  // Common base class for all Deserializer implementations in concrete
  // subclasses. Subclasses will initialize `mPrincipal` in `Read`, and then
  // calls to `QueryInterface` will QI on the target object.
  class Deserializer : public nsISerializable {
   public:
    NS_DECL_ISUPPORTS
    NS_IMETHOD Write(nsIObjectOutputStream* aStream) override;

   protected:
    virtual ~Deserializer() = default;
    RefPtr<BasePrincipal> mPrincipal;
  };

 private:
  static const char* JSONEnumKeyStrings[4];

  static void SetJSONValue(Json::Value& aObject, const char* aKey,
                           const nsCString& aValue);

 protected:
  template <size_t EnumValue>
  static inline constexpr const char* JSONEnumKeyString() {
    static_assert(EnumValue < ArrayLength(JSONEnumKeyStrings));
    return JSONEnumKeyStrings[EnumValue];
  }
  template <size_t EnumValue>
  static void SetJSONValue(Json::Value& aObject, const nsCString& aValue) {
    SetJSONValue(aObject, JSONEnumKeyString<EnumValue>(), aValue);
  }

 private:
  static already_AddRefed<BasePrincipal> CreateContentPrincipal(
      nsIURI* aURI, const OriginAttributes& aAttrs,
      const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain);

  bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther,
                               DocumentDomainConsideration aConsideration);

  const RefPtr<nsAtom> mOriginNoSuffix;
  const RefPtr<nsAtom> mOriginSuffix;

  const OriginAttributes mOriginAttributes;
  const PrincipalKind mKind;
  std::atomic<bool> mHasExplicitDomain;
};

inline bool BasePrincipal::FastEquals(nsIPrincipal* aOther) {
  MOZ_ASSERT(aOther);

  auto other = Cast(aOther);
  if (Kind() != other->Kind()) {
    // Principals of different kinds can't be equal.
    return false;
  }

  // Two principals are considered to be equal if their origins are the same.
  // If the two principals are content principals, their origin attributes
  // (aka the origin suffix) must also match.
  if (Kind() == eSystemPrincipal) {
    return this == other;
  }

  if (Kind() == eContentPrincipal || Kind() == eNullPrincipal) {
    return mOriginNoSuffix == other->mOriginNoSuffix &&
           mOriginSuffix == other->mOriginSuffix;
  }

  MOZ_ASSERT(Kind() == eExpandedPrincipal);
  return mOriginNoSuffix == other->mOriginNoSuffix;
}

inline bool BasePrincipal::FastEqualsConsideringDomain(nsIPrincipal* aOther) {
  MOZ_ASSERT(aOther);

  // If neither of the principals have document.domain set, we use the fast path
  // in Equals().  Otherwise, we fall back to the slow path below.
  auto other = Cast(aOther);
  if (!mHasExplicitDomain && !other->mHasExplicitDomain) {
    return FastEquals(aOther);
  }

  // Principals of different kinds can't be equal.
  if (Kind() != other->Kind()) {
    return false;
  }

  // Only ContentPrincipals should have mHasExplicitDomain set to true, so test
  // that we haven't ended up here instead of FastEquals by mistake.
  MOZ_ASSERT(IsContentPrincipal(),
             "Only content principals can set mHasExplicitDomain");

  return Subsumes(aOther, ConsiderDocumentDomain) &&
         other->Subsumes(this, ConsiderDocumentDomain);
}

inline bool BasePrincipal::FastSubsumes(nsIPrincipal* aOther) {
  MOZ_ASSERT(aOther);

  // If two principals are equal, then they both subsume each other.
  if (FastEquals(aOther)) {
    return true;
  }

  // Otherwise, fall back to the slow path.
  return Subsumes(aOther, DontConsiderDocumentDomain);
}

inline bool BasePrincipal::FastSubsumesConsideringDomain(nsIPrincipal* aOther) {
  MOZ_ASSERT(aOther);

  // If neither of the principals have document.domain set, we hand off to
  // FastSubsumes() which has fast paths for some special cases. Otherwise, we
  // fall back to the slow path below.
  if (!mHasExplicitDomain && !Cast(aOther)->mHasExplicitDomain) {
    return FastSubsumes(aOther);
  }

  return Subsumes(aOther, ConsiderDocumentDomain);
}

inline bool BasePrincipal::FastSubsumesIgnoringFPD(nsIPrincipal* aOther) {
  return FastSubsumesIgnoringFPD(aOther, DontConsiderDocumentDomain);
}

inline bool BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD(
    nsIPrincipal* aOther) {
  return FastSubsumesIgnoringFPD(aOther, ConsiderDocumentDomain);
}

inline bool BasePrincipal::IsSystemPrincipal() const {
  return Kind() == eSystemPrincipal;
}

}  // namespace mozilla

inline bool nsIPrincipal::IsSystemPrincipal() const {
  return mozilla::BasePrincipal::Cast(this)->IsSystemPrincipal();
}

#endif /* mozilla_BasePrincipal_h */
