| // 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 "chrome/browser/extensions/api/content_settings/content_settings_api.h" |
| |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/extensions/api/preference/preference_helpers.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/api/content_settings.h" |
| #include "components/content_settings/core/browser/content_settings_info.h" |
| #include "components/content_settings/core/browser/content_settings_registry.h" |
| #include "components/content_settings/core/browser/content_settings_utils.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/webplugininfo.h" |
| #include "extensions/browser/api/content_settings/content_settings_helpers.h" |
| #include "extensions/browser/api/content_settings/content_settings_service.h" |
| #include "extensions/browser/api/content_settings/content_settings_store.h" |
| #include "extensions/browser/extension_prefs_scope.h" |
| #include "extensions/browser/extension_util.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/error_utils.h" |
| |
| using content::BrowserThread; |
| |
| namespace Clear = extensions::api::content_settings::ContentSetting::Clear; |
| namespace Get = extensions::api::content_settings::ContentSetting::Get; |
| namespace Set = extensions::api::content_settings::ContentSetting::Set; |
| namespace pref_helpers = extensions::preference_helpers; |
| |
| namespace { |
| |
| bool RemoveContentType(base::Value::List& args, |
| ContentSettingsType* content_type) { |
| if (args.empty() || !args[0].is_string()) |
| return false; |
| |
| // Not a ref since we remove the underlying value after. |
| std::string content_type_str = args[0].GetString(); |
| |
| // We remove the ContentSettingsType parameter since this is added by the |
| // renderer, and is not part of the JSON schema. |
| args.erase(args.begin()); |
| *content_type = |
| extensions::content_settings_helpers::StringToContentSettingsType( |
| content_type_str); |
| return *content_type != ContentSettingsType::DEFAULT; |
| } |
| |
| // Errors. |
| constexpr char kIncognitoContextError[] = |
| "Can't modify regular settings from an incognito context."; |
| constexpr char kIncognitoSessionOnlyError[] = |
| "You cannot read incognito content settings when no incognito window " |
| "is open."; |
| constexpr char kInvalidUrlError[] = "The URL \"*\" is invalid."; |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| ExtensionFunction::ResponseAction |
| ContentSettingsContentSettingClearFunction::Run() { |
| ContentSettingsType content_type; |
| EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type)); |
| |
| absl::optional<Clear::Params> params = Clear::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (content_type == ContentSettingsType::DEPRECATED_PPAPI_BROKER) { |
| NOTREACHED(); |
| return RespondNow(Error(kUnknownErrorDoNotUse)); |
| } |
| |
| ExtensionPrefsScope scope = kExtensionPrefsScopeRegular; |
| bool incognito = false; |
| if (params->details.scope == |
| api::content_settings::Scope::kIncognitoSessionOnly) { |
| scope = kExtensionPrefsScopeIncognitoSessionOnly; |
| incognito = true; |
| } |
| |
| if (incognito) { |
| // We don't check incognito permissions here, as an extension should be |
| // always allowed to clear its own settings. |
| } else if (browser_context()->IsOffTheRecord()) { |
| // Incognito profiles can't access regular mode ever, they only exist in |
| // split mode. |
| return RespondNow(Error(kIncognitoContextError)); |
| } |
| |
| scoped_refptr<ContentSettingsStore> store = |
| ContentSettingsService::Get(browser_context())->content_settings_store(); |
| store->ClearContentSettingsForExtensionAndContentType(extension_id(), scope, |
| content_type); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| ContentSettingsContentSettingGetFunction::Run() { |
| ContentSettingsType content_type; |
| EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type)); |
| |
| absl::optional<Get::Params> params = Get::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (content_type == ContentSettingsType::DEPRECATED_PPAPI_BROKER) { |
| NOTREACHED(); |
| return RespondNow(Error(kUnknownErrorDoNotUse)); |
| } |
| |
| GURL primary_url(params->details.primary_url); |
| if (!primary_url.is_valid()) { |
| return RespondNow(Error(kInvalidUrlError, params->details.primary_url)); |
| } |
| |
| GURL secondary_url(primary_url); |
| if (params->details.secondary_url) { |
| secondary_url = GURL(*params->details.secondary_url); |
| if (!secondary_url.is_valid()) { |
| return RespondNow( |
| Error(kInvalidUrlError, *params->details.secondary_url)); |
| } |
| } |
| |
| bool incognito = false; |
| if (params->details.incognito) |
| incognito = *params->details.incognito; |
| if (incognito && !include_incognito_information()) |
| return RespondNow(Error(extension_misc::kIncognitoErrorMessage)); |
| |
| HostContentSettingsMap* map; |
| content_settings::CookieSettings* cookie_settings; |
| Profile* profile = Profile::FromBrowserContext(browser_context()); |
| if (incognito) { |
| if (!profile->HasPrimaryOTRProfile()) { |
| // TODO(bauerb): Allow reading incognito content settings |
| // outside of an incognito session. |
| return RespondNow(Error(kIncognitoSessionOnlyError)); |
| } |
| map = HostContentSettingsMapFactory::GetForProfile( |
| profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)); |
| cookie_settings = |
| CookieSettingsFactory::GetForProfile( |
| profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)) |
| .get(); |
| } else { |
| map = HostContentSettingsMapFactory::GetForProfile(profile); |
| cookie_settings = CookieSettingsFactory::GetForProfile(profile).get(); |
| } |
| |
| // TODO(crbug.com/1386190): Consider whether the following check should |
| // somehow determine real CookieSettingOverrides rather than default to none. |
| ContentSetting setting = |
| content_type == ContentSettingsType::COOKIES |
| ? cookie_settings->GetCookieSetting(primary_url, secondary_url, |
| net::CookieSettingOverrides(), |
| nullptr) |
| : map->GetContentSetting(primary_url, secondary_url, content_type); |
| |
| base::Value::Dict result; |
| std::string setting_string = |
| content_settings::ContentSettingToString(setting); |
| DCHECK(!setting_string.empty()); |
| result.Set(ContentSettingsStore::kContentSettingKey, setting_string); |
| |
| return RespondNow(WithArguments(std::move(result))); |
| } |
| |
| ExtensionFunction::ResponseAction |
| ContentSettingsContentSettingSetFunction::Run() { |
| ContentSettingsType content_type; |
| EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type)); |
| |
| absl::optional<Set::Params> params = Set::Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (content_type == ContentSettingsType::DEPRECATED_PPAPI_BROKER) { |
| NOTREACHED(); |
| return RespondNow(Error(kUnknownErrorDoNotUse)); |
| } |
| |
| std::string primary_error; |
| ContentSettingsPattern primary_pattern = |
| content_settings_helpers::ParseExtensionPattern( |
| params->details.primary_pattern, &primary_error); |
| if (!primary_pattern.IsValid()) |
| return RespondNow(Error(primary_error)); |
| |
| ContentSettingsPattern secondary_pattern = ContentSettingsPattern::Wildcard(); |
| if (params->details.secondary_pattern) { |
| std::string secondary_error; |
| secondary_pattern = content_settings_helpers::ParseExtensionPattern( |
| *params->details.secondary_pattern, &secondary_error); |
| if (!secondary_pattern.IsValid()) |
| return RespondNow(Error(secondary_error)); |
| } |
| |
| EXTENSION_FUNCTION_VALIDATE(params->details.setting.is_string()); |
| std::string setting_str = params->details.setting.GetString(); |
| ContentSetting setting; |
| EXTENSION_FUNCTION_VALIDATE( |
| content_settings::ContentSettingFromString(setting_str, &setting)); |
| // The content settings extensions API does not support setting any content |
| // settings to |CONTENT_SETTING_DEFAULT|. |
| EXTENSION_FUNCTION_VALIDATE(CONTENT_SETTING_DEFAULT != setting); |
| EXTENSION_FUNCTION_VALIDATE( |
| content_settings::ContentSettingsRegistry::GetInstance() |
| ->Get(content_type) |
| ->IsSettingValid(setting)); |
| |
| const content_settings::ContentSettingsInfo* info = |
| content_settings::ContentSettingsRegistry::GetInstance()->Get( |
| content_type); |
| |
| // The ANTI_ABUSE content setting does not support site-specific settings. |
| if (content_type == ContentSettingsType::ANTI_ABUSE && |
| (primary_pattern != ContentSettingsPattern::Wildcard() || |
| secondary_pattern != ContentSettingsPattern::Wildcard())) { |
| return RespondNow( |
| Error("Site-specific settings are not allowed for this type. The URL " |
| "pattern must be '<all_urls>'.")); |
| } |
| |
| // Some content setting types support the full set of values listed in |
| // content_settings.json only for exceptions. For the default setting, |
| // some values might not be supported. |
| // For example, camera supports [allow, ask, block] for exceptions, but only |
| // [ask, block] for the default setting. |
| if (primary_pattern == ContentSettingsPattern::Wildcard() && |
| secondary_pattern == ContentSettingsPattern::Wildcard() && |
| !info->IsDefaultSettingValid(setting)) { |
| static const char kUnsupportedDefaultSettingError[] = |
| "'%s' is not supported as the default setting of %s."; |
| |
| // TODO(msramek): Get the same human readable name as is presented |
| // externally in the API, i.e. chrome.contentSettings.<name>.set(). |
| std::string readable_type_name; |
| if (content_type == ContentSettingsType::MEDIASTREAM_MIC) { |
| readable_type_name = "microphone"; |
| } else if (content_type == ContentSettingsType::MEDIASTREAM_CAMERA) { |
| readable_type_name = "camera"; |
| } else { |
| NOTREACHED() << "No human-readable type name defined for this type."; |
| } |
| |
| return RespondNow(Error(base::StringPrintf(kUnsupportedDefaultSettingError, |
| setting_str.c_str(), |
| readable_type_name.c_str()))); |
| } |
| |
| size_t num_values = 0; |
| int histogram_value = |
| ContentSettingTypeToHistogramValue(content_type, &num_values); |
| if (primary_pattern != secondary_pattern && |
| secondary_pattern != ContentSettingsPattern::Wildcard()) { |
| UMA_HISTOGRAM_EXACT_LINEAR("ContentSettings.ExtensionEmbeddedSettingSet", |
| histogram_value, num_values); |
| } else { |
| UMA_HISTOGRAM_EXACT_LINEAR("ContentSettings.ExtensionNonEmbeddedSettingSet", |
| histogram_value, num_values); |
| } |
| |
| if (primary_pattern != secondary_pattern && |
| secondary_pattern != ContentSettingsPattern::Wildcard() && |
| !info->website_settings_info()->SupportsSecondaryPattern()) { |
| static const char kUnsupportedEmbeddedException[] = |
| "Embedded patterns are not supported for this setting."; |
| return RespondNow(Error(kUnsupportedEmbeddedException)); |
| } |
| |
| ExtensionPrefsScope scope = kExtensionPrefsScopeRegular; |
| bool incognito = false; |
| if (params->details.scope == |
| api::content_settings::Scope::kIncognitoSessionOnly) { |
| scope = kExtensionPrefsScopeIncognitoSessionOnly; |
| incognito = true; |
| } |
| |
| if (incognito) { |
| // Regular profiles can't access incognito unless the extension is allowed |
| // to run in incognito contexts. |
| if (!browser_context()->IsOffTheRecord() && |
| !extensions::util::IsIncognitoEnabled(extension_id(), |
| browser_context())) { |
| return RespondNow(Error(extension_misc::kIncognitoErrorMessage)); |
| } |
| } else { |
| // Incognito profiles can't access regular mode ever, they only exist in |
| // split mode. |
| if (browser_context()->IsOffTheRecord()) |
| return RespondNow(Error(kIncognitoContextError)); |
| } |
| |
| if (scope == kExtensionPrefsScopeIncognitoSessionOnly && |
| !Profile::FromBrowserContext(browser_context())->HasPrimaryOTRProfile()) { |
| return RespondNow(Error(extension_misc::kIncognitoSessionOnlyErrorMessage)); |
| } |
| |
| scoped_refptr<ContentSettingsStore> store = |
| ContentSettingsService::Get(browser_context())->content_settings_store(); |
| store->SetExtensionContentSetting(extension_id(), primary_pattern, |
| secondary_pattern, content_type, setting, |
| scope); |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction |
| ContentSettingsContentSettingGetResourceIdentifiersFunction::Run() { |
| // The only setting that supported resource identifiers was plugins. Since |
| // plugins have been deprecated since Chrome 87, there are no resource |
| // identifiers for existing settings (but we retain the function for |
| // backwards and potential forwards compatibility). |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace extensions |