/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et 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/. */

#include "mozilla/BasePrincipal.h"

#include "nsDocShell.h"

#include "ExpandedPrincipal.h"
#include "nsNetUtil.h"
#include "nsContentUtils.h"
#include "nsIOService.h"
#include "nsIURIWithSpecialOrigin.h"
#include "nsScriptSecurityManager.h"
#include "nsServiceManagerUtils.h"
#include "nsAboutProtocolUtils.h"
#include "ThirdPartyUtil.h"
#include "mozilla/ContentPrincipal.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/ChromeUtils.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/Components.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/StorageUtils.h"
#include "nsIURL.h"
#include "nsEffectiveTLDService.h"
#include "nsIURIMutator.h"
#include "mozilla/StaticPrefs_permissions.h"
#include "nsIURIMutator.h"
#include "nsMixedContentBlocker.h"
#include "prnetdb.h"
#include "nsIURIFixup.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/StorageAccess.h"
#include "nsPIDOMWindow.h"
#include "nsIURIMutator.h"
#include "mozilla/PermissionManager.h"

#include "json/json.h"
#include "nsSerializationHelper.h"

namespace mozilla {

const char* BasePrincipal::JSONEnumKeyStrings[4] = {
    "0",
    "1",
    "2",
    "3",
};

BasePrincipal::BasePrincipal(PrincipalKind aKind,
                             const nsACString& aOriginNoSuffix,
                             const OriginAttributes& aOriginAttributes)
    : mOriginNoSuffix(NS_Atomize(aOriginNoSuffix)),
      mOriginSuffix(aOriginAttributes.CreateSuffixAtom()),
      mOriginAttributes(aOriginAttributes),
      mKind(aKind),
      mHasExplicitDomain(false) {}

BasePrincipal::BasePrincipal(BasePrincipal* aOther,
                             const OriginAttributes& aOriginAttributes)
    : mOriginNoSuffix(aOther->mOriginNoSuffix),
      mOriginSuffix(aOriginAttributes.CreateSuffixAtom()),
      mOriginAttributes(aOriginAttributes),
      mKind(aOther->mKind),
      mHasExplicitDomain(aOther->mHasExplicitDomain.load()) {}

BasePrincipal::~BasePrincipal() = default;

NS_IMETHODIMP
BasePrincipal::GetOrigin(nsACString& aOrigin) {
  nsresult rv = GetOriginNoSuffix(aOrigin);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString suffix;
  rv = GetOriginSuffix(suffix);
  NS_ENSURE_SUCCESS(rv, rv);
  aOrigin.Append(suffix);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetAsciiOrigin(nsACString& aOrigin) {
  aOrigin.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  return nsContentUtils::GetASCIIOrigin(prinURI, aOrigin);
}

NS_IMETHODIMP
BasePrincipal::GetHostPort(nsACString& aRes) {
  aRes.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetHostPort(aRes);
}

NS_IMETHODIMP
BasePrincipal::GetHost(nsACString& aRes) {
  aRes.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetHost(aRes);
}

NS_IMETHODIMP
BasePrincipal::GetOriginNoSuffix(nsACString& aOrigin) {
  mOriginNoSuffix->ToUTF8String(aOrigin);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetSiteOrigin(nsACString& aSiteOrigin) {
  nsresult rv = GetSiteOriginNoSuffix(aSiteOrigin);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString suffix;
  rv = GetOriginSuffix(suffix);
  NS_ENSURE_SUCCESS(rv, rv);
  aSiteOrigin.Append(suffix);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) {
  return GetOriginNoSuffix(aSiteOrigin);
}

// Returns the inner Json::value of the serialized principal
// Example input and return values:
// Null principal:
// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}} ->
// {"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}
//
// Content principal:
// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"}
//
// Expanded principal:
// {"2":{"0":"<base64principal1>,<base64principal2>"}} ->
// {"0":"<base64principal1>,<base64principal2>"}
//
// System principal:
// {"3":{}} -> {}
// The aKey passed in also returns the corresponding PrincipalKind enum
//
// Warning: The Json::Value* pointer is into the aRoot object
static const Json::Value* GetPrincipalObject(const Json::Value& aRoot,
                                             int& aOutPrincipalKind) {
  const Json::Value::Members members = aRoot.getMemberNames();
  // We only support one top level key in the object
  if (members.size() != 1) {
    return nullptr;
  }
  // members[0] here is the "0", "1", "2", "3" principalKind
  // that is the top level of the serialized JSON principal
  const std::string stringPrincipalKind = members[0];

  // Next we take the string value from the JSON
  // and convert it into the int for the BasePrincipal::PrincipalKind enum

  // Verify that the key is within the valid range
  int principalKind = std::stoi(stringPrincipalKind);
  MOZ_ASSERT(BasePrincipal::eNullPrincipal == 0,
             "We need to rely on 0 being a bounds check for the first "
             "principal kind.");
  if (principalKind < 0 || principalKind > BasePrincipal::eKindMax) {
    return nullptr;
  }
  MOZ_ASSERT(principalKind == BasePrincipal::eNullPrincipal ||
             principalKind == BasePrincipal::eContentPrincipal ||
             principalKind == BasePrincipal::eExpandedPrincipal ||
             principalKind == BasePrincipal::eSystemPrincipal);
  aOutPrincipalKind = principalKind;

  if (!aRoot[stringPrincipalKind].isObject()) {
    return nullptr;
  }

  // Return the inner value of the principal object
  return &aRoot[stringPrincipalKind];
}

// Accepts the JSON inner object without the wrapping principalKind
// (See GetPrincipalObject for the inner object response examples)
// Creates an array of KeyVal objects that are all defined on the principal
// Each principal type (null, content, expanded) has a KeyVal that stores the
// fields of the JSON
//
// This simplifies deserializing elsewhere as we do the checking for presence
// and string values here for the complete set of serializable keys that the
// corresponding principal supports.
//
// The KeyVal object has the following fields:
// - valueWasSerialized: is true if the deserialized JSON contained a string
// value
// - value: The string that was serialized for this key
// - key: an SerializableKeys enum value specific to the principal.
//        For example content principal is an enum of: eURI, eDomain,
//        eSuffix, eCSP
//
//
//  Given an inner content principal:
//  {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}
//    |                |          |         |
//    -----------------------------         |
//         |           |                    |
//        Key          ----------------------
//                                |
//                              Value
//
// They Key "0" corresponds to ContentPrincipal::eURI
// They Key "1" corresponds to ContentPrincipal::eSuffix
template <typename T>
static nsTArray<typename T::KeyVal> GetJSONKeys(const Json::Value* aInput) {
  int size = T::eMax + 1;
  nsTArray<typename T::KeyVal> fields;
  for (int i = 0; i != size; i++) {
    typename T::KeyVal* field = fields.AppendElement();
    // field->valueWasSerialized returns if the field was found in the
    // deserialized code. This simplifies the consumers from having to check
    // length.
    field->valueWasSerialized = false;
    field->key = static_cast<typename T::SerializableKeys>(i);
    const std::string key = std::to_string(field->key);
    if (aInput->isMember(key)) {
      const Json::Value& val = (*aInput)[key];
      if (val.isString()) {
        field->value.Append(nsDependentCString(val.asCString()));
        field->valueWasSerialized = true;
      }
    }
  }
  return fields;
}

// Takes a JSON string and parses it turning it into a principal of the
// corresponding type
//
// Given a content principal:
//
//                               inner JSON object
//                                      |
//       ---------------------------------------------------------
//       |                                                       |
// {"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}
//   |     |             |             |            |
//   |     -----------------------------            |
//   |              |    |                          |
// PrincipalKind    |    |                          |
//                  |    ----------------------------
//           SerializableKeys           |
//                                    Value
//
// The string is first deserialized with jsoncpp to get the Json::Value of the
// object. The inner JSON object is parsed with GetPrincipalObject which returns
// a KeyVal array of the inner object's fields. PrincipalKind is returned by
// GetPrincipalObject which is then used to decide which principal
// implementation of FromProperties to call. The corresponding FromProperties
// call takes the KeyVal fields and turns it into a principal.
already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
    const nsACString& aJSON) {
  Json::Value root;
  Json::CharReaderBuilder builder;
  std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
  bool parseSuccess =
      reader->parse(aJSON.BeginReading(), aJSON.EndReading(), &root, nullptr);
  if (!parseSuccess) {
    MOZ_ASSERT(false,
               "Unable to parse string as JSON to deserialize as a principal");
    return nullptr;
  }

  return FromJSON(root);
}

// Checks if an ExpandedPrincipal is using the legacy format, where
// sub-principals are Base64 encoded.
//
// Given a legacy expanded principal:
//
//         *
// {"2": {"0": "eyIxIjp7IjAiOiJodHRwczovL2EuY29tLyJ9fQ=="}}
//   |     |                      |
//   |     ----------           Value
//   |              |
// PrincipalKind    |
//                  |
//           SerializableKeys
//
// The value is a CSV list of Base64 encoded prinipcals. The new format for this
// principal is:
//
//                       Subsumed principals
//                               |
//             ------------------------------------
//         *   |                                  |
// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}}
//   |            |                  |
//   --------------                Value
//         |
//   PrincipalKind
//
// It is possible to tell these apart by checking the type of the property noted
// in both diagrams with an asterisk. In the legacy format the type will be a
// string and in the new format it will be an array.
static bool IsLegacyFormat(const Json::Value& aValue) {
  const auto& specs = std::to_string(ExpandedPrincipal::eSpecs);
  return aValue.isMember(specs) && aValue[specs].isString();
}

/* static */
already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
    const Json::Value& aJSON) {
  int principalKind = -1;
  const Json::Value* value = GetPrincipalObject(aJSON, principalKind);
  if (!value) {
#ifdef DEBUG
    fprintf(stderr, "Unexpected JSON principal %s\n",
            aJSON.toStyledString().c_str());
#endif
    MOZ_ASSERT(false, "Unexpected JSON to deserialize as a principal");

    return nullptr;
  }
  MOZ_ASSERT(principalKind != -1,
             "PrincipalKind should always be >=0 by this point");

  if (principalKind == eSystemPrincipal) {
    RefPtr<BasePrincipal> principal =
        BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal());
    return principal.forget();
  }

