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

#include "services/network/resolve_host_request.h"

#include <algorithm>
#include <optional>
#include <string>
#include <utility>

#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/network_handle.h"
#include "net/base/url_util.h"
#include "net/log/net_log.h"
#include "net/log/net_log_with_source.h"
#include "url/url_canon.h"

namespace network {

namespace {

// Attempts URL canonicalization, but if unable, leaves `host_port_pair` alone.
void TryToCanonicalizeHost(net::HostPortPair& host_port_pair) {
  url::CanonHostInfo info;
  std::string canonicalized =
      net::CanonicalizeHost(host_port_pair.host(), &info);
  if (info.family != url::CanonHostInfo::BROKEN) {
    host_port_pair.set_host(canonicalized);
  }
}

}  // namespace

ResolveHostRequest::ResolveHostRequest(
    net::HostResolver* resolver,
    mojom::HostResolverHostPtr host,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    net::handles::NetworkHandle target_network,
    const std::optional<net::HostResolver::ResolveHostParameters>&
        optional_parameters,
    net::NetLog* net_log) {
  DCHECK(resolver);
  DCHECK(net_log);

  if (host->is_host_port_pair()) {
    // net::HostResolver expects canonicalized hostnames.
    net::HostPortPair& host_port_pair = host->get_host_port_pair();
    TryToCanonicalizeHost(host_port_pair);
    internal_request_ = resolver->CreateRequest(
        host_port_pair, network_anonymization_key, target_network,
        net::NetLogWithSource::Make(
            net_log, net::NetLogSourceType::NETWORK_SERVICE_HOST_RESOLVER),
        optional_parameters);
  } else {
    internal_request_ = resolver->CreateRequest(
        host->get_scheme_host_port(), network_anonymization_key, target_network,
        net::NetLogWithSource::Make(
            net_log, net::NetLogSourceType::NETWORK_SERVICE_HOST_RESOLVER),
        optional_parameters);
  }
}

ResolveHostRequest::~ResolveHostRequest() {
  control_handle_receiver_.reset();

  if (response_client_.is_bound()) {
    response_client_->OnComplete(net::ERR_NAME_NOT_RESOLVED,
                                 net::ResolveErrorInfo(net::ERR_FAILED),
                                 /*resolved_addresses=*/{},
                                 /*alternative_endpoints=*/{});
    response_client_.reset();
  }
}

int ResolveHostRequest::Start(
    mojo::PendingReceiver<mojom::ResolveHostHandle> control_handle_receiver,
    mojo::PendingRemote<mojom::ResolveHostClient> pending_response_client,
    net::CompletionOnceCallback callback) {
  DCHECK(internal_request_);
  DCHECK(!control_handle_receiver_.is_bound());
  DCHECK(!response_client_.is_bound());

  // Unretained |this| reference is safe because if |internal_request_| goes out
  // of scope, it will cancel the request and ResolveHost() will not call the
  // callback.
  int rv = internal_request_->Start(
      base::BindOnce(&ResolveHostRequest::OnComplete, base::Unretained(this)));

  mojo::Remote<mojom::ResolveHostClient> response_client(
      std::move(pending_response_client));
  if (rv != net::ERR_IO_PENDING) {
    response_client->OnComplete(rv, GetResolveErrorInfo(), GetAddressResults(),
                                GetAlternativeEndpoints());
    return rv;
  }

  if (control_handle_receiver)
    control_handle_receiver_.Bind(std::move(control_handle_receiver));

  response_client_ = std::move(response_client);
  // Unretained |this| reference is safe because connection error cannot occur
  // if |response_client_| goes out of scope.
  response_client_.set_disconnect_handler(base::BindOnce(
      &ResolveHostRequest::Cancel, base::Unretained(this), net::ERR_FAILED));

  callback_ = std::move(callback);

  return net::ERR_IO_PENDING;
}

void ResolveHostRequest::Cancel(int error) {
  DCHECK_NE(net::OK, error);

  if (cancelled_)
    return;

  internal_request_ = nullptr;
  cancelled_ = true;
  resolve_error_info_ = net::ResolveErrorInfo(error);
  OnComplete(error);
}

void ResolveHostRequest::OnComplete(int error) {
  DCHECK(response_client_.is_bound());
  DCHECK(callback_);

  control_handle_receiver_.reset();
  SignalNonAddressResults();
  response_client_->OnComplete(error, GetResolveErrorInfo(),
                               GetAddressResults(), GetAlternativeEndpoints());

  response_client_.reset();
  // Invoke completion callback last as it may delete |this|.
  std::move(callback_).Run(GetResolveErrorInfo().error);
}

net::ResolveErrorInfo ResolveHostRequest::GetResolveErrorInfo() const {
  if (cancelled_) {
    return resolve_error_info_;
  }

  DCHECK(internal_request_);
  return internal_request_->GetResolveErrorInfo();
}

net::AddressList ResolveHostRequest::GetAddressResults() const {
  if (cancelled_) {
    return net::AddressList();
  }

  DCHECK(internal_request_);
  return internal_request_->GetAddressResults();
}

net::HostResolverEndpointResults ResolveHostRequest::GetAlternativeEndpoints()
    const {
  if (cancelled_) {
    return {};
  }

  DCHECK(internal_request_);
  auto endpoints = internal_request_->GetEndpointResults();

  // `endpoints` contains both alternative endpoints (from HTTPS/SVCB) and
  // authority endpoints (from A/AAAA directly). The authority endpoints are
  // redundant with `AddressList`, so return only the alternative endpoints.
  //
  // TODO(crbug.com/40203587): This is the opposite of the design taken
  // everywhere else in the DNS logic, where we aimed to migrate `AddressList`
  // to `HostResolverEndpointResult`.
  net::HostResolverEndpointResults alternative_endpoints;
  std::ranges::copy_if(
      endpoints, std::back_inserter(alternative_endpoints),
      [](const auto& endpoint) { return endpoint.metadata.IsAlternative(); });
  return alternative_endpoints;
}

void ResolveHostRequest::SignalNonAddressResults() {
  if (cancelled_) {
    return;
  }
  DCHECK(internal_request_);

  if (!internal_request_->GetTextResults().empty()) {
    response_client_->OnTextResults(
        base::ToVector(internal_request_->GetTextResults()));
  }

  if (!internal_request_->GetHostnameResults().empty()) {
    response_client_->OnHostnameResults(
        base::ToVector(internal_request_->GetHostnameResults()));
  }
}

}  // namespace network
