// 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.

#include "components/safe_browsing/web_ui/safe_browsing_ui.h"

#include <stddef.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/base64url.h"
#include "base/callback.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_string_value_serializer.h"
#include "base/memory/ref_counted.h"
#include "base/memory/singleton.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/grit/components_resources.h"
#include "components/grit/components_scaled_resources.h"
#include "components/password_manager/core/browser/hash_password_manager.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/safe_browsing/features.h"
#include "components/safe_browsing/web_ui/constants.h"
#include "components/strings/grit/components_strings.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"

#if SAFE_BROWSING_DB_LOCAL
#include "components/safe_browsing/db/v4_local_database_manager.h"
#endif

using base::Time;
using PasswordReuseLookup =
    sync_pb::UserEventSpecifics::GaiaPasswordReuse::PasswordReuseLookup;
using PasswordReuseDetected =
    sync_pb::UserEventSpecifics::GaiaPasswordReuse::PasswordReuseDetected;
using PasswordReuseDialogInteraction = sync_pb::UserEventSpecifics::
    GaiaPasswordReuse::PasswordReuseDialogInteraction;

namespace safe_browsing {
WebUIInfoSingleton::WebUIInfoSingleton() = default;

WebUIInfoSingleton::~WebUIInfoSingleton() = default;

// static
WebUIInfoSingleton* WebUIInfoSingleton::GetInstance() {
  return base::Singleton<WebUIInfoSingleton>::get();
}

// static
bool WebUIInfoSingleton::HasListener() {
  return !GetInstance()->webui_instances_.empty();
}

void WebUIInfoSingleton::AddToClientDownloadRequestsSent(
    std::unique_ptr<ClientDownloadRequest> client_download_request) {
  if (webui_instances_.empty())
    return;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyClientDownloadRequestJsListener(
        client_download_request.get());
  client_download_requests_sent_.push_back(std::move(client_download_request));
}

void WebUIInfoSingleton::ClearClientDownloadRequestsSent() {
  std::vector<std::unique_ptr<ClientDownloadRequest>>().swap(
      client_download_requests_sent_);
}

void WebUIInfoSingleton::AddToClientDownloadResponsesReceived(
    std::unique_ptr<ClientDownloadResponse> client_download_response) {
  if (webui_instances_.empty())
    return;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyClientDownloadResponseJsListener(
        client_download_response.get());
  client_download_responses_received_.push_back(
      std::move(client_download_response));
}

void WebUIInfoSingleton::ClearClientDownloadResponsesReceived() {
  std::vector<std::unique_ptr<ClientDownloadResponse>>().swap(
      client_download_responses_received_);
}

void WebUIInfoSingleton::AddToCSBRRsSent(
    std::unique_ptr<ClientSafeBrowsingReportRequest> csbrr) {
  if (webui_instances_.empty())
    return;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyCSBRRJsListener(csbrr.get());
  csbrrs_sent_.push_back(std::move(csbrr));
}

void WebUIInfoSingleton::ClearCSBRRsSent() {
  std::vector<std::unique_ptr<ClientSafeBrowsingReportRequest>>().swap(
      csbrrs_sent_);
}

void WebUIInfoSingleton::AddToPGEvents(
    const sync_pb::UserEventSpecifics& event) {
  if (webui_instances_.empty())
    return;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyPGEventJsListener(event);

  pg_event_log_.push_back(event);
}

void WebUIInfoSingleton::ClearPGEvents() {
  std::vector<sync_pb::UserEventSpecifics>().swap(pg_event_log_);
}

int WebUIInfoSingleton::AddToPGPings(
    const LoginReputationClientRequest& request) {
  if (webui_instances_.empty())
    return -1;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyPGPingJsListener(pg_pings_.size(), request);

  pg_pings_.push_back(request);

  return pg_pings_.size() - 1;
}

void WebUIInfoSingleton::AddToPGResponses(
    int token,
    const LoginReputationClientResponse& response) {
  if (webui_instances_.empty())
    return;

  for (auto* webui_listener : webui_instances_)
    webui_listener->NotifyPGResponseJsListener(token, response);

  pg_responses_[token] = response;
}

void WebUIInfoSingleton::ClearPGPings() {
  std::vector<LoginReputationClientRequest>().swap(pg_pings_);
  std::map<int, LoginReputationClientResponse>().swap(pg_responses_);
}

void WebUIInfoSingleton::RegisterWebUIInstance(SafeBrowsingUIHandler* webui) {
  webui_instances_.push_back(webui);
}

void WebUIInfoSingleton::UnregisterWebUIInstance(SafeBrowsingUIHandler* webui) {
  webui_instances_.erase(
      std::remove(webui_instances_.begin(), webui_instances_.end(), webui),
      webui_instances_.end());
  if (webui_instances_.empty()) {
    ClearCSBRRsSent();
    ClearClientDownloadRequestsSent();
    ClearPGEvents();
  }
}

namespace {
#if SAFE_BROWSING_DB_LOCAL

base::Value UserReadableTimeFromMillisSinceEpoch(int64_t time_in_milliseconds) {
  base::Time time = base::Time::UnixEpoch() +
                    base::TimeDelta::FromMilliseconds(time_in_milliseconds);
  return base::Value(
      base::UTF16ToASCII(base::TimeFormatShortDateAndTime(time)));
}

void AddStoreInfo(const DatabaseManagerInfo::DatabaseInfo::StoreInfo store_info,
                  base::ListValue* database_info_list) {
  if (store_info.has_file_size_bytes() && store_info.has_file_name()) {
    database_info_list->GetList().push_back(
        base::Value(store_info.file_name()));
    database_info_list->GetList().push_back(
        base::Value(static_cast<double>(store_info.file_size_bytes())));
  }
  if (store_info.has_update_status()) {
    database_info_list->GetList().push_back(base::Value("Store update status"));
    database_info_list->GetList().push_back(
        base::Value(store_info.update_status()));
  }
  if (store_info.has_last_apply_update_time_millis()) {
    database_info_list->GetList().push_back(base::Value("Last update time"));
    database_info_list->GetList().push_back(
        UserReadableTimeFromMillisSinceEpoch(
            store_info.last_apply_update_time_millis()));
  }
  if (store_info.has_checks_attempted()) {
    database_info_list->GetList().push_back(
        base::Value("Number of database checks"));
    database_info_list->GetList().push_back(
        base::Value(static_cast<int>(store_info.checks_attempted())));
  }
}

void AddDatabaseInfo(const DatabaseManagerInfo::DatabaseInfo database_info,
                     base::ListValue* database_info_list) {
  if (database_info.has_database_size_bytes()) {
    database_info_list->GetList().push_back(
        base::Value("Database size in bytes"));
    database_info_list->GetList().push_back(
        base::Value(static_cast<double>(database_info.database_size_bytes())));
  }

  // Add the information specific to each store.
  for (int i = 0; i < database_info.store_info_size(); i++) {
    AddStoreInfo(database_info.store_info(i), database_info_list);
  }
}

void AddUpdateInfo(const DatabaseManagerInfo::UpdateInfo update_info,
                   base::ListValue* database_info_list) {
  if (update_info.has_network_status_code()) {
    // Network status of the last GetUpdate().
    database_info_list->GetList().push_back(
        base::Value("Last update network status code"));
    database_info_list->GetList().push_back(
        base::Value(update_info.network_status_code()));
  }
  if (update_info.has_last_update_time_millis()) {
    database_info_list->GetList().push_back(base::Value("Last update time"));
    database_info_list->GetList().push_back(
        UserReadableTimeFromMillisSinceEpoch(
            update_info.last_update_time_millis()));
  }
}

void ParseFullHashInfo(
    const FullHashCacheInfo::FullHashCache::CachedHashPrefixInfo::FullHashInfo
        full_hash_info,
    base::DictionaryValue* full_hash_info_dict) {
  if (full_hash_info.has_positive_expiry()) {
    full_hash_info_dict->SetString(
        "Positive expiry",
        UserReadableTimeFromMillisSinceEpoch(full_hash_info.positive_expiry())
            .GetString());
  }
  if (full_hash_info.has_full_hash()) {
    std::string full_hash;
    base::Base64UrlEncode(full_hash_info.full_hash(),
                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                          &full_hash);
    full_hash_info_dict->SetString("Full hash (base64)", full_hash);
  }
  if (full_hash_info.list_identifier().has_platform_type()) {
    full_hash_info_dict->SetInteger(
        "platform_type", full_hash_info.list_identifier().platform_type());
  }
  if (full_hash_info.list_identifier().has_threat_entry_type()) {
    full_hash_info_dict->SetInteger(
        "threat_entry_type",
        full_hash_info.list_identifier().threat_entry_type());
  }
  if (full_hash_info.list_identifier().has_threat_type()) {
    full_hash_info_dict->SetInteger(
        "threat_type", full_hash_info.list_identifier().threat_type());
  }
}

void ParseFullHashCache(const FullHashCacheInfo::FullHashCache full_hash_cache,
                        base::ListValue* full_hash_cache_list) {
  base::DictionaryValue full_hash_cache_parsed;

  if (full_hash_cache.has_hash_prefix()) {
    std::string hash_prefix;
    base::Base64UrlEncode(full_hash_cache.hash_prefix(),
                          base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                          &hash_prefix);
    full_hash_cache_parsed.SetString("Hash prefix (base64)", hash_prefix);
  }
  if (full_hash_cache.cached_hash_prefix_info().has_negative_expiry()) {
    full_hash_cache_parsed.SetString(
        "Negative expiry",
        UserReadableTimeFromMillisSinceEpoch(
            full_hash_cache.cached_hash_prefix_info().negative_expiry())
            .GetString());
  }

  full_hash_cache_list->GetList().push_back(std::move(full_hash_cache_parsed));

  for (auto full_hash_info_it :
       full_hash_cache.cached_hash_prefix_info().full_hash_info()) {
    base::DictionaryValue full_hash_info_dict;
    ParseFullHashInfo(full_hash_info_it, &full_hash_info_dict);
    full_hash_cache_list->GetList().push_back(std::move(full_hash_info_dict));
  }
}

void ParseFullHashCacheInfo(const FullHashCacheInfo full_hash_cache_info_proto,
                            base::ListValue* full_hash_cache_info) {
  if (full_hash_cache_info_proto.has_number_of_hits()) {
    base::DictionaryValue number_of_hits;
    number_of_hits.SetInteger("Number of cache hits",
                              full_hash_cache_info_proto.number_of_hits());
    full_hash_cache_info->GetList().push_back(std::move(number_of_hits));
  }

  // Record FullHashCache list.
  for (auto full_hash_cache_it : full_hash_cache_info_proto.full_hash_cache()) {
    base::ListValue full_hash_cache_list;
    ParseFullHashCache(full_hash_cache_it, &full_hash_cache_list);
    full_hash_cache_info->GetList().push_back(std::move(full_hash_cache_list));
  }
}

std::string AddFullHashCacheInfo(
    const FullHashCacheInfo full_hash_cache_info_proto) {
  std::string full_hash_cache_parsed;

  base::ListValue full_hash_cache;
  ParseFullHashCacheInfo(full_hash_cache_info_proto, &full_hash_cache);

  base::Value* full_hash_cache_tree = &full_hash_cache;

  JSONStringValueSerializer serializer(&full_hash_cache_parsed);
  serializer.set_pretty_print(true);
  serializer.Serialize(*full_hash_cache_tree);

  return full_hash_cache_parsed;
}

#endif

std::string SerializeClientDownloadRequest(const ClientDownloadRequest& cdr) {
  base::DictionaryValue dict;
  if (cdr.has_url())
    dict.SetString("url", cdr.url());
  if (cdr.has_download_type())
    dict.SetInteger("download_type", cdr.download_type());
  if (cdr.has_length())
    dict.SetInteger("length", cdr.length());
  if (cdr.has_file_basename())
    dict.SetString("file_basename", cdr.file_basename());
  if (cdr.has_archive_valid())
    dict.SetBoolean("archive_valid", cdr.archive_valid());

  auto archived_binaries = std::make_unique<base::ListValue>();
  for (const auto& archived_binary : cdr.archived_binary()) {
    auto dict_archived_binary = std::make_unique<base::DictionaryValue>();
    if (archived_binary.has_file_basename())
      dict_archived_binary->SetString("file_basename",
                                      archived_binary.file_basename());
    if (archived_binary.has_download_type())
      dict_archived_binary->SetInteger("download_type",
                                       archived_binary.download_type());
    if (archived_binary.has_length())
      dict_archived_binary->SetInteger("length", archived_binary.length());
    archived_binaries->Append(std::move(dict_archived_binary));
  }
  dict.SetList("archived_binary", std::move(archived_binaries));

  base::Value* request_tree = &dict;
  std::string request_serialized;
  JSONStringValueSerializer serializer(&request_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(*request_tree);
  return request_serialized;
}

std::string SerializeClientDownloadResponse(const ClientDownloadResponse& cdr) {
  base::DictionaryValue dict;

  switch (cdr.verdict()) {
    case ClientDownloadResponse::SAFE:
      dict.SetPath({"verdict"}, base::Value("SAFE"));
      break;
    case ClientDownloadResponse::DANGEROUS:
      dict.SetPath({"verdict"}, base::Value("DANGEROUS"));
      break;
    case ClientDownloadResponse::UNCOMMON:
      dict.SetPath({"verdict"}, base::Value("UNCOMMON"));
      break;
    case ClientDownloadResponse::POTENTIALLY_UNWANTED:
      dict.SetPath({"verdict"}, base::Value("POTENTIALLY_UNWANTED"));
      break;
    case ClientDownloadResponse::DANGEROUS_HOST:
      dict.SetPath({"verdict"}, base::Value("DANGEROUS_HOST"));
      break;
    case ClientDownloadResponse::UNKNOWN:
      dict.SetPath({"verdict"}, base::Value("UNKNOWN"));
      break;
  }

  if (cdr.has_more_info()) {
    dict.SetPath({"more_info", "description"},
                 base::Value(cdr.more_info().description()));
    dict.SetPath({"more_info", "url"}, base::Value(cdr.more_info().url()));
  }

  if (cdr.has_token()) {
    dict.SetPath({"token"}, base::Value(cdr.token()));
  }

  if (cdr.has_upload()) {
    dict.SetPath({"upload"}, base::Value(cdr.upload()));
  }

  base::Value* request_tree = &dict;
  std::string request_serialized;
  JSONStringValueSerializer serializer(&request_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(*request_tree);
  return request_serialized;
}

std::string SerializeCSBRR(const ClientSafeBrowsingReportRequest& report) {
  base::DictionaryValue report_request;
  if (report.has_type()) {
    report_request.SetInteger("type", static_cast<int>(report.type()));
  }
  if (report.has_page_url())
    report_request.SetString("page_url", report.page_url());
  if (report.has_client_country()) {
    report_request.SetString("client_country", report.client_country());
  }
  if (report.has_repeat_visit()) {
    report_request.SetInteger("repeat_visit", report.repeat_visit());
  }
  if (report.has_did_proceed()) {
    report_request.SetInteger("did_proceed", report.did_proceed());
  }
  std::string serialized;
  if (report.SerializeToString(&serialized)) {
    std::string base64_encoded;
    base::Base64Encode(serialized, &base64_encoded);
    report_request.SetString("csbrr(base64)", base64_encoded);
  }

  base::Value* report_request_tree = &report_request;
  std::string report_request_serialized;
  JSONStringValueSerializer serializer(&report_request_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(*report_request_tree);
  return report_request_serialized;
}

base::DictionaryValue SerializePGEvent(
    const sync_pb::UserEventSpecifics& event) {
  base::DictionaryValue result;

  base::Time timestamp = base::Time::FromDeltaSinceWindowsEpoch(
      base::TimeDelta::FromMicroseconds(event.event_time_usec()));
  result.SetDouble("time", timestamp.ToJsTime());

  base::DictionaryValue event_dict;

  sync_pb::UserEventSpecifics::GaiaPasswordReuse reuse =
      event.gaia_password_reuse_event();
  if (reuse.has_reuse_detected()) {
    event_dict.SetPath({"reuse_detected", "status", "enabled"},
                       base::Value(reuse.reuse_detected().status().enabled()));

    std::string reporting_population;
    switch (
        reuse.reuse_detected().status().safe_browsing_reporting_population()) {
      case PasswordReuseDetected::SafeBrowsingStatus::
          REPORTING_POPULATION_UNSPECIFIED:
        reporting_population = "REPORTING_POPULATION_UNSPECIFIED";
        break;
      case PasswordReuseDetected::SafeBrowsingStatus::NONE:
        reporting_population = "NONE";
        break;
      case PasswordReuseDetected::SafeBrowsingStatus::EXTENDED_REPORTING:
        reporting_population = "EXTENDED_REPORTING";
        break;
      case PasswordReuseDetected::SafeBrowsingStatus::SCOUT:
        reporting_population = "SCOUT";
        break;
    }
    event_dict.SetPath({"reuse_detected", "status", "reporting_population"},
                       base::Value(reporting_population));
  }

  if (reuse.has_reuse_lookup()) {
    std::string lookup_result;
    switch (reuse.reuse_lookup().lookup_result()) {
      case PasswordReuseLookup::UNSPECIFIED:
        lookup_result = "UNSPECIFIED";
        break;
      case PasswordReuseLookup::WHITELIST_HIT:
        lookup_result = "WHITELIST_HIT";
        break;
      case PasswordReuseLookup::CACHE_HIT:
        lookup_result = "CACHE_HIT";
        break;
      case PasswordReuseLookup::REQUEST_SUCCESS:
        lookup_result = "REQUEST_SUCCESS";
        break;
      case PasswordReuseLookup::REQUEST_FAILURE:
        lookup_result = "REQUEST_FAILURE";
        break;
      case PasswordReuseLookup::URL_UNSUPPORTED:
        lookup_result = "URL_UNSUPPORTED";
        break;
      case PasswordReuseLookup::ENTERPRISE_WHITELIST_HIT:
        lookup_result = "ENTERPRISE_WHITELIST_HIT";
        break;
      case PasswordReuseLookup::TURNED_OFF_BY_POLICY:
        lookup_result = "TURNED_OFF_BY_POLICY";
        break;
    }
    event_dict.SetPath({"reuse_lookup", "lookup_result"},
                       base::Value(lookup_result));

    std::string verdict;
    switch (reuse.reuse_lookup().verdict()) {
      case PasswordReuseLookup::VERDICT_UNSPECIFIED:
        verdict = "VERDICT_UNSPECIFIED";
        break;
      case PasswordReuseLookup::SAFE:
        verdict = "SAFE";
        break;
      case PasswordReuseLookup::LOW_REPUTATION:
        verdict = "LOW_REPUTATION";
        break;
      case PasswordReuseLookup::PHISHING:
        verdict = "PHISHING";
        break;
    }
    event_dict.SetPath({"reuse_lookup", "verdict"}, base::Value(verdict));
    event_dict.SetPath({"reuse_lookup", "verdict_token"},
                       base::Value(reuse.reuse_lookup().verdict_token()));
  }

  if (reuse.has_dialog_interaction()) {
    std::string interaction_result;
    switch (reuse.dialog_interaction().interaction_result()) {
      case PasswordReuseDialogInteraction::UNSPECIFIED:
        interaction_result = "UNSPECIFIED";
        break;
      case PasswordReuseDialogInteraction::WARNING_ACTION_TAKEN:
        interaction_result = "WARNING_ACTION_TAKEN";
        break;
      case PasswordReuseDialogInteraction::WARNING_ACTION_IGNORED:
        interaction_result = "WARNING_ACTION_IGNORED";
        break;
      case PasswordReuseDialogInteraction::WARNING_UI_IGNORED:
        interaction_result = "WARNING_UI_IGNORED";
        break;
    }
    event_dict.SetPath({"dialog_interaction", "interaction_result"},
                       base::Value(interaction_result));
  }

  std::string event_serialized;
  JSONStringValueSerializer serializer(&event_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(event_dict);
  result.SetString("message", event_serialized);
  return result;
}

base::Value SerializeReferrer(const ReferrerChainEntry& referrer) {
  base::DictionaryValue referrer_dict;
  referrer_dict.SetKey("url", base::Value(referrer.url()));
  referrer_dict.SetKey("main_frame_url",
                       base::Value(referrer.main_frame_url()));

  std::string url_type;
  switch (referrer.type()) {
    case ReferrerChainEntry::EVENT_URL:
      url_type = "EVENT_URL";
      break;
    case ReferrerChainEntry::LANDING_PAGE:
      url_type = "LANDING_PAGE";
      break;
    case ReferrerChainEntry::LANDING_REFERRER:
      url_type = "LANDING_REFERRER";
      break;
    case ReferrerChainEntry::CLIENT_REDIRECT:
      url_type = "CLIENT_REDIRECT";
      break;
    case ReferrerChainEntry::DEPRECATED_SERVER_REDIRECT:
      url_type = "DEPRECATED_SERVER_REDIRECT";
      break;
    case ReferrerChainEntry::RECENT_NAVIGATION:
      url_type = "RECENT_NAVIGATION";
      break;
  }
  referrer_dict.SetKey("type", base::Value(url_type));

  base::ListValue ip_addresses;
  for (const std::string& ip_address : referrer.ip_addresses()) {
    ip_addresses.GetList().push_back(base::Value(ip_address));
  }
  referrer_dict.SetKey("ip_addresses", std::move(ip_addresses));

  referrer_dict.SetKey("referrer_url", base::Value(referrer.referrer_url()));

  referrer_dict.SetKey("referrer_main_frame_url",
                       base::Value(referrer.referrer_main_frame_url()));

  referrer_dict.SetKey("is_retargeting",
                       base::Value(referrer.is_retargeting()));

  referrer_dict.SetKey("navigation_time_msec",
                       base::Value(referrer.navigation_time_msec()));

  base::ListValue server_redirects;
  for (const ReferrerChainEntry::ServerRedirect& server_redirect :
       referrer.server_redirect_chain()) {
    server_redirects.GetList().push_back(base::Value(server_redirect.url()));
  }
  referrer_dict.SetKey("server_redirect_chain", std::move(server_redirects));

  std::string navigation_initiation;
  switch (referrer.navigation_initiation()) {
    case ReferrerChainEntry::UNDEFINED:
      navigation_initiation = "UNDEFINED";
      break;
    case ReferrerChainEntry::BROWSER_INITIATED:
      navigation_initiation = "BROWSER_INITIATED";
      break;
    case ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE:
      navigation_initiation = "RENDERER_INITIATED_WITHOUT_USER_GESTURE";
      break;
    case ReferrerChainEntry::RENDERER_INITIATED_WITH_USER_GESTURE:
      navigation_initiation = "RENDERER_INITIATED_WITH_USER_GESTURE";
      break;
  }
  referrer_dict.SetKey("navigation_initiation",
                       base::Value(navigation_initiation));

  referrer_dict.SetKey(
      "maybe_launched_by_external_application",
      base::Value(referrer.maybe_launched_by_external_application()));

  return std::move(referrer_dict);
}

base::Value SerializeFrame(const LoginReputationClientRequest::Frame& frame) {
  base::DictionaryValue frame_dict;
  frame_dict.SetKey("frame_index", base::Value(frame.frame_index()));
  frame_dict.SetKey("parent_frame_index",
                    base::Value(frame.parent_frame_index()));
  frame_dict.SetKey("url", base::Value(frame.url()));
  frame_dict.SetKey("has_password_field",
                    base::Value(frame.has_password_field()));

  base::ListValue referrer_list;
  for (const ReferrerChainEntry& referrer : frame.referrer_chain()) {
    referrer_list.GetList().push_back(SerializeReferrer(referrer));
  }
  frame_dict.SetKey("referrer_chain", std::move(referrer_list));

  frame_dict.SetPath(
      {"referrer_chain_options", "recent_navigations_to_collect"},
      base::Value(
          frame.referrer_chain_options().recent_navigations_to_collect()));

  base::ListValue form_list;
  for (const LoginReputationClientRequest::Frame::Form& form : frame.forms()) {
    base::DictionaryValue form_dict;
    form_dict.SetKey("action_url", base::Value(form.action_url()));
    form_dict.SetKey("has_password_field",
                     base::Value(form.has_password_field()));
    form_list.GetList().push_back(std::move(form_dict));
  }
  frame_dict.SetKey("forms", std::move(form_list));

  return std::move(frame_dict);
}

base::Value SerializePasswordReuseEvent(
    const LoginReputationClientRequest::PasswordReuseEvent& event) {
  base::DictionaryValue event_dict;

  base::ListValue domains_list;
  for (const std::string& domain : event.domains_matching_password()) {
    domains_list.GetList().push_back(base::Value(domain));
  }
  event_dict.SetKey("domains_matching_password", std::move(domains_list));

  event_dict.SetKey("frame_id", base::Value(event.frame_id()));
  event_dict.SetKey("is_chrome_signin_password",
                    base::Value(event.is_chrome_signin_password()));

  std::string sync_account_type;
  switch (event.sync_account_type()) {
    case LoginReputationClientRequest::PasswordReuseEvent::NOT_SIGNED_IN:
      sync_account_type = "NOT_SIGNED_IN";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::GMAIL:
      sync_account_type = "GMAIL";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::GSUITE:
      sync_account_type = "GSUITE";
      break;
  }
  event_dict.SetKey("sync_account_type", base::Value(sync_account_type));

  std::string reused_password_type;
  switch (event.reused_password_type()) {
    case LoginReputationClientRequest::PasswordReuseEvent::
        REUSED_PASSWORD_TYPE_UNKNOWN:
      reused_password_type = "REUSED_PASSWORD_TYPE_UNKNOWN";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::SAVED_PASSWORD:
      reused_password_type = "SAVED_PASSWORD";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::SIGN_IN_PASSWORD:
      reused_password_type = "SIGN_IN_PASSWORD";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::OTHER_GAIA_PASSWORD:
      reused_password_type = "OTHER_GAIA_PASSWORD";
      break;
    case LoginReputationClientRequest::PasswordReuseEvent::ENTERPRISE_PASSWORD:
      reused_password_type = "ENTERPRISE_PASSWORD";
      break;
  }
  event_dict.SetKey("reused_password_type", base::Value(reused_password_type));

  return std::move(event_dict);
}

base::Value SerializeChromeUserPopulation(
    const ChromeUserPopulation& population) {
  base::DictionaryValue population_dict;

  std::string user_population;
  switch (population.user_population()) {
    case ChromeUserPopulation::UNKNOWN_USER_POPULATION:
      user_population = "REUSED_PASSWORD_TYPE_UNKNOWN";
      break;
    case ChromeUserPopulation::SAFE_BROWSING:
      user_population = "SAFE_BROWSING";
      break;
    case ChromeUserPopulation::EXTENDED_REPORTING:
      user_population = "EXTENDED_REPORTING";
      break;
  }
  population_dict.SetKey("user_population", base::Value(user_population));

  population_dict.SetKey("is_history_sync_enabled",
                         base::Value(population.is_history_sync_enabled()));

  base::ListValue finch_list;
  for (const std::string& finch_group : population.finch_active_groups()) {
    finch_list.GetList().push_back(base::Value(finch_group));
  }
  population_dict.SetKey("finch_active_groups", std::move(finch_list));

  std::string management_status;
  switch (population.profile_management_status()) {
    case ChromeUserPopulation::UNKNOWN:
      management_status = "UNKNOWN";
      break;
    case ChromeUserPopulation::UNAVAILABLE:
      management_status = "UNAVAILABLE";
      break;
    case ChromeUserPopulation::NOT_MANAGED:
      management_status = "NOT_MANAGED";
      break;
    case ChromeUserPopulation::ENTERPRISE_MANAGED:
      management_status = "ENTERPRISE_MANAGED";
      break;
  }
  population_dict.SetKey("profile_management_status",
                         base::Value(management_status));

  return std::move(population_dict);
}

std::string SerializePGPing(const LoginReputationClientRequest& request) {
  base::DictionaryValue request_dict;

  request_dict.SetKey("page_url", base::Value(request.page_url()));

  std::string trigger_type;
  switch (request.trigger_type()) {
    case LoginReputationClientRequest::TRIGGER_TYPE_UNSPECIFIED:
      trigger_type = "TRIGGER_TYPE_UNSPECIFIED";
      break;
    case LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE:
      trigger_type = "UNFAMILIAR_LOGIN_PAGE";
      break;
    case LoginReputationClientRequest::PASSWORD_REUSE_EVENT:
      trigger_type = "PASSWORD_REUSE_EVENT";
      break;
  }
  request_dict.SetKey("trigger_type", base::Value(trigger_type));

  base::ListValue frames_list;
  for (const LoginReputationClientRequest::Frame& frame : request.frames()) {
    frames_list.GetList().push_back(SerializeFrame(frame));
  }
  request_dict.SetKey("frames", std::move(frames_list));

  request_dict.SetKey(
      "password_reuse_event",
      SerializePasswordReuseEvent(request.password_reuse_event()));
  request_dict.SetKey("stored_verdict_cnt",
                      base::Value(request.stored_verdict_cnt()));
  request_dict.SetKey("population",
                      SerializeChromeUserPopulation(request.population()));
  request_dict.SetKey("clicked_through_interstitial",
                      base::Value(request.clicked_through_interstitial()));
  request_dict.SetKey("content_type", base::Value(request.content_type()));

  std::string request_serialized;
  JSONStringValueSerializer serializer(&request_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(request_dict);
  return request_serialized;
}

std::string SerializePGResponse(const LoginReputationClientResponse& response) {
  base::DictionaryValue response_dict;

  std::string verdict;
  switch (response.verdict_type()) {
    case LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED:
      verdict = "VERDICT_TYPE_UNSPECIFIED";
      break;
    case LoginReputationClientResponse::SAFE:
      verdict = "SAFE";
      break;
    case LoginReputationClientResponse::LOW_REPUTATION:
      verdict = "LOW_REPUTATION";
      break;
    case LoginReputationClientResponse::PHISHING:
      verdict = "PHISHING";
      break;
  }
  response_dict.SetKey("verdict_type", base::Value(verdict));
  response_dict.SetKey("cache_duration_sec",
                       base::Value(int(response.cache_duration_sec())));
  response_dict.SetKey("cache_expression",
                       base::Value(response.cache_expression()));
  response_dict.SetKey("verdict_token", base::Value(response.verdict_token()));

  std::string response_serialized;
  JSONStringValueSerializer serializer(&response_serialized);
  serializer.set_pretty_print(true);
  serializer.Serialize(response_dict);
  return response_serialized;
}

}  // namespace

SafeBrowsingUI::SafeBrowsingUI(content::WebUI* web_ui)
    : content::WebUIController(web_ui) {
  // Set up the chrome://safe-browsing source.

  content::WebUIDataSource* html_source = content::WebUIDataSource::Create(
      safe_browsing::kChromeUISafeBrowsingHost);

  content::BrowserContext* browser_context =
      web_ui->GetWebContents()->GetBrowserContext();

  // Register callback handler.
  // Handles messages from JavaScript to C++ via chrome.send().
  web_ui->AddMessageHandler(
      std::make_unique<SafeBrowsingUIHandler>(browser_context));

  // Add required resources.
  html_source->AddResourcePath("safe_browsing.css", IDR_SAFE_BROWSING_CSS);
  html_source->AddResourcePath("safe_browsing.js", IDR_SAFE_BROWSING_JS);
  html_source->SetDefaultResource(IDR_SAFE_BROWSING_HTML);
  html_source->UseGzip();

  content::WebUIDataSource::Add(browser_context, html_source);
}

SafeBrowsingUI::~SafeBrowsingUI() {}

SafeBrowsingUIHandler::SafeBrowsingUIHandler(content::BrowserContext* context)
    : browser_context_(context) {
  WebUIInfoSingleton::GetInstance()->RegisterWebUIInstance(this);
}

SafeBrowsingUIHandler::~SafeBrowsingUIHandler() {
  WebUIInfoSingleton::GetInstance()->UnregisterWebUIInstance(this);
}

void SafeBrowsingUIHandler::GetExperiments(const base::ListValue* args) {
  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), GetFeatureStatusList());
}

void SafeBrowsingUIHandler::GetPrefs(const base::ListValue* args) {
  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id),
                            safe_browsing::GetSafeBrowsingPreferencesList(
                                user_prefs::UserPrefs::Get(browser_context_)));
}

void SafeBrowsingUIHandler::GetSavedPasswords(const base::ListValue* args) {
  password_manager::HashPasswordManager hash_manager(
      user_prefs::UserPrefs::Get(browser_context_));

  base::ListValue saved_passwords;
  for (const password_manager::PasswordHashData& hash_data :
       hash_manager.RetrieveAllPasswordHashes()) {
    saved_passwords.AppendString(hash_data.username);
    saved_passwords.AppendBoolean(hash_data.is_gaia_password);
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), saved_passwords);
}

void SafeBrowsingUIHandler::GetDatabaseManagerInfo(
    const base::ListValue* args) {
  base::ListValue database_manager_info;

#if SAFE_BROWSING_DB_LOCAL
  const V4LocalDatabaseManager* local_database_manager_instance =
      V4LocalDatabaseManager::current_local_database_manager();
  if (local_database_manager_instance) {
    DatabaseManagerInfo database_manager_info_proto;
    FullHashCacheInfo full_hash_cache_info_proto;

    local_database_manager_instance->CollectDatabaseManagerInfo(
        &database_manager_info_proto, &full_hash_cache_info_proto);

    if (database_manager_info_proto.has_update_info()) {
      AddUpdateInfo(database_manager_info_proto.update_info(),
                    &database_manager_info);
    }
    if (database_manager_info_proto.has_database_info()) {
      AddDatabaseInfo(database_manager_info_proto.database_info(),
                      &database_manager_info);
    }

    database_manager_info.GetList().push_back(
        base::Value(AddFullHashCacheInfo(full_hash_cache_info_proto)));
  }
#endif

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);

