// 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/devtools/network_service_devtools_observer.h"

#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/protocol/audits_handler.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/network/public/mojom/http_raw_headers.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"

namespace content {

namespace {

template <typename Handler, typename... MethodArgs, typename... Args>
void DispatchToAgents(DevToolsAgentHostImpl* agent_host,
                      void (Handler::*method)(MethodArgs...),
                      Args&&... args) {
  for (auto* h : Handler::ForAgentHost(agent_host))
    (h->*method)(std::forward<Args>(args)...);
}

}  // namespace

NetworkServiceDevToolsObserver::NetworkServiceDevToolsObserver(
    base::PassKey<NetworkServiceDevToolsObserver> pass_key,
    const std::string& id,
    int frame_tree_node_id)
    : devtools_agent_id_(id), frame_tree_node_id_(frame_tree_node_id) {}

NetworkServiceDevToolsObserver::~NetworkServiceDevToolsObserver() = default;

DevToolsAgentHostImpl* NetworkServiceDevToolsObserver::GetDevToolsAgentHost() {
  if (frame_tree_node_id_ != FrameTreeNode::kFrameTreeNodeInvalidId) {
    auto* frame_tree_node =
        FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
    if (!frame_tree_node)
      return nullptr;
    return RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
  }
  auto host = DevToolsAgentHostImpl::GetForId(devtools_agent_id_);
  if (!host)
    return nullptr;
  return host.get();
}

void NetworkServiceDevToolsObserver::OnRawRequest(
    const std::string& devtools_request_id,
    const net::CookieAccessResultList& request_cookie_list,
    std::vector<network::mojom::HttpRawHeaderPairPtr> request_headers,
    base::TimeTicks timestamp,
    network::mojom::ClientSecurityStatePtr security_state,
    network::mojom::OtherPartitionInfoPtr other_partition_info) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(host,
                   &protocol::NetworkHandler::OnRequestWillBeSentExtraInfo,
                   devtools_request_id, request_cookie_list, request_headers,
                   timestamp, security_state, other_partition_info);
}

void NetworkServiceDevToolsObserver::OnRawResponse(
    const std::string& devtools_request_id,
    const net::CookieAndLineAccessResultList& response_cookie_list,
    std::vector<network::mojom::HttpRawHeaderPairPtr> response_headers,
    const absl::optional<std::string>& response_headers_text,
    network::mojom::IPAddressSpace resource_address_space,
    int32_t http_status_code,
    const absl::optional<net::CookiePartitionKey>& cookie_partition_key) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(host, &protocol::NetworkHandler::OnResponseReceivedExtraInfo,
                   devtools_request_id, response_cookie_list, response_headers,
                   response_headers_text, resource_address_space,
                   http_status_code, cookie_partition_key);
}

void NetworkServiceDevToolsObserver::OnTrustTokenOperationDone(
    const std::string& devtools_request_id,
    network::mojom::TrustTokenOperationResultPtr result) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(host, &protocol::NetworkHandler::OnTrustTokenOperationDone,
                   devtools_request_id, *result);
}

void NetworkServiceDevToolsObserver::OnLocalNetworkRequest(
    const absl::optional<std::string>& devtools_request_id,
    const GURL& url,
    bool is_warning,
    network::mojom::IPAddressSpace resource_address_space,
    network::mojom::ClientSecurityStatePtr client_security_state) {
  if (frame_tree_node_id_ == FrameTreeNode::kFrameTreeNodeInvalidId)
    return;
  auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
  if (!ftn)
    return;
  auto cors_error_status =
      protocol::Network::CorsErrorStatus::Create()
          .SetCorsError(
              protocol::Network::CorsErrorEnum::InsecurePrivateNetwork)
          .SetFailedParameter("")
          .Build();
  std::unique_ptr<protocol::Audits::AffectedRequest> affected_request =
      protocol::Audits::AffectedRequest::Create()
          .SetRequestId(devtools_request_id.value_or(""))
          .SetUrl(url.spec())
          .Build();
  auto cors_issue_details =
      protocol::Audits::CorsIssueDetails::Create()
          .SetIsWarning(is_warning)
          .SetResourceIPAddressSpace(
              protocol::NetworkHandler::BuildIpAddressSpace(
                  resource_address_space))
          .SetRequest(std::move(affected_request))
          .SetCorsErrorStatus(std::move(cors_error_status))
          .Build();
  auto maybe_protocol_security_state =
      protocol::NetworkHandler::MaybeBuildClientSecurityState(
          client_security_state);
  if (maybe_protocol_security_state.isJust()) {
    cors_issue_details->SetClientSecurityState(
        maybe_protocol_security_state.takeJust());
  }
  auto details = protocol::Audits::InspectorIssueDetails::Create()
                     .SetCorsIssueDetails(std::move(cors_issue_details))
                     .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::CorsIssue)
                   .SetDetails(std::move(details))
                   .Build();
  devtools_instrumentation::ReportBrowserInitiatedIssue(
      ftn->current_frame_host(), issue.get());
}

