// Copyright 2023 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_FENCED_FRAME_FENCED_FRAME_REPORTER_H_
#define CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_REPORTER_H_

#include <map>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <variant>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/types/pass_key.h"
#include "content/common/content_export.h"
#include "content/public/browser/privacy_sandbox_invoking_api.h"
#include "content/public/browser/render_frame_host.h"
#include "net/url_request/referrer_policy.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content {

class BrowserContext;
class RenderFrameHostImpl;

// An event to be sent to a preregistered url.
// `type` is the key for the `ReportingUrlMap`, and `data` is sent with the
// request as a POST.
struct DestinationEnumEvent {
  std::string type;
  std::string data;
  bool cross_origin_exposed;

  // The equal to operator is defined in order to enable comparison of
  // DestinationVariant.
  bool operator==(const DestinationEnumEvent& other) const = default;
};

// An event to be sent to a custom url.
// `url` is the custom destination url, and the request is sent as a GET.
// Macros are substituted using the `ReportingMacros`.
struct DestinationURLEvent {
  GURL url;
  bool cross_origin_exposed;

  // The equal to operator is defined in order to enable comparison of
  // DestinationVariant.
  bool operator==(const DestinationURLEvent& other) const = default;
};

// An event to be sent to a preregistered url as the result of an automatic
// beacon. `type` is the key for the `ReportingUrlMap`, and `data` is sent with
// the request as a POST.
struct AutomaticBeaconEvent {
  blink::mojom::AutomaticBeaconType type;
  std::string data;

  // The equal to operator is defined in order to enable comparison of
  // DestinationVariant.
  bool operator==(const AutomaticBeaconEvent& other) const = default;
};

