// 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 "extensions/browser/api/web_request/web_request_api.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "arkweb/build/features/features.h"
#include "base/containers/fixed_flat_set.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "components/safe_browsing/core/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/api/web_request/extension_web_request_event_router.h"
#include "extensions/browser/api/web_request/web_request_api_constants.h"
#include "extensions/browser/api/web_request/web_request_api_helpers.h"
#include "extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h"
#include "extensions/browser/api/web_request/web_request_proxying_websocket.h"
#include "extensions/browser/api/web_request/web_request_proxying_webtransport.h"
#include "extensions/browser/browser_frame_context_data.h"
#include "extensions/browser/browser_process_context_data.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/install_prefs_helper.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/warning_service.h"
#include "extensions/browser/warning_set.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/api/web_request.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"
#include "ipc/constants.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/auth.h"
#include "net/cookies/site_for_cookies.h"
#include "net/http/http_util.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/mojom/web_transport.mojom.h"
#include "third_party/blink/public/common/features_generated.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#endif

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

using content::BrowserThread;
using extension_web_request_api_helpers::ExtraInfoSpec;
using extensions::mojom::APIPermissionID;

namespace helpers = extension_web_request_api_helpers;
namespace keys = extension_web_request_api_constants;
using URLLoaderFactoryType =
    content::ContentBrowserClient::URLLoaderFactoryType;

namespace extensions {

namespace web_request = api::web_request;

namespace {

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
const char kWebRequestApiLogTag[] = "[WebRequestAPI]";
#endif

WebRequestAPI::TestObserver* g_test_observer = nullptr;

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(ProxyDecisionDetailsForExtension)
enum class ProxyDecisionDetailsForExtension {
  // Proxy will be used only for WebRequest* permissions.
  kOnlyForWebRequest = 0,
  // Proxy will be used only for Declarative{Web|Net}Request* permissions.
  kOnlyForDeclarativeRequest = 1,
  // Proxy will be used only for WebView permissions.
  kOnlyForWebView = 2,
  // Proxy will be used only for multiple kinds of permissions.
  kForMixedReasons = 3,

  kMaxValue = kForMixedReasons,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/extensions/enums.xml:WebRequestProxyDecisionDetailsForExtension)

// Converts an HttpHeaders dictionary to a |name|, |value| pair. Returns
// true if successful.
bool FromHeaderDictionary(const base::Value::Dict& header_value,
                          std::string* name,
                          std::string* out_value) {
  const std::string* name_ptr = header_value.FindString(keys::kHeaderNameKey);
  if (!name) {
    return false;
  }
  *name = *name_ptr;

  const base::Value* value = header_value.Find(keys::kHeaderValueKey);
  const base::Value* binary_value =
      header_value.Find(keys::kHeaderBinaryValueKey);
  // We require either a "value" or a "binaryValue" entry, but not both.
  if ((value == nullptr && binary_value == nullptr) ||
      (value != nullptr && binary_value != nullptr)) {
    return false;
  }

  if (value) {
    if (!value->is_string()) {
      return false;
    }
    *out_value = value->GetString();
  } else if (!binary_value->is_list() ||
             !helpers::CharListToString(binary_value->GetList(), out_value)) {
    return false;
  }
  return true;
}

template <size_t N>
bool DoesExtensionHasAnyOfPermission(
    const Extension& extension,
    const base::fixed_flat_set<APIPermissionID, N>& permissions) {
  const PermissionsData* permissions_data = extension.permissions_data();
  return std::ranges::any_of(permissions, [&permissions_data](auto permission) {
    return permissions_data->HasAPIPermission(permission);
  });
}

// Checks whether the extension has WebRequest* permissions.
bool HasAnyWebRequestPermissions(const Extension& extension) {
  static constexpr auto kPermissions = base::MakeFixedFlatSet<APIPermissionID>({
      APIPermissionID::kWebRequest,
      APIPermissionID::kWebRequestBlocking,
  });

  return DoesExtensionHasAnyOfPermission(extension, kPermissions);
}

// Checks whether the extension has Declarative{Web|Net}Request* permissions.
bool HasAnyDeclarativeWebRequestPermissions(const Extension& extension) {
  static constexpr auto kPermissions = base::MakeFixedFlatSet<APIPermissionID>({
      APIPermissionID::kDeclarativeWebRequest,
      APIPermissionID::kDeclarativeNetRequest,
      APIPermissionID::kDeclarativeNetRequestWithHostAccess,
  });

  return DoesExtensionHasAnyOfPermission(extension, kPermissions);
}

// Checks whether the extension has WebView permission.
bool HasWebViewPermission(const Extension& extension) {
  const PermissionsData* permissions = extension.permissions_data();
  return permissions->HasAPIPermission(APIPermissionID::kWebView);
}

// Mirrors the histogram enum of the same name. DO NOT REORDER THESE VALUES OR
// CHANGE THEIR MEANING.
enum class WebRequestEventListenerFlag {
  kTotal,
  kNone,
  kRequestHeaders,
  kResponseHeaders,
  kBlocking,
  kAsyncBlocking,
  kRequestBody,
  kExtraHeaders,
  kMaxValue = kExtraHeaders,
};

}  // namespace

void WebRequestAPI::Proxy::HandleAuthRequest(
    const net::AuthChallengeInfo& auth_info,
    scoped_refptr<net::HttpResponseHeaders> response_headers,
    int32_t request_id,
    AuthRequestCallback callback) {
  // Default implementation cancels the request.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), std::nullopt,
                                false /* should_cancel */));
}

WebRequestAPI::ProxySet::ProxySet() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

