blob: 2b59685a267d3511d594f378070ab7b33c4de68d [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_util.h"
#include <string_view>
#include <vector>
#include "base/check_is_test.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/permissions/permissions_updater.h"
#include "chrome/browser/extensions/shared_module_service.h"
#include "chrome/browser/extensions/sync/extension_sync_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
#include "chrome/common/extensions/sync_helper.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/site_instance.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/renderer_startup_helper.h"
#include "extensions/browser/updater/scoped_extension_updater_keep_alive.h"
#include "extensions/browser/user_script_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/icons/extension_icon_set.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/switches.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chromeos/ash/components/file_manager/app_id.h"
#endif
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/ui/browser.h"
#endif
namespace extensions::util {
namespace {
bool ExtensionsDisabledViaCommandLine(const base::CommandLine& command_line) {
return command_line.HasSwitch(switches::kDisableExtensions) ||
command_line.HasSwitch(switches::kDisableExtensionsExcept);
}
// Returns |extension_id|. See note below.
std::string ReloadExtension(const std::string& extension_id,
content::BrowserContext* context) {
// When we reload the extension the ID may be invalidated if we've passed it
// by const ref everywhere. Make a copy to be safe. http://crbug.com/103762
std::string id = extension_id;
ExtensionRegistrar::Get(context)->ReloadExtension(extension_id);
return id;
}
std::string ReloadExtensionIfEnabled(const std::string& extension_id,
content::BrowserContext* context) {
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
bool extension_is_enabled =
registry->enabled_extensions().Contains(extension_id);
if (!extension_is_enabled) {
return extension_id;
}
return ReloadExtension(extension_id, context);
}
#if BUILDFLAG(IS_CHROMEOS)
// Returns true if the extension ID is found in the InstallForceList policy. Is
// checked by HasIsolatedStorage() when the extension is not found in the
// registry.
bool IsForceInstalledExtension(const ExtensionId& extension_id,
content::BrowserContext* context) {
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context);
const PrefService::Preference* const pref =
extension_prefs->pref_service()->FindPreference(
pref_names::kInstallForceList);
if (!pref || !pref->IsManaged() ||
pref->GetType() != base::Value::Type::DICT) {
return false;
}
for (const auto item : pref->GetValue()->GetDict()) {
if (extension_id == item.first) {
return true;
}
}
return false;
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Returns true if the profile is a sign-in profile and the extension is policy
// installed. `is_policy_installed` can be passed to the method if its value is
// known (i.e. the extension was found in the registry and the extension
// location was checked). If no value is passed for `is_policy_installed`, the
// force-installed list will be queried for the extension ID.
bool IsLoginScreenExtension(
ExtensionId extension_id,
content::BrowserContext* context,
std::optional<bool> is_policy_installed = std::nullopt) {
#if BUILDFLAG(IS_CHROMEOS)
// Verify the force-installed extension list if no value for
// `is_policy_installed` was passed.
if (is_policy_installed == std::nullopt) {
is_policy_installed = IsForceInstalledExtension(extension_id, context);
}
Profile* profile = Profile::FromBrowserContext(context);
return profile && ash::ProfileHelper::IsSigninProfile(profile) &&
is_policy_installed.value();
#else
return false;
#endif
}
#if BUILDFLAG(IS_CHROMEOS)
bool IsPolicyInstalled(const ExtensionId& extension_id,
content::BrowserContext* context) {
const Extension* extension =
ExtensionRegistry::Get(context)->GetInstalledExtension(extension_id);
if (!extension) {
return false;
}
return Manifest::IsPolicyLocation(extension->location());
}
#endif
} // namespace
bool HasIsolatedStorage(const ExtensionId& extension_id,
content::BrowserContext* context) {
const Extension* extension =
ExtensionRegistry::Get(context)->GetInstalledExtension(extension_id);
// Extension is null when the extension is cleaned up after it's unloaded and
// won't be present in the ExtensionRegistry.
return extension ? HasIsolatedStorage(*extension, context)
: IsLoginScreenExtension(extension_id, context);
}
bool HasIsolatedStorage(const Extension& extension,
content::BrowserContext* context) {
#if BUILDFLAG(IS_CHROMEOS)
const bool is_policy_extension =
Manifest::IsPolicyLocation(extension.location());
if (IsLoginScreenExtension(extension.id(), context, is_policy_extension)) {
return true;
}
#endif
return extension.is_platform_app();
}
void SetIsIncognitoEnabled(const std::string& extension_id,
content::BrowserContext* context,
bool enabled) {
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
const Extension* extension =
registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
if (extension) {
if (!util::CanBeIncognitoEnabled(extension)) {
return;
}
// TODO(crbug.com/356905053): Enable handling component extensions on
// desktop android.
#if !BUILDFLAG(IS_ANDROID)
// TODO(treib,kalman): Should this be Manifest::IsComponentLocation(..)?
// (which also checks for kExternalComponent).
if (extension->location() == mojom::ManifestLocation::kComponent) {
// This shouldn't be called for component extensions unless it is called
// by sync, for syncable component extensions.
// See http://crbug.com/112290 and associated CLs for the sordid history.
bool syncable = sync_helper::IsSyncableComponentExtension(extension);
#if BUILDFLAG(IS_CHROMEOS)
// For some users, the file manager app somehow ended up being synced even
// though it's supposed to be unsyncable; see crbug.com/576964. If the bad
// data ever gets cleaned up, this hack should be removed.
syncable = syncable || extension->id() == file_manager::kFileManagerAppId;
#endif
DCHECK(syncable);
// If we are here, make sure the we aren't trying to change the value.
DCHECK_EQ(enabled, IsIncognitoEnabled(extension_id, context));
return;
}
#endif // !BUILDFLAG(IS_ANDROID)
}
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context);
#if BUILDFLAG(IS_CHROMEOS)
// Admin installed extensions should not be restartable, so we will apply the
// change when Chrome restarts.
if (IsPolicyInstalled(extension_id, context)) {
extension_prefs->SetIsIncognitoEnabledDelayed(extension_id, enabled);
return;
}
#endif
// Broadcast unloaded and loaded events to update browser state. Only bother
// if the value changed and the extension is actually enabled, since there is
// no UI otherwise.
bool old_enabled = extension_prefs->IsIncognitoEnabled(extension_id);
if (enabled == old_enabled) {
return;
}
extension_prefs->SetIsIncognitoEnabled(extension_id, enabled);
std::string id = ReloadExtensionIfEnabled(extension_id, context);
// Reloading the extension invalidates the |extension| pointer.
extension = registry->GetExtensionById(id, ExtensionRegistry::EVERYTHING);
if (extension) {
Profile* profile = Profile::FromBrowserContext(context);
ExtensionSyncService::Get(profile)->SyncExtensionChangeIfNeeded(*extension);
}
}
void SetAllowFileAccess(const std::string& extension_id,
content::BrowserContext* context,
bool allow) {
#if BUILDFLAG(IS_CHROMEOS)
// Admin installed extensions should not be restartable, so we will apply the
// change when Chrome restarts.
if (IsPolicyInstalled(extension_id, context)) {
ExtensionPrefs::Get(context)->SetAllowFileAccessDelayed(extension_id,
allow);
return;
}
#endif
// Reload to update browser state if the value changed. We need to reload even
// if the extension is disabled, in order to make sure file access is
// reinitialized correctly.
if (allow == util::AllowFileAccess(extension_id, context)) {
return;
}
ExtensionPrefs::Get(context)->SetAllowFileAccess(extension_id, allow);
ReloadExtension(extension_id, context);
}
base::Value::Dict GetExtensionInfo(const Extension* extension) {
DCHECK(extension);
base::Value::Dict dict;
dict.Set("id", extension->id());
dict.Set("name", extension->name());
GURL icon = extensions::ExtensionIconSource::GetIconURL(
extension, extension_misc::EXTENSION_ICON_SMALLISH,
ExtensionIconSet::Match::kBigger,
/*grayscale=*/false);
dict.Set("icon", icon.spec());
return dict;
}
std::unique_ptr<const PermissionSet> GetInstallPromptPermissionSetForExtension(
const Extension* extension,
Profile* profile) {
// Initialize permissions if they have not already been set so that
// any transformations are correctly reflected in the install prompt.
PermissionsUpdater(profile, PermissionsUpdater::INIT_FLAG_TRANSIENT)
.InitializePermissions(extension);
return extension->permissions_data()->active_permissions().Clone();
}
std::vector<content::BrowserContext*> GetAllRelatedProfiles(
Profile* profile,
const Extension& extension) {
std::vector<content::BrowserContext*> related_contexts;
related_contexts.push_back(profile->GetOriginalProfile());
// The returned `related_contexts` should include all the related incognito
// profiles if the extension is globally allowed in incognito (this is a
// global, rather than per-profile toggle - this is why we it can be checked
// globally here, rather than once for every incognito profile looped over
// below).
if (IsIncognitoEnabled(extension.id(), profile)) {
std::vector<Profile*> off_the_record_profiles =
profile->GetAllOffTheRecordProfiles();
related_contexts.reserve(related_contexts.size() +
off_the_record_profiles.size());
for (Profile* off_the_record_profile : off_the_record_profiles)
related_contexts.push_back(off_the_record_profile);
}
return related_contexts;
}
void SetDeveloperModeForProfile(Profile* profile, bool in_developer_mode) {
profile->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode,
in_developer_mode);
SetCurrentDeveloperMode(util::GetBrowserContextId(profile),
in_developer_mode);
RendererStartupHelperFactory::GetForBrowserContext(profile)
->OnDeveloperModeChanged(in_developer_mode);
// kDynamicUserScript scripts are allowed if and only if the user is in dev
// mode (since they allow raw code execution). Notify the user script manager
// to properly enable or disable any scripts.
UserScriptManager* user_script_manager =
ExtensionSystem::Get(profile)->user_script_manager();
if (!user_script_manager) {
CHECK_IS_TEST(); // `user_script_manager` can be null in unit tests.
return;
}
user_script_manager->SetUserScriptSourceEnabledForExtensions(
UserScript::Source::kDynamicUserScript, in_developer_mode);
}
std::u16string GetFixupExtensionNameForUIDisplay(
const std::u16string& extension_name) {
const size_t extension_name_char_limit =
75; // Extension name char limit on CWS
gfx::BreakType break_type = gfx::BreakType::CHARACTER_BREAK;
std::u16string fixup_extension_name = gfx::TruncateString(
extension_name, extension_name_char_limit, break_type);
return fixup_extension_name;
}
std::u16string GetFixupExtensionNameForUIDisplay(
const std::string& extension_name) {
return GetFixupExtensionNameForUIDisplay(base::UTF8ToUTF16(extension_name));
}
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kShouldGarbageCollectStoragePartitions,
false);
}
bool AreExtensionsDisabled(const base::CommandLine& command_line,
content::BrowserContext* context) {
Profile* profile = Profile::FromBrowserContext(context);
return ExtensionsDisabledViaCommandLine(command_line) ||
profile->GetPrefs()->GetBoolean(prefs::kDisableExtensions);
}
} // namespace extensions::util