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

#include "remoting/base/url_loader_network_service_observer.h"

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/net_errors.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_private_key.h"
#include "net/url_request/url_request.h"
#include "remoting/base/certificate_helpers.h"
#include "remoting/base/logging.h"
#include "remoting/base/ssl_private_key_wrapper.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"

namespace remoting {

namespace {

constexpr char kCertIssuerWildCard[] = "*";

void PrintCertificateDetails(const net::X509Certificate& cert) {
  // Formatted to make log output more readable.
  HOST_LOG << "\n  Client certificate details:\n"
           << "    issued by: " << GetPreferredIssuerFieldValue(cert) << "\n"
           << "    with start date: " << cert.valid_start() << "\n"
           << "    and expiry date: " << cert.valid_expiry();
}

}  // namespace

UrlLoaderNetworkServiceObserver::UrlLoaderNetworkServiceObserver(
    std::unique_ptr<net::ClientCertStore> client_cert_store)
    : client_cert_store_(std::move(client_cert_store)) {
  CHECK(client_cert_store_);
}

UrlLoaderNetworkServiceObserver::~UrlLoaderNetworkServiceObserver() = default;

mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
UrlLoaderNetworkServiceObserver::Bind() {
  mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
      pending_remote;
  receivers_.Add(this, pending_remote.InitWithNewPipeAndPassReceiver());
  return pending_remote;
}

void UrlLoaderNetworkServiceObserver::OnSSLCertificateError(
    const GURL& url,
    int net_error,
    const net::SSLInfo& ssl_info,
    bool fatal,
    OnSSLCertificateErrorCallback response) {
  std::move(response).Run(net_error);
}

void UrlLoaderNetworkServiceObserver::OnCertificateRequested(
    const std::optional<base::UnguessableToken>& window_id,
    const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
    mojo::PendingRemote<network::mojom::ClientCertificateResponder>
        client_cert_responder) {
  // Note: |cert_info| is bound to the callback as it is the caller's
  // responsibility to keep them alive until the callback has been run by
  // ClientCertStore.
  client_cert_store_->GetClientCerts(
      cert_info,
      base::BindOnce(&UrlLoaderNetworkServiceObserver::OnCertificatesSelected,
                     weak_factory_.GetWeakPtr(),
                     std::move(client_cert_responder), cert_info));
}

void UrlLoaderNetworkServiceObserver::OnAuthRequired(
    const std::optional<base::UnguessableToken>& window_id,
    int32_t request_id,
    const GURL& url,
    bool first_auth_attempt,
    const net::AuthChallengeInfo& auth_info,
    const scoped_refptr<net::HttpResponseHeaders>& head_headers,
    mojo::PendingRemote<network::mojom::AuthChallengeResponder>
        auth_challenge_responder) {}

void UrlLoaderNetworkServiceObserver::OnLocalNetworkAccessPermissionRequired(
    network::mojom::TransportType type,
    network::mojom::IPAddressSpace ip_address_space,
    OnLocalNetworkAccessPermissionRequiredCallback callback) {}

void UrlLoaderNetworkServiceObserver::OnPlatformLocalNetworkPermissionRequired(
    OnPlatformLocalNetworkPermissionRequiredCallback callback) {
  std::move(callback).Run(/*granted=*/false);
}

void UrlLoaderNetworkServiceObserver::OnClearSiteData(
    const GURL& url,
    const std::string& header_value,
    int32_t load_flags,
    const std::optional<net::CookiePartitionKey>& cookie_partition_key,
    bool partitioned_state_allowed_only,
    OnClearSiteDataCallback callback) {
  std::move(callback).Run();
}

void UrlLoaderNetworkServiceObserver::OnLoadingStateUpdate(
    network::mojom::LoadInfoPtr info,
    OnLoadingStateUpdateCallback callback) {
  std::move(callback).Run();
}

void UrlLoaderNetworkServiceObserver::OnDataUseUpdate(
    int32_t network_traffic_annotation_id_hash,
    base::ByteSize recv_bytes,
    base::ByteSize sent_bytes) {}


void UrlLoaderNetworkServiceObserver::Clone(
    mojo::PendingReceiver<network::mojom::URLLoaderNetworkServiceObserver>
        observer) {
  receivers_.Add(this, std::move(observer));
}

void UrlLoaderNetworkServiceObserver::OnWebSocketConnectedToLocalNetwork(
    const GURL& request_url,
    network::mojom::IPAddressSpace ip_address_space) {}

void UrlLoaderNetworkServiceObserver::OnUrlLoaderConnectedToLocalNetwork(
    const GURL& request_url,
    network::mojom::IPAddressSpace response_address_space,
    network::mojom::IPAddressSpace client_address_space,
    network::mojom::IPAddressSpace target_address_space) {}

void UrlLoaderNetworkServiceObserver::OnCertificatesSelected(
    mojo::PendingRemote<network::mojom::ClientCertificateResponder>
        client_cert_responder,
    const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
    net::ClientCertIdentityList selected_certs) {
  base::Time now = base::Time::Now();
  auto best_match =
      GetBestMatchFromCertificateList(kCertIssuerWildCard, now, selected_certs);
  if (!best_match) {
    ContinueWithCertificate(std::move(client_cert_responder), nullptr, nullptr);
    return;
  }

  scoped_refptr<net::X509Certificate> cert = best_match->certificate();
  if (!IsCertificateValid(kCertIssuerWildCard, now, *cert.get())) {
    LOG(ERROR) << "Best client certificate match was not valid.";
    PrintCertificateDetails(*cert.get());
    ContinueWithCertificate(std::move(client_cert_responder), nullptr, nullptr);
    return;
  }

  net::ClientCertIdentity::SelfOwningAcquirePrivateKey(
      std::move(best_match),
      base::BindOnce(&UrlLoaderNetworkServiceObserver::ContinueWithCertificate,
                     weak_factory_.GetWeakPtr(),
                     std::move(client_cert_responder), std::move(cert)));
}

void UrlLoaderNetworkServiceObserver::ContinueWithCertificate(
    mojo::PendingRemote<network::mojom::ClientCertificateResponder>
        client_cert_responder,
    scoped_refptr<net::X509Certificate> client_cert,
    scoped_refptr<net::SSLPrivateKey> private_key) {
  if (client_cert) {
    PrintCertificateDetails(*client_cert);
  }

  mojo::Remote<network::mojom::ClientCertificateResponder> responder(
      std::move(client_cert_responder));

  if (!client_cert || !private_key) {
    responder->ContinueWithoutCertificate();
    return;
  }

  mojo::PendingRemote<network::mojom::SSLPrivateKey> ssl_private_key;
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<SSLPrivateKeyWrapper>(private_key),
      ssl_private_key.InitWithNewPipeAndPassReceiver());

  responder->ContinueWithCertificate(
      client_cert, private_key->GetProviderName(),
      private_key->GetAlgorithmPreferences(), std::move(ssl_private_key));
}

}  // namespace remoting