WebRequestAPI::ProxySet::~ProxySet() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void WebRequestAPI::ProxySet::AddProxy(std::unique_ptr<Proxy> proxy) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  proxies_.insert(std::move(proxy));
}

void WebRequestAPI::ProxySet::RemoveProxy(Proxy* proxy) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  auto requests_it = proxy_to_request_id_map_.find(proxy);
  if (requests_it != proxy_to_request_id_map_.end()) {
    for (const auto& id : requests_it->second) {
      request_id_to_proxy_map_.erase(id);
    }
    proxy_to_request_id_map_.erase(requests_it);
  }

  auto proxy_it = proxies_.find(proxy);
  CHECK(proxy_it != proxies_.end());
  proxies_.erase(proxy_it);
}

void WebRequestAPI::ProxySet::AssociateProxyWithRequestId(
    Proxy* proxy,
    const content::GlobalRequestID& id) {
  DCHECK(proxy);
  DCHECK(proxies_.count(proxy));
  DCHECK(id.request_id);
  auto result = request_id_to_proxy_map_.emplace(id, proxy);
  DCHECK(result.second) << "Unexpected request ID collision.";
  proxy_to_request_id_map_[proxy].insert(id);
}

void WebRequestAPI::ProxySet::DisassociateProxyWithRequestId(
    Proxy* proxy,
    const content::GlobalRequestID& id) {
  DCHECK(proxy);
  DCHECK(proxies_.count(proxy));
  DCHECK(id.request_id);
  size_t count = request_id_to_proxy_map_.erase(id);
  DCHECK_GT(count, 0u);
  count = proxy_to_request_id_map_[proxy].erase(id);
  DCHECK_GT(count, 0u);
}

WebRequestAPI::Proxy* WebRequestAPI::ProxySet::GetProxyFromRequestId(
    const content::GlobalRequestID& id) {
  auto it = request_id_to_proxy_map_.find(id);
  return it == request_id_to_proxy_map_.end() ? nullptr : it->second;
}

void WebRequestAPI::ProxySet::MaybeProxyAuthRequest(
    const net::AuthChallengeInfo& auth_info,
    scoped_refptr<net::HttpResponseHeaders> response_headers,
    const content::GlobalRequestID& request_id,
    AuthRequestCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  Proxy* proxy = GetProxyFromRequestId(request_id);
  if (!proxy) {
    // Run the |callback| which will display a dialog for the user to enter
    // their auth credentials.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), std::nullopt,
                                  false /* should_cancel */));
    return;
  }

  proxy->HandleAuthRequest(auth_info, std::move(response_headers),
                           request_id.request_id, std::move(callback));
}

void WebRequestAPI::ProxySet::OnDNRExtensionUnloaded(
    const Extension* extension) {
  for (const auto& proxy : proxies_) {
    proxy->OnDNRExtensionUnloaded(extension);
  }
}

WebRequestAPI::RequestIDGenerator::RequestIDGenerator() = default;
WebRequestAPI::RequestIDGenerator::~RequestIDGenerator() = default;

int64_t WebRequestAPI::RequestIDGenerator::Generate(
    int32_t routing_id,
    int32_t network_service_request_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto it = saved_id_map_.find({routing_id, network_service_request_id});
  if (it != saved_id_map_.end()) {
    int64_t id = it->second;
    saved_id_map_.erase(it);
    return id;
  }
  return ++id_;
}

void WebRequestAPI::RequestIDGenerator::SaveID(
    int32_t routing_id,
    int32_t network_service_request_id,
    uint64_t request_id) {
  // If |network_service_request_id| is 0, we cannot reliably match the
  // generated ID to a future request, so ignore it.
  if (network_service_request_id != 0) {
    saved_id_map_.insert(
        {{routing_id, network_service_request_id}, request_id});
  }
}

WebRequestAPI::WebRequestAPI(content::BrowserContext* context)
    : browser_context_(context),
      proxies_(std::make_unique<ProxySet>()),
      may_have_proxies_(MayHaveProxies()) {
  EventRouter* event_router = EventRouter::Get(browser_context_);
  // TODO(crbug.com/40393861): Once ExtensionWebRequestEventRouter is a per-
  // BrowserContext instance, it can observe these events itself. That's a
  // bit tricky right now because the singleton instance would need to
  // observe the EventRouter for each BrowserContext that has webRequest
  // API event listeners.
  // Observe related events in the EventRouter for the WebRequestEventRouter.
  for (const std::string& event_name : WebRequestEventRouter::GetEventNames()) {
    event_router->RegisterObserver(this, event_name);
  }
  extensions::ExtensionRegistry::Get(browser_context_)->AddObserver(this);
}

WebRequestAPI::~WebRequestAPI() = default;

void WebRequestAPI::Shutdown() {
  proxies_.reset();
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
  extensions::ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
  // TODO(crbug.com/40264286): Remove this once WebRequestEventRouter
  // implements `KeyedService::Shutdown` correctly.
  WebRequestEventRouter::Get(browser_context_)
      ->OnBrowserContextShutdown(browser_context_);
}

static base::LazyInstance<
    BrowserContextKeyedAPIFactory<WebRequestAPI>>::DestructorAtExit g_factory =
    LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<WebRequestAPI>*
WebRequestAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

// static
void WebRequestAPI::SetObserverForTest(TestObserver* observer) {
  g_test_observer = observer;
}

WebRequestAPI::TestObserver::TestObserver() = default;

WebRequestAPI::TestObserver::~TestObserver() = default;