  ResolveJavascriptCallback(base::Value(callback_id), database_manager_info);
}

void SafeBrowsingUIHandler::GetSentClientDownloadRequests(
    const base::ListValue* args) {
  const std::vector<std::unique_ptr<ClientDownloadRequest>>& cdrs =
      WebUIInfoSingleton::GetInstance()->client_download_requests_sent();

  base::ListValue cdrs_sent;

  for (const auto& cdr : cdrs) {
    cdrs_sent.GetList().push_back(
        base::Value(SerializeClientDownloadRequest(*cdr)));
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), cdrs_sent);
}

void SafeBrowsingUIHandler::GetReceivedClientDownloadResponses(
    const base::ListValue* args) {
  const std::vector<std::unique_ptr<ClientDownloadResponse>>& cdrs =
      WebUIInfoSingleton::GetInstance()->client_download_responses_received();

  base::ListValue cdrs_received;

  for (const auto& cdr : cdrs) {
    cdrs_received.GetList().push_back(
        base::Value(SerializeClientDownloadResponse(*cdr)));
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), cdrs_received);
}

void SafeBrowsingUIHandler::GetSentCSBRRs(const base::ListValue* args) {
  const std::vector<std::unique_ptr<ClientSafeBrowsingReportRequest>>& reports =
      WebUIInfoSingleton::GetInstance()->csbrrs_sent();

  base::ListValue sent_reports;

  for (const auto& report : reports) {
    sent_reports.GetList().push_back(base::Value(SerializeCSBRR(*report)));
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), sent_reports);
}