  if (principalKind == eNullPrincipal) {
    nsTArray<NullPrincipal::KeyVal> res = GetJSONKeys<NullPrincipal>(value);
    return NullPrincipal::FromProperties(res);
  }

  if (principalKind == eContentPrincipal) {
    nsTArray<ContentPrincipal::KeyVal> res =
        GetJSONKeys<ContentPrincipal>(value);
    return ContentPrincipal::FromProperties(res);
  }

  if (principalKind == eExpandedPrincipal) {
    // Check if expanded principals is stored in the new or the old format. See
    // comment for `IsLegacyFormat`.
    if (IsLegacyFormat(*value)) {
      nsTArray<ExpandedPrincipal::KeyVal> res =
          GetJSONKeys<ExpandedPrincipal>(value);
      return ExpandedPrincipal::FromProperties(res);
    }

    return ExpandedPrincipal::FromProperties(*value);
  }

  MOZ_RELEASE_ASSERT(false, "Unexpected enum to deserialize as a principal");
}

nsresult BasePrincipal::PopulateJSONObject(Json::Value& aObject) {
  return NS_OK;
}

// Returns a JSON representation of the principal.
// Calling BasePrincipal::FromJSON will deserialize the JSON into
// the corresponding principal type.
nsresult BasePrincipal::ToJSON(nsACString& aJSON) {
  MOZ_ASSERT(aJSON.IsEmpty(), "ToJSON only supports an empty result input");
  aJSON.Truncate();

  Json::Value root = Json::objectValue;
  nsresult rv = ToJSON(root);
  NS_ENSURE_SUCCESS(rv, rv);

  static StaticAutoPtr<Json::StreamWriterBuilder> sJSONBuilderForPrincipals;
  if (!sJSONBuilderForPrincipals) {
    sJSONBuilderForPrincipals = new Json::StreamWriterBuilder();
    (*sJSONBuilderForPrincipals)["indentation"] = "";
    (*sJSONBuilderForPrincipals)["emitUTF8"] = true;
    ClearOnShutdown(&sJSONBuilderForPrincipals);
  }
  std::string result = Json::writeString(*sJSONBuilderForPrincipals, root);
  aJSON.Append(result);
  if (aJSON.Length() == 0) {
    MOZ_ASSERT(false, "JSON writer failed to output a principal serialization");
    return NS_ERROR_UNEXPECTED;
  }

  return NS_OK;
}