void WebRequestAPI::OnListenerRemoved(const EventListenerInfo& details) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // TODO(fsamuel): <webview> events will not be removed through this code path.
  // <webview> events will be removed in RemoveWebViewEventListeners. Ideally,
  // this code should be decoupled from extensions, we should use the host ID
  // instead, and not have two different code paths. This is a huge undertaking
  // unfortunately, so we'll resort to two code paths for now.

  // Note that details.event_name includes the sub-event details (e.g. "/123").
  const std::string& sub_event_name = details.event_name;

#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  LOG(INFO) << kWebRequestApiLogTag
            << " Remove listener <sub_event_name:" << sub_event_name
            << ", is_lazy:" << details.is_lazy
            << "> for extension_id=" << details.extension_id;
#endif

  // The way we handle the listener removal depends on whether this was a
  // lazy listener registration (indicated by a null browser context on
  // `details`).
  base::OnceClosure remove_listener;

  if (details.is_lazy) {
    // This is a removed lazy listener. This happens when an extension uses
    // removeListener() in its lazy context to forcibly remove a listener
    // registration (as opposed to when the context is torn down, in which case
    // it's the active listener registration that's removed).
    // Due to https://crbug.com/1347597, we only have a single lazy listener
    // registration shared for both the on- and off-the-record contexts, so we
    // use the original context (associated with this KeyedService) to remove
    // the listener from both contexts.
    // Note that we unwrap the raw_ptr BrowserContext instance using
    // raw_ptr::get() so we truly have a raw pointer to bind into the callback.
    remove_listener = base::BindOnce(
        &WebRequestAPI::RemoveLazyListener, weak_factory_.GetWeakPtr(),
        browser_context_.get(), details.extension_id, sub_event_name);
  } else {
    // This was an active listener registration.
    auto update_type = WebRequestEventRouter::ListenerUpdateType::kRemove;
    if (details.service_worker_version_id !=
        blink::mojom::kInvalidServiceWorkerVersionId) {
      // This was a listener removed for a service worker, but it wasn't the
      // lazy listener registration. In this case, we only deactivate the
      // listener (rather than removing it).
      update_type = WebRequestEventRouter::ListenerUpdateType::kDeactivate;
    }

    // Note that we unwrap the raw_ptr BrowserContext instance using
    // raw_ptr::get() so we truly have a raw pointer to bind into the callback.
    remove_listener = base::BindOnce(
        &WebRequestAPI::UpdateActiveListener, weak_factory_.GetWeakPtr(),
        base::UnsafeDanglingUntriaged(details.browser_context.get()),
        update_type, details.extension_id, sub_event_name,
        details.worker_thread_id, details.service_worker_version_id);
  }

  // This PostTask is necessary even though we are already on the UI thread to
  // allow cases where blocking listeners remove themselves inside the handler.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, std::move(remove_listener));
}

bool WebRequestAPI::MaybeProxyURLLoaderFactory(
    content::BrowserContext* browser_context,
    content::RenderFrameHost* frame,
    int render_process_id,
    URLLoaderFactoryType type,
    std::optional<int64_t> navigation_id,
    ukm::SourceIdObj ukm_source_id,
    network::URLLoaderFactoryBuilder& factory_builder,
    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
        header_client,
    scoped_refptr<base::SequencedTaskRunner> navigation_response_task_runner,
    const url::Origin& request_initiator) {
  const ProxyDecision decision = MaybeProxyURLLoaderFactoryInternal(
      browser_context, frame, render_process_id, type, navigation_id,
      ukm_source_id, factory_builder, header_client,
      std::move(navigation_response_task_runner), request_initiator);
  base::UmaHistogramEnumeration("Extensions.WebRequest.ProxyDecision2",
                                decision);
  const size_t kMaxCount = 10u;
  base::UmaHistogramExactLinear(
      "Extensions.WebRequest.WebRequestDependentExtensionCount",
      web_request_extension_count_, kMaxCount);
  base::UmaHistogramExactLinear(
      "Extensions.WebRequest.DeclarativeRequestDependentExtensionCount",
      declarative_request_extension_count_, kMaxCount);
  base::UmaHistogramExactLinear(
      "Extensions.WebRequest.WebViewDependentExtensionCount",
      web_view_extension_count_, kMaxCount);

  if (decision == ProxyDecision::kWillProxyForExtension &&
      !base::FeatureList::IsEnabled(
          extensions_features::kForceWebRequestProxyForTest)) {
    // Check if kWillProxyForExtension is decided only for one type of
    // permissions, or mixed reasons.
    ProxyDecisionDetailsForExtension details =
        ProxyDecisionDetailsForExtension::kForMixedReasons;
    if (web_request_extension_count_ == 0 &&
        declarative_request_extension_count_ == 0) {
      CHECK_NE(web_view_extension_count_, 0);
      details = ProxyDecisionDetailsForExtension::kOnlyForWebView;
    } else if (web_view_extension_count_ == 0 &&
               declarative_request_extension_count_ == 0) {
      CHECK_NE(web_request_extension_count_, 0);
      details = ProxyDecisionDetailsForExtension::kOnlyForWebRequest;
    } else if (web_request_extension_count_ == 0 &&
               web_view_extension_count_ == 0) {
      CHECK_NE(declarative_request_extension_count_, 0);
      details = ProxyDecisionDetailsForExtension::kOnlyForDeclarativeRequest;
    }
    base::UmaHistogramEnumeration(
        "Extensions.WebRequest.ProxyDecisionDetailsForExtension", details);
  }
  return decision != ProxyDecision::kWillNotProxy;
}

