| // 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 "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/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 "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 { |
| |
| 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; |
| |
| // 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()) { |
| 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 (!may_have_proxies_ && may_have_proxies) { |
| ResetURLLoaderFactories(); |
| } |
| 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 (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( |
| browser_context(), 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 (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 |