void SafeBrowsingUIHandler::GetPGEvents(const base::ListValue* args) {
  const std::vector<sync_pb::UserEventSpecifics>& events =
      WebUIInfoSingleton::GetInstance()->pg_event_log();

  base::ListValue events_sent;

  for (const sync_pb::UserEventSpecifics& event : events)
    events_sent.GetList().push_back(SerializePGEvent(event));

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), events_sent);
}

void SafeBrowsingUIHandler::GetPGPings(const base::ListValue* args) {
  const std::vector<LoginReputationClientRequest> requests =
      WebUIInfoSingleton::GetInstance()->pg_pings();

  base::ListValue pings_sent;
  for (size_t request_index = 0; request_index < requests.size();
       request_index++) {
    base::ListValue ping_entry;
    ping_entry.GetList().push_back(base::Value(int(request_index)));
    ping_entry.GetList().push_back(
        base::Value(SerializePGPing(requests[request_index])));
    pings_sent.GetList().push_back(std::move(ping_entry));
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), pings_sent);
}

void SafeBrowsingUIHandler::GetPGResponses(const base::ListValue* args) {
  const std::map<int, LoginReputationClientResponse> responses =
      WebUIInfoSingleton::GetInstance()->pg_responses();

  base::ListValue responses_sent;
  for (const auto& token_and_response : responses) {
    base::ListValue response_entry;
    response_entry.GetList().push_back(base::Value(token_and_response.first));
    response_entry.GetList().push_back(
        base::Value(SerializePGResponse(token_and_response.second)));
    responses_sent.GetList().push_back(std::move(response_entry));
  }

  AllowJavascript();
  std::string callback_id;
  args->GetString(0, &callback_id);
  ResolveJavascriptCallback(base::Value(callback_id), responses_sent);
}