WebRequestAPI::ProxyDecision WebRequestAPI::MaybeProxyURLLoaderFactoryInternal(
    content::BrowserContext* browser_context,
    content::RenderFrameHost* frame,
    int render_process_id,
    URLLoaderFactoryType type,
    std::optional<int64_t> navigation_id,
    ukm::SourceIdObj ukm_source_id,
    network::URLLoaderFactoryBuilder& factory_builder,
    mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
        header_client,
    scoped_refptr<base::SequencedTaskRunner> navigation_response_task_runner,
    const url::Origin& request_initiator) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ProxyDecision decision = MayHaveProxies()
                               ? ProxyDecision::kWillProxyForExtension
                               : ProxyDecision::kWillNotProxy;
  if (decision != ProxyDecision::kWillProxyForExtension) {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
    // There are a few internal WebUIs that use WebView tag that are allowlisted
    // for webRequest.
    // TODO(crbug.com/40288053): Remove the scheme check once we're sure
    // that WebUIs with WebView run in real WebUI processes and check the
    // context type using |IsAvailableToWebViewEmbedderFrame()| below.
    if (WebViewGuest::IsGuest(frame)) {
      content::RenderFrameHost* embedder =
          frame->GetOutermostMainFrameOrEmbedder();
      const auto& embedder_url = embedder->GetLastCommittedURL();
      if (embedder_url.SchemeIs(content::kChromeUIScheme)) {
        auto* feature = FeatureProvider::GetAPIFeature("webRequestInternal");
        if (feature
                ->IsAvailableToContext(
                    nullptr, mojom::ContextType::kWebUi, embedder_url,
                    util::GetBrowserContextId(browser_context),
                    BrowserFrameContextData(frame))
                .is_available()) {
          decision = ProxyDecision::kWillProxyForWebUI;
        }
      } else {
        if (IsAvailableToWebViewEmbedderFrame(frame)) {
          decision = ProxyDecision::kWillProxyForEmbedderWebView;
        }
      }
    }
#endif

    if (decision == ProxyDecision::kWillNotProxy) {
      return decision;
    }
  }

  std::unique_ptr<ExtensionNavigationUIData> navigation_ui_data;
  const bool is_navigation = (type == URLLoaderFactoryType::kNavigation);
  if (is_navigation) {
    DCHECK(frame);
    DCHECK(navigation_id);
    int tab_id;
    int window_id;
    ExtensionsBrowserClient::Get()->GetTabAndWindowIdForWebContents(
        content::WebContents::FromRenderFrameHost(frame), &tab_id, &window_id);
    navigation_ui_data =
        std::make_unique<ExtensionNavigationUIData>(frame, tab_id, window_id);
  }

  mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient>
      header_client_receiver;
  if (header_client) {
    header_client_receiver = header_client->InitWithNewPipeAndPassReceiver();
  }

  // NOTE: This request may be proxied on behalf of an incognito frame, but
  // |this| will always be bound to a regular profile (see
  // |BrowserContextKeyedAPI::kServiceRedirectedInIncognito|).
  DCHECK(browser_context == browser_context_ ||
         (browser_context->IsOffTheRecord() &&
          ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context) ==
              browser_context_));
  WebRequestProxyingURLLoaderFactory::StartProxying(
      browser_context, is_navigation ? -1 : render_process_id,
      frame ? frame->GetRoutingID() : IPC::mojom::kRoutingIdNone,
      frame ? frame->GetRenderViewHost()->GetRoutingID()
            : IPC::mojom::kRoutingIdNone,
      &request_id_generator_, std::move(navigation_ui_data),
      std::move(navigation_id), ukm_source_id, factory_builder,
      std::move(header_client_receiver), proxies_.get(), type,
      std::move(navigation_response_task_runner));
  return decision;
}

bool WebRequestAPI::MaybeProxyAuthRequest(
    content::BrowserContext* browser_context,
    const net::AuthChallengeInfo& auth_info,
    scoped_refptr<net::HttpResponseHeaders> response_headers,
    const content::GlobalRequestID& request_id,
    bool is_request_for_navigation,
    AuthRequestCallback callback,
    WebViewGuest* web_view_guest) {
  if (!MayHaveProxies()) {
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
    LOG(INFO) << kWebRequestApiLogTag << " May not be proxy auth request";
#endif
    bool needed_for_webview = false;
#if BUILDFLAG(ENABLE_GUEST_VIEW)
    needed_for_webview =
        web_view_guest &&
        IsAvailableToWebViewEmbedderFrame(web_view_guest->GetGuestMainFrame());
#endif
    if (!needed_for_webview) {
      return false;
    }
  }

  content::GlobalRequestID proxied_request_id = request_id;
  // In MaybeProxyURLLoaderFactory, we use -1 as render_process_id for
  // navigation requests. Applying the same logic here so that we can correctly
  // identify the request.
  if (is_request_for_navigation) {
    proxied_request_id.child_id = -1;
  }

  // NOTE: This request may be proxied on behalf of an incognito frame, but
  // |this| will always be bound to a regular profile (see
  // |BrowserContextKeyedAPI::kServiceRedirectedInIncognito|).
  DCHECK(browser_context == browser_context_ ||
         (browser_context->IsOffTheRecord() &&
          ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context) ==
              browser_context_));
  proxies_->MaybeProxyAuthRequest(auth_info, std::move(response_headers),
                                  proxied_request_id, std::move(callback));
  return true;
}