// Class that receives report events from fenced frames, and uses a
// per-destination-type maps of events to URLs to send reports. The maps may be
// received after the report event calls, in which case the reports will be
// queued until the corresponding map types have been received.
class CONTENT_EXPORT FencedFrameReporter
    : public base::RefCounted<FencedFrameReporter> {
 public:
  using ReportingUrlMap = base::flat_map<std::string, GURL>;

  using ReportingMacros = std::vector<std::pair<std::string, std::string>>;

  using DestinationVariant = std::
      variant<DestinationEnumEvent, DestinationURLEvent, AutomaticBeaconEvent>;

  // TODO(crbug.com/40285398): Once the CL that stops repeating checks for
  // fenced frame reporting beacons is landed, this observer will be extended to
  // observe whether the beacon is eventually sent or not.
  class ObserverForTesting : public base::CheckedObserver {
   public:
    virtual void OnBeaconQueued(const DestinationVariant& event_variant,
                                bool is_queued) = 0;
  };

  void AddObserverForTesting(ObserverForTesting* observer);
  void RemoveObserverForTesting(const ObserverForTesting* observer);

  // Creates a FencedFrameReporter that only maps kSharedStorageSelectUrl
  // destinations, using the passed in map.
  //
  // `url_loader_factory` is used to send all reports, and must not be null.
  //
  // `browser_context` is used to check attestations before sending out the
  // beacons.
  //
  // `reporting_url_declarer_origin` is used to set the request initiator on
  // outgoing beacon network requests. The initiator is the entity that chose
  // the destination URLs in order to prevent CSRF.
  //
  // `main_frame_origin` is the main frame of the page where Shared Storage
  // was called.
  static scoped_refptr<FencedFrameReporter> CreateForSharedStorage(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      BrowserContext* browser_context,
      const std::optional<url::Origin>& reporting_url_declarer_origin,
      ReportingUrlMap reporting_url_map,
      const url::Origin& main_frame_origin = url::Origin());

  // Don't use this constructor directly, but use factory methods instead.
  // See factory methods for details.
  FencedFrameReporter(
      base::PassKey<FencedFrameReporter> pass_key,
      PrivacySandboxInvokingAPI invoking_api,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      BrowserContext* browser_context,
      const url::Origin& main_frame_origin);

  // Called when a mapping for reports of type `reporting_destination` is ready.
  // The reporter must currently be considering maps of type
  // `reporting_destination` pending - that is:
  //
  // 1) It must have been created by CreateForFledge()
  // 2) `reporting_destination` must be one of kBuyer, kSeller, kDirectSeller or
  // kComponentSeller.
  // 3) OnUrlMappingReady() must not yet have been invoked with
  // `reporting_destination` yet.
  //
  // When invoked, any pending reports of type `reporting_destination` will be
  // sent if there's a matching entry in `reporting_url_map`. Any future reports
  // of that type will be immediately sent using the provided map. Errors will
  // not be displayed anywhere, as it's unclear where to send them to - the
  // originally associated frame may have already been closed.
  //
  // If it is learned that there are no events types for a particular
  // destination, should be called with an empty ReportingUrlMap for that
  // destination, so it can discard reports for that destination, and provide
  // errors messages for subsequent SendReporter() using that destination.
  //
  // Reports that use the urls declared in `reporting_url_map` will be sent
  // with the request initiator set to `reporting_url_declarer_origin`
  //
  // `reporting_ad_macros` is std::nullopt unless when `reporting_destination`
  // is kBuyer. If it is learned that there are no ad macros for kBuyer, should
  // be called with an empty ReportingMacros, so it can discard macro reports,
  // and provide errors messages for subsequent SendReporter().
  //
  // TODO(crbug.com/40253851): Consider investing in outputting error to
  // correct frame, if it still exists. `frame_tree_node_id` somewhat does this,
  // though it doesn't change across navigations, so could end up displaying an
  // error for a page a frame was previously displaying. There may be other
  // options.
  void OnUrlMappingReady(
      blink::FencedFrame::ReportingDestination reporting_destination,
      const std::optional<url::Origin>& reporting_url_declarer_origin,
      ReportingUrlMap reporting_url_map,
      std::optional<ReportingMacros> reporting_ad_macros = std::nullopt);

  // Sends a report for the specified event, using the ReportingUrlMap
  // associated with `reporting_destination`. If the map for
  // `reporting_destination` is pending, queues the report until the mapping
  // information is received.
  //
  // The event is specified with `event_variant`, which is either:
  // * a `DestinationEnumEvent`, which contains a `type` and `data`
  //   * Sends a POST to the url specified by `type` in the ReportingUrlMap,
  //     with `data` attached.
  //   * If there's no matching `type`, no beacon is sent.
  // * a `DestinationURLEvent`, which contains a `url`
  //   * Sends a GET to `url`.
  //   * Substitutes macros from the ReportingMacros.
  // * an `AutomaticBeaconEvent`, which contains a `type` and `data`
  //   * Like the enum variant, the data is sent as a POST to the url specified
  //     by `type`. However, given that these events are triggered automatically
  //     by the browser, they are semantically different from enum events.
  //
  // Returns false and populated `error_message` and `console_message_level` if
  // no network request was attempted, unless the reporting URL map for
  // `reporting_destination` is pending. In that case, errors are currently
  // never reported, even if the reporting URL map results in no request being
  // sent.
  //
  // `initiator_frame_tree_node_id` is used for DevTools support only.
  //
  // Note: `navigation_id` will only be non-null in the case of an automatic
  // beacon sent as a result of a top-level navigation from a fenced frame. It
  // will be set to the ID of the navigation request initiated from the fenced
  // frame and targeting the new top-level frame. In all other cases (including
  // the fence.reportEvent() case), the navigation id will be null.
  // Note: `ad_root_origin` will only be set for automatic beacons originating
  // from ad components.
  bool SendReport(
      const DestinationVariant& event_variant,
      blink::FencedFrame::ReportingDestination reporting_destination,
      RenderFrameHostImpl* request_initiator_frame,
      std::string& error_message,
      blink::mojom::ConsoleMessageLevel& console_message_level,
      FrameTreeNodeId initiator_frame_tree_node_id = FrameTreeNodeId(),
      std::optional<int64_t> navigation_id = std::nullopt);

  // Returns a list of reporting destinations that have at least 1 URL
  // registered with them.
  const std::vector<blink::FencedFrame::ReportingDestination>
  ReportingDestinations();

  // Returns a copy of the internal reporting metadata's
  // `reporting_url_declarer_origin` for each reporting destination, so it can
  // be validated in tests. Only includes reporting destinations for which maps
  // have been received - i.e., if OnUrlMappingReady() has not yet been invoked
  // for a reporting destination, it is not included in the returned map.
  base::flat_map<blink::FencedFrame::ReportingDestination, url::Origin>
  GetReportingUrlDeclarerOriginsForTesting();

  // Returns a copy of the internal reporting metadata's `reporting_url_map`, so
  // it can be validated in tests. Only includes ad beacon maps for which maps
  // have been received - i.e., if OnUrlMappingReady() has not yet been invoked
  // for a reporting destination, it is not included in the returned map.
  base::flat_map<blink::FencedFrame::ReportingDestination, ReportingUrlMap>
  GetAdBeaconMapForTesting();

  // Returns a copy of the internal reporting metadata's
  // `reporting_ad_macros`, so it can be validated in tests. Only includes ad
  // macros for which maps have been received - i.e., if OnUrlMappingReady()
  // has not yet been invoked for a reporting destination, it is not
  // included in the returned map.
  base::flat_map<blink::FencedFrame::ReportingDestination, ReportingMacros>
  GetAdMacrosForTesting();

 private:
  friend class base::RefCounted<FencedFrameReporter>;
  friend class FencedFrameURLMappingTestPeer;

  struct PendingEvent {
    PendingEvent(
        const DestinationVariant& event,
        const url::Origin& request_initiator,
        const net::ReferrerPolicy request_referrer_policy,
        FrameTreeNodeId initiator_frame_tree_node_id);

    PendingEvent(const PendingEvent&);
    PendingEvent(PendingEvent&&);

    PendingEvent& operator=(const PendingEvent&);
    PendingEvent& operator=(PendingEvent&&);

    ~PendingEvent();

    DestinationVariant event;
    url::Origin request_initiator;
    net::ReferrerPolicy request_referrer_policy;
    FrameTreeNodeId initiator_frame_tree_node_id;
  };

  // The per-blink::FencedFrame::ReportingDestination reporting information.
  struct ReportingDestinationInfo {
    explicit ReportingDestinationInfo(
        std::optional<url::Origin> reporting_url_declarer_origin = std::nullopt,
        std::optional<ReportingUrlMap> reporting_url_map = std::nullopt);
    ReportingDestinationInfo(ReportingDestinationInfo&&);
    ~ReportingDestinationInfo();

    ReportingDestinationInfo& operator=(ReportingDestinationInfo&&);

    // `reporting_url_declarer_origin`, `reporting_url_map`, and
    // `reporting_ad_macros` are set asynchronously, all at once.
    // If they are null, any reports that are attempted to be sent of the
    // corresponding type will be added to `pending_events` and only sent once
    // these are populated.
    std::optional<url::Origin> reporting_url_declarer_origin;
    std::optional<ReportingUrlMap> reporting_url_map;
    std::optional<ReportingMacros> reporting_ad_macros;

    // Pending report strings received while `reporting_url_map` was
    // std::nullopt. Once the map is received, this is cleared, and reports are
    // sent. The events are used for post-impression reporting. They are
    // from either `reportEvent()` calls or automatic beacons.
    std::vector<PendingEvent> pending_events;
  };

  ~FencedFrameReporter();

  // Helper to send a report, used by both SendReport() and OnUrlMappingReady().
  bool SendReportInternal(
      const ReportingDestinationInfo& reporting_destination_info,
      const DestinationVariant& event,
      blink::FencedFrame::ReportingDestination reporting_destination,
      const url::Origin& request_initiator,
      const net::ReferrerPolicy request_referrer_policy,
      FrameTreeNodeId initiator_frame_tree_node_id,
      std::string& error_message,
      blink::mojom::ConsoleMessageLevel& console_message_level,
      const std::string& devtools_request_id);

  // Used by FencedFrameURLMappingTestPeer.
  const base::flat_map<blink::FencedFrame::ReportingDestination,
                       ReportingDestinationInfo>&
  reporting_metadata() const {
    return reporting_metadata_;
  }

  // Notify the installed observers whether the beacon is queued to be sent or
  // not.
  void NotifyIsBeaconQueued(const DestinationVariant& event_variant,
                            bool is_queued);

  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;

  const raw_ptr<BrowserContext> browser_context_;

  base::flat_map<blink::FencedFrame::ReportingDestination,
                 ReportingDestinationInfo>
      reporting_metadata_;

  // The origin of the page's main frame. Used for:
  // * Private aggregation (Protected Audience only)
  // * 3rd party cookie permission check for credentialed automatic beacons
  const url::Origin main_frame_origin_;

  // Which API created this fenced frame reporter instance.
  PrivacySandboxInvokingAPI invoking_api_;

  base::ObserverList<ObserverForTesting> observers_;

  // Tracks the number of beacons sent during the lifetime of the reporter.
  // Logged as a histogram in the destructor.
  unsigned int beacons_sent_same_origin_ = 0;
};

}  // namespace content

#endif  // CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_REPORTER_H_
