// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_SSL_HTTPS_UPGRADES_INTERCEPTOR_H_
#define CHROME_BROWSER_SSL_HTTPS_UPGRADES_INTERCEPTOR_H_

#include <memory>
#include <optional>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "chrome/browser/ssl/https_only_mode_tab_helper.h"
#include "components/security_interstitials/core/https_only_mode_metrics.h"
#include "content/public/browser/url_loader_request_interceptor.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/request_priority.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "url/gurl.h"

namespace blink {
class ThrottlingURLLoader;
}  // namespace blink

namespace content {
class BrowserContext;
class NavigationUIData;
class WebContents;
}  // namespace content

// A class that attempts to intercept HTTP navigation requests and redirect them
// to HTTPS, and then if the upgraded requests fail redirect them back to HTTP.
// Its lifetime matches that of the content/ navigation loader code.
//
// (Aside: An alternate implementation of this class would be as a
// NavigationLoaderInterceptor in content/, which could have a slightly simpler
// implementation, but by having this in chrome/ we don't need a Delegate
// interface for the embedder logic. If we move this into content/ in the future
// -- for example, in order to have a default HTTPS Upgrades implementation in
// the content/ platform -- then we can make that switch.)
class HttpsUpgradesInterceptor : public content::URLLoaderRequestInterceptor,
                                 public network::mojom::URLLoader {
 public:
  static std::unique_ptr<HttpsUpgradesInterceptor> MaybeCreateInterceptor(
      content::FrameTreeNodeId frame_tree_node_id,
      content::NavigationUIData* navigation_ui_data_);

  HttpsUpgradesInterceptor(content::FrameTreeNodeId frame_tree_node_id,
                           content::NavigationUIData* navigation_ui_data_);
  ~HttpsUpgradesInterceptor() override;

  HttpsUpgradesInterceptor(const HttpsUpgradesInterceptor&) = delete;
  HttpsUpgradesInterceptor& operator=(const HttpsUpgradesInterceptor&) = delete;

  // content::URLLoaderRequestInterceptor:
  void MaybeCreateLoader(
      const network::ResourceRequest& tentative_resource_request,
      content::BrowserContext* browser_context,
      content::URLLoaderRequestInterceptor::LoaderCallback callback) override;
  bool MaybeCreateLoaderForResponse(
      const network::URLLoaderCompletionStatus& status,
      const network::ResourceRequest& request,
      network::mojom::URLResponseHeadPtr* response_head,
      mojo::ScopedDataPipeConsumerHandle* response_body,
      mojo::PendingReceiver<network::mojom::URLLoaderClient>* client_receiver,
      blink::ThrottlingURLLoader* url_loader) override;

  // Sets the ports used by the EmbeddedTestServer (which uses random ports)
  // to determine the correct port to upgrade/fallback to in tests.
  static void SetHttpsPortForTesting(int port);
  static void SetHttpPortForTesting(int port);
  static int GetHttpsPortForTesting();
  static int GetHttpPortForTesting();

 private:
  // Continuation of MaybeCreateLoader() after querying the network service for
  // the HSTS status for the hostname in the request.
  void MaybeCreateLoaderOnHstsQueryCompleted(
      GURL url,
      bool is_outermost_main_frame,
      std::string method,
      int transition_type,
      content::URLLoaderRequestInterceptor::LoaderCallback callback,
      bool is_hsts_active_for_host);

  // network::mojom::URLLoader:
  void FollowRedirect(
      network::HttpRequestHeadersUpdateParams headers_update_params,
      const std::optional<GURL>& new_url) override {}
  void SetPriority(net::RequestPriority priority,
                   int intra_priority_value) override {}

  // Returns a RequestHandler callback that can be passed to the underlying
  // LoaderCallback to serve an artificial redirect to `new_url`.
  RequestHandler CreateRedirectHandler(const GURL& new_url);

  // Passed to the LoaderCallback as the ResponseHandler with `new_url` bound,
  // this method receives the receiver and client_remote from the
  // NavigationLoader, to bind against. Triggers a redirect to `new_url` using
  // `client`.
  void RedirectHandler(
      const GURL& new_url,
      const network::ResourceRequest& request,
      mojo::PendingReceiver<network::mojom::URLLoader> receiver,
      mojo::PendingRemote<network::mojom::URLLoaderClient> client);

  // Mojo error handling. Resets `receiver_` and `client_`.
  void OnConnectionClosed();

  // Used to access the WebContents for the navigation.
  content::FrameTreeNodeId frame_tree_node_id_;

  // Parameters about whether the throttle should trigger the interstitial
  // warning before navigating to the HTTP fallback URL. Can be null if the
  // current load isn't eligible for an upgrade.
  std::unique_ptr<
      security_interstitials::https_only_mode::HttpInterstitialState>
      interstitial_state_;

  // URLs seen by the interceptor, used to detect a redirect loop.
  std::set<GURL> urls_seen_;

  // Receiver for the URLLoader interface.
  mojo::Receiver<network::mojom::URLLoader> receiver_{this};

  // The owning client. Used for serving redirects.
  mojo::Remote<network::mojom::URLLoaderClient> client_;

  // Owned by NavigationURLLoaderImpl, which should outlive the interceptor.
  raw_ptr<content::NavigationUIData> navigation_ui_data_;
  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<HttpsUpgradesInterceptor> weak_factory_{this};
};

#endif  // CHROME_BROWSER_SSL_HTTPS_UPGRADES_INTERCEPTOR_H_