void WebRequestAPI::ProxyWebSocket(
    content::RenderFrameHost* frame,
    content::ContentBrowserClient::WebSocketFactory factory,
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const std::optional<std::string>& user_agent,
    mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
        handshake_client) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(MayHaveProxies() || IsAvailableToWebViewEmbedderFrame(frame));

  content::BrowserContext* browser_context =
      frame->GetProcess()->GetBrowserContext();
  const bool has_extra_headers =
      WebRequestEventRouter::Get(browser_context)
          ->HasAnyExtraHeadersListener(browser_context);

  WebRequestProxyingWebSocket::StartProxying(
      std::move(factory), url, site_for_cookies, user_agent,
      std::move(handshake_client), has_extra_headers,
      frame->GetProcess()->GetDeprecatedID(), frame->GetRoutingID(),
      &request_id_generator_, frame->GetLastCommittedOrigin(),
      frame->GetProcess()->GetBrowserContext(), proxies_.get());
}

void WebRequestAPI::ProxyWebTransport(
    content::RenderProcessHost& render_process_host,
    int frame_routing_id,
    const GURL& url,
    const url::Origin& initiator_origin,
    mojo::PendingRemote<network::mojom::WebTransportHandshakeClient>
        handshake_client,
    content::ContentBrowserClient::WillCreateWebTransportCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!MayHaveProxies()) {
    auto* render_frame_host = content::RenderFrameHost::FromID(
        render_process_host.GetDeprecatedID(), frame_routing_id);
    if (!IsAvailableToWebViewEmbedderFrame(render_frame_host)) {
      std::move(callback).Run(std::move(handshake_client), std::nullopt);
      return;
    }
  }
  DCHECK(proxies_);
  StartWebRequestProxyingWebTransport(
      render_process_host, frame_routing_id, url, initiator_origin,
      std::move(handshake_client),
      request_id_generator_.Generate(IPC::mojom::kRoutingIdNone, 0),
      *proxies_.get(), std::move(callback));
}

void WebRequestAPI::ForceProxyForTesting() {
  ++web_request_extension_count_;
  UpdateMayHaveProxies();
}

bool WebRequestAPI::MayHaveProxies() const {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (base::FeatureList::IsEnabled(
          extensions_features::kForceWebRequestProxyForTest)) {
    return true;
  }

  return (web_request_extension_count_ > 0) ||
         (declarative_request_extension_count_ > 0) ||
         (web_view_extension_count_ > 0);
}

bool WebRequestAPI::IsAvailableToWebViewEmbedderFrame(
    content::RenderFrameHost* render_frame_host) const {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
  if (!render_frame_host || !WebViewGuest::IsGuest(render_frame_host)) {
    return false;
  }

  content::BrowserContext* browser_context =
      render_frame_host->GetBrowserContext();
  content::RenderFrameHost* embedder_frame =
      render_frame_host->GetOutermostMainFrameOrEmbedder();

  if (!ProcessMap::Get(browser_context)
           ->CanProcessHostContextType(/*extension=*/nullptr,
                                       *embedder_frame->GetProcess(),
                                       mojom::ContextType::kWebPage)) {
    return false;
  }

  Feature::Availability availability =
      ExtensionAPI::GetSharedInstance()->IsAvailable(
          "webRequestInternal", /*extension=*/nullptr,
          mojom::ContextType::kWebPage, embedder_frame->GetLastCommittedURL(),
          CheckAliasStatus::ALLOWED, util::GetBrowserContextId(browser_context),
          BrowserFrameContextData(embedder_frame));
  return availability.is_available();
#else
  return false;
#endif
}

bool WebRequestAPI::HasExtraHeadersListenerForTesting() {
  return WebRequestEventRouter::Get(browser_context_)
      ->HasAnyExtraHeadersListener(browser_context_);
}

void WebRequestAPI::ResetURLLoaderFactories() {
  browser_context_->GetDefaultStoragePartition()->ResetURLLoaderFactories();
  if (g_test_observer) {
    g_test_observer->OnDidResetURLLoaderFactories();
  }
}

void WebRequestAPI::UpdateMayHaveProxies() {
  bool may_have_proxies = MayHaveProxies();
#if !BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
  if (!may_have_proxies_ && may_have_proxies) {
    ResetURLLoaderFactories();
  }
#endif
  may_have_proxies_ = may_have_proxies;
}

void WebRequestAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
                                      const Extension* extension) {
  CHECK(extension);
  bool update_may_have_proxies = false;
  if (HasAnyWebRequestPermissions(*extension)) {
    ++web_request_extension_count_;
    update_may_have_proxies = true;
    if (BackgroundInfo::IsServiceWorkerBased(extension)) {
      WebRequestEventRouter::Get(browser_context)
          ->LoadPersistedLazyListeners(browser_context, extension->id());
    }
  }
  if (HasAnyDeclarativeWebRequestPermissions(*extension)) {
    ++declarative_request_extension_count_;
    update_may_have_proxies = true;
  }
  if (HasWebViewPermission(*extension)) {
    ++web_view_extension_count_;
    update_may_have_proxies = true;
  }
  if (update_may_have_proxies) {
    UpdateMayHaveProxies();
  }
}

void WebRequestAPI::OnExtensionUnloaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UnloadedExtensionReason reason) {
  CHECK(extension);
  bool update_may_have_proxies = false;
  if (HasAnyWebRequestPermissions(*extension)) {
    --web_request_extension_count_;
    update_may_have_proxies = true;
  }
  if (HasAnyDeclarativeWebRequestPermissions(*extension)) {
    --declarative_request_extension_count_;
    update_may_have_proxies = true;
  }
  if (HasWebViewPermission(*extension)) {
    --web_view_extension_count_;
    update_may_have_proxies = true;
  }
  if (update_may_have_proxies) {
    UpdateMayHaveProxies();
  }

  if (declarative_net_request::HasAnyDNRPermission(*extension)) {
    proxies_->OnDNRExtensionUnloaded(extension);
  }
}