void SafeBrowsingUIHandler::NotifyClientDownloadRequestJsListener(
    ClientDownloadRequest* client_download_request) {
  AllowJavascript();
  FireWebUIListener(
      "sent-client-download-requests-update",
      base::Value(SerializeClientDownloadRequest(*client_download_request)));
}

void SafeBrowsingUIHandler::NotifyClientDownloadResponseJsListener(
    ClientDownloadResponse* client_download_response) {
  AllowJavascript();
  FireWebUIListener(
      "received-client-download-responses-update",
      base::Value(SerializeClientDownloadResponse(*client_download_response)));
}

void SafeBrowsingUIHandler::NotifyCSBRRJsListener(
    ClientSafeBrowsingReportRequest* csbrr) {
  AllowJavascript();
  FireWebUIListener("sent-csbrr-update", base::Value(SerializeCSBRR(*csbrr)));
}

void SafeBrowsingUIHandler::NotifyPGEventJsListener(
    const sync_pb::UserEventSpecifics& event) {
  AllowJavascript();
  FireWebUIListener("sent-pg-event", SerializePGEvent(event));
}

void SafeBrowsingUIHandler::NotifyPGPingJsListener(
    int token,
    const LoginReputationClientRequest& request) {
  base::ListValue request_list;
  request_list.GetList().push_back(base::Value(token));
  request_list.GetList().push_back(base::Value(SerializePGPing(request)));

  AllowJavascript();
  FireWebUIListener("pg-pings-update", request_list);
}

