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

#ifndef COMPONENTS_SECURITY_INTERSTITIALS_CONTENT_SSL_ERROR_HANDLER_H_
#define COMPONENTS_SECURITY_INTERSTITIALS_CONTENT_SSL_ERROR_HANDLER_H_

#include <memory>
#include <string>
#include <vector>

#include "base/feature_list.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "components/captive_portal/content/captive_portal_service.h"
#include "components/captive_portal/core/buildflags.h"
#include "components/security_interstitials/content/common_name_mismatch_handler.h"
#include "components/security_interstitials/content/security_interstitial_page.h"
#include "components/security_interstitials/content/ssl_error_assistant.pb.h"
#include "components/ssl_errors/error_classification.h"
#include "content/public/browser/certificate_request_result_type.h"
#include "content/public/browser/restore_type.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "net/ssl/ssl_info.h"
#include "url/gurl.h"

class SecurityBlockingPageFactory;
class CommonNameMismatchHandler;
struct DynamicInterstitialInfo;

namespace base {
class Clock;
class TimeDelta;
}  // namespace base

namespace content {
class WebContents;
}

namespace network_time {
class NetworkTimeTracker;
}

BASE_DECLARE_FEATURE(kMITMSoftwareInterstitial);

// This class is responsible for deciding what type of interstitial to display
// for an SSL validation error and actually displaying it. The display of the
// interstitial might be delayed by a few seconds while trying to determine the
// cause of the error. During this window, the class will:
// - Check for a clock error
// - Check for a known captive portal certificate SPKI
// - Wait for a name-mismatch suggested URL
// - or Wait for a captive portal result to arrive.
//
// Based on the result of these checks, SSLErrorHandler will show a customized
// interstitial, redirect to a different suggested URL, or, if all else fails,
// show the normal SSL interstitial. When --committed--interstitials is enabled,
// it passes a constructed blocking page to the |blocking_page_ready_callback|
// that was given to HandleSSLError(), instead of showing the interstitial
// directly.
//
// This class should only be used on the UI thread because its implementation
// uses captive_portal::CaptivePortalService which can only be accessed on the
// UI thread.
class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
                        public content::WebContentsObserver {
 public:
  using TimerStartedCallback =
      base::RepeatingCallback<void(content::WebContents*)>;
  using BlockingPageReadyCallback = base::OnceCallback<void(
      std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>;

  // Callback that is optionally used to inform the client that a blocking page
  // has been shown in the specified WebContents for the specified URL with the
  // given error string and network error code.
  using OnBlockingPageShownCallback = base::RepeatingCallback<
      void(content::WebContents*, const GURL&, const std::string&, int)>;

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

  ~SSLErrorHandler() override;

  // Events for UMA. Do not rename or remove values, add new values to the end.
  // Public for testing.
  // If you change this, change the values in CaptivePortalTest.java as well.
  enum UMAEvent {
    HANDLE_ALL = 0,
    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE = 1,
    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE = 2,
    SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE = 3,
    SHOW_SSL_INTERSTITIAL_OVERRIDABLE = 4,
    WWW_MISMATCH_FOUND = 5,  // Deprecated in M59 by WWW_MISMATCH_FOUND_IN_SAN.
    WWW_MISMATCH_URL_AVAILABLE = 6,
    WWW_MISMATCH_URL_NOT_AVAILABLE = 7,
    SHOW_BAD_CLOCK = 8,
    CAPTIVE_PORTAL_CERT_FOUND = 9,
    WWW_MISMATCH_FOUND_IN_SAN = 10,
    SHOW_MITM_SOFTWARE_INTERSTITIAL = 11,
    OS_REPORTS_CAPTIVE_PORTAL = 12,
    SHOW_BLOCKED_INTERCEPTION_INTERSTITIAL = 13,
    SHOW_LEGACY_TLS_INTERSTITIAL = 14,  // Deprecated in M98.
    SHOW_LOCAL_SELF_SIGNED_INTERSTITIAL = 15,
    SSL_ERROR_HANDLER_EVENT_COUNT
  };

  // This delegate allows unit tests to provide their own Chrome specific
  // actions.
  class Delegate {
   public:
    virtual ~Delegate() = default;
    virtual void CheckForCaptivePortal() = 0;
    virtual bool DoesOSReportCaptivePortal() = 0;
    virtual bool GetSuggestedUrl(const std::vector<std::string>& dns_names,
                                 GURL* suggested_url) const = 0;
    virtual void CheckSuggestedUrl(
        const GURL& suggested_url,
        CommonNameMismatchHandler::CheckUrlCallback callback) = 0;
    virtual void NavigateToSuggestedURL(const GURL& suggested_url) = 0;
    virtual bool IsErrorOverridable() const = 0;
    virtual void ShowCaptivePortalInterstitial(const GURL& landing_url) = 0;
    virtual void ShowMITMSoftwareInterstitial(
        const std::string& mitm_software_name) = 0;
    virtual void ShowSSLInterstitial(const GURL& support_url) = 0;
    virtual void ShowBadClockInterstitial(
        const base::Time& now,
        ssl_errors::ClockState clock_state) = 0;
    virtual void ShowBlockedInterceptionInterstitial() = 0;
    virtual void ShowLocalSelfSignedInterstitial() = 0;
    virtual void ReportNetworkConnectivity(base::OnceClosure callback) = 0;
    virtual bool HasBlockedInterception() const = 0;
  };

  // Entry point for the class. Most parameters are the same as
  // SSLBlockingPage constructor.
  // Extra parameters:
  // |blocking_page_ready_callback| is intended for committed interstitials. If
  // |blocking_page_ready_callback| is null, this function will create a
  // blocking page and call Show() on it. Otherwise, this function creates an
  // interstitial and passes it to |blocking_page_ready_callback|.
  // |blocking_page_ready_callback| is guaranteed not to be called
  // synchronously.
  // |user_can_proceed_past_interstitial| can be given a value of false to
  // change the default behavior of giving users the option to proceed past
  // SSL error interstitials.
  // |blocking_page_factory| will be used to create the interstitial pages
  // shown.
  static void HandleSSLError(
      content::WebContents* web_contents,
      net::Error cert_error,
      const net::SSLInfo& ssl_info,
      const GURL& request_url,
      BlockingPageReadyCallback blocking_page_ready_callback,
      network_time::NetworkTimeTracker* network_time_tracker,
      captive_portal::CaptivePortalService* captive_portal_service,
      std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory,
      bool user_can_proceed_past_interstitial = true);

  // Sets the binary proto for SSL error assistant. The binary proto
  // can be downloaded by the component updater, or set by tests.
  static void SetErrorAssistantProto(
      std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
          config_proto);

  // Invoke this method to have |callback| called whenever an interstitial is
  // shown in an SSLErrorHandler instance.
  static void SetClientCallbackOnInterstitialsShown(
      OnBlockingPageShownCallback callback);

  // Testing methods.
  static void ResetConfigForTesting();
  static void SetInterstitialDelayForTesting(const base::TimeDelta& delay);
  // The callback pointer must remain valid for the duration of error handling.
  static void SetInterstitialTimerStartedCallbackForTesting(
      TimerStartedCallback* callback);
  static void SetClockForTesting(base::Clock* testing_clock);
  static void SetReportNetworkConnectivityCallbackForTesting(
      base::OnceClosure callback);
  static void SetEnterpriseManagedForTesting(bool enterprise_managed);
  static bool IsEnterpriseManagedFlagSetForTesting();
  static std::string GetHistogramNameForTesting();
  static int GetErrorAssistantProtoVersionIdForTesting();
  static void SetOSReportsCaptivePortalForTesting(
      bool os_reports_captive_portal);
  static void SetIsMultiNetworkCCTWorkflowForTesting(
      bool is_multi_network_cct_workflow);
  bool IsTimerRunningForTesting() const;

 protected:
  SSLErrorHandler(std::unique_ptr<Delegate> delegate,
                  content::WebContents* web_contents,
                  net::Error cert_error,
                  const net::SSLInfo& ssl_info,
                  network_time::NetworkTimeTracker* network_time_tracker,
                  captive_portal::CaptivePortalService* captive_portal_service,
                  const GURL& request_url);

  // Called when an SSL cert error is encountered. Triggers a captive portal
  // check and fires a one shot timer to wait for a "captive portal detected"
  // result to arrive. Protected for testing.
  void StartHandlingError();

 private:
  friend class content::WebContentsUserData<SSLErrorHandler>;
  FRIEND_TEST_ALL_PREFIXES(SSLErrorHandlerTest, CalculateOptionsMask);
  FRIEND_TEST_ALL_PREFIXES(SSLErrorHandlerTest,
                           NonPrimaryMainframeShouldNotAffectSSLErrorHandler);
  FRIEND_TEST_ALL_PREFIXES(SSLErrorHandlerNameMismatchTest,
                           ShouldShowCustomInterstitialOnCaptivePortalResult);
  FRIEND_TEST_ALL_PREFIXES(SSLErrorHandlerNameMismatchTest,
                           ShouldShowSSLInterstitialOnNoCaptivePortalResult);

  void ShowCaptivePortalInterstitial(const GURL& landing_url);
  void ShowMITMSoftwareInterstitial(const std::string& mitm_software_name);
  void ShowSSLInterstitial();
  void ShowBadClockInterstitial(const base::Time& now,
                                ssl_errors::ClockState clock_state);
  void ShowDynamicInterstitial(const DynamicInterstitialInfo interstitial);
  void ShowBlockedInterceptionInterstitial();
  void ShowLocalSelfSignedInterstitial();

  // Gets the result of whether the suggested URL is valid. Displays
  // common name mismatch interstitial or ssl interstitial accordingly.
  void CommonNameMismatchHandlerCallback(
      CommonNameMismatchHandler::SuggestedUrlCheckResult result,
      const GURL& suggested_url);

  // Callback invoked with the result of a query for captive portal status.
  void Observe(const captive_portal::CaptivePortalService::Results& results);

  // content::WebContentsObserver:
  void DidStartNavigation(
      content::NavigationHandle* navigation_handle) override;

  // content::WebContentsObserver:
  void NavigationStopped() override;

  // Deletes the SSLErrorHandler. This method is called when the page
  // load stops or when there is a new navigation.
  void DeleteSSLErrorHandler();

  void HandleCertDateInvalidError();
  void HandleCertDateInvalidErrorImpl(base::TimeTicks started_handling_error);

  bool IsOnlyCertError(net::CertStatus only_cert_error_expected) const;

  std::unique_ptr<Delegate> delegate_;
  const net::Error cert_error_;
  const net::SSLInfo ssl_info_;
  const GURL request_url_;
  raw_ptr<network_time::NetworkTimeTracker> network_time_tracker_;

  // The below field is unused if captive portal detection is not enabled,
  // which causes a compiler error.
#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
  raw_ptr<captive_portal::CaptivePortalService, DanglingUntriaged>
      captive_portal_service_;
#endif

  base::CallbackListSubscription subscription_;

  base::OneShotTimer timer_;

  std::unique_ptr<CommonNameMismatchHandler> common_name_mismatch_handler_;

  base::WeakPtrFactory<SSLErrorHandler> weak_ptr_factory_{this};

  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

#endif  // COMPONENTS_SECURITY_INTERSTITIALS_CONTENT_SSL_ERROR_HANDLER_H_