void WebRequestAPI::UpdateActiveListener(
    void* browser_context_id,
    WebRequestEventRouter::ListenerUpdateType update_type,
    const ExtensionId& extension_id,
    const std::string& sub_event_name,
    int worker_thread_id,
    int64_t service_worker_version_id) {
  if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context_id)) {
    return;
  }

  content::BrowserContext* browser_context =
      reinterpret_cast<content::BrowserContext*>(browser_context_id);
  WebRequestEventRouter::Get(browser_context)
      ->UpdateActiveListener(browser_context, update_type, extension_id,
                             sub_event_name, worker_thread_id,
                             service_worker_version_id);
}

void WebRequestAPI::RemoveLazyListener(content::BrowserContext* browser_context,
                                       const ExtensionId& extension_id,
                                       const std::string& sub_event_name) {
  if (!ExtensionsBrowserClient::Get()->IsValidContext(browser_context)) {
    return;
  }
  WebRequestEventRouter::Get(browser_context)
      ->RemoveLazyListener(browser_context, extension_id, sub_event_name);
}

// Special QuotaLimitHeuristic for WebRequestHandlerBehaviorChangedFunction.
//
// Each call of webRequest.handlerBehaviorChanged() clears the in-memory cache
// of WebKit at the time of the next page load (top level navigation event).
// This quota heuristic is intended to limit the number of times the cache is
// cleared by an extension.
//
// As we want to account for the number of times the cache is really cleared
// (opposed to the number of times webRequest.handlerBehaviorChanged() is
// called), we cannot decide whether a call of
// webRequest.handlerBehaviorChanged() should trigger a quota violation at the
// time it is called. Instead we only decrement the bucket counter at the time
// when the cache is cleared (when page loads happen).
class ClearCacheQuotaHeuristic : public QuotaLimitHeuristic {
 public:
  ClearCacheQuotaHeuristic(const Config& config,
                           std::unique_ptr<BucketMapper> map)
      : QuotaLimitHeuristic(
            config,
            std::move(map),
            "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES"),
        callback_registered_(false) {}

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

  ~ClearCacheQuotaHeuristic() override {}
  bool Apply(Bucket* bucket, const base::TimeTicks& event_time) override;

 private:
  // Callback that is triggered by the WebRequestEventRouter on a page load.
  //
  // We don't need to take care of the life time of |bucket|: It is owned by the
  // BucketMapper of our base class in |QuotaLimitHeuristic::bucket_mapper_|. As
  // long as |this| exists, the respective BucketMapper and its bucket will
  // exist as well.
  void OnPageLoad(Bucket* bucket);

  // Flag to prevent that we register more than one call back in-between
  // clearing the cache.
  bool callback_registered_;

  base::WeakPtrFactory<ClearCacheQuotaHeuristic> weak_ptr_factory_{this};
};

bool ClearCacheQuotaHeuristic::Apply(Bucket* bucket,
                                     const base::TimeTicks& event_time) {
  if (event_time > bucket->expiration()) {
    bucket->Reset(config(), event_time);
  }

  // Call bucket->DeductToken() on a new page load, this is when
  // webRequest.handlerBehaviorChanged() clears the cache.
  if (!callback_registered_) {
    WebRequestEventRouter::AddCallbackForPageLoad(
        base::BindOnce(&ClearCacheQuotaHeuristic::OnPageLoad,
                       weak_ptr_factory_.GetWeakPtr(), bucket));
    callback_registered_ = true;
  }

  // We only check whether tokens are left here. Deducting a token happens in
  // OnPageLoad().
  return bucket->has_tokens();
}

void ClearCacheQuotaHeuristic::OnPageLoad(Bucket* bucket) {
  callback_registered_ = false;
  bucket->DeductToken();
}

