// Copyright 2020 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/public/browser/url_loader_throttles.h"

#include <optional>

#include "base/feature_list.h"
#include "base/memory/safe_ref.h"
#include "components/variations/net/variations_url_loader_throttle.h"
#include "content/browser/client_hints/client_hints.h"
#include "content/browser/client_hints/critical_client_hints_throttle.h"
#include "content/browser/origin_trials/critical_origin_trials_throttle.h"
#include "content/browser/preloading/prefetch/cancel_unrelated_prefetch_url_loader_throttle.h"
#include "content/browser/preloading/prerender/prerender_url_loader_throttle.h"
#include "content/browser/reduce_accept_language/reduce_accept_language_throttle.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/webid/webid_utils.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/browser/reduce_accept_language_controller_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/web_identity.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_util.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"

namespace content {

std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
CreateContentBrowserURLLoaderThrottles(
    const network::ResourceRequest& request,
    BrowserContext* browser_context,
    const base::RepeatingCallback<WebContents*()>& wc_getter,
    NavigationUIData* navigation_ui_data,
    FrameTreeNodeId frame_tree_node_id,
    std::optional<int64_t> navigation_id) {
  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
      GetContentClient()->browser()->CreateURLLoaderThrottles(
          request, browser_context, wc_getter, navigation_ui_data,
          frame_tree_node_id, navigation_id);
  // TODO(crbug.com/40135370): Consider whether we want to use the WebContents
  // to determine the value for variations::Owner. Alternatively, this is the
  // browser side, and we might be fine with Owner::kUnknown.
  variations::VariationsURLLoaderThrottle::AppendThrottleIfNeeded(
      browser_context->GetVariationsClient(), &throttles);

  ClientHintsControllerDelegate* client_hint_delegate =
      browser_context->GetClientHintsControllerDelegate();
  // TODO(bokan): How to handle client hints in a fenced frame is still an open
  // question, see:
  // https://github.com/WICG/fenced-frame/blob/master/explainer/permission_document_policies.md#ua-client-hints-open-question
  if (base::FeatureList::IsEnabled(features::kCriticalClientHint) &&
      net::HttpUtil::IsMethodSafe(request.method) &&
      request.is_outermost_main_frame && client_hint_delegate) {
    throttles.push_back(std::make_unique<CriticalClientHintsThrottle>(
        browser_context, client_hint_delegate, frame_tree_node_id));
  }

  // Creating a throttle only for outermost main frames to persist the reduced
  // accept language for an origin and to restart requests if needed, due to
  // language negotiation.
  if (auto reduce_accept_lang_utils =
          ReduceAcceptLanguageUtils::Create(browser_context);
      reduce_accept_lang_utils && request.is_outermost_main_frame) {
    throttles.push_back(std::make_unique<ReduceAcceptLanguageThrottle>(
        std::move(reduce_accept_lang_utils.value()),
        browser_context->GetOriginTrialsControllerDelegate(),
        frame_tree_node_id));
  }

  // frame_tree_node_id may be invalid if we are loading the first frame
  // of the tab.
  FrameTreeNode* frame_tree_node = nullptr;
  if (frame_tree_node_id) {
    frame_tree_node = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  }

  // Handle Critical Origin Trial headers if the context supports it and this
  // is a navigation request.
  OriginTrialsControllerDelegate* origin_trials_delegate =
      browser_context->GetOriginTrialsControllerDelegate();
  if (origin_trials_delegate &&
      CriticalOriginTrialsThrottle::IsNavigationRequest(request)) {
    // Critical Origin Trials may restart the network request, so only allow on
    // safe methods, since the origin trials in question may change request
    // headers or other aspects of the network request. We want to avoid servers
    // making any changes twice as a result of the duplicate request, and if
    // headers are changed, any idempotent method is still allowed to make
    // further changes to server state.
    if (net::HttpUtil::IsMethodSafe(request.method) && origin_trials_delegate) {
      std::optional<url::Origin> top_origin = std::nullopt;
      // The throttle should only use a top-frame origin for partitioning if
      // this is not the outermost frame.
      if (frame_tree_node && frame_tree_node->GetParentOrOuterDocument()) {
        top_origin = frame_tree_node->GetParentOrOuterDocument()
                         ->GetOutermostMainFrame()
                         ->GetLastCommittedOrigin();
      }
      std::optional<ukm::SourceId> source_id =
          navigation_id.has_value()
              ? std::make_optional(ukm::ConvertToSourceId(
                    navigation_id.value(), ukm::SourceIdType::NAVIGATION_ID))
              : std::nullopt;
      throttles.push_back(std::make_unique<CriticalOriginTrialsThrottle>(
          *origin_trials_delegate, std::move(top_origin), source_id));
    }
  }

  if (auto throttle = MaybeCreateIdentityUrlLoaderThrottle(
          base::BindRepeating(webid::SetIdpSigninStatus,
                              browser_context->GetWeakPtr(),
                              request.destination, frame_tree_node_id),
          GetSetLoginHeaderDataDecoderParser())) {
    throttles.push_back(std::move(throttle));
  }

  if (auto throttle =
          PrerenderURLLoaderThrottle::MaybeCreate(frame_tree_node_id)) {
    throttles.push_back(std::move(throttle));
  }

  if (auto throttle = CancelUnrelatedPrefetchURLLoaderThrottle::MaybeCreate(
          frame_tree_node_id)) {
    throttles.push_back(std::move(throttle));
  }

  return throttles;
}

std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
CreateContentBrowserURLLoaderThrottlesForKeepAlive(
    BrowserContext* browser_context,
    FrameTreeNodeId frame_tree_node_id) {
  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
      GetContentClient()->browser()->CreateURLLoaderThrottlesForKeepAlive(
          browser_context, frame_tree_node_id);
  // TODO(crbug.com/40135370): Consider whether we want to use the WebContents
  // to determine the value for variations::Owner. Alternatively, this is the
  // browser side, and we might be fine with Owner::kUnknown.
  variations::VariationsURLLoaderThrottle::AppendThrottleIfNeeded(
      browser_context->GetVariationsClient(), &throttles);

  return throttles;
}
}  // namespace content
