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

/**
 * This is the principal that has no rights and can't be accessed by
 * anything other than itself and chrome; null principals are not
 * same-origin with anything but themselves.
 */

#include "mozilla/ArrayUtils.h"

#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsDocShell.h"
#include "NullPrincipal.h"
#include "DefaultURI.h"
#include "nsSimpleURI.h"
#include "nsIClassInfoImpl.h"
#include "nsNetCID.h"
#include "nsError.h"
#include "nsEscape.h"
#include "ContentPrincipal.h"
#include "nsScriptSecurityManager.h"
#include "pratom.h"
#include "nsIObjectInputStream.h"

#include "json/json.h"

using namespace mozilla;

NS_IMPL_CLASSINFO(NullPrincipal, nullptr, 0, NS_NULLPRINCIPAL_CID)
NS_IMPL_QUERY_INTERFACE_CI(NullPrincipal, nsIPrincipal)
NS_IMPL_CI_INTERFACE_GETTER(NullPrincipal, nsIPrincipal)

NullPrincipal::NullPrincipal(nsIURI* aURI, const nsACString& aOriginNoSuffix,
                             const OriginAttributes& aOriginAttributes)
    : BasePrincipal(eNullPrincipal, aOriginNoSuffix, aOriginAttributes),
      mURI(aURI) {}

/* static */
already_AddRefed<NullPrincipal> NullPrincipal::CreateWithInheritedAttributes(
    nsIPrincipal* aInheritFrom) {
  MOZ_ASSERT(aInheritFrom);
  nsCOMPtr<nsIURI> uri = CreateURI(aInheritFrom);
  return Create(Cast(aInheritFrom)->OriginAttributesRef(), uri);
}

/* static */
already_AddRefed<NullPrincipal> NullPrincipal::Create(
    const OriginAttributes& aOriginAttributes, nsIURI* aNullPrincipalURI) {
  nsCOMPtr<nsIURI> uri = aNullPrincipalURI;
  if (!uri) {
    uri = NullPrincipal::CreateURI(nullptr);
  }

  MOZ_RELEASE_ASSERT(uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME));

  nsAutoCString originNoSuffix;
  DebugOnly<nsresult> rv = uri->GetSpec(originNoSuffix);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  RefPtr<NullPrincipal> nullPrin =
      new NullPrincipal(uri, originNoSuffix, aOriginAttributes);
  return nullPrin.forget();
}

/* static */
already_AddRefed<NullPrincipal> NullPrincipal::CreateWithoutOriginAttributes() {
  return NullPrincipal::Create(OriginAttributes(), nullptr);
}

void NullPrincipal::EscapePrecursorQuery(nsACString& aPrecursorQuery) {
  // origins should not contain existing escape sequences, so set `esc_Forced`
  // to force any `%` in the input to be escaped in addition to non-ascii,
  // control characters and DEL.
  nsCString modified;
  if (NS_EscapeURLSpan(aPrecursorQuery, esc_Query | esc_Forced, modified)) {
    aPrecursorQuery.Assign(std::move(modified));
  }
}

void NullPrincipal::UnescapePrecursorQuery(nsACString& aPrecursorQuery) {
  nsCString modified;
  if (NS_UnescapeURL(aPrecursorQuery.BeginReading(), aPrecursorQuery.Length(),
                     /* aFlags */ 0, modified)) {
    aPrecursorQuery.Assign(std::move(modified));
  }
}

