// 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_RENDERER_HOST_NAVIGATION_POLICY_CONTAINER_BUILDER_H_
#define CONTENT_BROWSER_RENDERER_HOST_NAVIGATION_POLICY_CONTAINER_BUILDER_H_

#include <memory>

#include "base/memory/scoped_refptr.h"
#include "base/unguessable_token.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/common/content_export.h"
#include "services/network/public/mojom/ip_address_space.mojom-forward.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-forward.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/frame/policy_container.mojom.h"
#include "url/gurl.h"

namespace content {

class FrameNavigationEntry;
class NavigationHandle;
class RenderFrameHostImpl;

// Keeps track of a few important sets of policies during a navigation: those of
// the parent document, of the navigation initiator, etc. Computes the policies
// of the new document being navigated to, and creates the new document's
// `PolicyContainerHost`.
//
// Instances of this class live in `NavigationRequest`. They are instantiated
// when the `NavigationRequest` is constructed and destroyed at commit time.
//
// Setters can be called as the navigation progresses to record interesting
// properties for later.
//
// When the potential response to commit is known, `ComputePolicies()` can be
// called to set the final polices of the new document and create a new policy
// container host.
// For error documents, `ComputePoliciesForError()` should be used instead. It
// can also be called after `ComputePolicies()` in some cases when the error is
// only detected after receiving a response.
//
// At commit time, `TakePolicyContainerHost()` can be called to transfer
// ownership of the policy container host to the target `RenderFrameHostImpl`.
class CONTENT_EXPORT NavigationPolicyContainerBuilder {
 public:
  // All arguments may be nullptr and need only outlive this call.
  //
  // If `parent` is not nullptr, its policies are copied.
  // If `history_entry` is not nullptr and contains policies, those are copied.
  //
  // This must only be called on the browser's UI thread.
  NavigationPolicyContainerBuilder(
      RenderFrameHostImpl* parent,
      const FrameNavigationEntry* history_entry);

  ~NavigationPolicyContainerBuilder();

  // Instances of this class are neither copyable nor movable.
  NavigationPolicyContainerBuilder(const NavigationPolicyContainerBuilder&) =
      delete;
  NavigationPolicyContainerBuilder& operator=(
      const NavigationPolicyContainerBuilder&) = delete;
  NavigationPolicyContainerBuilder(NavigationPolicyContainerBuilder&&) = delete;
  NavigationPolicyContainerBuilder& operator=(
      NavigationPolicyContainerBuilder&&) = delete;

  // Returns a pointer to a snapshot of the parent's policies captured at
  // construction time. Returns nullptr if there was no parent.
  const PolicyContainerPolicies* ParentPolicies() const;

  // Returns a pointer to a snapshot of the navigation history entry's policies
  // captured at construction time. Returns nullptr if there was no entry, of
  // if the entry had no policies.
  const PolicyContainerPolicies* HistoryPolicies() const;

  // Sets the connection allowlists for the new document.
  //
  // This must be called before `ComputePolicies()`.
  void SetConnectionAllowlists(network::ConnectionAllowlists allowlists);

  // Sets the cross origin opener policy of the new document.
  //
  // This should be called before `ComputePolicies()` to set the COOP delivered
  // in the header response. For subframes, it should be called again after the
  // policies have been computed, to potentially inherit COOP from the top-level
  // frame if the subframe is same-origin with the top level frame. This
  // requires knowing the origin of the document to commit, which in turns
  // requires having computed the sandbox flags, which happens in
  // `ComputePolicies()`.
  void SetCrossOriginOpenerPolicy(network::CrossOriginOpenerPolicy coop);

  // Sets the cross origin embedder policy of the new document.
  //
  // This must be called before `ComputePolicies()`.
  void SetCrossOriginEmbedderPolicy(network::CrossOriginEmbedderPolicy coep);

  // Sets the document isolation policy of the new document.
  //
  // This must be called before `ComputePolicies()`.
  void SetDocumentIsolationPolicy(const network::DocumentIsolationPolicy& dip);

  void SetIntegrityPolicy(network::IntegrityPolicy ip);
  void SetIntegrityPolicyReportOnly(network::IntegrityPolicy ip);

