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

#include "net/dns/host_resolver.h"

#include <algorithm>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

#include "base/check.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time_delta_from_string.h"
#include "base/values.h"
#include "mapped_host_resolver.h"
#include "net/base/address_list.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/network_change_notifier.h"
#include "net/base/task/task_runner.h"
#include "net/dns/context_host_resolver.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_util.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver_manager.h"
#include "net/dns/mapped_host_resolver.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/dns/resolve_context.h"
#include "net/dns/stale_host_resolver.h"
#include "stale_host_resolver.h"
#include "url/scheme_host_port.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/android_info.h"
#include "net/android/network_library.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace net {

namespace {

// The experiment settings of features::kUseDnsHttpsSvcb. See the comments in
// net/base/features.h for more details.
const char kUseDnsHttpsSvcbInsecureExtraTimeMax[] = "insecure_extra_time_max";
const char kUseDnsHttpsSvcbInsecureExtraTimePercent[] =
    "insecure_extra_time_percent";
const char kUseDnsHttpsSvcbInsecureExtraTimeMin[] = "insecure_extra_time_min";
const char kUseDnsHttpsSvcbSecureExtraTimeMax[] = "secure_extra_time_max";
const char kUseDnsHttpsSvcbSecureExtraTimePercent[] =
    "secure_extra_time_percent";
const char kUseDnsHttpsSvcbSecureExtraTimeMin[] = "secure_extra_time_min";

// An implementation of HostResolver::{ResolveHost,Probe}Request that always
// fails immediately.
class FailingRequestImpl : public HostResolver::ResolveHostRequest,
                           public HostResolver::ProbeRequest {
 public:
  explicit FailingRequestImpl(int error) : error_(error) {}

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

  ~FailingRequestImpl() override = default;

  int Start(CompletionOnceCallback callback) override { return error_; }
  int Start() override { return error_; }

  const AddressList& GetAddressResults() const override {
    static const base::NoDestructor<AddressList> kEmptyResult;
    return *kEmptyResult;
  }

  base::span<const HostResolverEndpointResult> GetEndpointResults()
      const override {
    return {};
  }

  base::span<const std::string> GetTextResults() const override { return {}; }

  base::span<const HostPortPair> GetHostnameResults() const override {
    return {};
  }

  const std::set<std::string>& GetDnsAliasResults() const override {
    static const base::NoDestructor<std::set<std::string>> kEmptyResult;
    return *kEmptyResult;
  }

  ResolveErrorInfo GetResolveErrorInfo() const override {
    return ResolveErrorInfo(error_);
  }

  const std::optional<HostCache::EntryStaleness>& GetStaleInfo()
      const override {
    static const std::optional<HostCache::EntryStaleness> nullopt_result;
    return nullopt_result;
  }

  std::optional<ResolutionDetails> GetResolutionDetails() const override {
    return std::nullopt;
  }

 private:
  const int error_;
};

// Similar to FailingRequestImpl, but for ServiceEndpointRequest.
class FailingServiceEndpointRequestImpl
    : public HostResolver::ServiceEndpointRequest {
 public:
  explicit FailingServiceEndpointRequestImpl(int error) : error_(error) {}

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

  ~FailingServiceEndpointRequestImpl() override = default;

  int Start(Delegate* delegate) override { return error_; }

  base::span<const ServiceEndpoint> GetEndpointResults() override { return {}; }

  const std::set<std::string>& GetDnsAliasResults() override {
    static const base::NoDestructor<std::set<std::string>> kEmptyResult;
    return *kEmptyResult.get();
  }

  bool EndpointsCryptoReady() override { return false; }

  ResolveErrorInfo GetResolveErrorInfo() override {
    return ResolveErrorInfo(error_);
  }

  const HostCache::EntryStaleness* GetStaleInfo() const override {
    return nullptr;
  }

  bool IsStaleWhileRefreshing() const override { return false; }

  std::optional<ResolutionDetails> GetResolutionDetails() const override {
    return std::nullopt;
  }

  void ChangeRequestPriority(RequestPriority priority) override {}

 private:
  const int error_;
};

void GetTimeDeltaFromDictString(const base::DictValue& args,
                                std::string_view key,
                                base::TimeDelta* out) {
  const std::string* value_string = args.FindString(key);
  if (!value_string)
    return;
  *out = base::TimeDeltaFromString(*value_string).value_or(*out);
}

}  // namespace

