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

#ifndef COMPONENTS_SAFE_BROWSING_WEBUI_SAFE_BROWSING_UI_H_
#define COMPONENTS_SAFE_BROWSING_WEBUI_SAFE_BROWSING_UI_H_

#include "base/bind.h"
#include "base/macros.h"
#include "components/safe_browsing/browser/safe_browsing_network_context.h"
#include "components/safe_browsing/proto/csd.pb.h"
#include "components/safe_browsing/proto/webui.pb.h"
#include "components/sync/protocol/user_event_specifics.pb.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace base {
class ListValue;
template <typename T>
struct DefaultSingletonTraits;
}  // namespace base

namespace safe_browsing {
class WebUIInfoSingleton;
class ReferrerChainProvider;

class SafeBrowsingUIHandler : public content::WebUIMessageHandler {
 public:
  SafeBrowsingUIHandler(content::BrowserContext* context);
  ~SafeBrowsingUIHandler() override;

  // Callback when Javascript becomes allowed in the WebUI.
  void OnJavascriptAllowed() override;

  // Callback when Javascript becomes disallowed in the WebUI.
  void OnJavascriptDisallowed() override;

  // Get the experiments that are currently enabled per Chrome instance.
  void GetExperiments(const base::ListValue* args);

  // Get the Safe Browsing related preferences for the current user.
  void GetPrefs(const base::ListValue* args);

  // Get the Safe Browsing cookie.
  void GetCookie(const base::ListValue* args);

  // Get the current captured passwords.
  void GetSavedPasswords(const base::ListValue* args);

  // Get the information related to the Safe Browsing database and full hash
  // cache.
  void GetDatabaseManagerInfo(const base::ListValue* args);

  // Get the ClientDownloadRequests that have been collected since the oldest
  // currently open chrome://safe-browsing tab was opened.
  void GetSentClientDownloadRequests(const base::ListValue* args);

  // Get the ClientDownloadReponses that have been collected since the oldest
  // currently open chrome://safe-browsing tab was opened.
  void GetReceivedClientDownloadResponses(const base::ListValue* args);

  // Get the ThreatDetails that have been collected since the oldest currently
  // open chrome://safe-browsing tab was opened.
  void GetSentCSBRRs(const base::ListValue* args);

  // Get the PhishGuard events that have been collected since the oldest
  // currently open chrome://safe-browsing tab was opened.
  void GetPGEvents(const base::ListValue* args);

  // Get the PhishGuard pings that have been sent since the oldest currently
  // open chrome://safe-browsing tab was opened.
  void GetPGPings(const base::ListValue* args);

  // Get the PhishGuard responses that have been received since the oldest
  // currently open chrome://safe-browsing tab was opened.
  void GetPGResponses(const base::ListValue* args);

  // Get the current referrer chain for a given URL.
  void GetReferrerChain(const base::ListValue* args);

  // Get the list of log messages that have been received since the oldest
  // currently open chrome://safe-browsing tab was opened.
  void GetLogMessages(const base::ListValue* args);

  // Register callbacks for WebUI messages.
  void RegisterMessages() override;

  // Sets the WebUI for testing
  void SetWebUIForTesting(content::WebUI* web_ui);

 private:
  friend class WebUIInfoSingleton;

  // Called when any new ClientDownloadRequest messages are sent while one or
  // more WebUI tabs are open.
  void NotifyClientDownloadRequestJsListener(
      ClientDownloadRequest* client_download_request);

  // Called when any new ClientDownloadResponse messages are received while one
  // or more WebUI tabs are open.
  void NotifyClientDownloadResponseJsListener(
      ClientDownloadResponse* client_download_response);

  // Get the new ThreatDetails messages sent from ThreatDetails when a ping is
  // sent, while one or more WebUI tabs are opened.
  void NotifyCSBRRJsListener(ClientSafeBrowsingReportRequest* csbrr);

  // Called when any new PhishGuard events are sent while one or more WebUI tabs
  // are open.
  void NotifyPGEventJsListener(const sync_pb::UserEventSpecifics& event);

  // Called when any new PhishGuard pings are sent while one or more WebUI tabs
  // are open.
  void NotifyPGPingJsListener(int token,
                              const LoginReputationClientRequest& request);

  // Called when any new PhishGuard responses are received while one or more
  // WebUI tabs are open.
  void NotifyPGResponseJsListener(
      int token,
      const LoginReputationClientResponse& response);

  // Called when any new log messages are received while one or more WebUI tabs
  // are open.
  void NotifyLogMessageJsListener(const base::Time& timestamp,
                                  const std::string& message);

