| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/plugins/plugin_info_host_impl.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/singleton.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/plugins/chrome_plugin_service_filter.h" |
| #include "chrome/browser/plugins/plugin_finder.h" |
| #include "chrome/browser/plugins/plugin_metadata.h" |
| #include "chrome/browser/plugins/plugin_prefs.h" |
| #include "chrome/browser/plugins/plugin_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_otr_state.h" |
| #include "chrome/common/buildflags.h" |
| #include "chrome/common/chrome_content_client.h" |
| #include "chrome/common/plugin.mojom.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/component_updater/component_updater_service.h" |
| #include "components/content_settings/core/browser/content_settings_utils.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h" |
| #include "components/nacl/common/buildflags.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/ukm/content/source_url_recorder.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/plugin_service.h" |
| #include "content/public/browser/plugin_service_filter.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/common/content_constants.h" |
| #include "extensions/buildflags/buildflags.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "ppapi/buildflags/buildflags.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "components/guest_view/browser/guest_view_base.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest_handlers/webview_info.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_NACL) |
| #include "components/nacl/common/nacl_constants.h" |
| #endif |
| |
| using content::PluginService; |
| using content::WebPluginInfo; |
| |
| namespace { |
| |
| class PluginInfoHostImplShutdownNotifierFactory |
| : public BrowserContextKeyedServiceShutdownNotifierFactory { |
| public: |
| static PluginInfoHostImplShutdownNotifierFactory* GetInstance() { |
| return base::Singleton<PluginInfoHostImplShutdownNotifierFactory>::get(); |
| } |
| |
| private: |
| friend struct base::DefaultSingletonTraits< |
| PluginInfoHostImplShutdownNotifierFactory>; |
| |
| PluginInfoHostImplShutdownNotifierFactory() |
| : BrowserContextKeyedServiceShutdownNotifierFactory( |
| "PluginInfoHostImpl") {} |
| |
| ~PluginInfoHostImplShutdownNotifierFactory() override {} |
| |
| DISALLOW_COPY_AND_ASSIGN(PluginInfoHostImplShutdownNotifierFactory); |
| }; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| // Returns whether a request from a plugin to load |resource| from a renderer |
| // with process id |process_id| is a request for an internal resource by an app |
| // listed in |accessible_resources| in its manifest. |
| bool IsPluginLoadingAccessibleResourceInWebView( |
| extensions::ExtensionRegistry* extension_registry, |
| int process_id, |
| const GURL& resource) { |
| extensions::WebViewRendererState* renderer_state = |
| extensions::WebViewRendererState::GetInstance(); |
| std::string partition_id; |
| if (!renderer_state->IsGuest(process_id) || |
| !renderer_state->GetPartitionID(process_id, &partition_id)) { |
| return false; |
| } |
| |
| const std::string extension_id = resource.host(); |
| const extensions::Extension* extension = extension_registry->GetExtensionById( |
| extension_id, extensions::ExtensionRegistry::ENABLED); |
| if (!extension || !extensions::WebviewInfo::IsResourceWebviewAccessible( |
| extension, partition_id, resource.path())) { |
| return false; |
| } |
| |
| // Make sure the renderer making the request actually belongs to the |
| // same extension. |
| std::string owner_extension; |
| return renderer_state->GetOwnerInfo(process_id, nullptr, &owner_extension) && |
| owner_extension == extension_id; |
| } |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| } // namespace |
| |
| PluginInfoHostImpl::Context::Context(int render_process_id, Profile* profile) |
| : render_process_id_(render_process_id), |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| extension_registry_(extensions::ExtensionRegistry::Get(profile)), |
| #endif |
| host_content_settings_map_( |
| HostContentSettingsMapFactory::GetForProfile(profile)), |
| plugin_prefs_(PluginPrefs::GetForProfile(profile)) { |
| allow_outdated_plugins_.Init(prefs::kPluginsAllowOutdated, |
| profile->GetPrefs()); |
| run_all_flash_in_allow_mode_.Init(prefs::kRunAllFlashInAllowMode, |
| profile->GetPrefs()); |
| } |
| |
| PluginInfoHostImpl::Context::~Context() {} |
| |
| void PluginInfoHostImpl::Context::ShutdownOnUIThread() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| allow_outdated_plugins_.Destroy(); |
| run_all_flash_in_allow_mode_.Destroy(); |
| } |
| |
| PluginInfoHostImpl::PluginInfoHostImpl(int render_process_id, Profile* profile) |
| : context_(render_process_id, profile) { |
| shutdown_notifier_ = |
| PluginInfoHostImplShutdownNotifierFactory::GetInstance() |
| ->Get(profile) |
| ->Subscribe(base::Bind(&PluginInfoHostImpl::ShutdownOnUIThread, |
| base::Unretained(this))); |
| } |
| |
| void PluginInfoHostImpl::ShutdownOnUIThread() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| context_.ShutdownOnUIThread(); |
| shutdown_notifier_.reset(); |
| } |
| |
| // static |
| void PluginInfoHostImpl::RegisterUserPrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref(prefs::kPluginsAllowOutdated, false); |
| registry->RegisterBooleanPref(prefs::kRunAllFlashInAllowMode, false); |
| } |
| |
| PluginInfoHostImpl::~PluginInfoHostImpl() {} |
| |
| struct PluginInfoHostImpl::GetPluginInfo_Params { |
| int render_frame_id; |
| GURL url; |
| url::Origin main_frame_origin; |
| std::string mime_type; |
| }; |
| |
| void PluginInfoHostImpl::GetPluginInfo(int32_t render_frame_id, |
| const GURL& url, |
| const url::Origin& origin, |
| const std::string& mime_type, |
| GetPluginInfoCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| GetPluginInfo_Params params = {render_frame_id, url, origin, mime_type}; |
| PluginService::GetInstance()->GetPlugins( |
| base::BindOnce(&PluginInfoHostImpl::PluginsLoaded, |
| weak_factory_.GetWeakPtr(), params, std::move(callback))); |
| } |
| |
| void PluginInfoHostImpl::PluginsLoaded( |
| const GetPluginInfo_Params& params, |
| GetPluginInfoCallback callback, |
| const std::vector<WebPluginInfo>& plugins) { |
| chrome::mojom::PluginInfoPtr output = chrome::mojom::PluginInfo::New(); |
| // This also fills in |actual_mime_type|. |
| std::unique_ptr<PluginMetadata> plugin_metadata; |
| if (context_.FindEnabledPlugin(params.render_frame_id, params.url, |
| params.main_frame_origin, params.mime_type, |
| &output->status, &output->plugin, |
| &output->actual_mime_type, &plugin_metadata)) { |
| context_.DecidePluginStatus( |
| params.url, params.main_frame_origin, output->plugin, |
| plugin_metadata->GetSecurityStatus(output->plugin), |
| plugin_metadata->identifier(), &output->status); |
| } |
| |
| if (output->status == chrome::mojom::PluginStatus::kNotFound) { |
| // Check to see if the component updater can fetch an implementation. |
| std::unique_ptr<component_updater::ComponentInfo> cus_plugin_info = |
| g_browser_process->component_updater()->GetComponentForMimeType( |
| params.mime_type); |
| ComponentPluginLookupDone(params, std::move(output), std::move(callback), |
| std::move(plugin_metadata), |
| std::move(cus_plugin_info)); |
| } else { |
| GetPluginInfoFinish(params, std::move(output), std::move(callback), |
| std::move(plugin_metadata)); |
| } |
| } |
| |
| void PluginInfoHostImpl::Context::DecidePluginStatus( |
| const GURL& url, |
| const url::Origin& main_frame_origin, |
| const WebPluginInfo& plugin, |
| PluginMetadata::SecurityStatus security_status, |
| const std::string& plugin_identifier, |
| chrome::mojom::PluginStatus* status) const { |
| if (security_status == PluginMetadata::SECURITY_STATUS_FULLY_TRUSTED) { |
| *status = chrome::mojom::PluginStatus::kAllowed; |
| return; |
| } |
| |
| // This block is separate from the outdated check, because the deprecated UI |
| // must take precedence over any content setting or HTML5 by Default. |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| if (security_status == PluginMetadata::SECURITY_STATUS_DEPRECATED) { |
| *status = chrome::mojom::PluginStatus::kDeprecated; |
| return; |
| } |
| #endif |
| |
| ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT; |
| bool uses_default_content_setting = true; |
| bool is_managed = false; |
| // Check plugin content settings. The primary URL is the top origin URL and |
| // the secondary URL is the plugin URL. |
| PluginUtils::GetPluginContentSetting( |
| host_content_settings_map_, plugin, main_frame_origin, url, |
| plugin_identifier, &plugin_setting, &uses_default_content_setting, |
| &is_managed); |
| |
| DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT); |
| DCHECK(plugin_setting != CONTENT_SETTING_ASK); |
| |
| if (*status == chrome::mojom::PluginStatus::kFlashHiddenPreferHtml) { |
| if (plugin_setting == CONTENT_SETTING_BLOCK) { |
| *status = is_managed ? chrome::mojom::PluginStatus::kBlockedByPolicy |
| : chrome::mojom::PluginStatus::kBlockedNoLoading; |
| } |
| return; |
| } |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| // Check if the plugin is outdated. |
| if (security_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE && |
| !allow_outdated_plugins_.GetValue()) { |
| if (allow_outdated_plugins_.IsManaged()) { |
| *status = chrome::mojom::PluginStatus::kOutdatedDisallowed; |
| } else { |
| *status = chrome::mojom::PluginStatus::kOutdatedBlocked; |
| } |
| return; |
| } |
| #endif // BUILDFLAG(ENABLE_PLUGINS) |
| |
| // Check if the plugin is crashing too much. |
| if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) && |
| plugin_setting != CONTENT_SETTING_BLOCK && uses_default_content_setting) { |
| *status = chrome::mojom::PluginStatus::kUnauthorized; |
| return; |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| // If an app has explicitly made internal resources available by listing them |
| // in |accessible_resources| in the manifest, then allow them to be loaded by |
| // plugins inside a guest-view. |
| if (url.SchemeIs(extensions::kExtensionScheme) && !is_managed && |
| plugin_setting == CONTENT_SETTING_BLOCK && |
| IsPluginLoadingAccessibleResourceInWebView(extension_registry_, |
| render_process_id_, url)) { |
| plugin_setting = CONTENT_SETTING_ALLOW; |
| } |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| if (plugin_setting == CONTENT_SETTING_DETECT_IMPORTANT_CONTENT || |
| (plugin_setting == CONTENT_SETTING_ALLOW && |
| !run_all_flash_in_allow_mode_.GetValue())) { |
| *status = chrome::mojom::PluginStatus::kPlayImportantContent; |
| } else if (plugin_setting == CONTENT_SETTING_BLOCK) { |
| *status = is_managed ? chrome::mojom::PluginStatus::kBlockedByPolicy |
| : chrome::mojom::PluginStatus::kBlocked; |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| // Allow an embedder of <webview> to block a plugin from being loaded inside |
| // the guest. In order to do this, set the status to 'Unauthorized' here, |
| // and update the status as appropriate depending on the response from the |
| // embedder. |
| if (*status == chrome::mojom::PluginStatus::kAllowed || |
| *status == chrome::mojom::PluginStatus::kBlocked || |
| *status == chrome::mojom::PluginStatus::kPlayImportantContent) { |
| if (extensions::WebViewRendererState::GetInstance()->IsGuest( |
| render_process_id_)) |
| *status = chrome::mojom::PluginStatus::kUnauthorized; |
| } |
| #endif |
| } |
| |
| bool PluginInfoHostImpl::Context::FindEnabledPlugin( |
| int render_frame_id, |
| const GURL& url, |
| const url::Origin& main_frame_origin, |
| const std::string& mime_type, |
| chrome::mojom::PluginStatus* status, |
| WebPluginInfo* plugin, |
| std::string* actual_mime_type, |
| std::unique_ptr<PluginMetadata>* plugin_metadata) const { |
| *status = chrome::mojom::PluginStatus::kAllowed; |
| |
| bool allow_wildcard = true; |
| std::vector<WebPluginInfo> matching_plugins; |
| std::vector<std::string> mime_types; |
| PluginService::GetInstance()->GetPluginInfoArray( |
| url, mime_type, allow_wildcard, &matching_plugins, &mime_types); |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| base::EraseIf(matching_plugins, [&](const WebPluginInfo& info) { |
| return info.path.value() == ChromeContentClient::kNotPresent; |
| }); |
| #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| if (matching_plugins.empty()) { |
| *status = chrome::mojom::PluginStatus::kNotFound; |
| return false; |
| } |
| |
| content::PluginServiceFilter* filter = |
| PluginService::GetInstance()->GetFilter(); |
| size_t i = 0; |
| for (; i < matching_plugins.size(); ++i) { |
| if (!filter || |
| filter->IsPluginAvailable(render_process_id_, render_frame_id, url, |
| main_frame_origin, &matching_plugins[i])) { |
| break; |
| } |
| } |
| |
| // If we broke out of the loop, we have found an enabled plugin. |
| bool enabled = i < matching_plugins.size(); |
| if (!enabled) { |
| // Otherwise, we only found disabled plugins, so we take the first one. |
| i = 0; |
| *status = chrome::mojom::PluginStatus::kDisabled; |
| } |
| |
| *plugin = matching_plugins[i]; |
| *actual_mime_type = mime_types[i]; |
| if (plugin_metadata) |
| *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin); |
| |
| return enabled; |
| } |
| |
| void PluginInfoHostImpl::ComponentPluginLookupDone( |
| const GetPluginInfo_Params& params, |
| chrome::mojom::PluginInfoPtr output, |
| GetPluginInfoCallback callback, |
| std::unique_ptr<PluginMetadata> plugin_metadata, |
| std::unique_ptr<component_updater::ComponentInfo> cus_plugin_info) { |
| if (cus_plugin_info) { |
| output->status = chrome::mojom::PluginStatus::kComponentUpdateRequired; |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| if (cus_plugin_info->version != base::Version("0")) { |
| output->status = chrome::mojom::PluginStatus::kRestartRequired; |
| } |
| #endif |
| // Component Updater wouldn't provide a deprecated plugin. |
| bool plugin_is_deprecated = false; |
| plugin_metadata = std::make_unique<PluginMetadata>( |
| cus_plugin_info->id, cus_plugin_info->name, false, GURL(), GURL(), |
| base::ASCIIToUTF16(cus_plugin_info->id), std::string(), |
| plugin_is_deprecated); |
| } |
| GetPluginInfoFinish(params, std::move(output), std::move(callback), |
| std::move(plugin_metadata)); |
| } |
| |
| void PluginInfoHostImpl::GetPluginInfoFinish( |
| const GetPluginInfo_Params& params, |
| chrome::mojom::PluginInfoPtr output, |
| GetPluginInfoCallback callback, |
| std::unique_ptr<PluginMetadata> plugin_metadata) { |
| if (plugin_metadata) { |
| output->group_identifier = plugin_metadata->identifier(); |
| output->group_name = plugin_metadata->name(); |
| } |
| |
| context_.MaybeGrantAccess(output->status, output->plugin.path); |
| |
| std::move(callback).Run(std::move(output)); |
| } |
| |
| void PluginInfoHostImpl::Context::MaybeGrantAccess( |
| chrome::mojom::PluginStatus status, |
| const base::FilePath& path) const { |
| if (status == chrome::mojom::PluginStatus::kAllowed || |
| status == chrome::mojom::PluginStatus::kPlayImportantContent) { |
| ChromePluginServiceFilter::GetInstance()->AuthorizePlugin( |
| render_process_id_, path); |
| } |
| } |
| |
| bool PluginInfoHostImpl::Context::IsPluginEnabled( |
| const content::WebPluginInfo& plugin) const { |
| return plugin_prefs_->IsPluginEnabled(plugin); |
| } |