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

#ifndef CONTENT_BROWSER_PROCESS_LOCK_H_
#define CONTENT_BROWSER_PROCESS_LOCK_H_

#include <optional>

#include "content/browser/site_info.h"
#include "content/browser/url_info.h"
#include "content/browser/web_exposed_isolation_info.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/browser/web_exposed_isolation_level.h"
#include "url/origin.h"

namespace content {

class IsolationContext;

// ProcessLock is a core part of Site Isolation, which is used to determine
// which documents are allowed to load in a process and which site data the
// process is allowed to access, based on the SiteInfo principal.
//
// If a process has a ProcessLock in the "invalid" state, then no SiteInstances
// have been associated with the process and access should not be granted to
// anything.
//
// Once a process is associated with its first SiteInstance, it transitions to
// the "locked_to_site" or "allow_any_site" state depending on whether the
// SiteInstance requires the process to be locked to a specific site or not.
// If the SiteInstance does not require the process to be locked to a site, the
// process will transition to the "allow_any_site" state and will allow any
// site to commit in the process. Such a process can later be upgraded to the
// "locked_to_site" state if something later determines that the process should
// only allow access to a single site, but only if it hasn't otherwise been used
// to render content. Once the process is in the "locked_to_site" state, it will
// not be able to access site data from other sites.
//
// ProcessLock is currently defined in terms of a single SiteInfo with a process
// lock URL, but it could be possible to define it in terms of multiple
// SiteInfos that are compatible with each other.
class CONTENT_EXPORT ProcessLock {
 public:
  // Create a lock that that represents a process that is associated with at
  // least one SiteInstance, but is not locked to a specific site. Any request
  // that wants to commit in this process must have a StoragePartitionConfig
  // and web-exposed isolation information (COOP/COEP, for example) that
  // match the values used to create this lock.
  static ProcessLock CreateAllowAnySite(
      const StoragePartitionConfig& storage_partition_config,
      const WebExposedIsolationInfo& web_exposed_isolation_info);

  // Create a lock for a specific UrlInfo. This method can be called from both
  // the UI and IO threads. Locks created with the same parameters must always
  // be considered equal independent of what thread they are called on. Special
  // care must be taken since SiteInfos created on different threads don't
  // always have the same contents for all their fields (e.g. site_url field is
  // thread dependent).
  static ProcessLock Create(const IsolationContext& isolation_context,
                            const UrlInfo& url_info);

  // Returns a ProcessLock representing what the given |site_info| requires.
  // Note that this may be different from the actual ProcessLock of the
  // resulting process, in cases where a locked process is not required (e.g.,
  // SiteInfos for http://unisolated.invalid).
  static ProcessLock FromSiteInfo(const SiteInfo& site_info);

  ProcessLock();
  ProcessLock(const ProcessLock& rhs);
  ProcessLock& operator=(const ProcessLock& rhs);

  ~ProcessLock();

  // Returns true if no information has been set on the lock.
  bool is_invalid() const { return !site_info_.has_value(); }

  // Returns true if the process is locked, but it is not restricted to a
  // specific site. Any site is allowed to commit in the process as long as
  // the request's COOP/COEP information matches the info provided when
  // the lock was created.
  bool allows_any_site() const {
    return site_info_.has_value() && site_info_->process_lock_url().is_empty();
  }

  // Returns true if the lock is restricted to a specific site and requires
  // the request's COOP/COEP information to match the values provided when
  // the lock was created.
  bool is_locked_to_site() const {
    return site_info_.has_value() && !site_info_->process_lock_url().is_empty();
  }

  // Returns the url that corresponds to the SiteInfo the lock is used with. It
  // will always be the same as the site URL, except in cases where effective
  // urls are in use. Always empty if the SiteInfo uses the default site url.
  // TODO(wjmaclean): Delete this accessor once we get to the point where we can
  // safely just compare ProcessLocks directly.
  const GURL lock_url() const {
    return site_info_.has_value() ? site_info_->process_lock_url() : GURL();
  }

  // Returns the site URL of the SiteInfo with which the lock was constructed.
  // Prefer comparing ProcessLocks directly or using lock_url(), unless you
  // care about effective URLs.
  const GURL site_url() const {
    return site_info_.has_value() ? site_info_->site_url() : GURL();
  }

  // Returns the AgentClusterKey shared by agents allowed in this ProcessLock.
  std::optional<AgentClusterKey> agent_cluster_key() const {
    return site_info_.has_value() ? site_info_->agent_cluster_key()
                                  : std::nullopt;
  }

  // Returns whether this ProcessLock is specific to an origin rather than
  // including subdomains, such as due to opt-in origin isolation. This resolves
  // an ambiguity of whether a process with a lock_url() like
  // "https://foo.example" is allowed to include "https://sub.foo.example" or
  // not.
  bool is_origin_keyed_process() const {
    return site_info_.has_value() &&
           site_info_->requires_origin_keyed_process();
  }