  // Callback when the CookieManager has returned the cookie.
  void OnGetCookie(const std::string& callback_id,
                   const std::vector<net::CanonicalCookie>& cookies);

  content::BrowserContext* browser_context_;

  // List that keeps all the WebUI listener objects.
  static std::vector<SafeBrowsingUIHandler*> webui_list_;

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

  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingUIHandler);
};

// The WebUI for chrome://safe-browsing
class SafeBrowsingUI : public content::WebUIController {
 public:
  explicit SafeBrowsingUI(content::WebUI* web_ui);
  ~SafeBrowsingUI() override;

 private:
  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingUI);
};

class WebUIInfoSingleton {
 public:
  static WebUIInfoSingleton* GetInstance();

  // Returns true when there is a listening chrome://safe-browsing tab.
  static bool HasListener();

  // Add the new message in |client_download_requests_sent_| and send it to all
  // the open chrome://safe-browsing tabs.
  void AddToClientDownloadRequestsSent(
      std::unique_ptr<ClientDownloadRequest> report_request);

  // Clear the list of the sent ClientDownloadRequest messages.
  void ClearClientDownloadRequestsSent();

  // Add the new message in |client_download_responses_received_| and send it to
  // all the open chrome://safe-browsing tabs.
  void AddToClientDownloadResponsesReceived(
      std::unique_ptr<ClientDownloadResponse> response);

  // Clear the list of the received ClientDownloadResponse messages.
  void ClearClientDownloadResponsesReceived();

  // Add the new message in |csbrrs_sent_| and send it to all the open
  // chrome://safe-browsing tabs.
  void AddToCSBRRsSent(std::unique_ptr<ClientSafeBrowsingReportRequest> csbrr);

  // Clear the list of the sent ClientSafeBrowsingReportRequest messages.
  void ClearCSBRRsSent();

  // Add the new message in |pg_event_log_| and send it to all the open
  // chrome://safe-browsing tabs.
  void AddToPGEvents(const sync_pb::UserEventSpecifics& event);

  // Clear the list of sent PhishGuard events.
  void ClearPGEvents();

  // Add the new ping to |pg_pings_| and send it to all the open
  // chrome://safe-browsing tabs. Returns a token that can be used in
  // |AddToPGReponses| to correlate a ping and response.
  int AddToPGPings(const LoginReputationClientRequest& request);

  // Add the new response to |pg_responses_| and send it to all the open
  // chrome://safe-browsing tabs.
  void AddToPGResponses(int token,
                        const LoginReputationClientResponse& response);

  // Clear the list of sent PhishGuard pings and responses.
  void ClearPGPings();

  // Log an arbitrary message. Frequently used for debugging.
  void LogMessage(const std::string& message);

  // Clear the log messages.
  void ClearLogMessages();

  // Notify listeners of changes to the log messages. Static to avoid this being
  // called after the destruction of the WebUIInfoSingleton
  static void NotifyLogMessageListeners(const base::Time& timestamp,
                                        const std::string& message);

  // Register the new WebUI listener object.
  void RegisterWebUIInstance(SafeBrowsingUIHandler* webui);

  // Unregister the WebUI listener object, and clean the list of reports, if
  // this is last listener.
  void UnregisterWebUIInstance(SafeBrowsingUIHandler* webui);

  // Get the list of the sent ClientDownloadRequests that have been collected
  // since the oldest currently open chrome://safe-browsing tab was opened.
  const std::vector<std::unique_ptr<ClientDownloadRequest>>&
  client_download_requests_sent() const {
    return client_download_requests_sent_;
  }

  // Get the list of the sent ClientDownloadResponses that have been collected
  // since the oldest currently open chrome://safe-browsing tab was opened.
  const std::vector<std::unique_ptr<ClientDownloadResponse>>&
  client_download_responses_received() const {
    return client_download_responses_received_;
  }

  // Get the list of the sent CSBRR reports that have been collected since the
  // oldest currently open chrome://safe-browsing tab was opened.
  const std::vector<std::unique_ptr<ClientSafeBrowsingReportRequest>>&
  csbrrs_sent() const {
    return csbrrs_sent_;
  }

  // Get the list of WebUI listener objects.
  const std::vector<SafeBrowsingUIHandler*>& webui_instances() const {
    return webui_instances_;
  }

  // Get the list of PhishGuard events since the oldest currently open
  // chrome://safe-browsing tab was opened.
  const std::vector<sync_pb::UserEventSpecifics>& pg_event_log() const {
    return pg_event_log_;
  }

  // Get the list of PhishGuard pings since the oldest currently open
  // chrome://safe-browsing tab was opened.
  const std::vector<LoginReputationClientRequest>& pg_pings() const {
    return pg_pings_;
  }

