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

#include "components/safe_browsing/core/common/utils.h"

#include <algorithm>

#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/threat_enums.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "crypto/sha2.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/ip_address.h"
#include "net/base/url_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/cookie_access_observer.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
#include "base/enterprise_util.h"
#endif

namespace safe_browsing {

namespace {

// The bearer token prefix in authorization header. Used when various Safe
// Browsing requests are GAIA-keyed by attaching oauth2 tokens as bearer tokens.
const char kAuthHeaderBearer[] = "Bearer ";

// Represents the HTTP response code when it has not explicitly been set.
const int kUnsetHttpResponseCode = 0;

}  // namespace

std::string ShortURLForReporting(const GURL& url) {
  std::string spec(url.spec());
  if (url.SchemeIs(url::kDataScheme)) {
    size_t comma_pos = spec.find(',');
    if (comma_pos != std::string::npos && comma_pos != spec.size() - 1) {
      std::string hash_value = crypto::SHA256HashString(spec);
      spec.erase(comma_pos + 1);
      spec += base::HexEncode(hash_value);
    }
  }
  return spec;
}

ChromeUserPopulation::ProfileManagementStatus GetProfileManagementStatus(
    const policy::BrowserPolicyConnector* bpc) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
  if (base::IsManagedDevice()) {
    return ChromeUserPopulation::ENTERPRISE_MANAGED;
  } else {
    return ChromeUserPopulation::NOT_MANAGED;
  }
#elif BUILDFLAG(IS_CHROMEOS)
  if (bpc && bpc->IsDeviceEnterpriseManaged()) {
    return ChromeUserPopulation::ENTERPRISE_MANAGED;
  }
  return ChromeUserPopulation::NOT_MANAGED;
#else
  return ChromeUserPopulation::UNAVAILABLE;
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
}

void SetDelayInPref(PrefService* prefs,
                    const char* pref_name,
                    const base::TimeDelta& delay) {
  base::Time next_event = base::Time::Now() + delay;
  int64_t seconds_since_epoch =
      next_event.ToDeltaSinceWindowsEpoch().InSeconds();
  prefs->SetInt64(pref_name, seconds_since_epoch);
}

base::TimeDelta GetDelayFromPref(PrefService* prefs, const char* pref_name) {
  const base::TimeDelta zero_delay;
  if (!prefs->HasPrefPath(pref_name)) {
    return zero_delay;
  }

  int64_t seconds_since_epoch = prefs->GetInt64(pref_name);
  if (seconds_since_epoch <= 0) {
    return zero_delay;
  }

  base::Time next_event = base::Time::FromDeltaSinceWindowsEpoch(
      base::Seconds(seconds_since_epoch));
  base::Time now = base::Time::Now();
  if (now > next_event) {
    return zero_delay;
  } else {
    return next_event - now;
  }
}

bool CanGetReputationOfUrl(const GURL& url) {
  // net::IsLocalhost(url) includes: "//localhost/", "//127.0.0.1/"
  if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS() || net::IsLocalhost(url)) {
    return false;
  }
  const std::string hostname = url.GetHost();
  // There is no reason to send URLs with very short or single-label hosts.
  // The Safe Browsing server does not check them.
  if (hostname.size() < 4 || std::ranges::count(hostname, '.') < 1) {
    return false;
  }

  net::IPAddress ip_address;
  if (url.HostIsIPAddress() && ip_address.AssignFromIPLiteral(hostname) &&
      !ip_address.IsPubliclyRoutable()) {
    // Includes: "//192.168.1.1/", "//172.16.2.2/", "//10.1.1.1/"
    return false;
  }

  return true;
}

void SetAccessToken(network::ResourceRequest* resource_request,
                    const std::string& access_token) {
  resource_request->headers.SetHeader(
      net::HttpRequestHeaders::kAuthorization,
      base::StrCat({kAuthHeaderBearer, access_token}));
}

void RecordHttpResponseOrErrorCode(const char* metric_name,
                                   int net_error,
                                   int response_code) {
  base::UmaHistogramSparse(
      metric_name,
      net_error == net::OK || net_error == net::ERR_HTTP_RESPONSE_CODE_FAILURE
          ? response_code
          : net_error);
}

bool ErrorIsRetriable(int net_error, int http_error) {
  return (net_error == net::ERR_INTERNET_DISCONNECTED ||
          net_error == net::ERR_NETWORK_CHANGED) &&
         (http_error == kUnsetHttpResponseCode || http_error == net::HTTP_OK);
}

// LINT.IfChange(GetExtraMetricsSuffix)
std::string_view GetExtraMetricsSuffix(
    security_interstitials::UnsafeResource unsafe_resource) {
  switch (unsafe_resource.threat_source) {
    case safe_browsing::ThreatSource::LOCAL_PVER4:
      return "from_device_v4";
    case safe_browsing::ThreatSource::LOCAL_PVER5_LOCAL_BLOCKLIST:
      return "from_local_blocklist_v5";
    case safe_browsing::ThreatSource::CLIENT_SIDE_DETECTION:
      return "from_client_side_detection";
    case safe_browsing::ThreatSource::URL_REAL_TIME_CHECK:
      return "from_real_time_check";
    case safe_browsing::ThreatSource::NATIVE_PVER5_REAL_TIME:
      return "from_hash_prefix_real_time_check_v5";
    case safe_browsing::ThreatSource::ANDROID_SAFEBROWSING_REAL_TIME:
      return "from_android_safebrowsing_real_time";
    case safe_browsing::ThreatSource::ANDROID_SAFEBROWSING:
      return "from_android_safebrowsing";
    case safe_browsing::ThreatSource::GLIC_COUNTER_ABUSE:
      return "from_glic_counter_abuse";
    case safe_browsing::ThreatSource::UNKNOWN:
      break;
  }
  NOTREACHED();
}
// LINT.ThenChange(//tools/metrics/histograms/metadata/interstitial/histograms.xml:SecurityInterstitialSBWithSourceType)