HostResolver::Host::Host(std::variant<url::SchemeHostPort, HostPortPair> host)
    : host_(std::move(host)) {
#if DCHECK_IS_ON()
  if (std::holds_alternative<url::SchemeHostPort>(host_)) {
    DCHECK(std::get<url::SchemeHostPort>(host_).IsValid());
  } else {
    DCHECK(std::holds_alternative<HostPortPair>(host_));
    DCHECK(!std::get<HostPortPair>(host_).IsEmpty());
  }
#endif  // DCHECK_IS_ON()
}

HostResolver::Host::~Host() = default;

HostResolver::Host::Host(const Host&) = default;

HostResolver::Host& HostResolver::Host::operator=(const Host&) = default;

HostResolver::Host::Host(Host&&) = default;

HostResolver::Host& HostResolver::Host::operator=(Host&&) = default;

bool HostResolver::Host::HasScheme() const {
  return std::holds_alternative<url::SchemeHostPort>(host_);
}

const std::string& HostResolver::Host::GetScheme() const {
  DCHECK(std::holds_alternative<url::SchemeHostPort>(host_));
  return std::get<url::SchemeHostPort>(host_).scheme();
}

std::string HostResolver::Host::GetHostname() const {
  if (std::holds_alternative<url::SchemeHostPort>(host_)) {
    return std::get<url::SchemeHostPort>(host_).host();
  } else {
    DCHECK(std::holds_alternative<HostPortPair>(host_));
    return std::get<HostPortPair>(host_).HostForURL();
  }
}

std::string_view HostResolver::Host::GetHostnameWithoutBrackets() const {
  if (std::holds_alternative<url::SchemeHostPort>(host_)) {
    std::string_view hostname = std::get<url::SchemeHostPort>(host_).host();
    if (hostname.size() > 2 && hostname.front() == '[' &&
        hostname.back() == ']') {
      return hostname.substr(1, hostname.size() - 2);
    } else {
      return hostname;
    }
  } else {
    DCHECK(std::holds_alternative<HostPortPair>(host_));
    return std::get<HostPortPair>(host_).host();
  }
}

uint16_t HostResolver::Host::GetPort() const {
  if (std::holds_alternative<url::SchemeHostPort>(host_)) {
    return std::get<url::SchemeHostPort>(host_).port();
  } else {
    DCHECK(std::holds_alternative<HostPortPair>(host_));
    return std::get<HostPortPair>(host_).port();
  }
}

std::string HostResolver::Host::ToString() const {
  if (std::holds_alternative<url::SchemeHostPort>(host_)) {
    return std::get<url::SchemeHostPort>(host_).Serialize();
  } else {
    DCHECK(std::holds_alternative<HostPortPair>(host_));
    return std::get<HostPortPair>(host_).ToString();
  }
}

const url::SchemeHostPort& HostResolver::Host::AsSchemeHostPort() const {
  const url::SchemeHostPort* scheme_host_port =
      std::get_if<url::SchemeHostPort>(&host_);
  DCHECK(scheme_host_port);
  return *scheme_host_port;
}

const HostPortPair& HostResolver::Host::AsHostPortPair() const {
  const HostPortPair* host_port_pair = std::get_if<HostPortPair>(&host_);
  DCHECK(host_port_pair);
  return *host_port_pair;
}

HostResolver::HttpsSvcbOptions::HttpsSvcbOptions() = default;

HostResolver::HttpsSvcbOptions::HttpsSvcbOptions(
    const HttpsSvcbOptions& other) = default;
HostResolver::HttpsSvcbOptions::HttpsSvcbOptions(HttpsSvcbOptions&& other) =
    default;

HostResolver::HttpsSvcbOptions::~HttpsSvcbOptions() = default;

// static
HostResolver::HttpsSvcbOptions HostResolver::HttpsSvcbOptions::FromDict(
    const base::DictValue& dict) {
  net::HostResolver::HttpsSvcbOptions options;
  GetTimeDeltaFromDictString(dict, kUseDnsHttpsSvcbInsecureExtraTimeMax,
                             &options.insecure_extra_time_max);

  options.insecure_extra_time_percent =
      dict.FindInt(kUseDnsHttpsSvcbInsecureExtraTimePercent)
          .value_or(options.insecure_extra_time_percent);
  GetTimeDeltaFromDictString(dict, kUseDnsHttpsSvcbInsecureExtraTimeMin,
                             &options.insecure_extra_time_min);

  GetTimeDeltaFromDictString(dict, kUseDnsHttpsSvcbSecureExtraTimeMax,
                             &options.secure_extra_time_max);

  options.secure_extra_time_percent =
      dict.FindInt(kUseDnsHttpsSvcbSecureExtraTimePercent)
          .value_or(options.secure_extra_time_percent);
  GetTimeDeltaFromDictString(dict, kUseDnsHttpsSvcbSecureExtraTimeMin,
                             &options.secure_extra_time_min);

  return options;
}