nsresult BasePrincipal::ToJSON(Json::Value& aObject) {
  static_assert(eKindMax < ArrayLength(JSONEnumKeyStrings));
  nsresult rv = PopulateJSONObject(
      (aObject[Json::StaticString(JSONEnumKeyStrings[Kind()])] =
           Json::objectValue));
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

bool BasePrincipal::FastSubsumesIgnoringFPD(
    nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) {
  MOZ_ASSERT(aOther);

  if (Kind() == eContentPrincipal &&
      !dom::ChromeUtils::IsOriginAttributesEqualIgnoringFPD(
          mOriginAttributes, Cast(aOther)->mOriginAttributes)) {
    return false;
  }

  return SubsumesInternal(aOther, aConsideration);
}

bool BasePrincipal::Subsumes(nsIPrincipal* aOther,
                             DocumentDomainConsideration aConsideration) {
  MOZ_ASSERT(aOther);
  MOZ_ASSERT_IF(Kind() == eContentPrincipal, mOriginSuffix);

  // Expanded principals handle origin attributes for each of their
  // sub-principals individually, null principals do only simple checks for
  // pointer equality, and system principals are immune to origin attributes
  // checks, so only do this check for content principals.
  if (Kind() == eContentPrincipal &&
      mOriginSuffix != Cast(aOther)->mOriginSuffix) {
    return false;
  }

  return SubsumesInternal(aOther, aConsideration);
}

NS_IMETHODIMP
BasePrincipal::Equals(nsIPrincipal* aOther, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aOther);

  *aResult = FastEquals(aOther);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::EqualsForPermission(nsIPrincipal* aOther, bool aExactHost,
                                   bool* aResult) {
  *aResult = false;
  NS_ENSURE_ARG_POINTER(aOther);
  NS_ENSURE_ARG_POINTER(aResult);

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

  if (Kind() == eSystemPrincipal) {
    *aResult = this == other;
    return NS_OK;
  }

  if (Kind() == eNullPrincipal) {
    // We don't store permissions for NullPrincipals.
    return NS_OK;
  }

  MOZ_ASSERT(Kind() == eExpandedPrincipal || Kind() == eContentPrincipal);

  // Certain origin attributes should not be used to isolate permissions.
  // Create a stripped copy of both OA sets to compare.
  mozilla::OriginAttributes ourAttrs = mOriginAttributes;
  PermissionManager::MaybeStripOriginAttributes(false, ourAttrs);
  mozilla::OriginAttributes theirAttrs = aOther->OriginAttributesRef();
  PermissionManager::MaybeStripOriginAttributes(false, theirAttrs);

  if (ourAttrs != theirAttrs) {
    return NS_OK;
  }

  if (mOriginNoSuffix == other->mOriginNoSuffix) {
    *aResult = true;
    return NS_OK;
  }

  // If we are matching with an exact host, we're done now - the permissions
  // don't match otherwise, we need to start comparing subdomains!
  if (aExactHost) {
    return NS_OK;
  }

  nsCOMPtr<nsIURI> ourURI;
  nsresult rv = GetURI(getter_AddRefs(ourURI));
  NS_ENSURE_SUCCESS(rv, rv);
  // Some principal types may indicate success, but still return nullptr for
  // URI.
  NS_ENSURE_TRUE(ourURI, NS_ERROR_FAILURE);

  nsCOMPtr<nsIURI> otherURI;
  rv = other->GetURI(getter_AddRefs(otherURI));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(otherURI, NS_ERROR_FAILURE);

  // Compare schemes
  nsAutoCString otherScheme;
  rv = otherURI->GetScheme(otherScheme);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString ourScheme;
  rv = ourURI->GetScheme(ourScheme);
  NS_ENSURE_SUCCESS(rv, rv);

  if (otherScheme != ourScheme) {
    return NS_OK;
  }

  // Compare ports
  int32_t otherPort;
  rv = otherURI->GetPort(&otherPort);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t ourPort;
  rv = ourURI->GetPort(&ourPort);
  NS_ENSURE_SUCCESS(rv, rv);

  if (otherPort != ourPort) {
    return NS_OK;
  }

  // Check if the host or any subdomain of their host matches.
  nsAutoCString otherHost;
  rv = otherURI->GetHost(otherHost);
  if (NS_FAILED(rv) || otherHost.IsEmpty()) {
    return NS_OK;
  }

  nsAutoCString ourHost;
  rv = ourURI->GetHost(ourHost);
  if (NS_FAILED(rv) || ourHost.IsEmpty()) {
    return NS_OK;
  }

  nsCOMPtr<nsIEffectiveTLDService> tldService =
      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
  if (!tldService) {
    NS_ERROR("Should have a tld service!");
    return NS_ERROR_FAILURE;
  }

  // This loop will not loop forever, as GetNextSubDomain will eventually fail
  // with NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
  while (otherHost != ourHost) {
    rv = tldService->GetNextSubDomain(otherHost, otherHost);
    if (NS_FAILED(rv)) {
      if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
        return NS_OK;
      }
      return rv;
    }
  }

  *aResult = true;
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::EqualsConsideringDomain(nsIPrincipal* aOther, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aOther);

  *aResult = FastEqualsConsideringDomain(aOther);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::EqualsURI(nsIURI* aOtherURI, bool* aResult) {
  *aResult = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->EqualsExceptRef(aOtherURI, aResult);
}

NS_IMETHODIMP
BasePrincipal::Subsumes(nsIPrincipal* aOther, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aOther);

  *aResult = FastSubsumes(aOther);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::SubsumesConsideringDomain(nsIPrincipal* aOther, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aOther);

  *aResult = FastSubsumesConsideringDomain(aOther);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::SubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther,
                                                    bool* aResult) {
  NS_ENSURE_ARG_POINTER(aOther);

  *aResult = FastSubsumesConsideringDomainIgnoringFPD(aOther);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::CheckMayLoad(nsIURI* aURI, bool aAllowIfInheritsPrincipal) {
  AssertIsOnMainThread();
  return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, false, 0);
}