ExtensionFunction::ResponseAction
WebRequestInternalAddEventListenerFunction::Run() {
  EXTENSION_FUNCTION_VALIDATE(args().size() == 6);

  // Argument 0 is the callback, which we don't use here.
  WebRequestEventRouter::RequestFilter filter;
  EXTENSION_FUNCTION_VALIDATE(args()[1].is_dict());
  // Failure + an empty error string means a fatal error.
  std::string error;
  EXTENSION_FUNCTION_VALIDATE(
      filter.InitFromValue(args()[1].GetDict(), &error) || !error.empty());
  if (!error.empty()) {
    return RespondNow(Error(std::move(error)));
  }

  int extra_info_spec = 0;
  if (HasOptionalArgument(2)) {
    EXTENSION_FUNCTION_VALIDATE(
        ExtraInfoSpec::InitFromValue(args()[2], &extra_info_spec));
  }

  const auto& event_name_value = args()[3];
  const auto& sub_event_name_value = args()[4];
  const auto& web_view_instance_id_value = args()[5];
  EXTENSION_FUNCTION_VALIDATE(event_name_value.is_string());
  EXTENSION_FUNCTION_VALIDATE(sub_event_name_value.is_string());
  EXTENSION_FUNCTION_VALIDATE(web_view_instance_id_value.is_int());
  std::string event_name = event_name_value.GetString();
  std::string sub_event_name = sub_event_name_value.GetString();
  int web_view_instance_id = web_view_instance_id_value.GetInt();

  int render_process_id = source_process_id();

  const Extension* extension = ExtensionRegistry::Get(browser_context())
                                   ->enabled_extensions()
                                   .GetByID(extension_id_safe());
  std::string extension_name =
      extension ? extension->name() : extension_id_safe();

  if (extra_info_spec & ExtraInfoSpec::SECURITY_INFO) {
    if (extension && extension->is_platform_app()) {
      // The security info should not be available in Chrome Apps.
      return RespondNow(Error(keys::kSecurityInfoAPINotAvailable));
    }

    if (extension) {
      if (!base::FeatureList::IsEnabled(
              extensions_features::kWebRequestSecurityInfo)) {
        return RespondNow(Error(keys::kSecurityInfoFlagAbsentInExtensions));
      }
    } else {
      if (!GetContextData()->HasControlledFrameCapability()) {
        // Available only in extensions and Controlled Frame.
        return RespondNow(Error(keys::kSecurityInfoAPINotAvailable));
      }

      if (!base::FeatureList::IsEnabled(
              blink::features::kControlledFrameWebRequestSecurityInfo)) {
        return RespondNow(
            Error(keys::kSecurityInfoFlagAbsentInControlledFrame));
      }
    }
  }

  if (web_view_instance_id) {
    // If a web view ID has been supplied and the call is from an extension
    // (i.e. not from WebUI), we require the extension to have the webview
    // permission.
    if (extension && !extension->permissions_data()->HasAPIPermission(
                         mojom::APIPermissionID::kWebView)) {
      return RespondNow(Error("Missing webview permission."));
    }
  } else {
    auto has_blocking_permission = [&extension, &event_name]() {
      if (extension->permissions_data()->HasAPIPermission(
              APIPermissionID::kWebRequestBlocking)) {
        return true;
      }

      return event_name == keys::kOnAuthRequiredEvent &&
             extension->permissions_data()->HasAPIPermission(
                 APIPermissionID::kWebRequestAuthProvider);
    };

    // We check automatically whether the extension has the 'webRequest'
    // permission. For blocking calls we require the additional permission
    // 'webRequestBlocking' or 'webRequestAuthProvider'.
    bool is_blocking = extra_info_spec & (ExtraInfoSpec::BLOCKING |
                                          ExtraInfoSpec::ASYNC_BLOCKING);
    if (is_blocking && !has_blocking_permission()) {
      return RespondNow(Error(keys::kBlockingPermissionRequired));
    }

    // We allow to subscribe to patterns that are broader than the host
    // permissions. E.g., we could subscribe to http://www.example.com/*
    // while having host permissions for http://www.example.com/foo/* and
    // http://www.example.com/bar/*.
    // For this reason we do only a coarse check here to warn the extension
    // developer if they do something obviously wrong.
    if (extension->permissions_data()
            ->GetEffectiveHostPermissions()
            .is_empty() &&
        extension->permissions_data()
            ->withheld_permissions()
            .explicit_hosts()
            .is_empty()) {
      return RespondNow(Error(keys::kHostPermissionsRequired));
    }
  }

  bool success =
      WebRequestEventRouter::Get(browser_context())
          ->AddEventListener(browser_context(), extension_id_safe(),
                             extension_name, event_name, sub_event_name,
                             std::move(filter), extra_info_spec,
                             render_process_id, web_view_instance_id,
                             worker_thread_id(), service_worker_version_id());
  EXTENSION_FUNCTION_VALIDATE(success);

  helpers::ClearCacheOnNavigation();

  return RespondNow(NoArguments());
}

void WebRequestInternalEventHandledFunction::OnError(
    const std::string& event_name,
    const std::string& sub_event_name,
    uint64_t request_id,
    int render_process_id,
    int web_view_instance_id,
    std::unique_ptr<WebRequestEventRouter::EventResponse> response) {
  WebRequestEventRouter::Get(browser_context())
      ->OnEventHandled(browser_context(), extension_id_safe(), event_name,
                       sub_event_name, request_id, render_process_id,
                       web_view_instance_id, worker_thread_id(),
                       service_worker_version_id(), std::move(response));
}