// static
HostResolver::HttpsSvcbOptions HostResolver::HttpsSvcbOptions::FromFeatures() {
  net::HostResolver::HttpsSvcbOptions options;
  options.insecure_extra_time_max =
      features::kUseDnsHttpsSvcbInsecureExtraTimeMax.Get();
  options.insecure_extra_time_percent =
      features::kUseDnsHttpsSvcbInsecureExtraTimePercent.Get();
  options.insecure_extra_time_min =
      features::kUseDnsHttpsSvcbInsecureExtraTimeMin.Get();
  options.secure_extra_time_max =
      features::kUseDnsHttpsSvcbSecureExtraTimeMax.Get();
  options.secure_extra_time_percent =
      features::kUseDnsHttpsSvcbSecureExtraTimePercent.Get();
  options.secure_extra_time_min =
      features::kUseDnsHttpsSvcbSecureExtraTimeMin.Get();
  return options;
}

HostResolver::ManagerOptions::ManagerOptions() = default;

HostResolver::ManagerOptions::ManagerOptions(const ManagerOptions& other) =
    default;
HostResolver::ManagerOptions::ManagerOptions(ManagerOptions&& other) = default;

HostResolver::ManagerOptions::~ManagerOptions() = default;

std::unique_ptr<HostResolver> HostResolver::Factory::CreateResolver(
    HostResolverManager* manager,
    std::string_view host_mapping_rules,
    bool enable_caching,
    bool enable_stale) {
  return HostResolver::CreateResolver(manager, host_mapping_rules,
                                      enable_caching, enable_stale);
}

std::unique_ptr<HostResolver> HostResolver::Factory::CreateStandaloneResolver(
    NetLog* net_log,
    const ManagerOptions& options,
    std::string_view host_mapping_rules,
    bool enable_caching,
    bool enable_stale) {
  return HostResolver::CreateStandaloneResolver(
      net_log, options, host_mapping_rules, enable_caching, enable_stale);
}

HostResolver::ResolveHostParameters::ResolveHostParameters() = default;

HostResolver::ResolveHostParameters::ResolveHostParameters(
    const ResolveHostParameters&) = default;

HostResolver::ResolveHostParameters&
HostResolver::ResolveHostParameters::operator=(const ResolveHostParameters&) =
    default;

std::string HostResolver::ServiceEndpointRequest::DebugString() const {
  return "";
}

HostResolver::~HostResolver() = default;

std::unique_ptr<HostResolver::ProbeRequest>
HostResolver::CreateDohProbeRequest() {
  // Should be overridden in any HostResolver implementation where this method
  // may be called.
  NOTREACHED();
}

std::unique_ptr<HostResolver::MdnsListener> HostResolver::CreateMdnsListener(
    const HostPortPair& host,
    DnsQueryType query_type) {
  // Should be overridden in any HostResolver implementation where this method
  // may be called.
  NOTREACHED();
}

HostCache* HostResolver::GetHostCache() {
  return nullptr;
}

base::DictValue HostResolver::GetDnsConfigAsValue() const {
  return base::DictValue();
}

void HostResolver::SetRequestContext(URLRequestContext* request_context) {
  // Should be overridden in any HostResolver implementation where this method
  // may be called.
  NOTREACHED();
}

HostResolverManager* HostResolver::GetManagerForTesting() {
  // Should be overridden in any HostResolver implementation where this method
  // may be called.
  NOTREACHED();
}

const URLRequestContext* HostResolver::GetContextForTesting() const {
  // Should be overridden in any HostResolver implementation where this method
  // may be called.
  NOTREACHED();
}

handles::NetworkHandle HostResolver::GetTargetNetworkForTesting() const {
  return handles::kInvalidNetworkHandle;
}