NS_IMETHODIMP
BasePrincipal::CheckMayLoadWithReporting(nsIURI* aURI,
                                         bool aAllowIfInheritsPrincipal,
                                         uint64_t aInnerWindowID) {
  AssertIsOnMainThread();
  return CheckMayLoadHelper(aURI, aAllowIfInheritsPrincipal, true,
                            aInnerWindowID);
}

nsresult BasePrincipal::CheckMayLoadHelper(nsIURI* aURI,
                                           bool aAllowIfInheritsPrincipal,
                                           bool aReport,
                                           uint64_t aInnerWindowID) {
  AssertIsOnMainThread();  // Accesses non-threadsafe URI flags and the
                           // non-threadsafe ExtensionPolicyService
  NS_ENSURE_ARG_POINTER(aURI);
  MOZ_ASSERT(
      aReport || aInnerWindowID == 0,
      "Why do we have an inner window id if we're not supposed to report?");

  // Check the internal method first, which allows us to quickly approve loads
  // for the System Principal.
  if (MayLoadInternal(aURI)) {
    return NS_OK;
  }

  nsresult rv;
  if (aAllowIfInheritsPrincipal) {
    // If the caller specified to allow loads of URIs that inherit
    // our principal, allow the load if this URI inherits its principal.
    bool doesInheritSecurityContext;
    rv = NS_URIChainHasFlags(aURI,
                             nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                             &doesInheritSecurityContext);
    if (NS_SUCCEEDED(rv) && doesInheritSecurityContext) {
      return NS_OK;
    }
  }

  // Web Accessible Resources in MV2 Extensions are marked with
  // URI_FETCHABLE_BY_ANYONE
  bool fetchableByAnyone;
  rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FETCHABLE_BY_ANYONE,
                           &fetchableByAnyone);
  if (NS_SUCCEEDED(rv) && fetchableByAnyone) {
    return NS_OK;
  }

  // Get the principal uri for the last flag check or error.
  nsCOMPtr<nsIURI> prinURI;
  rv = GetURI(getter_AddRefs(prinURI));
  if (!(NS_SUCCEEDED(rv) && prinURI)) {
    return NS_ERROR_DOM_BAD_URI;
  }

  // If MV3 Extension uris are web accessible by this principal it is allowed to
  // load.
  bool maybeWebAccessible = false;
  NS_URIChainHasFlags(aURI, nsIProtocolHandler::WEBEXT_URI_WEB_ACCESSIBLE,
                      &maybeWebAccessible);
  NS_ENSURE_SUCCESS(rv, rv);
  if (maybeWebAccessible) {
    bool isWebAccessible = false;
    rv = ExtensionPolicyService::GetSingleton().SourceMayLoadExtensionURI(
        prinURI, aURI, &isWebAccessible);
    if (NS_SUCCEEDED(rv) && isWebAccessible) {
      return NS_OK;
    }
  }

  if (aReport) {
    nsScriptSecurityManager::ReportError(
        "CheckSameOriginError", prinURI, aURI,
        mOriginAttributes.mPrivateBrowsingId > 0, aInnerWindowID);
  }

  return NS_ERROR_DOM_BAD_URI;
}

NS_IMETHODIMP
BasePrincipal::IsThirdPartyURI(nsIURI* aURI, bool* aRes) {
  if (IsSystemPrincipal() || (AddonPolicyCore() && AddonAllowsLoad(aURI))) {
    *aRes = false;
    return NS_OK;
  }

  *aRes = true;
  // If we do not have a URI its always 3rd party.
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
  return thirdPartyUtil->IsThirdPartyURI(prinURI, aURI, aRes);
}

NS_IMETHODIMP
BasePrincipal::IsThirdPartyPrincipal(nsIPrincipal* aPrin, bool* aRes) {
  *aRes = true;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return aPrin->IsThirdPartyURI(prinURI, aRes);
}

NS_IMETHODIMP
BasePrincipal::IsThirdPartyChannel(nsIChannel* aChan, bool* aRes) {
  AssertIsOnMainThread();
  if (IsSystemPrincipal()) {
    // Nothing is 3rd party to the system principal.
    *aRes = false;
    return NS_OK;
  }

  nsCOMPtr<nsIURI> prinURI;
  GetURI(getter_AddRefs(prinURI));
  ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
  return thirdPartyUtil->IsThirdPartyChannel(aChan, prinURI, aRes);
}