already_AddRefed<nsIURI> NullPrincipal::CreateURI(
    nsIPrincipal* aPrecursor, const nsID* aNullPrincipalID) {
  nsCOMPtr<nsIURIMutator> iMutator;
  if (StaticPrefs::network_url_useDefaultURI()) {
    iMutator = new mozilla::net::DefaultURI::Mutator();
  } else {
    iMutator = new mozilla::net::nsSimpleURI::Mutator();
  }

  nsID uuid = aNullPrincipalID ? *aNullPrincipalID : nsID::GenerateUUID();

  NS_MutateURI mutator(iMutator);
  mutator.SetSpec(NS_NULLPRINCIPAL_SCHEME ":"_ns +
                  nsDependentCString(nsIDToCString(uuid).get()));

  // If there's a precursor URI, encode it in the null principal URI's query.
  if (aPrecursor) {
    nsAutoCString precursorOrigin;
    switch (BasePrincipal::Cast(aPrecursor)->Kind()) {
      case eNullPrincipal: {
        // If the precursor null principal has a precursor, inherit it.
        if (nsCOMPtr<nsIURI> nullPrecursorURI = aPrecursor->GetURI()) {
          MOZ_ALWAYS_SUCCEEDS(nullPrecursorURI->GetQuery(precursorOrigin));
        }
        break;
      }
      case eContentPrincipal: {
        MOZ_ALWAYS_SUCCEEDS(aPrecursor->GetOriginNoSuffix(precursorOrigin));
#ifdef DEBUG
        nsAutoCString original(precursorOrigin);
#endif
        EscapePrecursorQuery(precursorOrigin);
#ifdef DEBUG
        nsAutoCString unescaped(precursorOrigin);
        UnescapePrecursorQuery(unescaped);
        MOZ_ASSERT(unescaped == original,
                   "cannot recover original precursor origin after escape");
#endif
        break;
      }

      // For now, we won't track expanded or system principal precursors. We may
      // want to track expanded principal precursors in the future, but it's
      // unlikely we'll want to track system principal precursors.
      case eExpandedPrincipal:
      case eSystemPrincipal:
        break;
    }
    if (!precursorOrigin.IsEmpty()) {
      mutator.SetQuery(precursorOrigin);
    }
  }

  nsCOMPtr<nsIURI> uri;
  MOZ_ALWAYS_SUCCEEDS(mutator.Finalize(getter_AddRefs(uri)));
  return uri.forget();
}

nsresult NullPrincipal::GetScriptLocation(nsACString& aStr) {
  return mURI->GetSpec(aStr);
}

/**
 * nsIPrincipal implementation
 */

uint32_t NullPrincipal::GetHashValue() { return (NS_PTR_TO_INT32(this) >> 2); }

NS_IMETHODIMP
NullPrincipal::GetURI(nsIURI** aURI) {
  nsCOMPtr<nsIURI> uri = mURI;
  uri.forget(aURI);
  return NS_OK;
}
NS_IMETHODIMP
NullPrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
  *aResult = false;
  return NS_OK;
}

NS_IMETHODIMP
NullPrincipal::GetDomain(nsIURI** aDomain) {
  nsCOMPtr<nsIURI> uri = mURI;
  uri.forget(aDomain);
  return NS_OK;
}

NS_IMETHODIMP
NullPrincipal::SetDomain(nsIURI* aDomain) {
  // I think the right thing to do here is to just throw...  Silently failing
  // seems counterproductive.
  return NS_ERROR_NOT_AVAILABLE;
}

bool NullPrincipal::MayLoadInternal(nsIURI* aURI) {
  // Also allow the load if we are the principal of the URI being checked.
  nsCOMPtr<nsIPrincipal> blobPrincipal;
  if (dom::BlobURLProtocolHandler::GetBlobURLPrincipal(
          aURI, getter_AddRefs(blobPrincipal))) {
    MOZ_ASSERT(blobPrincipal);
    return SubsumesInternal(blobPrincipal,
                            BasePrincipal::ConsiderDocumentDomain);
  }

  return false;
}

NS_IMETHODIMP
NullPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
  // For a null principal, we use our unique uuid as the base domain.
  return mURI->GetPathQueryRef(aBaseDomain);
}

NS_IMETHODIMP
NullPrincipal::GetAddonId(nsAString& aAddonId) {
  aAddonId.Truncate();
  return NS_OK;
};

/**
 * nsISerializable implementation
 */