  // Get the list of PhishGuard pings since the oldest currently open
  // chrome://safe-browsing tab was opened.
  const std::map<int, LoginReputationClientResponse>& pg_responses() const {
    return pg_responses_;
  }

  ReferrerChainProvider* referrer_chain_provider() {
    return referrer_chain_provider_;
  }

  void set_referrer_chain_provider(ReferrerChainProvider* provider) {
    referrer_chain_provider_ = provider;
  }

  const std::vector<std::pair<base::Time, std::string>>& log_messages() {
    return log_messages_;
  }

  network::mojom::CookieManager* GetCookieManager();

  void set_network_context(SafeBrowsingNetworkContext* network_context) {
    network_context_ = network_context;
  }

  void AddListenerForTesting() { has_test_listener_ = true; }

 private:
  WebUIInfoSingleton();
  ~WebUIInfoSingleton();

  void InitializeCookieManager();

  friend struct base::DefaultSingletonTraits<WebUIInfoSingleton>;

  // List of ClientDownloadRequests sent since since the oldest currently open
  // chrome://safe-browsing tab was opened.
  // "ClientDownloadRequests" cannot be const, due to being used by functions
  // that call AllowJavascript(), which is not marked const.
  std::vector<std::unique_ptr<ClientDownloadRequest>>
      client_download_requests_sent_;

  // List of ClientDownloadResponses received since since the oldest currently
  // open chrome://safe-browsing tab was opened. "ClientDownloadReponse" cannot
  // be const, due to being used by functions that call AllowJavascript(), which
  // is not marked const.
  std::vector<std::unique_ptr<ClientDownloadResponse>>
      client_download_responses_received_;

  // List of CSBRRs sent since since the oldest currently open
  // chrome://safe-browsing tab was opened.
  // "ClientSafeBrowsingReportRequest" cannot be const, due to being used by
  // functions that call AllowJavascript(), which is not marked const.
  std::vector<std::unique_ptr<ClientSafeBrowsingReportRequest>> csbrrs_sent_;

  // List of PhishGuard events sent since the oldest currently open
  // chrome://safe-browsing tab was opened.
  std::vector<sync_pb::UserEventSpecifics> pg_event_log_;

  // List of PhishGuard pings sent since the oldest currently open
  // chrome://safe-browsing tab was opened.
  std::vector<LoginReputationClientRequest> pg_pings_;

  // List of PhishGuard responses received since the oldest currently open
  // chrome://safe-browsing tab was opened.
  std::map<int, LoginReputationClientResponse> pg_responses_;

  // List of WebUI listener objects. "SafeBrowsingUIHandler*" cannot be const,
  // due to being used by functions that call AllowJavascript(), which is not
  // marked const.
  std::vector<SafeBrowsingUIHandler*> webui_instances_;

  // List of messages logged since the oldest currently open
  // chrome://safe-browsing tab was opened.
  std::vector<std::pair<base::Time, std::string>> log_messages_;

  // The current referrer chain provider, if any. Can be nullptr.
  ReferrerChainProvider* referrer_chain_provider_ = nullptr;

  // The current NetworkContext for Safe Browsing pings.
  SafeBrowsingNetworkContext* network_context_ = nullptr;

  // The current CookieManager for the Safe Browsing cookie.
  network::mojom::CookieManagerPtr cookie_manager_ptr_ = nullptr;

  // Whether there is a test listener.
  bool has_test_listener_ = false;

  DISALLOW_COPY_AND_ASSIGN(WebUIInfoSingleton);
};

// Used for streaming messages to the WebUIInfoSingleton. Collects streamed
// messages, then sends them to the WebUIInfoSingleton when destroyed. Intended
// to be used in CRSBLOG macro.
class CrSBLogMessage {
 public:
  CrSBLogMessage();
  ~CrSBLogMessage();

  std::ostream& stream() { return stream_; }

 private:
  std::ostringstream stream_;
};

// Used to consume a stream so that we don't even evaluate the streamed data if
// there are no chrome://safe-browsing tabs open.
class CrSBLogVoidify {
 public:
  CrSBLogVoidify() = default;

  // This has to be an operator with a precedence lower than <<,
  // but higher than ?:
  void operator&(std::ostream&) {}
};

#define CRSBLOG                                         \
  (!::safe_browsing::WebUIInfoSingleton::HasListener()) \
      ? static_cast<void>(0)                            \
      : ::safe_browsing::CrSBLogVoidify() &             \
            ::safe_browsing::CrSBLogMessage().stream()

}  // namespace safe_browsing

#endif  // COMPONENTS_SAFE_BROWSING_WEBUI_SAFE_BROWSING_UI_H_