ExtensionFunction::ResponseAction
WebRequestInternalEventHandledFunction::Run() {
  EXTENSION_FUNCTION_VALIDATE(args().size() >= 5);
  const auto& event_name_value = args()[0];
  const auto& sub_event_name_value = args()[1];
  const auto& request_id_str_value = args()[2];
  const auto& web_view_instance_id_value = args()[3];
  EXTENSION_FUNCTION_VALIDATE(event_name_value.is_string());
  EXTENSION_FUNCTION_VALIDATE(sub_event_name_value.is_string());
  EXTENSION_FUNCTION_VALIDATE(request_id_str_value.is_string());
  EXTENSION_FUNCTION_VALIDATE(web_view_instance_id_value.is_int());
  std::string event_name = event_name_value.GetString();
  std::string sub_event_name = sub_event_name_value.GetString();
  std::string request_id_str = request_id_str_value.GetString();
  int web_view_instance_id = web_view_instance_id_value.GetInt();

  uint64_t request_id;
  EXTENSION_FUNCTION_VALIDATE(
      base::StringToUint64(request_id_str, &request_id));

  int render_process_id = source_process_id();

  std::unique_ptr<WebRequestEventRouter::EventResponse> response;
  if (HasOptionalArgument(4)) {
    EXTENSION_FUNCTION_VALIDATE(args()[4].is_dict());
    const base::Value::Dict& dict_value = args()[4].GetDict();

    if (!dict_value.empty()) {
      base::Time install_time = GetLastUpdateTime(
          ExtensionPrefs::Get(browser_context()), extension_id_safe());
      response = std::make_unique<WebRequestEventRouter::EventResponse>(
          extension_id_safe(), install_time);
    }

    const base::Value* redirect_url_value = dict_value.Find("redirectUrl");
    const base::Value* auth_credentials_value =
        dict_value.Find(keys::kAuthCredentialsKey);
    const base::Value* request_headers_value =
        dict_value.Find("requestHeaders");
    const base::Value* response_headers_value =
        dict_value.Find("responseHeaders");

    const base::Value* cancel_value = dict_value.Find("cancel");
    if (cancel_value) {
      // Don't allow cancel mixed with other keys.
      if (dict_value.size() != 1) {
        OnError(event_name, sub_event_name, request_id, render_process_id,
                web_view_instance_id, std::move(response));
        return RespondNow(Error(keys::kInvalidBlockingResponse));
      }

      EXTENSION_FUNCTION_VALIDATE(cancel_value->is_bool());
      response->cancel = cancel_value->GetBool();
    }

    if (redirect_url_value) {
      EXTENSION_FUNCTION_VALIDATE(redirect_url_value->is_string());
      std::string new_url_str = redirect_url_value->GetString();
      response->new_url = GURL(new_url_str);
      if (!response->new_url.is_valid()) {
        OnError(event_name, sub_event_name, request_id, render_process_id,
                web_view_instance_id, std::move(response));
        return RespondNow(Error(keys::kInvalidRedirectUrl, new_url_str));
      }
    }

    const bool has_request_headers = request_headers_value != nullptr;
    const bool has_response_headers = response_headers_value != nullptr;
    if (has_request_headers || has_response_headers) {
      if (has_request_headers && has_response_headers) {
        // Allow only one of the keys, not both.
        OnError(event_name, sub_event_name, request_id, render_process_id,
                web_view_instance_id, std::move(response));
        return RespondNow(Error(keys::kInvalidHeaderKeyCombination));
      }

      const base::Value::List* headers_value = nullptr;
      std::unique_ptr<net::HttpRequestHeaders> request_headers;
      std::unique_ptr<helpers::ResponseHeaders> response_headers;
      if (has_request_headers) {
        request_headers = std::make_unique<net::HttpRequestHeaders>();
        headers_value = dict_value.FindList(keys::kRequestHeadersKey);
      } else {
        response_headers = std::make_unique<helpers::ResponseHeaders>();
        headers_value = dict_value.FindList(keys::kResponseHeadersKey);
      }
      EXTENSION_FUNCTION_VALIDATE(headers_value);

      for (const base::Value& elem : *headers_value) {
        EXTENSION_FUNCTION_VALIDATE(elem.is_dict());
        const base::Value::Dict& header_value = elem.GetDict();
        std::string name;
        std::string value;
        if (!FromHeaderDictionary(header_value, &name, &value)) {
          std::string serialized_header =
              base::WriteJson(header_value).value_or("");
          OnError(event_name, sub_event_name, request_id, render_process_id,
                  web_view_instance_id, std::move(response));
          return RespondNow(Error(keys::kInvalidHeader, serialized_header));
        }
        if (!net::HttpUtil::IsValidHeaderName(name)) {
          OnError(event_name, sub_event_name, request_id, render_process_id,
                  web_view_instance_id, std::move(response));
          return RespondNow(Error(keys::kInvalidHeaderName));
        }
        if (!net::HttpUtil::IsValidHeaderValue(value)) {
          OnError(event_name, sub_event_name, request_id, render_process_id,
                  web_view_instance_id, std::move(response));
          return RespondNow(Error(keys::kInvalidHeaderValue, name));
        }
        if (has_request_headers) {
          request_headers->SetHeader(name, value);
        } else {
          response_headers->push_back(helpers::ResponseHeader(name, value));
        }
      }
      if (has_request_headers) {
        response->request_headers = std::move(request_headers);
      } else {
        response->response_headers = std::move(response_headers);
      }
    }

    if (auth_credentials_value) {
      const base::Value::Dict* credentials_value =
          auth_credentials_value->GetIfDict();
      EXTENSION_FUNCTION_VALIDATE(credentials_value);
      const std::string* username =
          credentials_value->FindString(keys::kUsernameKey);
      const std::string* password =
          credentials_value->FindString(keys::kPasswordKey);
      EXTENSION_FUNCTION_VALIDATE(username);
      EXTENSION_FUNCTION_VALIDATE(password);
      response->auth_credentials = net::AuthCredentials(
          base::UTF8ToUTF16(*username), base::UTF8ToUTF16(*password));
    }
  }

  WebRequestEventRouter::Get(browser_context())
      ->OnEventHandled(browser_context(), extension_id_safe(), event_name,
                       sub_event_name, request_id, render_process_id,
                       web_view_instance_id, worker_thread_id(),
                       service_worker_version_id(), std::move(response));

  return RespondNow(NoArguments());
}

void WebRequestHandlerBehaviorChangedFunction::GetQuotaLimitHeuristics(
    QuotaLimitHeuristics* heuristics) const {
  QuotaLimitHeuristic::Config config = {
      // See web_request.json for current value.
      web_request::MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES,
      base::Minutes(10)};
  heuristics->push_back(std::make_unique<ClearCacheQuotaHeuristic>(
      config, std::make_unique<QuotaLimitHeuristic::SingletonBucketMapper>()));
}

void WebRequestHandlerBehaviorChangedFunction::OnQuotaExceeded(
    std::string violation_error) {
  // Post warning message.
  WarningSet warnings;
  warnings.insert(
      Warning::CreateRepeatedCacheFlushesWarning(extension_id_safe()));
  WarningService::NotifyWarningsOnUI(browser_context(), warnings);

  // Continue gracefully.
  RunWithValidation().Execute();
}

ExtensionFunction::ResponseAction
WebRequestHandlerBehaviorChangedFunction::Run() {
  helpers::ClearCacheOnNavigation();
  return RespondNow(NoArguments());
}

}  // namespace extensions