void NetworkServiceDevToolsObserver::OnCorsPreflightRequest(
    const base::UnguessableToken& devtools_request_id,
    const net::HttpRequestHeaders& request_headers,
    network::mojom::URLRequestDevToolsInfoPtr request_info,
    const GURL& initiator_url,
    const std::string& initiator_devtools_request_id) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  auto timestamp = base::TimeTicks::Now();
  auto id = devtools_request_id.ToString();
  DispatchToAgents(host, &protocol::NetworkHandler::RequestSent, id,
                   /* loader_id=*/"", request_headers, *request_info,
                   protocol::Network::Initiator::TypeEnum::Preflight,
                   initiator_url, initiator_devtools_request_id, timestamp);
}

void NetworkServiceDevToolsObserver::OnCorsPreflightResponse(
    const base::UnguessableToken& devtools_request_id,
    const GURL& url,
    network::mojom::URLResponseHeadDevToolsInfoPtr head) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  auto id = devtools_request_id.ToString();
  DispatchToAgents(host, &protocol::NetworkHandler::ResponseReceived, id,
                   /* loader_id=*/"", url,
                   protocol::Network::ResourceTypeEnum::Preflight, *head,
                   protocol::Maybe<std::string>());
}

void NetworkServiceDevToolsObserver::OnCorsPreflightRequestCompleted(
    const base::UnguessableToken& devtools_request_id,
    const network::URLLoaderCompletionStatus& status) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  auto id = devtools_request_id.ToString();
  DispatchToAgents(host, &protocol::NetworkHandler::LoadingComplete, id,
                   protocol::Network::ResourceTypeEnum::Preflight, status);
}

void NetworkServiceDevToolsObserver::OnCorsError(
    const absl::optional<std::string>& devtools_request_id,
    const absl::optional<::url::Origin>& initiator_origin,
    network::mojom::ClientSecurityStatePtr client_security_state,
    const GURL& url,
    const network::CorsErrorStatus& cors_error_status,
    bool is_warning) {
  if (frame_tree_node_id_ == FrameTreeNode::kFrameTreeNodeInvalidId)
    return;

  auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
  if (!ftn)
    return;

  RenderFrameHostImpl* rfhi = ftn->current_frame_host();
  if (!rfhi)
    return;

  // TODO(https://crbug.com/1268378): Remove this once enforcement is always
  // enabled and warnings are no more.
  if (is_warning && initiator_origin.has_value()) {
    if (!initiator_origin->IsSameOriginWith(url)) {
      GetContentClient()->browser()->LogWebFeatureForCurrentPage(
          rfhi, blink::mojom::WebFeature::
                    kPrivateNetworkAccessIgnoredCrossOriginPreflightError);
    }

    if (net::SchemefulSite(initiator_origin.value()) !=
        net::SchemefulSite(url)) {
      GetContentClient()->browser()->LogWebFeatureForCurrentPage(
          rfhi, blink::mojom::WebFeature::
                    kPrivateNetworkAccessIgnoredCrossSitePreflightError);
    }
  }

  std::unique_ptr<protocol::Audits::AffectedRequest> affected_request =
      protocol::Audits::AffectedRequest::Create()
          .SetRequestId(devtools_request_id ? *devtools_request_id : "")
          .SetUrl(url.spec())
          .Build();

  auto cors_issue_details =
      protocol::Audits::CorsIssueDetails::Create()
          .SetIsWarning(is_warning)
          .SetRequest(std::move(affected_request))
          .SetCorsErrorStatus(
              protocol::NetworkHandler::BuildCorsErrorStatus(cors_error_status))
          .Build();
  if (initiator_origin) {
    cors_issue_details->SetInitiatorOrigin(initiator_origin->GetURL().spec());
  }
  auto maybe_protocol_security_state =
      protocol::NetworkHandler::MaybeBuildClientSecurityState(
          client_security_state);
  if (maybe_protocol_security_state.isJust()) {
    cors_issue_details->SetClientSecurityState(
        maybe_protocol_security_state.takeJust());
  }

  auto details = protocol::Audits::InspectorIssueDetails::Create()
                     .SetCorsIssueDetails(std::move(cors_issue_details))
                     .Build();
  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::CorsIssue)
                   .SetDetails(std::move(details))
                   .SetIssueId(cors_error_status.issue_id.ToString())
                   .Build();
  devtools_instrumentation::ReportBrowserInitiatedIssue(rfhi, issue.get());
}

