// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/process_lock.h"

#include "base/feature_list.h"
#include "base/strings/stringprintf.h"
#include "content/browser/agent_cluster_key.h"
#include "content/common/features.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_exposed_isolation_level.h"

namespace content {

// static
ProcessLock ProcessLock::CreateAllowAnySite(
    const StoragePartitionConfig& storage_partition_config,
    const WebExposedIsolationInfo& web_exposed_isolation_info,
    const std::optional<AgentClusterKey::CrossOriginIsolationKey>&
        cross_origin_isolation_key,
    const base::UnguessableToken& browser_context_id) {
  WebExposedIsolationLevel web_exposed_isolation_level =
      SiteInfo::ComputeWebExposedIsolationLevelForEmptySite(
          web_exposed_isolation_info);

  AgentClusterKey agent_cluster_key =
      cross_origin_isolation_key.has_value()
          ? AgentClusterKey::CreateWithCrossOriginIsolationKey(
                SiteInfo::GetOriginForUnlockedProcess(),
                cross_origin_isolation_key.value(),
                AgentClusterKey::OACStatus::kSiteKeyedByDefault)
          : AgentClusterKey::CreateSiteKeyed(
                GURL(), AgentClusterKey::OACStatus::kSiteKeyedByDefault);

  return ProcessLock(SiteInfo(
      agent_cluster_key,
      /*site_url=*/GURL(),
      /*is_sandboxed=*/false, UrlInfo::kInvalidUniqueSandboxId,
      storage_partition_config, web_exposed_isolation_info,
      web_exposed_isolation_level, /*is_guest=*/false,
      /*does_site_request_dedicated_process_for_coop=*/false,
      /*is_jit_disabled=*/false, /*are_v8_optimizations_disabled=*/false,
      /*is_fenced=*/false, browser_context_id,
      EmbedderIsolationInfo::CreateNone()));
}

// static
ProcessLock ProcessLock::Create(const IsolationContext& isolation_context,
                                const UrlInfo& url_info) {
  DCHECK(url_info.storage_partition_config.has_value());
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  return ProcessLock(SiteInfo::Create(isolation_context, url_info));
}

// static
ProcessLock ProcessLock::FromSiteInfo(const SiteInfo& site_info) {
  return ProcessLock(site_info);
}

ProcessLock::ProcessLock(const SiteInfo& site_info) : site_info_(site_info) {}

ProcessLock::ProcessLock() = default;

ProcessLock::ProcessLock(const ProcessLock&) = default;

ProcessLock& ProcessLock::operator=(const ProcessLock&) = default;

ProcessLock::~ProcessLock() = default;

bool ProcessLock::AllowsAnySite() const {
  if (!site_info_.has_value()) {
    return false;
  }

  if (agent_cluster_key().IsSiteKeyed()) {
    return agent_cluster_key().GetSite().is_empty();
  }

  return agent_cluster_key().GetOrigin() ==
         SiteInfo::GetOriginForUnlockedProcess();
}

bool ProcessLock::IsLockedToSite() const {
  if (!site_info_.has_value()) {
    return false;
  }

  if (agent_cluster_key().IsSiteKeyed()) {
    return !agent_cluster_key().GetSite().is_empty();
  }

  return agent_cluster_key().GetOrigin() !=
         SiteInfo::GetOriginForUnlockedProcess();
}

GURL ProcessLock::GetProcessLockURL() const {
  if (!site_info_.has_value()) {
    return GURL();
  }
  return agent_cluster_key().GetURL();
}

StoragePartitionConfig ProcessLock::GetStoragePartitionConfig() const {
  DCHECK(site_info_.has_value());
  return site_info_->GetStoragePartitionConfig();
}

WebExposedIsolationInfo ProcessLock::GetWebExposedIsolationInfo() const {
  return site_info_.has_value() ? site_info_->web_exposed_isolation_info()
                                : WebExposedIsolationInfo::CreateNonIsolated();
}

WebExposedIsolationLevel ProcessLock::GetWebExposedIsolationLevel() const {
  return site_info_.has_value() ? site_info_->web_exposed_isolation_level()
                                : WebExposedIsolationLevel::kNotIsolated;
}

bool ProcessLock::IsASiteOrOrigin() const {
  if (agent_cluster_key().IsSiteKeyed()) {
    const GURL lock_url = agent_cluster_key().GetSite();
    return lock_url.has_scheme() && lock_url.has_host() && lock_url.is_valid();
  }

  return agent_cluster_key().GetOrigin() !=
         SiteInfo::GetOriginForUnlockedProcess();
}

bool ProcessLock::MatchesScheme(const std::string& scheme) const {
  std::string agent_cluster_key_scheme =
      agent_cluster_key().IsOriginKeyed()
          ? agent_cluster_key().GetOrigin().scheme()
          : agent_cluster_key().GetSite().GetScheme();
  return agent_cluster_key_scheme == scheme;
}

bool ProcessLock::HasOpaqueOrigin() const {
  DCHECK(IsLockedToSite());
  if (agent_cluster_key().IsOriginKeyed()) {
    return agent_cluster_key().GetOrigin().opaque();
  }
  return url::Origin::Create(agent_cluster_key().GetSite()).opaque();
}

bool ProcessLock::MatchesOrigin(const url::Origin& origin) const {
  if (agent_cluster_key().IsOriginKeyed()) {
    return agent_cluster_key().GetOrigin().IsSameOriginWith(origin);
  }
  url::Origin process_lock_origin =
      url::Origin::Create(agent_cluster_key().GetSite());
  return origin == process_lock_origin;
}

bool ProcessLock::IsCompatibleWithWebExposedIsolation(
    const SiteInfo& site_info) const {
  if (!site_info_.has_value()) {
    return true;
  }

  // Check if the WebExposedIsolationInfos are compatible.
  if (site_info_->web_exposed_isolation_info() !=
      site_info.web_exposed_isolation_info()) {
    return false;
  }

  // Without SiteIsolation or partial SiteIsolation, DocumentIsolationPolicy is
  // not backed by process isolation. If it is not backed by process isolation,
  // a process that allows any site can host any context regardless of the
  // cross-origin isolation status of the context set by
  // DocumentIsolationPolicy.
  if (AllowsAnySite() &&
      !SiteIsolationPolicy::UseDedicatedProcessesForAllSites() &&
      !SiteIsolationPolicy::AreDynamicIsolatedOriginsEnabled() &&
      base::FeatureList::IsEnabled(
          features::kDocumentIsolationPolicyWithoutSiteIsolation)) {
    return true;
  }

  // Check if the CrossOriginIsolationKeys are compatible.
  //
  // TODO(crbug.com/349755777): Currently, this prevents a RenderProcessHost
  // with a ProcessLock created with ProcessLock::CreateAllowAnySite to be
  // reused for navigations to documents with DocumentIsolationPolicy, even if
  // the RenderProcessHost has not been used and it would be safe to reuse it.
  //
  // Historically, ProcessLock::CreateAllowAnySite would result in the
  // associated RenderProcessHost to be marked as crossOriginIsolated or not,
  // depending on the passed WebExposedIsolationInfo. It could not be set to a
  // different crossOriginIsolated status again without removing checks that the
  // COI status of the process could not change.
  //
  // However, we're now checking COI status per agent-cluster, so the code could
  // be refactored to update the process lock of an unused process to whatever
  // COI state is needed for the navigation, and allow process reuse.
  return site_info_->agent_cluster_key().GetCrossOriginIsolationKey() ==
         site_info.agent_cluster_key().GetCrossOriginIsolationKey();
}

bool ProcessLock::operator==(const ProcessLock& rhs) const {
  if (site_info_.has_value() != rhs.site_info_.has_value())
    return false;

  if (!site_info_.has_value())  // Neither has a value, so they're equal.
    return true;

  // At this point, both `this` and `rhs` are known to have valid SiteInfos.
  // Here we proceed with a comparison almost identical to
  // SiteInfo::MakeSecurityPrincipalKey(), except that `site_url_` is excluded.
  return site_info_->ProcessLockCompareTo(rhs.site_info_.value()) == 0;
}

bool ProcessLock::operator<(const ProcessLock& rhs) const {
  if (!site_info_.has_value() && !rhs.site_info_.has_value())
    return false;
  if (!site_info_.has_value())  // Here rhs.site_info_.has_value() is true.
    return true;
  if (!rhs.site_info_.has_value())  // Here site_info_.has_value() is true.
    return false;

  // At this point, both `this` and `rhs` are known to have valid SiteInfos.
  // Here we proceed with a comparison almost identical to
  // SiteInfo::MakeSecurityPrincipalKey(), except that `site_url_` is excluded.
  return site_info_->ProcessLockCompareTo(rhs.site_info_.value()) < 0;
}

std::string ProcessLock::ToString() const {
  std::string ret = "{ ";

  if (site_info_.has_value()) {
    ret += GetProcessLockURL().possibly_invalid_spec();
    if (agent_cluster_key().IsOriginKeyed()) {
      ret += " origin-keyed";
    }

    if (is_sandboxed()) {
      ret += " sandboxed";
      if (site_info_->unique_sandbox_id() != UrlInfo::kInvalidUniqueSandboxId)
        ret += base::StringPrintf(" (id=%d)", site_info_->unique_sandbox_id());
    }

    if (is_pdf())
      ret += " pdf";

    if (is_guest())
      ret += " guest";

    if (is_fenced())
      ret += " fenced";

    if (GetWebExposedIsolationInfo().is_isolated()) {
      ret += " cross-origin-isolated";
      if (GetWebExposedIsolationInfo().is_isolated_application())
        ret += "-application";
      ret += " coi-origin='" +
             GetWebExposedIsolationInfo().origin().GetDebugString() + "'";
    }

    if (!GetStoragePartitionConfig().is_default()) {
      ret += ", partition=" + GetStoragePartitionConfig().partition_domain() +
             "." + GetStoragePartitionConfig().partition_name();
      if (GetStoragePartitionConfig().in_memory())
        ret += ", in-memory";
    }
  } else {
    ret += " no-site-info";
  }
  ret += " }";

  return ret;
}

std::ostream& operator<<(std::ostream& out, const ProcessLock& process_lock) {
  return out << process_lock.ToString();
}

}  // namespace content