NS_IMETHODIMP
NullPrincipal::Deserializer::Read(nsIObjectInputStream* aStream) {
  nsAutoCString spec;
  nsresult rv = aStream->ReadCString(spec);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIURI> uri;
  rv = NS_NewURI(getter_AddRefs(uri), spec);
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString suffix;
  rv = aStream->ReadCString(suffix);
  NS_ENSURE_SUCCESS(rv, rv);

  OriginAttributes attrs;
  bool ok = attrs.PopulateFromSuffix(suffix);
  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);

  mPrincipal = NullPrincipal::Create(attrs, uri);
  NS_ENSURE_TRUE(mPrincipal, NS_ERROR_FAILURE);

  return NS_OK;
}

nsresult NullPrincipal::PopulateJSONObject(Json::Value& aObject) {
  nsAutoCString principalURI;
  nsresult rv = mURI->GetSpec(principalURI);
  NS_ENSURE_SUCCESS(rv, rv);
  SetJSONValue<eSpec>(aObject, principalURI);

  nsAutoCString suffix;
  OriginAttributesRef().CreateSuffix(suffix);
  if (suffix.Length() > 0) {
    SetJSONValue<eSuffix>(aObject, suffix);
  }

  return NS_OK;
}

already_AddRefed<BasePrincipal> NullPrincipal::FromProperties(
    nsTArray<NullPrincipal::KeyVal>& aFields) {
  MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
  nsresult rv;
  nsCOMPtr<nsIURI> uri;
  OriginAttributes attrs;

  // The odd structure here is to make the code to not compile
  // if all the switch enum cases haven't been codified
  for (const auto& field : aFields) {
    switch (field.key) {
      case NullPrincipal::eSpec:
        if (!field.valueWasSerialized) {
          MOZ_ASSERT(false,
                     "Null principals require a spec URI in serialized JSON");
          return nullptr;
        }
        rv = NS_NewURI(getter_AddRefs(uri), field.value);
        NS_ENSURE_SUCCESS(rv, nullptr);
        break;
      case NullPrincipal::eSuffix:
        bool ok = attrs.PopulateFromSuffix(field.value);
        if (!ok) {
          return nullptr;
        }
        break;
    }
  }

  if (!uri) {
    MOZ_ASSERT(false, "No URI deserialized");
    return nullptr;
  }

  return NullPrincipal::Create(attrs, uri);
}

NS_IMETHODIMP
NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) {
  *aPrincipal = nullptr;

  nsAutoCString query;
  if (NS_FAILED(mURI->GetQuery(query)) || query.IsEmpty()) {
    return NS_OK;
  }
  UnescapePrecursorQuery(query);

  nsCOMPtr<nsIURI> precursorURI;
  if (NS_FAILED(NS_NewURI(getter_AddRefs(precursorURI), query))) {
    MOZ_ASSERT_UNREACHABLE(
        "Failed to parse precursor from nullprincipal query");
    return NS_OK;
  }

  // If our precursor is another null principal, re-construct it. This can
  // happen if a null principal without a precursor causes another principal to
  // be created.
  if (precursorURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) {
#ifdef DEBUG
    nsAutoCString precursorQuery;
    precursorURI->GetQuery(precursorQuery);
    MOZ_ASSERT(precursorQuery.IsEmpty(),
               "Null principal with nested precursors?");
#endif
    *aPrincipal =
        NullPrincipal::Create(OriginAttributesRef(), precursorURI).take();
    return NS_OK;
  }

  RefPtr<BasePrincipal> contentPrincipal =
      BasePrincipal::CreateContentPrincipal(precursorURI,
                                            OriginAttributesRef());
  // If `CreateContentPrincipal` failed, it will create a new NullPrincipal and
  // return that instead. We only want to return real content principals here.
  if (!contentPrincipal || !contentPrincipal->Is<ContentPrincipal>()) {
    return NS_OK;
  }
  contentPrincipal.forget(aPrincipal);
  return NS_OK;
}