void NetworkServiceDevToolsObserver::OnSubresourceWebBundleMetadata(
    const std::string& devtools_request_id,
    const std::vector<GURL>& urls) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(host,
                   &protocol::NetworkHandler::OnSubresourceWebBundleMetadata,
                   devtools_request_id, urls);
}

void NetworkServiceDevToolsObserver::OnSubresourceWebBundleMetadataError(
    const std::string& devtools_request_id,
    const std::string& error_message) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(
      host, &protocol::NetworkHandler::OnSubresourceWebBundleMetadataError,
      devtools_request_id, error_message);
}

void NetworkServiceDevToolsObserver::OnSubresourceWebBundleInnerResponse(
    const std::string& inner_request_devtools_id,
    const GURL& url,
    const absl::optional<std::string>& bundle_request_devtools_id) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(
      host, &protocol::NetworkHandler::OnSubresourceWebBundleInnerResponse,
      inner_request_devtools_id, url, bundle_request_devtools_id);
}

void NetworkServiceDevToolsObserver::OnSubresourceWebBundleInnerResponseError(
    const std::string& inner_request_devtools_id,
    const GURL& url,
    const std::string& error_message,
    const absl::optional<std::string>& bundle_request_devtools_id) {
  auto* host = GetDevToolsAgentHost();
  if (!host)
    return;
  DispatchToAgents(
      host, &protocol::NetworkHandler::OnSubresourceWebBundleInnerResponseError,
      inner_request_devtools_id, url, error_message,
      bundle_request_devtools_id);
}

void NetworkServiceDevToolsObserver::Clone(
    mojo::PendingReceiver<network::mojom::DevToolsObserver> observer) {
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<NetworkServiceDevToolsObserver>(
          base::PassKey<NetworkServiceDevToolsObserver>(), devtools_agent_id_,
          frame_tree_node_id_),
      std::move(observer));
}

mojo::PendingRemote<network::mojom::DevToolsObserver>
NetworkServiceDevToolsObserver::MakeSelfOwned(const std::string& id) {
  mojo::PendingRemote<network::mojom::DevToolsObserver> remote;
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<NetworkServiceDevToolsObserver>(
          base::PassKey<NetworkServiceDevToolsObserver>(), id,
          FrameTreeNode::kFrameTreeNodeInvalidId),
      remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

mojo::PendingRemote<network::mojom::DevToolsObserver>
NetworkServiceDevToolsObserver::MakeSelfOwned(FrameTreeNode* frame_tree_node) {
  mojo::PendingRemote<network::mojom::DevToolsObserver> remote;
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<NetworkServiceDevToolsObserver>(
          base::PassKey<NetworkServiceDevToolsObserver>(), std::string(),
          frame_tree_node->frame_tree_node_id()),
      remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

}  // namespace content