NS_IMETHODIMP
BasePrincipal::IsSameOrigin(nsIURI* aURI, bool* aRes) {
  *aRes = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    // Note that expanded and system principals return here, because they have
    // no URI.
    return NS_OK;
  }
  *aRes = nsScriptSecurityManager::SecurityCompareURIs(prinURI, aURI);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::IsL10nAllowed(nsIURI* aURI, bool* aRes) {
  AssertIsOnMainThread();  // URI_DANGEROUS_TO_LOAD is not threadsafe to query.
  *aRes = false;

  if (nsContentUtils::IsErrorPage(aURI)) {
    *aRes = true;
    return NS_OK;
  }

  // The system principal is always allowed.
  if (IsSystemPrincipal()) {
    *aRes = true;
    return NS_OK;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, NS_OK);

  bool hasFlags;

  // Allow access to uris that cannot be loaded by web content.
  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
                           &hasFlags);
  NS_ENSURE_SUCCESS(rv, NS_OK);
  if (hasFlags) {
    *aRes = true;
    return NS_OK;
  }

  // UI resources also get access.
  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                           &hasFlags);
  NS_ENSURE_SUCCESS(rv, NS_OK);
  if (hasFlags) {
    *aRes = true;
    return NS_OK;
  }

  auto policy = AddonPolicyCore();
  *aRes = (policy && policy->IsPrivileged());
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::AllowsRelaxStrictFileOriginPolicy(nsIURI* aURI, bool* aRes) {
  *aRes = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  *aRes = NS_RelaxStrictFileOriginPolicy(aURI, prinURI);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetPrefLightCacheKey(nsIURI* aURI, bool aWithCredentials,
                                    const OriginAttributes& aOriginAttributes,
                                    nsACString& _retval) {
  _retval.Truncate();
  constexpr auto space = " "_ns;

  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString scheme, host, port;
  if (uri) {
    uri->GetScheme(scheme);
    uri->GetHost(host);
    port.AppendInt(NS_GetRealPort(uri));
  }

  if (aWithCredentials) {
    _retval.AssignLiteral("cred");
  } else {
    _retval.AssignLiteral("nocred");
  }

  nsAutoCString spec;
  rv = aURI->GetSpec(spec);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString originAttributesSuffix;
  aOriginAttributes.CreateSuffix(originAttributesSuffix);

  _retval.Append(space + scheme + space + host + space + port + space + spec +
                 space + originAttributesSuffix);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::HasFirstpartyStorageAccess(mozIDOMWindow* aCheckWindow,
                                          uint32_t* aRejectedReason,
                                          bool* aOutAllowed) {
  AssertIsOnMainThread();
  *aRejectedReason = 0;
  *aOutAllowed = false;

  nsPIDOMWindowInner* win = nsPIDOMWindowInner::From(aCheckWindow);
  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  if (NS_FAILED(rv)) {
    return rv;
  }
  *aOutAllowed = ShouldAllowAccessFor(win, uri, aRejectedReason);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsNullPrincipal(bool* aResult) {
  *aResult = Kind() == eNullPrincipal;
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsContentPrincipal(bool* aResult) {
  *aResult = Kind() == eContentPrincipal;
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsExpandedPrincipal(bool* aResult) {
  *aResult = Kind() == eExpandedPrincipal;
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetAsciiSpec(nsACString& aSpec) {
  aSpec.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetAsciiSpec(aSpec);
}

NS_IMETHODIMP
BasePrincipal::GetSpec(nsACString& aSpec) {
  aSpec.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetSpec(aSpec);
}

NS_IMETHODIMP
BasePrincipal::GetAsciiHost(nsACString& aHost) {
  aHost.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetAsciiHost(aHost);
}

NS_IMETHODIMP
BasePrincipal::GetExposablePrePath(nsACString& aPrepath) {
  aPrepath.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(prinURI);
  return exposableURI->GetDisplayPrePath(aPrepath);
}

NS_IMETHODIMP
BasePrincipal::GetExposableSpec(nsACString& aSpec) {
  aSpec.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  nsCOMPtr<nsIURI> clone;
  rv = NS_MutateURI(prinURI)
           .SetQuery(""_ns)
           .SetRef(""_ns)
           .SetUserPass(""_ns)
           .Finalize(clone);
  NS_ENSURE_SUCCESS(rv, rv);
  return clone->GetAsciiSpec(aSpec);
}

NS_IMETHODIMP
BasePrincipal::GetPrePath(nsACString& aPath) {
  aPath.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetPrePath(aPath);
}

NS_IMETHODIMP
BasePrincipal::GetFilePath(nsACString& aPath) {
  aPath.Truncate();
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  return prinURI->GetFilePath(aPath);
}

NS_IMETHODIMP
BasePrincipal::GetIsSystemPrincipal(bool* aResult) {
  *aResult = IsSystemPrincipal();
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult) {
  *aResult = AddonPolicyCore() || ContentScriptAddonPolicyCore();
  return NS_OK;
}

NS_IMETHODIMP BasePrincipal::GetIsOnion(bool* aIsOnion) {
  *aIsOnion = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  nsAutoCString host;
  rv = prinURI->GetHost(host);
  if (NS_FAILED(rv)) {
    return NS_OK;
  }
  *aIsOnion = StringEndsWith(host, ".onion"_ns);
  return NS_OK;
}

NS_IMETHODIMP BasePrincipal::GetIsIpAddress(bool* aIsIpAddress) {
  *aIsIpAddress = false;

  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  nsAutoCString host;
  rv = prinURI->GetHost(host);
  if (NS_FAILED(rv)) {
    return NS_OK;
  }

  PRNetAddr prAddr;
  memset(&prAddr, 0, sizeof(prAddr));

  if (PR_StringToNetAddr(host.get(), &prAddr) == PR_SUCCESS) {
    *aIsIpAddress = true;
  }

  return NS_OK;
}

NS_IMETHODIMP BasePrincipal::GetIsLocalIpAddress(bool* aIsIpAddress) {
  *aIsIpAddress = false;

  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
  if (NS_FAILED(rv) || !ioService) {
    return NS_OK;
  }
  rv = ioService->HostnameIsLocalIPAddress(prinURI, aIsIpAddress);
  if (NS_FAILED(rv)) {
    *aIsIpAddress = false;
  }
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetScheme(nsACString& aScheme) {
  aScheme.Truncate();

  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  return prinURI->GetScheme(aScheme);
}

NS_IMETHODIMP
BasePrincipal::SchemeIs(const char* aScheme, bool* aResult) {
  *aResult = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_WARN_IF(NS_FAILED(rv)) || !prinURI) {
    return NS_OK;
  }
  *aResult = prinURI->SchemeIs(aScheme);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::IsURIInPrefList(const char* aPref, bool* aResult) {
  AssertIsOnMainThread();
  *aResult = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  *aResult = nsContentUtils::IsURIInPrefList(prinURI, aPref);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::IsURIInList(const nsACString& aList, bool* aResult) {
  *aResult = false;
  nsCOMPtr<nsIURI> prinURI;

  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  *aResult = nsContentUtils::IsURIInList(prinURI, nsCString(aList));
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::IsContentAccessibleAboutURI(bool* aResult) {
  *aResult = false;

  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }

  if (!prinURI->SchemeIs("about")) {
    return NS_OK;
  }

  *aResult = NS_IsContentAccessibleAboutURI(prinURI);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
  AssertIsOnMainThread();
  *aResult = false;

  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  if (NS_FAILED(rv) || !uri) {
    return NS_OK;
  }

  *aResult = nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsLoopbackHost(bool* aRes) {
  AssertIsOnMainThread();
  *aRes = false;
  nsAutoCString host;
  nsresult rv = GetHost(host);
  // Swallow potential failure as this method is infallible.
  NS_ENSURE_SUCCESS(rv, NS_OK);

  *aRes = nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetAboutModuleFlags(uint32_t* flags) {
  AssertIsOnMainThread();
  *flags = 0;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  if (!prinURI->SchemeIs("about")) {
    return NS_OK;
  }

  nsCOMPtr<nsIAboutModule> aboutModule;
  rv = NS_GetAboutModule(prinURI, getter_AddRefs(aboutModule));
  if (NS_FAILED(rv) || !aboutModule) {
    return rv;
  }
  return aboutModule->GetURIFlags(prinURI, flags);
}

NS_IMETHODIMP
BasePrincipal::GetOriginAttributes(JSContext* aCx,
                                   JS::MutableHandle<JS::Value> aVal) {
  if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) {
  MOZ_ASSERT(mOriginSuffix);
  mOriginSuffix->ToUTF8String(aOriginAttributes);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetUserContextId(uint32_t* aUserContextId) {
  *aUserContextId = UserContextId();
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) {
  *aPrivateBrowsingId = PrivateBrowsingId();
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsInIsolatedMozBrowserElement(
    bool* aIsInIsolatedMozBrowserElement) {
  *aIsInIsolatedMozBrowserElement = IsInIsolatedMozBrowserElement();
  return NS_OK;
}

nsresult BasePrincipal::GetAddonPolicy(
    extensions::WebExtensionPolicy** aResult) {
  AssertIsOnMainThread();
  RefPtr<extensions::WebExtensionPolicy> policy(AddonPolicy());
  policy.forget(aResult);
  return NS_OK;
}

nsresult BasePrincipal::GetContentScriptAddonPolicy(
    extensions::WebExtensionPolicy** aResult) {
  RefPtr<extensions::WebExtensionPolicy> policy(ContentScriptAddonPolicy());
  policy.forget(aResult);
  return NS_OK;
}

extensions::WebExtensionPolicy* BasePrincipal::AddonPolicy() {
  AssertIsOnMainThread();
  RefPtr<extensions::WebExtensionPolicyCore> core = AddonPolicyCore();
  return core ? core->GetMainThreadPolicy() : nullptr;
}

RefPtr<extensions::WebExtensionPolicyCore> BasePrincipal::AddonPolicyCore() {
  if (Is<ContentPrincipal>()) {
    return As<ContentPrincipal>()->AddonPolicyCore();
  }
  return nullptr;
}

bool BasePrincipal::AddonHasPermission(const nsAtom* aPerm) {
  if (auto policy = AddonPolicyCore()) {
    return policy->HasPermission(aPerm);
  }
  return false;
}

nsIPrincipal* BasePrincipal::PrincipalToInherit(nsIURI* aRequestedURI) {
  if (Is<ExpandedPrincipal>()) {
    return As<ExpandedPrincipal>()->PrincipalToInherit(aRequestedURI);
  }
  return this;
}

bool BasePrincipal::OverridesCSP(nsIPrincipal* aDocumentPrincipal) {
  MOZ_ASSERT(aDocumentPrincipal);

  // Expanded principals override CSP if and only if they subsume the document
  // principal.
  if (mKind == eExpandedPrincipal) {
    return FastSubsumes(aDocumentPrincipal);
  }
  // Extension principals always override the CSP of non-extension principals.
  // This is primarily for the sake of their stylesheets, which are usually
  // loaded from channels and cannot have expanded principals.
  return (AddonPolicyCore() &&
          !BasePrincipal::Cast(aDocumentPrincipal)->AddonPolicyCore());
}

already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
    nsIURI* aURI, const OriginAttributes& aAttrs, nsIURI* aInitialDomain) {
  MOZ_ASSERT(aURI);

  nsAutoCString originNoSuffix;
  nsresult rv =
      ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, originNoSuffix);
  if (NS_FAILED(rv)) {
    // If the generation of the origin fails, we still want to have a valid
    // principal. Better to return a null principal here.
    return NullPrincipal::Create(aAttrs);
  }

  return CreateContentPrincipal(aURI, aAttrs, originNoSuffix, aInitialDomain);
}

already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
    nsIURI* aURI, const OriginAttributes& aAttrs,
    const nsACString& aOriginNoSuffix, nsIURI* aInitialDomain) {
  MOZ_ASSERT(aURI);
  MOZ_ASSERT(!aOriginNoSuffix.IsEmpty());

  // If the URI is supposed to inherit the security context of whoever loads it,
  // we shouldn't make a content principal for it.
  bool inheritsPrincipal;
  nsresult rv = NS_URIChainHasFlags(
      aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
      &inheritsPrincipal);
  if (NS_FAILED(rv) || inheritsPrincipal) {
    return NullPrincipal::Create(aAttrs);
  }

  // Check whether the URI knows what its principal is supposed to be.
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
  nsCOMPtr<nsIURIWithSpecialOrigin> uriWithSpecialOrigin =
      do_QueryInterface(aURI);
  if (uriWithSpecialOrigin) {
    nsCOMPtr<nsIURI> origin;
    rv = uriWithSpecialOrigin->GetOrigin(getter_AddRefs(origin));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return nullptr;
    }
    MOZ_ASSERT(origin);
    OriginAttributes attrs;
    RefPtr<BasePrincipal> principal =
        CreateContentPrincipal(origin, attrs, aInitialDomain);
    return principal.forget();
  }
#endif

  nsCOMPtr<nsIPrincipal> blobPrincipal;
  if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
          aURI, getter_AddRefs(blobPrincipal))) {
    MOZ_ASSERT(blobPrincipal);
    MOZ_ASSERT(!aInitialDomain,
               "an initial domain for a blob URI makes no sense");
    RefPtr<BasePrincipal> principal = Cast(blobPrincipal);
    return principal.forget();
  }

  // Mint a content principal.
  RefPtr<ContentPrincipal> principal =
      new ContentPrincipal(aURI, aAttrs, aOriginNoSuffix, aInitialDomain);
  return principal.forget();
}

already_AddRefed<BasePrincipal> BasePrincipal::CreateContentPrincipal(
    const nsACString& aOrigin) {
  MOZ_ASSERT(!StringBeginsWith(aOrigin, "["_ns),
             "CreateContentPrincipal does not support System and Expanded "
             "principals");

  MOZ_ASSERT(
      !StringBeginsWith(aOrigin, nsLiteralCString(NS_NULLPRINCIPAL_SCHEME ":")),
      "CreateContentPrincipal does not support NullPrincipal");

  nsAutoCString originNoSuffix;
  OriginAttributes attrs;
  if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
    return nullptr;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
  NS_ENSURE_SUCCESS(rv, nullptr);

  return BasePrincipal::CreateContentPrincipal(uri, attrs);
}

already_AddRefed<BasePrincipal> BasePrincipal::CloneForcingOriginAttributes(
    const OriginAttributes& aOriginAttributes) {
  if (NS_WARN_IF(!IsContentPrincipal())) {
    return nullptr;
  }

  nsAutoCString originNoSuffix;
  nsresult rv = GetOriginNoSuffix(originNoSuffix);
  NS_ENSURE_SUCCESS(rv, nullptr);

  nsCOMPtr<nsIURI> uri;
  MOZ_ALWAYS_SUCCEEDS(GetURI(getter_AddRefs(uri)));

  // XXX: This does not copy over the domain. Should it?
  RefPtr<ContentPrincipal> copy =
      new ContentPrincipal(uri, aOriginAttributes, originNoSuffix, nullptr);
  return copy.forget();
}

extensions::WebExtensionPolicy* BasePrincipal::ContentScriptAddonPolicy() {
  AssertIsOnMainThread();
  RefPtr<extensions::WebExtensionPolicyCore> core =
      ContentScriptAddonPolicyCore();
  return core ? core->GetMainThreadPolicy() : nullptr;
}

RefPtr<extensions::WebExtensionPolicyCore>
BasePrincipal::ContentScriptAddonPolicyCore() {
  if (!Is<ExpandedPrincipal>()) {
    return nullptr;
  }

  auto* expanded = As<ExpandedPrincipal>();
  for (const auto& prin : expanded->AllowList()) {
    if (RefPtr<extensions::WebExtensionPolicyCore> policy =
            BasePrincipal::Cast(prin)->AddonPolicyCore()) {
      return policy;
    }
  }

  return nullptr;
}

bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI,
                                    bool aExplicit /* = false */) {
  if (Is<ExpandedPrincipal>()) {
    return As<ExpandedPrincipal>()->AddonAllowsLoad(aURI, aExplicit);
  }
  if (auto policy = AddonPolicyCore()) {
    return policy->CanAccessURI(aURI, aExplicit);
  }
  return false;
}

NS_IMETHODIMP
BasePrincipal::GetLocalStorageQuotaKey(nsACString& aKey) {
  aKey.Truncate();

  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);

  // The special handling of the file scheme should be consistent with
  // GetStorageOriginKey.

  nsAutoCString baseDomain;
  rv = uri->GetAsciiHost(baseDomain);
  NS_ENSURE_SUCCESS(rv, rv);

  if (baseDomain.IsEmpty() && uri->SchemeIs("file")) {
    nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = url->GetDirectory(baseDomain);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    nsCOMPtr<nsIEffectiveTLDService> eTLDService(
        do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoCString eTLDplusOne;
    rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
    if (NS_SUCCEEDED(rv)) {
      baseDomain = eTLDplusOne;
    } else if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
               rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
      rv = NS_OK;
    }
    NS_ENSURE_SUCCESS(rv, rv);
  }

  OriginAttributesRef().CreateSuffix(aKey);

  nsAutoCString subdomainsDBKey;
  rv = dom::StorageUtils::CreateReversedDomain(baseDomain, subdomainsDBKey);
  NS_ENSURE_SUCCESS(rv, rv);

  aKey.Append(':');
  aKey.Append(subdomainsDBKey);

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetNextSubDomainPrincipal(
    nsIPrincipal** aNextSubDomainPrincipal) {
  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  if (NS_FAILED(rv) || !uri) {
    return NS_OK;
  }

  nsAutoCString host;
  rv = uri->GetHost(host);
  if (NS_FAILED(rv) || host.IsEmpty()) {
    return NS_OK;
  }

  nsCString subDomain;
  rv = nsEffectiveTLDService::GetInstance()->GetNextSubDomain(host, subDomain);

  if (NS_FAILED(rv) || subDomain.IsEmpty()) {
    return NS_OK;
  }

  nsCOMPtr<nsIURI> subDomainURI;
  rv = NS_MutateURI(uri).SetHost(subDomain).Finalize(subDomainURI);
  if (NS_FAILED(rv) || !subDomainURI) {
    return NS_OK;
  }
  // Copy the attributes over
  mozilla::OriginAttributes attrs = OriginAttributesRef();

  if (!StaticPrefs::permissions_isolateBy_userContext()) {
    // Disable userContext for permissions.
    attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
  }
  RefPtr<nsIPrincipal> principal =
      mozilla::BasePrincipal::CreateContentPrincipal(subDomainURI, attrs);

  if (!principal) {
    return NS_OK;
  }
  principal.forget(aNextSubDomainPrincipal);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetStorageOriginKey(nsACString& aOriginKey) {
  aOriginKey.Truncate();

  nsCOMPtr<nsIURI> uri;
  nsresult rv = GetURI(getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);

  // The special handling of the file scheme should be consistent with
  // GetLocalStorageQuotaKey.

  nsAutoCString domainOrigin;
  rv = uri->GetAsciiHost(domainOrigin);
  NS_ENSURE_SUCCESS(rv, rv);

  if (domainOrigin.IsEmpty()) {
    // For the file:/// protocol use the exact directory as domain.
    if (uri->SchemeIs("file")) {
      nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = url->GetDirectory(domainOrigin);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  // Append reversed domain
  nsAutoCString reverseDomain;
  rv = dom::StorageUtils::CreateReversedDomain(domainOrigin, reverseDomain);
  NS_ENSURE_SUCCESS(rv, rv);

  aOriginKey.Append(reverseDomain);

  // Append scheme
  nsAutoCString scheme;
  rv = uri->GetScheme(scheme);
  NS_ENSURE_SUCCESS(rv, rv);

  aOriginKey.Append(':');
  aOriginKey.Append(scheme);

  // Append port if any
  int32_t port = NS_GetRealPort(uri);
  if (port != -1) {
    aOriginKey.Append(nsPrintfCString(":%d", port));
  }

  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetIsScriptAllowedByPolicy(bool* aIsScriptAllowedByPolicy) {
  AssertIsOnMainThread();
  *aIsScriptAllowedByPolicy = false;
  nsCOMPtr<nsIURI> prinURI;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    return NS_OK;
  }
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
  if (!ssm) {
    return NS_ERROR_UNEXPECTED;
  }
  return ssm->PolicyAllowsScript(prinURI, aIsScriptAllowedByPolicy);
}

bool SiteIdentifier::Equals(const SiteIdentifier& aOther) const {
  MOZ_ASSERT(IsInitialized());
  MOZ_ASSERT(aOther.IsInitialized());
  return mPrincipal->FastEquals(aOther.mPrincipal);
}

NS_IMETHODIMP
BasePrincipal::CreateReferrerInfo(mozilla::dom::ReferrerPolicy aReferrerPolicy,
                                  nsIReferrerInfo** _retval) {
  nsCOMPtr<nsIURI> prinURI;
  RefPtr<dom::ReferrerInfo> info;
  nsresult rv = GetURI(getter_AddRefs(prinURI));
  if (NS_FAILED(rv) || !prinURI) {
    info = new dom::ReferrerInfo(nullptr);
    info.forget(_retval);
    return NS_OK;
  }
  info = new dom::ReferrerInfo(prinURI, aReferrerPolicy);
  info.forget(_retval);
  return NS_OK;
}

NS_IMETHODIMP
BasePrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrecursor) {
  *aPrecursor = nullptr;
  return NS_OK;
}

NS_IMPL_ADDREF(BasePrincipal::Deserializer)
NS_IMPL_RELEASE(BasePrincipal::Deserializer)

NS_INTERFACE_MAP_BEGIN(BasePrincipal::Deserializer)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
  NS_INTERFACE_MAP_ENTRY(nsISerializable)
  if (mPrincipal) {
    return mPrincipal->QueryInterface(aIID, aInstancePtr);
  } else
NS_INTERFACE_MAP_END

NS_IMETHODIMP
BasePrincipal::Deserializer::Write(nsIObjectOutputStream* aStream) {
  // Read is used still for legacy principals
  MOZ_RELEASE_ASSERT(false, "Old style serialization is removed");
  return NS_OK;
}

/* static */
void BasePrincipal::SetJSONValue(Json::Value& aObject, const char* aKey,
                                 const nsCString& aValue) {
  aObject[Json::StaticString(aKey)] =
      Json::Value(aValue.BeginReading(), aValue.EndReading());
}

}  // namespace mozilla