// 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/network_service_network_delegate.h"

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

#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/optional_ref.h"
#include "base/types/optional_util.h"
#include "build/build_config.h"
#include "components/domain_reliability/monitor.h"
#include "net/base/features.h"
#include "net/base/isolation_info.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_util.h"
#include "net/url_request/clear_site_data.h"
#include "net/url_request/referrer_policy.h"
#include "net/url_request/url_request.h"
#include "services/network/cookie_manager.h"
#include "services/network/cookie_settings.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/network_service_proxy_delegate.h"
#include "services/network/pending_callback_chain.h"
#include "services/network/public/cpp/features.h"
#include "services/network/url_loader.h"
#include "services/network/url_loader_util.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_WEBSOCKETS)
#include "services/network/websocket.h"
#endif

namespace network {

namespace {
// Returns the permissions policy saved for the request in the loader. The
// loader should outlive the caller of this method.
base::optional_ref<const network::PermissionsPolicy> GetPermissionsPolicy(
    const net::URLRequest& request) {
  const auto* const loader = URLLoader::ForRequest(request);
  return loader ? loader->GetPermissionsPolicy() : std::nullopt;
}
}  // namespace

NetworkServiceNetworkDelegate::NetworkServiceNetworkDelegate(
    bool enable_referrers,
    bool validate_referrer_policy_on_initial_request,
    mojo::PendingRemote<mojom::ProxyErrorClient> proxy_error_client_remote,
    NetworkContext* network_context)
    : enable_referrers_(enable_referrers),
      validate_referrer_policy_on_initial_request_(
          validate_referrer_policy_on_initial_request),
      network_context_(network_context) {
  if (proxy_error_client_remote)
    proxy_error_client_.Bind(std::move(proxy_error_client_remote));
}

NetworkServiceNetworkDelegate::~NetworkServiceNetworkDelegate() = default;

void NetworkServiceNetworkDelegate::MaybeTruncateReferrer(
    net::URLRequest* const request,
    const GURL& effective_url) {
  if (!enable_referrers_) {
    request->SetReferrer(std::string());
    request->set_referrer_policy(net::ReferrerPolicy::NO_REFERRER);
    return;
  }
}

int NetworkServiceNetworkDelegate::OnBeforeURLRequest(
    net::URLRequest* request,
    net::CompletionOnceCallback callback,
    GURL* new_url) {
  DCHECK(request);

  auto* const loader = URLLoader::ForRequest(*request);
  const GURL* effective_url = nullptr;
  if (loader && loader->new_redirect_url()) {
    DCHECK(new_url);
    *new_url = loader->new_redirect_url().value();
    effective_url = new_url;
  } else {
    effective_url = &request->url();
  }

  MaybeTruncateReferrer(request, *effective_url);

  NetworkService* network_service = network_context_->network_service();
  if (network_service) {
    network_service->NotifyNetworkRequestWithAnnotation(
        request->traffic_annotation());
  }

  return net::OK;
}

int NetworkServiceNetworkDelegate::OnBeforeStartTransaction(
    net::URLRequest* request,
    const net::HttpRequestHeaders& headers,
    OnBeforeStartTransactionCallback callback) {
  URLLoader* url_loader = URLLoader::ForRequest(*request);
  if (url_loader)
    return url_loader->OnBeforeStartTransaction(headers, std::move(callback));

#if BUILDFLAG(ENABLE_WEBSOCKETS)
  WebSocket* web_socket = WebSocket::ForRequest(*request);
  if (web_socket)
    return web_socket->OnBeforeStartTransaction(headers, std::move(callback));
#endif  // BUILDFLAG(ENABLE_WEBSOCKETS)

  return net::OK;
}

int NetworkServiceNetworkDelegate::OnHeadersReceived(
    net::URLRequest* request,
    net::CompletionOnceCallback callback,
    const net::HttpResponseHeaders* original_response_headers,
    scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
    const net::IPEndPoint& endpoint,
    std::optional<GURL>* preserve_fragment_on_redirect_url,
    const std::optional<net::SSLInfo>& ssl_info) {
  auto chain = base::MakeRefCounted<PendingCallbackChain>(std::move(callback));
  URLLoader* url_loader = URLLoader::ForRequest(*request);

  if (url_loader) {
    chain->AddResult(url_loader->OnHeadersReceived(
        chain->CreateCallback(), original_response_headers,
        override_response_headers, endpoint, preserve_fragment_on_redirect_url,
        ssl_info));
  }

#if BUILDFLAG(ENABLE_WEBSOCKETS)
  WebSocket* web_socket = WebSocket::ForRequest(*request);
  if (web_socket) {
    chain->AddResult(web_socket->OnHeadersReceived(
        chain->CreateCallback(), original_response_headers,
        override_response_headers, preserve_fragment_on_redirect_url,
        ssl_info));
  }
#endif  // BUILDFLAG(ENABLE_WEBSOCKETS)

  chain->AddResult(HandleClearSiteDataHeader(request, chain->CreateCallback(),
                                             original_response_headers));

  return chain->GetResult();
}

void NetworkServiceNetworkDelegate::OnBeforeRedirect(net::URLRequest* request,
                                                     const GURL& new_location) {
  if (network_context_->domain_reliability_monitor())
    network_context_->domain_reliability_monitor()->OnBeforeRedirect(request);
}

void NetworkServiceNetworkDelegate::OnResponseStarted(net::URLRequest* request,
                                                      int net_error) {
  ForwardProxyErrors(net_error);
}

void NetworkServiceNetworkDelegate::OnCompleted(net::URLRequest* request,
                                                bool started,
                                                int net_error) {
  // TODO(mmenke): Once the network service ships on all platforms, can move
  // this logic into URLLoader's completion method.
  DCHECK_NE(net::ERR_IO_PENDING, net_error);

  if (network_context_->domain_reliability_monitor()) {
    network_context_->domain_reliability_monitor()->OnCompleted(
        request, started, net_error);
  }

  ForwardProxyErrors(net_error);
}

void NetworkServiceNetworkDelegate::OnPACScriptError(
    int line_number,
    const std::u16string& error) {
  if (!proxy_error_client_)
    return;

  proxy_error_client_->OnPACScriptError(line_number, base::UTF16ToUTF8(error));
}

std::optional<net::cookie_util::StorageAccessStatus>
NetworkServiceNetworkDelegate::OnGetStorageAccessStatus(
    const net::URLRequest& request,
    base::optional_ref<const net::RedirectInfo> redirect_info) const {
  if (redirect_info) {
    return network_context_->cookie_manager()
        ->cookie_settings()
        .GetStorageAccessStatus(
            redirect_info->new_url, redirect_info->new_site_for_cookies,
            request.isolation_info().top_frame_origin(),
            request.cookie_setting_overrides(), request.cookie_partition_key(),
            GetPermissionsPolicy(request));
  }
  return network_context_->cookie_manager()
      ->cookie_settings()
      .GetStorageAccessStatus(request.url(), request.site_for_cookies(),
                              request.isolation_info().top_frame_origin(),
                              request.cookie_setting_overrides(),
                              request.cookie_partition_key(),
                              GetPermissionsPolicy(request));
}

bool NetworkServiceNetworkDelegate::OnAnnotateAndMoveUserBlockedCookies(
    const net::URLRequest& request,
    const net::FirstPartySetMetadata& first_party_set_metadata,
    net::CookieAccessResultList& maybe_included_cookies,
    net::CookieAccessResultList& excluded_cookies) {
  if (!network_context_->cookie_manager()
           ->cookie_settings()
           .AnnotateAndMoveUserBlockedCookies(
               request.url(), request.site_for_cookies(),
               base::OptionalToPtr(request.isolation_info().top_frame_origin()),
               first_party_set_metadata, request.cookie_setting_overrides(),
               maybe_included_cookies, excluded_cookies)) {
    // CookieSettings has already moved and annotated the cookies.
    return false;
  }

  bool allowed = true;
  URLLoader* url_loader = URLLoader::ForRequest(request);
  if (url_loader) {
    allowed =
        url_loader->AllowFullCookies(request.url(), request.site_for_cookies());
    if (!allowed) {
      // AllowFullCookies returns false if "all" cookies are not allowed by a
      // URL but it could still be the case that cookies are allowed, but it was
      // 3PCs that were not allowed. If that is the case, we should still
      // preserve partitioned cookies.
      if (url_loader->CookiesDisabled()) {
        ExcludeAllCookies(net::CookieInclusionStatus::ExclusionReason::
                              EXCLUDE_USER_PREFERENCES,
                          maybe_included_cookies, excluded_cookies);
      } else {
        ExcludeAllCookiesExceptPartitioned(
            net::CookieInclusionStatus::ExclusionReason::
                EXCLUDE_USER_PREFERENCES,
            maybe_included_cookies, excluded_cookies);
      }
    }
#if BUILDFLAG(ENABLE_WEBSOCKETS)
  } else {
    WebSocket* web_socket = WebSocket::ForRequest(request);
    if (web_socket) {
      allowed = web_socket->AllowCookies(request.url());
      // TODO(crbug/324211435): Fix partitioned cookies for web sockets.
      if (!allowed) {
        ExcludeAllCookies(net::CookieInclusionStatus::ExclusionReason::
                              EXCLUDE_USER_PREFERENCES,
                          maybe_included_cookies, excluded_cookies);
      }
    }
#endif  // BUILDFLAG(ENABLE_WEBSOCKETS)
  }

  return allowed;
}

bool NetworkServiceNetworkDelegate::OnCanSetCookie(
    const net::URLRequest& request,
    const net::CanonicalCookie& cookie,
    net::CookieOptions* options,
    const net::FirstPartySetMetadata& first_party_set_metadata,
    net::CookieInclusionStatus* inclusion_status) {
  bool allowed =
      network_context_->cookie_manager()->cookie_settings().IsCookieAccessible(
          cookie, request.url(), request.site_for_cookies(),
          request.isolation_info().top_frame_origin(), first_party_set_metadata,
          request.cookie_setting_overrides(), inclusion_status);
  if (!allowed)
    return false;
  // The remaining checks do not consider setting overrides since they enforce
  // explicit disablement via Android Webview APIs.
  URLLoader* url_loader = URLLoader::ForRequest(request);
  if (url_loader) {
    return url_loader->AllowCookie(cookie, request.url(),
                                   request.site_for_cookies());
  }
#if BUILDFLAG(ENABLE_WEBSOCKETS)
  WebSocket* web_socket = WebSocket::ForRequest(request);
  if (web_socket)
    return web_socket->AllowCookies(request.url());
#endif  // BUILDFLAG(ENABLE_WEBSOCKETS)
  return true;
}

bool NetworkServiceNetworkDelegate::OnShouldForceIgnoreSiteForCookies(
    const net::URLRequest& request) {
  return url_loader_util::ShouldForceIgnoreSiteForCookies(
      request.url(), request.initiator(), request.site_for_cookies(),
      network_context_->cors_origin_access_list());
}

net::NetworkDelegate::PrivacySetting
NetworkServiceNetworkDelegate::OnForcePrivacyMode(
    const net::URLRequest& request) const {
  return network_context_->cookie_manager()
      ->cookie_settings()
      .IsPrivacyModeEnabled(request.url(), request.site_for_cookies(),
                            request.isolation_info().top_frame_origin(),
                            request.cookie_setting_overrides());
}

bool NetworkServiceNetworkDelegate::
    OnCancelURLRequestWithPolicyViolatingReferrerHeader(
        const net::URLRequest& request,
        const GURL& target_url,
        const GURL& referrer_url) const {
  // TODO(mmenke): Once the network service has shipped on all platforms,
  // consider moving this logic into URLLoader, and removing this method from
  // NetworkDelegate. Can just have a DCHECK in URLRequest instead.
  if (!validate_referrer_policy_on_initial_request_)
    return false;

  LOG(ERROR) << "Cancelling request to " << target_url
             << " with invalid referrer " << referrer_url;
  // Record information to help debug issues like http://crbug.com/422871.
  if (target_url.SchemeIsHTTPOrHTTPS()) {
    auto referrer_policy = request.referrer_policy();
    SCOPED_CRASH_KEY_NUMBER("Bug1485060", "referrer_policy",
                            static_cast<int>(referrer_policy));
    SCOPED_CRASH_KEY_STRING256("Bug1485060", "target_url", target_url.spec());
    SCOPED_CRASH_KEY_STRING256("Bug1485060", "referrer_url",
                               referrer_url.spec());
    base::debug::DumpWithoutCrashing();
  }
  return true;
}

bool NetworkServiceNetworkDelegate::OnCanQueueReportingReport(
    const url::Origin& origin) const {
  return network_context_->cookie_manager()
      ->cookie_settings()
      .IsFullCookieAccessAllowed(
          origin.GetURL(), net::SiteForCookies::FromOrigin(origin), origin,
          net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt);
}

void NetworkServiceNetworkDelegate::OnCanSendReportingReports(
    std::set<url::Origin> origins,
    base::OnceCallback<void(std::set<url::Origin>)> result_callback) const {
  auto* client = network_context_->client();
  if (!client) {
    origins.clear();
    std::move(result_callback).Run(std::move(origins));
    return;
  }

  if (network_context_->SkipReportingPermissionCheck()) {
    std::move(result_callback).Run(std::move(origins));
    return;
  }

  std::vector<url::Origin> origin_vector;
  std::ranges::copy(origins, std::back_inserter(origin_vector));
  client->OnCanSendReportingReports(
      origin_vector,
      base::BindOnce(
          &NetworkServiceNetworkDelegate::FinishedCanSendReportingReports,
          weak_ptr_factory_.GetWeakPtr(), std::move(result_callback)));
}

bool NetworkServiceNetworkDelegate::OnCanSetReportingClient(
    const url::Origin& origin,
    const GURL& endpoint) const {
  return network_context_->cookie_manager()
      ->cookie_settings()
      .IsFullCookieAccessAllowed(
          origin.GetURL(), net::SiteForCookies::FromOrigin(origin), origin,
          net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt);
}

bool NetworkServiceNetworkDelegate::OnCanUseReportingClient(
    const url::Origin& origin,
    const GURL& endpoint) const {
  return network_context_->cookie_manager()
      ->cookie_settings()
      .IsFullCookieAccessAllowed(
          origin.GetURL(), net::SiteForCookies::FromOrigin(origin), origin,
          net::CookieSettingOverrides(), /*cookie_partition_key=*/std::nullopt);
}

int NetworkServiceNetworkDelegate::HandleClearSiteDataHeader(
    net::URLRequest* request,
    net::CompletionOnceCallback callback,
    const net::HttpResponseHeaders* original_response_headers) {
  DCHECK(request);
  if (!original_response_headers)
    return net::OK;

  URLLoader* url_loader = URLLoader::ForRequest(*request);
  if (!url_loader)
    return net::OK;

  auto* url_loader_network_observer =
      url_loader->GetURLLoaderNetworkServiceObserver();
  if (!url_loader_network_observer)
    return net::OK;

  std::optional<std::string> header_value =
      original_response_headers->GetNormalizedHeader(net::kClearSiteDataHeader);
  if (!header_value) {
    return net::OK;
  }

  auto& cookie_settings = network_context_->cookie_manager()->cookie_settings();
  net::NetworkDelegate::PrivacySetting privacy_settings =
      cookie_settings.IsPrivacyModeEnabled(
          request->url(), request->isolation_info().site_for_cookies(),
          request->isolation_info().top_frame_origin(),
          request->cookie_setting_overrides());
  bool partitioned_state_allowed_only =
      privacy_settings ==
      net::NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly;

  url_loader_network_observer->OnClearSiteData(
      request->url(), *header_value, request->load_flags(),
      request->cookie_partition_key(), partitioned_state_allowed_only,
      base::BindOnce(&NetworkServiceNetworkDelegate::FinishedClearSiteData,
                     weak_ptr_factory_.GetWeakPtr(), request->GetWeakPtr(),
                     std::move(callback)));

  return net::ERR_IO_PENDING;
}

void NetworkServiceNetworkDelegate::FinishedClearSiteData(
    base::WeakPtr<net::URLRequest> request,
    net::CompletionOnceCallback callback) {
  if (request)
    std::move(callback).Run(net::OK);
}

void NetworkServiceNetworkDelegate::FinishedCanSendReportingReports(
    base::OnceCallback<void(std::set<url::Origin>)> result_callback,
    const std::vector<url::Origin>& origins) {
  std::set<url::Origin> origin_set(origins.begin(), origins.end());
  std::move(result_callback).Run(origin_set);
}

void NetworkServiceNetworkDelegate::ForwardProxyErrors(int net_error) {
  if (!proxy_error_client_)
    return;

  // TODO(crbug.com/41409550): Provide justification for the currently
  // enumerated errors.
  switch (net_error) {
    case net::ERR_PROXY_AUTH_UNSUPPORTED:
    case net::ERR_PROXY_CONNECTION_FAILED:
    case net::ERR_TUNNEL_CONNECTION_FAILED:
      proxy_error_client_->OnRequestMaybeFailedDueToProxySettings(net_error);
      break;
  }
}

}  // namespace network