  // Sets the IP address space of the delivered policies of the new document.
  //
  // This must be called before `ComputePolicies()`.
  void SetIPAddressSpace(network::mojom::IPAddressSpace address_space);

  // Sets whether the origin of the document being navigated to is
  // potentially-trustworthy, as defined in:
  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy.
  //
  // This must be called before `ComputePolicies()`.
  void SetIsOriginPotentiallyTrustworthy(bool value);

  // Sets whether crossOriginIsolation is enabled by DocumentIsolationPolicy.
  // This must be called after `ComputePolicies()`.
  void SetCrossOriginIsolationEnabledByDIP();

  // Sets an override of the CrossOriginIsolationKey for the context. This
  // should only be used when no form of SiteInstance switching is available
  // (i.e. on Android WebView) and after `ComputePolicies()` has been called.
  // TODO(crbug.com/419595581): Remove this once default SiteInstanceGroups
  // ships on Android WebView.
  void SetCrossOriginIsolationKeyOverride(
      const AgentClusterKey::CrossOriginIsolationKey& coi_key);

  // Records an additional Content Security Policy that will apply to the new
  // document. `policy` must not be null. Policies added this way are ignored
  // for failed navigations and history navigations.
  void AddContentSecurityPolicy(
      network::mojom::ContentSecurityPolicyPtr policy);

  // Same as calling `AddContentSecurityPolicy()` on each item in `policies`.
  void AddContentSecurityPolicies(
      std::vector<network::mojom::ContentSecurityPolicyPtr> policies);

  // Returns the delivered policies, as set so far by:
  //
  //  - `SetIPAddressSpace()` for `ip_address_space`
  //  - `SetIsOriginPotentiallyTrustworthy()` and `ComputePolicies()` for
  //    `is_web_secure_context`
  const PolicyContainerPolicies& DeliveredPoliciesForTesting() const;

  // Sets final policies to defaults suitable for error pages, and builds a
  // policy container host.
  //
  // This method must only be called once. However it can be called after
  // `ComputePolicies()`.
  void ComputePoliciesForError();

  // Sets final policies to their correct values and builds a policy container
  // host.
  //
  // `navigation_handle` should be the handle for the navigation to compute
  // policies for. Its URL should designate the URL of the document after all
  // redirects have been followed.
  // `initiator_policies` should be the policies of the initiator of the
  // navigation, if the navigation has an initiator whose policies can be
  // inherited.
  // `is_inside_mhtml` specifies whether the navigation loads an MHTML document
  // or a subframe of an MHTML document. This influences computed sandbox flags.
  // `frame_sandbox_flags` represents the frame's sandbox flags.
  // If `is_secure_context_root` is true, the new document is treated as a
  // secure-context inheritance root that evaluates the frame based solely on
  // its own origin's trustworthiness, ignoring the parent's secure context
  // status. Used for embedder-identified boundaries (today, MIME-handler
  // OOPIFs; see `ContentBrowserClient::IsSecureContextRoot()`) where the
  // frame acts as an independent security boundary that does not inherit an
  // insecure state from its embedder.
  //
  // Also sets `DeliveredPoliciesForTesting().is_web_secure_context` to its
  // final value.
  //
  // This method must only be called once. `ComputePoliciesForError()` may be
  // called later, in which case it overrides the final policies.
  void ComputePolicies(NavigationHandle* navigation_handle,
                       const PolicyContainerPolicies* initiator_policies,
                       bool is_inside_mhtml,
                       network::mojom::WebSandboxFlags frame_sandbox_flags,
                       bool is_credentialless,
                       bool is_secure_context_root);

  // Returns a reference to the policies of the new document, i.e. the policies
  // in the policy container host to be committed.
  //
  // `ComputePolicies()` or `ComputePoliciesForError()` must have been called
  // previously.
  const PolicyContainerPolicies& FinalPolicies() const;

  // Creates a PolicyContainer linked to this builder's `PolicyContainerHost`.
  //
  // Should only be called once. `ComputePolicies()` or
  // `ComputePoliciesForError()` must have been called previously.
  blink::mojom::PolicyContainerPtr CreatePolicyContainerForBlink();