  // True if this ProcessLock is for a sandboxed iframe without
  // allow-same-origin.
  // TODO(wjmaclean): This function's return type could mutate to an enum in
  // future if required for sandboxed iframes that are restricted with different
  // sandbox flags.
  bool is_sandboxed() const {
    return site_info_.has_value() && site_info_->is_sandboxed();
  }

  // If this ProcessLock is for a sandboxed iframe without allow-same-origin,
  // and per-document grouping has been enabled for kIsolateSandboxedIframes,
  // then each SiteInfo will have a unique sandbox id encoded as part of the
  // lock. If per-document grouping is not enabled, this returns
  // UrlInfo::kInvalidUniqueSandboxId.
  int unique_sandbox_id() const {
    return (site_info_.has_value() ? site_info_->unique_sandbox_id()
                                   : UrlInfo::kInvalidUniqueSandboxId);
  }

  // Returns whether this ProcessLock is specific to PDF contents.
  bool is_pdf() const { return site_info_.has_value() && site_info_->is_pdf(); }

  // Returns whether this ProcessLock can only be used for error pages.
  bool is_error_page() const {
    return site_info_.has_value() && site_info_->is_error_page();
  }

  // Returns whether this ProcessLock is used for a <webview> guest process.
  // This may be false for other types of GuestView.
  bool is_guest() const {
    return site_info_.has_value() && site_info_->is_guest();
  }

  // Returns whether this ProcessLock is used for a process that exclusively
  // hosts content inside a <fencedframe>.
  bool is_fenced() const {
    return site_info_.has_value() && site_info_->is_fenced();
  }

  // Returns the StoragePartitionConfig that corresponds to the SiteInfo the
  // lock is used with.
  StoragePartitionConfig GetStoragePartitionConfig() const;

  // Returns the cross-origin isolation mode of the BrowsingInstance that all
  // agents allowed in this ProcessLock belong to. See
  // https://html.spec.whatwg.org/multipage/document-sequences.html#cross-origin-isolation-mode
  // This is tracked on ProcessLock because a RenderProcessHost can host only
  // cross-origin isolated agents or only non-cross-origin isolated agents, not
  // both.
  WebExposedIsolationInfo GetWebExposedIsolationInfo() const;

  // Returns the cross-origin isolated capability of all agents allowed in this
  // ProcessLock, without taking into account the 'cross-origin-isolated'
  // permissions policy. This ignores permissions policy because it's currently
  // possible for agents with the same ProcessLock to have different
  // 'cross-origin-isolated' permission policies. This can return a lower
  // isolation level than `GetWebExposedIsolationInfo()` if this ProcessLock
  // hosts agents that are cross-origin to a top-level document with the
  // 'isolated application' isolation level. See
  // https://html.spec.whatwg.org/multipage/webappapis.html#dom-crossoriginisolated
  WebExposedIsolationLevel GetWebExposedIsolationLevel() const;

  // Returns whether lock_url() is at least at the granularity of a site (i.e.,
  // a scheme plus eTLD+1, like https://google.com).  Also returns true if the
  // lock is to a more specific origin (e.g., https://accounts.google.com), but
  // not if the lock is empty or applies to an entire scheme (e.g., file://).
  bool IsASiteOrOrigin() const;

  bool matches_scheme(const std::string& scheme) const {
    return scheme == lock_url().scheme();
  }

  // Returns true if lock_url() has an opaque origin.
  bool HasOpaqueOrigin() const;

  // Returns true if |origin| matches the lock's origin.
  bool MatchesOrigin(const url::Origin& origin) const;

  // Returns true if the COOP/COEP origin isolation information in this lock
  // is set and matches the information in |site_info|.
  // Returns true if the web-exposed isolation level in this lock is set and
  // matches (or exceeds) the level set in |site_info|.|.
  bool IsCompatibleWithWebExposedIsolation(const SiteInfo& site_info) const;

  bool operator==(const ProcessLock& rhs) const;
  bool operator!=(const ProcessLock& rhs) const;
  // Defined to allow this object to act as a key for std::map.
  bool operator<(const ProcessLock& rhs) const;

  std::string ToString() const;

 private:
  explicit ProcessLock(const SiteInfo& site_info);

  // TODO(creis): Consider tracking multiple compatible SiteInfos in ProcessLock
  // (e.g., multiple sites when Site Isolation is disabled). This can better
  // restrict what the process has access to in cases that we currently use an
  // allows-any-site ProcessLock.
  std::optional<SiteInfo> site_info_;
};

CONTENT_EXPORT std::ostream& operator<<(std::ostream& out,
                                        const ProcessLock& process_lock);

}  // namespace content

#endif  // CONTENT_BROWSER_PROCESS_LOCK_H_