// static
std::unique_ptr<HostResolver> HostResolver::CreateResolver(
    HostResolverManager* manager,
    std::string_view host_mapping_rules,
    bool enable_caching,
    bool enable_stale) {
  DCHECK(manager);

  auto resolve_context = std::make_unique<ResolveContext>(
      nullptr /* url_request_context */, enable_caching);

  std::unique_ptr<ContextHostResolver> context_resolver =
      std::make_unique<ContextHostResolver>(manager,
                                            std::move(resolve_context));

  std::unique_ptr<HostResolver> resolver;

  // Wrap in StaleHostResolver if needed.
  if (enable_stale) {
    resolver = std::make_unique<StaleHostResolver>(
        std::move(context_resolver), StaleHostResolver::StaleOptions());
  } else {
    resolver = std::move(context_resolver);
  }

  // Wrap in MappedHostResolver if needed.
  if (!host_mapping_rules.empty()) {
    auto remapped_resolver =
        std::make_unique<MappedHostResolver>(std::move(resolver));
    remapped_resolver->SetRulesFromString(host_mapping_rules);
    resolver = std::move(remapped_resolver);
  }

  return resolver;
}

// static
std::unique_ptr<HostResolver> HostResolver::CreateStandaloneResolver(
    NetLog* net_log,
    std::optional<ManagerOptions> options,
    std::string_view host_mapping_rules,
    bool enable_caching,
    bool enable_stale) {
  std::unique_ptr<ContextHostResolver> context_resolver =
      CreateStandaloneContextResolver(net_log, std::move(options),
                                      enable_caching);

  std::unique_ptr<HostResolver> resolver;

  // Wrap in StaleHostResolver if needed.
  if (enable_stale) {
    resolver = std::make_unique<StaleHostResolver>(
        std::move(context_resolver), StaleHostResolver::StaleOptions());
  } else {
    resolver = std::move(context_resolver);
  }

  // Wrap in MappedHostResolver if needed.
  if (!host_mapping_rules.empty()) {
    auto remapped_resolver =
        std::make_unique<MappedHostResolver>(std::move(resolver));
    remapped_resolver->SetRulesFromString(host_mapping_rules);
    resolver = std::move(remapped_resolver);
  }

  return resolver;
}

// static
std::unique_ptr<ContextHostResolver>
HostResolver::CreateStandaloneContextResolver(
    NetLog* net_log,
    std::optional<ManagerOptions> options,
    bool enable_caching) {
  auto resolve_context = std::make_unique<ResolveContext>(
      nullptr /* url_request_context */, enable_caching);

  return std::make_unique<ContextHostResolver>(
      std::make_unique<HostResolverManager>(
          std::move(options).value_or(ManagerOptions()),
          NetworkChangeNotifier::GetSystemDnsConfigNotifier(), net_log),
      std::move(resolve_context));
}

// static
std::unique_ptr<HostResolver>
HostResolver::CreateStandaloneNetworkBoundResolver(
    NetLog* net_log,
    handles::NetworkHandle target_network,
    std::optional<ManagerOptions> options,
    std::string_view host_mapping_rules,
    bool enable_caching) {
#if BUILDFLAG(IS_ANDROID)
  // Note that the logic below uses Android APIs that don't work on a sandboxed
  // process: This is not problematic because this function is used only by
  // Cronet which doesn't enable sandboxing.

  auto resolve_context = std::make_unique<ResolveContext>(
      nullptr /*url_request_context */, enable_caching);
  auto manager_options = std::move(options).value_or(ManagerOptions());
  // Support the use of the built-in resolver when possible.
  bool is_builtin_resolver_supported =
      manager_options.insecure_dns_mode != InsecureDnsMode::kDisabled &&
      base::android::android_info::sdk_int() >=
          base::android::android_info::SDK_VERSION_P;
  if (is_builtin_resolver_supported) {
    // Pre-existing DnsConfigOverrides is currently ignored, consider extending
    // if a use case arises.
    DCHECK(manager_options.dns_config_overrides == DnsConfigOverrides());

    std::vector<IPEndPoint> dns_servers;
    bool dns_over_tls_active;
    std::string dns_over_tls_hostname;
    std::vector<std::string> search_suffixes;
    if (android::GetDnsServersForNetwork(&dns_servers, &dns_over_tls_active,
                                         &dns_over_tls_hostname,
                                         &search_suffixes, target_network)) {
      DnsConfigOverrides dns_config_overrides =
          DnsConfigOverrides::CreateOverridingEverythingWithDefaults();
      dns_config_overrides.nameservers = dns_servers;
      // Android APIs don't specify whether to use DoT or DoH. So, leave the
      // decision to `DnsConfig::allow_dns_over_https_upgrade` default value.
      dns_config_overrides.dns_over_tls_active = dns_over_tls_active;
      dns_config_overrides.dns_over_tls_hostname = dns_over_tls_hostname;
      dns_config_overrides.search = search_suffixes;

      manager_options.dns_config_overrides = dns_config_overrides;
      // Regardless of DoH vs DoT, the important contract to respect is not to
      // perform insecure DNS lookups if `dns_over_tls_active` == true.
      manager_options.additional_types_via_insecure_dns_enabled =
          !dns_over_tls_active;
    } else {
      // Disable when android::GetDnsServersForNetwork fails.
      is_builtin_resolver_supported = false;
    }
  }

  if (!is_builtin_resolver_supported) {
    manager_options.insecure_dns_mode = InsecureDnsMode::kDisabled;
  }
  return std::make_unique<ContextHostResolver>(
      HostResolverManager::CreateNetworkBoundHostResolverManager(
          manager_options, target_network, net_log),
      std::move(resolve_context));
#else   // !BUILDFLAG(IS_ANDROID)
  NOTIMPLEMENTED();
  return nullptr;
#endif  // BUILDFLAG(IS_ANDROID)
}