  // Returns a new refptr to the `PolicyContainerHost`.
  //
  // `ComputePolicies()` or `ComputePoliciesForError()` must have been called
  // previously.
  // It is invalid to call after `TakePolicyContainerHost()`.
  scoped_refptr<PolicyContainerHost> GetPolicyContainerHost();

  // Moves the `PolicyContainerHost` out of this builder. The returned host
  // contains the same policies as `FinalPolicies()`.
  //
  // `ComputePolicies()` or `ComputePoliciesForError()` must have been called
  // previously.
  scoped_refptr<PolicyContainerHost> TakePolicyContainerHost() &&;

  // Resets this instance to its freshly-constructed state.
  //
  // Called by same-document navigation requests that need to be restarted as
  // cross-document navigations. This happens when a same-document commit fails
  // due to another navigation committing in the meantime.
  void ResetForCrossDocumentRestart();

  // Whether either of `ComputePolicies()` or `ComputePoliciesForError()` has
  // been called yet.
  bool HasComputedPolicies() const;

  // Modifies the bit that would allow top-level navigation without sticky
  // user activation.
  void SetAllowTopNavigationWithoutUserGesture(bool allow_top);

 private:
  // Sets `delivered_policies_.is_web_secure_context` to its final value.
  // If `is_secure_context_root` is true, the bit is decided solely by the
  // frame's own origin trustworthiness, ignoring the parent -- see the
  // `ComputePolicies()` documentation.
  //
  // Helper for `ComputePolicies()`.
  void ComputeIsWebSecureContext(bool is_secure_context_root);

  // Sets `policies.sandbox_flags` to its final value. This merges the CSP
  // sandbox flags with the frame's sandbox flag.
  //
  // `is_inside_mhtml` Whether the navigation loads an MHTML document or a
  // subframe of an MHTML document. When true, this forces all sandbox flags on
  // the document except popups and popups-to-escape-sandbox.
  // `frame_sandbox_flags` The frame's sandbox flags.
  // `policies` The policies computed for the document except for the sandbox
  // flags.
  //
  // Helper for `ComputePolicies()` and `ComputePoliciesForError()`.
  void ComputeSandboxFlags(bool is_inside_mhtml,
                           network::mojom::WebSandboxFlags frame_sandbox_flags,
                           PolicyContainerPolicies& policies);

  // Sets `host_`.
  void SetFinalPolicies(PolicyContainerPolicies policies);

  // Helper for `FinalizePolicies()`. Called for local scheme urls only,
  // incorporates `delivered_policies_` into `policies` (e.g. appending the
  // delivered Content Security Policies). This is needed for example for CSP
  // Embedded Enforcement, in order to merge the inherited policies with the
  // policies forced by the `csp` attribute (which are contained in
  // `delivered_policies_`).
  void IncorporateDeliveredPoliciesForLocalURL(
      PolicyContainerPolicies& policies);

  // Helper for `FinalizePolicies()`. Returns, depending on `url`, the policies
  // that this document inherits from parent/initiator.
  PolicyContainerPolicies ComputeInheritedPolicies(
      const GURL& url,
      const PolicyContainerPolicies* initiator_policies);

  // Helper for `FinalizePolicies()`. Returns, depending on `navigation_handle`,
  // the final policies for the document that is going to be committed.
  PolicyContainerPolicies ComputeFinalPolicies(
      NavigationHandle* navigation_handle,
      const PolicyContainerPolicies* initiator_policies,
      bool is_inside_mhtml,
      network::mojom::WebSandboxFlags frame_sandbox_flags,
      bool is_credentialless);

  // The policies of the parent document, if any.
  const std::unique_ptr<PolicyContainerPolicies> parent_policies_;

  // The policies restored from the history navigation entry, if any.
  const std::unique_ptr<PolicyContainerPolicies> history_policies_;

  // The policies extracted from the response as it is loaded.
  //
  // See the comment on `SetIsOriginPotentiallyTrustworthy()` regarding this
  // member's `is_web_secure_context` field.
  PolicyContainerPolicies delivered_policies_;

  // Nullptr until `ComputePolicies()` or `ComputePoliciesForError()` is
  // called, then moved from by `TakePolicyContainerHost()`.
  scoped_refptr<PolicyContainerHost> host_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_NAVIGATION_POLICY_CONTAINER_BUILDER_H_