std::string_view GetExtraExtraMetricsSuffix(
    security_interstitials::UnsafeResource unsafe_resource) {
  switch (unsafe_resource.threat_subtype) {
    case safe_browsing::ThreatSubtype::SCAM_EXPERIMENT_VERDICT_1:
      return "scam_experiment_verdict_1";
    case safe_browsing::ThreatSubtype::SCAM_EXPERIMENT_VERDICT_2:
      return "scam_experiment_verdict_2";
    case safe_browsing::ThreatSubtype::SCAM_EXPERIMENT_VERDICT_3:
      return "scam_experiment_verdict_3";
    case safe_browsing::ThreatSubtype::SCAM_EXPERIMENT_VERDICT_4:
      return "scam_experiment_verdict_4";
    case safe_browsing::ThreatSubtype::SCAM_EXPERIMENT_CATCH_ALL_ENFORCEMENT:
      return "scam_experiment_catch_all";
    case safe_browsing::ThreatSubtype::UNKNOWN:
      return "";
  }
  // Subtype is not always set.
  return "";
}

std::string GetThreatTypeStringForInterstitial(
    safe_browsing::SBThreatType threat_type) {
  using enum SBThreatType;

  switch (threat_type) {
    case SB_THREAT_TYPE_URL_PHISHING:
    case SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING:
      return "SOCIAL_ENGINEERING";
    case SB_THREAT_TYPE_URL_MALWARE:
      return "MALWARE";
    case SB_THREAT_TYPE_URL_UNWANTED:
      return "UNWANTED_SOFTWARE";
    case SB_THREAT_TYPE_BILLING:
      return "THREAT_TYPE_UNSPECIFIED";
    case SB_THREAT_TYPE_MANAGED_POLICY_WARN:
      return "MANAGED_POLICY_WARN";
    case SB_THREAT_TYPE_MANAGED_POLICY_BLOCK:
      return "MANAGED_POLICY_BLOCK";
    case SB_THREAT_TYPE_UNUSED:
    case SB_THREAT_TYPE_SAFE:
    case SB_THREAT_TYPE_URL_BINARY_MALWARE:
    case SB_THREAT_TYPE_EXTENSION:
    case SB_THREAT_TYPE_API_ABUSE:
    case SB_THREAT_TYPE_SUBRESOURCE_FILTER:
    case SB_THREAT_TYPE_CSD_ALLOWLIST:
    case DEPRECATED_SB_THREAT_TYPE_URL_PASSWORD_PROTECTION_PHISHING:
    case DEPRECATED_SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE:
    case SB_THREAT_TYPE_SAVED_PASSWORD_REUSE:
    case SB_THREAT_TYPE_SIGNED_IN_SYNC_PASSWORD_REUSE:
    case SB_THREAT_TYPE_SIGNED_IN_NON_SYNC_PASSWORD_REUSE:
    case SB_THREAT_TYPE_AD_SAMPLE:
    case SB_THREAT_TYPE_BLOCKED_AD_POPUP:
    case SB_THREAT_TYPE_BLOCKED_AD_REDIRECT:
    case SB_THREAT_TYPE_SUSPICIOUS_SITE:
    case SB_THREAT_TYPE_ENTERPRISE_PASSWORD_REUSE:
    case SB_THREAT_TYPE_APK_DOWNLOAD:
    case SB_THREAT_TYPE_HIGH_CONFIDENCE_ALLOWLIST:
    case SB_THREAT_TYPE_CSD_DOWNLOAD_ALLOWLIST:
    case SB_THREAT_TYPE_WARNABLE_SUSPICIOUS_SITE:
      NOTREACHED();
  }
  return {};
}

// Information on ClientSideDetectionType bucket allocation can be found at
// go/crca-csd-trigger-priority.
int GetClientSideDetectionTypeTier(ClientSideDetectionType type) {
  switch (type) {
    case ClientSideDetectionType::NOTIFICATION_PERMISSION_PROMPT:
      return 0;
    case ClientSideDetectionType::FORCE_REQUEST:
    case ClientSideDetectionType::KEYBOARD_LOCK_REQUESTED:
    case ClientSideDetectionType::USER_REPORT:
      return 1;
    case ClientSideDetectionType::VIBRATION_API:
    case ClientSideDetectionType::CLIPBOARD_COPY_API:
    case ClientSideDetectionType::CREDIT_CARD_FORM:
    case ClientSideDetectionType::UNFAMILIAR_LOGIN_PAGE:
      return 2;
    case ClientSideDetectionType::TRIGGER_MODELS:
    case ClientSideDetectionType::IMAGE_EMBEDDING_MATCH:
      return 3;
    case ClientSideDetectionType::CLIENT_SIDE_DETECTION_TYPE_UNSPECIFIED:
    case ClientSideDetectionType::POINTER_LOCK_REQUESTED:
    case ClientSideDetectionType::FULLSCREEN_API:
      NOTREACHED();
  }
}

}  // namespace safe_browsing
