#include "extensions/browser/api/web_request/web_request_permissions.h"
#include <string_view>
#include "base/debug/crash_logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "build/chromeos_buildflags.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/api/web_request/permission_helper.h"
#include "extensions/browser/api/web_request/web_request_api_constants.h"
#include "extensions/browser/api/web_request/web_request_info.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/process_map.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "third_party/abseil-cpp/absl/strings/ascii.h"
#include "third_party/blink/public/common/loader/resource_type_util.h"
#include "url/gurl.h"
#include "url/origin.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using extensions::PermissionsData;
namespace {
bool HasWebRequestScheme(const GURL& url) {
return (url.SchemeIs(url::kAboutScheme) || url.SchemeIs(url::kFileScheme) ||
url.SchemeIs(url::kFileSystemScheme) ||
url.SchemeIs(url::kFtpScheme) || url.SchemeIsHTTPOrHTTPS() ||
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
url.SchemeIs(extensions::kArkwebExtensionScheme) ||
#endif
url.SchemeIs(extensions::kExtensionScheme) || url.SchemeIsWSOrWSS() ||
url.SchemeIs(url::kUuidInPackageScheme));
}
PermissionsData::PageAccess GetHostAccessForURL(
const extensions::Extension& extension,
const GURL& url,
int tab_id) {
if (url.SchemeIs(url::kAboutScheme) ||
url::IsSameOriginWith(url, extension.url())) {
return PermissionsData::PageAccess::kAllowed;
}
return extension.permissions_data()->GetPageAccess(url, tab_id,
nullptr );
}
bool IsWebRequestResourceTypeFrame(
extensions::WebRequestResourceType web_request_type) {
return web_request_type == extensions::WebRequestResourceType::MAIN_FRAME ||
web_request_type == extensions::WebRequestResourceType::SUB_FRAME;
}
PermissionsData::PageAccess CanExtensionAccessURLInternal(
extensions::PermissionHelper* permission_helper,
const extensions::ExtensionId& extension_id,
const GURL& url,
int tab_id,
bool crosses_incognito,
WebRequestPermissions::HostPermissionsCheck host_permissions_check,
const std::optional<url::Origin>& initiator,
const std::optional<extensions::WebRequestResourceType>& web_request_type) {
const extensions::Extension* extension =
permission_helper->extension_registry()->enabled_extensions().GetByID(
extension_id);
if (!extension) {
return PermissionsData::PageAccess::kDenied;
}
if (initiator &&
extension->permissions_data()->IsPolicyBlockedHost(initiator->GetURL())) {
return PermissionsData::PageAccess::kDenied;
}
if (crosses_incognito && !permission_helper->CanCrossIncognito(extension)) {
return PermissionsData::PageAccess::kDenied;
}
switch (host_permissions_check) {
case WebRequestPermissions::DO_NOT_CHECK_HOST:
return PermissionsData::PageAccess::kAllowed;
case WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL: {
PermissionsData::PageAccess access =
GetHostAccessForURL(*extension, url, tab_id);
bool is_navigation_request =
web_request_type && IsWebRequestResourceTypeFrame(*web_request_type);
if (!is_navigation_request &&
access == PermissionsData::PageAccess::kWithheld) {
PermissionsData::PageAccess initiator_access =
initiator
? GetHostAccessForURL(*extension, initiator->GetURL(), tab_id)
: PermissionsData::PageAccess::kDenied;
if (initiator_access == PermissionsData::PageAccess::kAllowed) {
access = PermissionsData::PageAccess::kAllowed;
}
}
return access;
}
case WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL_AND_INITIATOR: {
PermissionsData::PageAccess request_access =
GetHostAccessForURL(*extension, url, tab_id);
bool is_navigation_request =
web_request_type && IsWebRequestResourceTypeFrame(*web_request_type);
if (is_navigation_request) {
return request_access;
}
if (request_access == PermissionsData::PageAccess::kDenied) {
return request_access;
}
if (!initiator || initiator->opaque()) {
return request_access;
}
DCHECK(request_access == PermissionsData::PageAccess::kWithheld ||
request_access == PermissionsData::PageAccess::kAllowed);
return GetHostAccessForURL(*extension, initiator->GetURL(), tab_id);
}
case WebRequestPermissions::REQUIRE_ALL_URLS:
return extension->permissions_data()
->active_permissions()
.HasEffectiveAccessToAllHosts()
? PermissionsData::PageAccess::kAllowed
: PermissionsData::PageAccess::kDenied;
}
NOTREACHED();
}
bool IsSensitiveGoogleClientUrl(const extensions::WebRequestInfo& request) {
const GURL& url = request.url;
static constexpr char kGoogleCom[] = "google.com";
static constexpr char kClient[] = "clients";
constexpr size_t kGoogleComLength = std::size(kGoogleCom) - 1;
constexpr size_t kClientLength = std::size(kClient) - 1;
if (!url.DomainIs(kGoogleCom)) {
return false;
}
std::string_view host = url.host();
while (base::EndsWith(host, ".")) {
host.remove_suffix(1u);
}
std::string_view::size_type pos = host.rfind(kClient);
if (pos == std::string_view::npos) {
return false;
}
if (pos > 0 && host[pos - 1] != '.') {
return false;
}
for (std::string_view::const_iterator
i = host.begin() + pos + kClientLength,
end = host.end() - (kGoogleComLength + 1);
i != end; ++i) {
if (!absl::ascii_isdigit(static_cast<unsigned char>(*i))) {
return false;
}
}
return true;
}
bool IsMainFrameNavigationRequest(const extensions::WebRequestInfo& request) {
return request.is_navigation_request &&
request.web_request_type ==
extensions::WebRequestResourceType::MAIN_FRAME;
}
}
bool WebRequestPermissions::HideRequest(
extensions::PermissionHelper* permission_helper,
const extensions::WebRequestInfo& request) {
if (!HasWebRequestScheme(request.url)) {
return true;
}
if (request.is_web_view) {
return false;
}
bool is_request_from_browser = request.render_process_id == -1;
if (is_request_from_browser) {
if (request.is_service_worker_script) {
DCHECK(request.web_request_type ==
extensions::WebRequestResourceType::SCRIPT);
return false;
}
if (!request.is_navigation_request) {
return true;
}
DCHECK(request.web_request_type ==
extensions::WebRequestResourceType::MAIN_FRAME ||
request.web_request_type ==
extensions::WebRequestResourceType::SUB_FRAME ||
request.web_request_type ==
extensions::WebRequestResourceType::OBJECT);
if (request.web_request_type !=
extensions::WebRequestResourceType::MAIN_FRAME &&
IsSensitiveGoogleClientUrl(request)) {
return true;
}
}
if (!is_request_from_browser &&
permission_helper->process_map()->Contains(extensions::kWebStoreAppId,
request.render_process_id)) {
return true;
}
if (request.initiator) {
const auto& request_tuple_or_precursor_tuple =
request.initiator->GetTupleOrPrecursorTupleIfOpaque();
if (request_tuple_or_precursor_tuple ==
url::SchemeHostPort(extension_urls::GetNewWebstoreLaunchURL())) {
return true;
}
}
const GURL& url = request.url;
bool is_request_from_webui_renderer =
!is_request_from_browser &&
content::ChildProcessSecurityPolicy::GetInstance()->HasWebUIBindings(
request.render_process_id);
if (is_request_from_webui_renderer) {
#if DCHECK_IS_ON()
const bool is_network_request =
url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS();
if (is_network_request) {
DCHECK(request.initiator.has_value());
DCHECK(extensions::ExtensionsBrowserClient::Get()
->IsWebUIAllowedToMakeNetworkRequests(*request.initiator))
<< "Unsupported network request from "
<< request.initiator->GetURL().spec() << " for " << url.spec();
}
#endif
return true;
}
if (request.initiator.has_value() &&
request.initiator->scheme() == content::kChromeUIUntrustedScheme) {
bool allowlist = IsMainFrameNavigationRequest(request);
if (!allowlist) {
return true;
}
}
if (permission_helper->ShouldHideBrowserNetworkRequest(request)) {
return true;
}
if (extension_urls::IsWebstoreUpdateUrl(url) ||
extension_urls::IsWebstoreApiUrl(url) ||
extension_urls::IsBlocklistUpdateUrl(url) ||
extension_urls::IsSafeBrowsingUrl(url) ||
(url.DomainIs("chrome.google.com") &&
base::StartsWith(url.path(), "/webstore",
base::CompareCase::SENSITIVE)) ||
url.DomainIs(extension_urls::GetNewWebstoreLaunchURL().host())) {
return true;
}
return false;
}
PermissionsData::PageAccess WebRequestPermissions::CanExtensionAccessURL(
extensions::PermissionHelper* permission_helper,
const extensions::ExtensionId& extension_id,
const GURL& url,
int tab_id,
bool crosses_incognito,
HostPermissionsCheck host_permissions_check,
const std::optional<url::Origin>& initiator,
extensions::WebRequestResourceType web_request_type) {
return CanExtensionAccessURLInternal(
permission_helper, extension_id, url, tab_id, crosses_incognito,
host_permissions_check, initiator, web_request_type);
}
bool WebRequestPermissions::CanExtensionAccessInitiator(
extensions::PermissionHelper* permission_helper,
const extensions::ExtensionId extension_id,
const std::optional<url::Origin>& initiator,
int tab_id,
bool crosses_incognito) {
if (!initiator) {
return true;
}
return CanExtensionAccessURLInternal(
permission_helper, extension_id, initiator->GetURL(), tab_id,
crosses_incognito,
WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL,
std::nullopt , std::nullopt ) ==
PermissionsData::PageAccess::kAllowed;
}