// static
AddressFamily HostResolver::DnsQueryTypeSetToAddressFamily(
    DnsQueryTypeSet dns_query_types) {
  DCHECK(HasAddressType(dns_query_types));
  // If the set of query types contains A and AAAA, defer the choice of address
  // family. Otherwise, pick the corresponding address family.
  if (dns_query_types.HasAll({DnsQueryType::A, DnsQueryType::AAAA}))
    return ADDRESS_FAMILY_UNSPECIFIED;
  if (dns_query_types.Has(DnsQueryType::AAAA))
    return ADDRESS_FAMILY_IPV6;
  DCHECK(dns_query_types.Has(DnsQueryType::A));
  return ADDRESS_FAMILY_IPV4;
}

// static
HostResolverFlags HostResolver::ParametersToHostResolverFlags(
    const ResolveHostParameters& parameters) {
  HostResolverFlags flags = 0;
  if (parameters.include_canonical_name)
    flags |= HOST_RESOLVER_CANONNAME;
  if (parameters.loopback_only)
    flags |= HOST_RESOLVER_LOOPBACK_ONLY;
  if (parameters.avoid_multicast_resolution)
    flags |= HOST_RESOLVER_AVOID_MULTICAST;
  return flags;
}

// static
int HostResolver::SquashErrorCode(int error) {
  // TODO(crbug.com/40668952): Consider squashing ERR_INTERNET_DISCONNECTED.
  if (error == OK || error == ERR_IO_PENDING ||
      error == ERR_INTERNET_DISCONNECTED || error == ERR_NAME_NOT_RESOLVED ||
      error == ERR_DNS_NAME_HTTPS_ONLY) {
    return error;
  } else {
    return ERR_NAME_NOT_RESOLVED;
  }
}

// static
AddressList HostResolver::EndpointResultToAddressList(
    base::span<const HostResolverEndpointResult> endpoints,
    const std::set<std::string>& aliases) {
  AddressList list;

  auto authority_endpoint = std::ranges::find_if_not(
      endpoints,
      [](const auto& endpoint) { return endpoint.metadata.IsAlternative(); });
  if (authority_endpoint == endpoints.end()) {
    return list;
  }

  list.endpoints() = authority_endpoint->ip_endpoints;

  std::vector<std::string> aliases_vector(aliases.begin(), aliases.end());
  list.SetDnsAliases(std::move(aliases_vector));

  return list;
}

// static
bool HostResolver::MayUseNAT64ForIPv4Literal(HostResolverFlags flags,
                                             HostResolverSource source,
                                             const IPAddress& ip_address) {
  return !(flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) &&
         ip_address.IsValid() && ip_address.IsIPv4() &&
         (source != HostResolverSource::LOCAL_ONLY);
}

// static
const scoped_refptr<base::SingleThreadTaskRunner>& HostResolver::GetTaskRunner(
    RequestPriority priority) {
  if (base::FeatureList::IsEnabled(features::kNetTaskSchedulerHostResolver)) {
    return net::GetTaskRunner(priority);
  }
  return base::SingleThreadTaskRunner::GetCurrentDefault();
}

HostResolver::HostResolver() = default;

// static
std::unique_ptr<HostResolver::ResolveHostRequest>
HostResolver::CreateFailingRequest(int error) {
  return std::make_unique<FailingRequestImpl>(error);
}

// static
std::unique_ptr<HostResolver::ProbeRequest>
HostResolver::CreateFailingProbeRequest(int error) {
  return std::make_unique<FailingRequestImpl>(error);
}

// static
std::unique_ptr<HostResolver::ServiceEndpointRequest>
HostResolver::CreateFailingServiceEndpointRequest(int error) {
  return std::make_unique<FailingServiceEndpointRequestImpl>(error);
}

}  // namespace net
