// 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 "content/browser/network_service_client.h"

#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/sequence_checker.h"
#include "base/sequence_token.h"
#include "base/threading/sequence_bound.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "content/browser/browsing_data/clear_site_data_handler.h"
#include "content/browser/buildflags.h"
#include "content/browser/ssl/ssl_manager.h"
#include "content/browser/webrtc/webrtc_connections_observer.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/network_service_util.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/network_change_notifier.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/network_change_manager.mojom-forward.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/content_uri_utils.h"
#endif

#if BUILDFLAG(IS_MAC)
#include "base/task/current_thread.h"
#endif

#if BUILDFLAG(IS_LINUX)
#include "net/base/address_map_linux.h"
#include "net/base/address_tracker_linux.h"
#endif

namespace content {

#if BUILDFLAG(IS_LINUX)
namespace {

// Takes care of passing updates to AddressTrackerLinux's AddressMap and set of
// online links to the network service to update its cache.
class NetworkInterfaceChangeHelper {
 public:
  explicit NetworkInterfaceChangeHelper(
      mojo::PendingAssociatedRemote<
          network::mojom::NetworkInterfaceChangeListener>
          network_interface_change_listener_pending)
      : network_interface_change_listener_pending_(
            std::move(network_interface_change_listener_pending)) {
    // This is constructed by NetworkServiceClient and only used on
    // AddressTrackerLinux's sequence.
    DETACH_FROM_SEQUENCE(sequence_checker_);
  }

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

  ~NetworkInterfaceChangeHelper() = default;

  // Callback for AddressTrackerLinux::SetDiffCallback.
  void SendAddressTrackerDiffsToNetworkService(
      const net::AddressMapOwnerLinux::AddressMapDiff& addr_diff,
      const net::AddressMapOwnerLinux::OnlineLinksDiff& online_links_diff) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    // On the first call, this binds the |network_interface_change_listener_| on
    // AddressTrackerLinux's sequence using
    // |network_interface_change_listener_pending_|.
    if (!network_interface_change_listener_) {
      DCHECK(network_interface_change_listener_pending_);
      network_interface_change_listener_.Bind(
          std::move(network_interface_change_listener_pending_));
    }
    auto params = network::mojom::NetworkInterfaceChangeParams::New(
        addr_diff, online_links_diff);
    network_interface_change_listener_->OnNetworkInterfacesChanged(
        std::move(params));
  }

 private:
  SEQUENCE_CHECKER(sequence_checker_);

  mojo::PendingAssociatedRemote<network::mojom::NetworkInterfaceChangeListener>
      network_interface_change_listener_pending_;
  mojo::AssociatedRemote<network::mojom::NetworkInterfaceChangeListener>
      network_interface_change_listener_ GUARDED_BY_CONTEXT(sequence_checker_);
};
}  // namespace
#endif

NetworkServiceClient::NetworkServiceClient()
#if BUILDFLAG(IS_ANDROID)
    : app_status_listener_(base::android::ApplicationStatusListener::New(
          base::BindRepeating(&NetworkServiceClient::OnApplicationStateChange,
                              base::Unretained(this))))
#endif
{

#if BUILDFLAG(IS_MAC)
  if (base::CurrentUIThread::IsSet())  // Not set in some unit tests.
    net::CertDatabase::GetInstance()->StartListeningForKeychainEvents();
#endif

  if (IsOutOfProcessNetworkService()) {
    net::CertDatabase::GetInstance()->AddObserver(this);
    memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
        FROM_HERE, base::BindRepeating(&NetworkServiceClient::OnMemoryPressure,
                                       base::Unretained(this)));
  }

  webrtc_connections_observer_ =
      std::make_unique<content::WebRtcConnectionsObserver>(base::BindRepeating(
          &NetworkServiceClient::OnPeerToPeerConnectionsCountChange,
          base::Unretained(this)));
}

NetworkServiceClient::~NetworkServiceClient() {
  if (IsOutOfProcessNetworkService()) {
    net::CertDatabase::GetInstance()->RemoveObserver(this);
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
    bool remove_ncn_observers = true;
#if BUILDFLAG(IS_LINUX)
    remove_ncn_observers = base::FeatureList::IsEnabled(
        net::features::kAddressTrackerLinuxIsProxied);
#endif  // BUILDFLAG(IS_LINUX)
    if (remove_ncn_observers) {
      net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
      net::NetworkChangeNotifier::RemoveMaxBandwidthObserver(this);
      net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
    }
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
  }
}

void NetworkServiceClient::OnCertDBChanged() {
  GetNetworkService()->OnCertDBChanged();
}

void NetworkServiceClient::OnMemoryPressure(
    base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
  GetNetworkService()->OnMemoryPressure(memory_pressure_level);
}

void NetworkServiceClient::OnPeerToPeerConnectionsCountChange(uint32_t count) {
  GetNetworkService()->OnPeerToPeerConnectionsCountChange(count);
}

#if BUILDFLAG(IS_ANDROID)
void NetworkServiceClient::OnApplicationStateChange(
    base::android::ApplicationState state) {
  GetNetworkService()->OnApplicationStateChange(state);
}
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
void NetworkServiceClient::OnConnectionTypeChanged(
    net::NetworkChangeNotifier::ConnectionType type) {
  network_change_manager_->OnNetworkChanged(
      false /* dns_changed */, false /* ip_address_changed */,
      true /* connection_type_changed */, network::mojom::ConnectionType(type),
      false /* connection_subtype_changed */,
      network::mojom::ConnectionSubtype(
          net::NetworkChangeNotifier::GetConnectionSubtype()));
}