void SafeBrowsingUIHandler::NotifyPGResponseJsListener(
    int token,
    const LoginReputationClientResponse& response) {
  base::ListValue response_list;
  response_list.GetList().push_back(base::Value(token));
  response_list.GetList().push_back(base::Value(SerializePGResponse(response)));

  AllowJavascript();
  FireWebUIListener("pg-responses-update", response_list);
}

void SafeBrowsingUIHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "getExperiments",
      base::BindRepeating(&SafeBrowsingUIHandler::GetExperiments,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPrefs", base::BindRepeating(&SafeBrowsingUIHandler::GetPrefs,
                                      base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getSavedPasswords",
      base::BindRepeating(&SafeBrowsingUIHandler::GetSavedPasswords,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getDatabaseManagerInfo",
      base::BindRepeating(&SafeBrowsingUIHandler::GetDatabaseManagerInfo,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getSentClientDownloadRequests",
      base::BindRepeating(&SafeBrowsingUIHandler::GetSentClientDownloadRequests,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getReceivedClientDownloadResponses",
      base::BindRepeating(
          &SafeBrowsingUIHandler::GetReceivedClientDownloadResponses,
          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getSentCSBRRs",
      base::BindRepeating(&SafeBrowsingUIHandler::GetSentCSBRRs,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPGEvents", base::BindRepeating(&SafeBrowsingUIHandler::GetPGEvents,
                                         base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPGPings", base::BindRepeating(&SafeBrowsingUIHandler::GetPGPings,
                                        base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPGResponses",
      base::BindRepeating(&SafeBrowsingUIHandler::GetPGResponses,
                          base::Unretained(this)));
}

}  // namespace safe_browsing