void NetworkServiceClient::OnMaxBandwidthChanged(
    double max_bandwidth_mbps,
    net::NetworkChangeNotifier::ConnectionType type) {
  // The connection subtype change will trigger a max bandwidth change in the
  // network service notifier.
  network_change_manager_->OnNetworkChanged(
      false /* dns_changed */, false /* ip_address_changed */,
      false /* connection_type_changed */, network::mojom::ConnectionType(type),
      true /* connection_subtype_changed */,
      network::mojom::ConnectionSubtype(
          net::NetworkChangeNotifier::GetConnectionSubtype()));
}

void NetworkServiceClient::OnIPAddressChanged() {
  network_change_manager_->OnNetworkChanged(
      false /* dns_changed */, true /* ip_address_changed */,
      false /* connection_type_changed */,
      network::mojom::ConnectionType(
          net::NetworkChangeNotifier::GetConnectionType()),
      false /* connection_subtype_changed */,
      network::mojom::ConnectionSubtype(
          net::NetworkChangeNotifier::GetConnectionSubtype()));
}
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(USE_SOCKET_BROKER)
mojo::PendingRemote<network::mojom::SocketBroker>
NetworkServiceClient::BindSocketBroker() {
  return socket_broker_.BindNewRemote();
}
#endif  // BUILDFLAG(USE_SOCKET_BROKER)

mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
NetworkServiceClient::BindURLLoaderNetworkServiceObserver() {
  mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver> remote;
  url_loader_network_service_observers_.Add(
      this, remote.InitWithNewPipeAndPassReceiver());
  return remote;
}

void NetworkServiceClient::OnNetworkServiceInitialized(
    network::mojom::NetworkService* service) {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
  bool add_ncn_observers = true;
#if BUILDFLAG(IS_LINUX)
  add_ncn_observers = base::FeatureList::IsEnabled(
      net::features::kAddressTrackerLinuxIsProxied);
#endif  // BUILDFLAG(IS_LINUX)
  if (IsOutOfProcessNetworkService() && add_ncn_observers) {
    DCHECK(!net::NetworkChangeNotifier::CreateIfNeeded());
    service->GetNetworkChangeManager(
        network_change_manager_.BindNewPipeAndPassReceiver());
#if BUILDFLAG(IS_LINUX)
    // Keep the tracking AddressTrackerLinux in sync with the caching version in
    // the network service, which cannot use AddressTrackerLinux in the sandbox.
    mojo::PendingAssociatedRemote<
        network::mojom::NetworkInterfaceChangeListener>
        network_interface_change_listener_pending;
    network_change_manager_->BindNetworkInterfaceChangeListener(
        network_interface_change_listener_pending
            .InitWithNewEndpointAndPassReceiver());
    // Have the AddressTrackerLinux send any changes to the AddressMap or set of
    // online links over |network_interface_change_listener_pending|.
    auto diff_callback_helper = std::make_unique<NetworkInterfaceChangeHelper>(
        std::move(network_interface_change_listener_pending));
    net::NetworkChangeNotifier::GetAddressMapOwner()
        ->GetAddressTrackerLinux()
        ->SetDiffCallback(
            base::BindRepeating(&NetworkInterfaceChangeHelper::
                                    SendAddressTrackerDiffsToNetworkService,
                                std::move(diff_callback_helper)));
#endif  // BUILDFLAG(IS_LINUX)
    net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
    net::NetworkChangeNotifier::AddMaxBandwidthObserver(this);
    net::NetworkChangeNotifier::AddIPAddressObserver(this);
  }
#endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
}

void NetworkServiceClient::OnSSLCertificateError(
    const GURL& url,
    int net_error,
    const net::SSLInfo& ssl_info,
    bool fatal,
#ifdef OHOS_NETWORK_LOAD
    const GURL& origin_url,
    const std::string& referrer,
#endif
    OnSSLCertificateErrorCallback response) {
  std::move(response).Run(net::ERR_INSECURE_RESPONSE);
}

void NetworkServiceClient::OnCertificateRequested(
    const absl::optional<base::UnguessableToken>& window_id,
    const scoped_refptr<net::SSLCertRequestInfo>& cert_info,
    mojo::PendingRemote<network::mojom::ClientCertificateResponder>
        cert_responder_remote) {
  mojo::Remote<network::mojom::ClientCertificateResponder> cert_responder(
      std::move(cert_responder_remote));

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          network::switches::kIgnoreUrlFetcherCertRequests)) {
    cert_responder->ContinueWithoutCertificate();
    return;
  }
  cert_responder->CancelRequest();
}

void NetworkServiceClient::OnAuthRequired(
    const absl::optional<base::UnguessableToken>& window_id,
    uint32_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) {
  mojo::Remote<network::mojom::AuthChallengeResponder>
      auth_challenge_responder_remote(std::move(auth_challenge_responder));
  auth_challenge_responder_remote->OnAuthCredentials(absl::nullopt);
}

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

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

void NetworkServiceClient::OnDataUseUpdate(
    int32_t network_traffic_annotation_id_hash,
    int64_t recv_bytes,
    int64_t sent_bytes) {
  GetContentClient()->browser()->OnNetworkServiceDataUseUpdate(
      GlobalRenderFrameHostId(), network_traffic_annotation_id_hash, recv_bytes,
      sent_bytes);
}

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

}  // namespace content