blob: 7092c5d5d10d7377fa7baaf58dfdf36de1c38459 [file] [log] [blame]
// 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/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_task_runner_handle.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/plugins/plugins_field_trial.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/chrome_features.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/rappor/rappor_service_impl.h"
#include "components/ukm/content/source_url_recorder.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),
resource_context_(profile->GetResourceContext()),
#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());
allow_outdated_plugins_.MoveToThread(
content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO));
run_all_flash_in_allow_mode_.Init(prefs::kRunAllFlashInAllowMode,
profile->GetPrefs());
run_all_flash_in_allow_mode_.MoveToThread(
content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO));
}
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),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
binding_(this) {
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();
}
void PluginInfoHostImplTraits::Destruct(const PluginInfoHostImpl* impl) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::BindOnce(&PluginInfoHostImpl::DestructOnBrowserThread,
base::Unretained(impl)));
}
void PluginInfoHostImpl::DestructOnBrowserThread() const {
binding_.Close();
// Destroy on the UI thread because we contain a |PrefMember|.
content::BrowserThread::DeleteOnUIThread::Destruct(this);
}
// 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::OnPluginInfoHostRequest(
chrome::mojom::PluginInfoHostAssociatedRequest request) {
binding_.Bind(std::move(request));
}
void PluginInfoHostImpl::GetPluginInfo(int32_t render_frame_id,
const GURL& url,
const url::Origin& origin,
const std::string& mime_type,
GetPluginInfoCallback callback) {
GetPluginInfo_Params params = {render_frame_id, url, origin, mime_type};
PluginService::GetInstance()->GetPlugins(base::BindOnce(
&PluginInfoHostImpl::PluginsLoaded, this, 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.
base::PostTaskAndReplyWithResult(
main_thread_task_runner_.get(), FROM_HERE,
base::BindOnce(
&component_updater::ComponentUpdateService::GetComponentForMimeType,
base::Unretained(g_browser_process->component_updater()),
params.mime_type),
base::BindOnce(&PluginInfoHostImpl::ComponentPluginLookupDone, this,
params, std::move(output), std::move(callback),
std::move(plugin_metadata)));
} 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;
}
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);
// TODO(tommycli): Remove once we deprecate the plugin ASK policy.
bool legacy_ask_user = plugin_setting == CONTENT_SETTING_ASK;
plugin_setting = PluginsFieldTrial::EffectiveContentSetting(
host_content_settings_map_, CONTENT_SETTINGS_TYPE_PLUGINS,
plugin_setting);
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 && !legacy_ask_user
? 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 &&
PluginUtils::ShouldPreferHtmlOverPlugins(host_content_settings_map_) &&
!run_all_flash_in_allow_mode_.GetValue())) {
*status = chrome::mojom::PluginStatus::kPlayImportantContent;
} else if (plugin_setting == CONTENT_SETTING_BLOCK) {
// For managed users with the ASK policy, we allow manually running plugins
// via context menu. This is the closest to admin intent.
*status = is_managed && !legacy_ask_user
? 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 defined(GOOGLE_CHROME_BUILD)
base::FilePath not_present =
base::FilePath::FromUTF8Unsafe(ChromeContentClient::kNotPresent);
matching_plugins.erase(
std::remove_if(matching_plugins.begin(), matching_plugins.end(),
[&not_present](const WebPluginInfo& info) {
return info.path == not_present;
}),
matching_plugins.end());
#endif // defined(GOOGLE_CHROME_BUILD)
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, resource_context_,
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;
if (PluginUtils::ShouldPreferHtmlOverPlugins(host_content_settings_map_) &&
matching_plugins[0].name ==
base::ASCIIToUTF16(content::kFlashPluginName)) {
*status = chrome::mojom::PluginStatus::kFlashHiddenPreferHtml;
// In the Prefer HTML case, the plugin is actually enabled, but hidden.
// It will still be blocked in the body of DecidePluginStatus.
enabled = true;
}
}
*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)
if (cus_plugin_info->version != base::Version("0")) {
output->status = chrome::mojom::PluginStatus::kRestartRequired;
}
#endif
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());
}
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);
if (output->status != chrome::mojom::PluginStatus::kNotFound) {
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&PluginInfoHostImpl::ReportMetrics, this,
params.render_frame_id, output->actual_mime_type,
params.url, params.main_frame_origin));
}
std::move(callback).Run(std::move(output));
}
void PluginInfoHostImpl::ReportMetrics(int render_frame_id,
const base::StringPiece& mime_type,
const GURL& url,
const url::Origin& main_frame_origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
context_.render_process_id(), render_frame_id);
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(frame);
// This can occur the web contents has already been closed or navigated away.
if (!web_contents)
return;
if (web_contents->GetBrowserContext()->IsOffTheRecord())
return;
rappor::RapporServiceImpl* rappor_service =
g_browser_process->rappor_service();
if (!rappor_service)
return;
if (main_frame_origin.unique())
return;
if (mime_type != content::kFlashPluginSwfMimeType &&
mime_type != content::kFlashPluginSplMimeType) {
return;
}
rappor_service->RecordSampleString(
"Plugins.FlashOriginUrl", rappor::ETLD_PLUS_ONE_RAPPOR_TYPE,
net::registry_controlled_domains::GetDomainAndRegistry(
main_frame_origin.GetURL(),
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
rappor_service->RecordSampleString(
"Plugins.FlashUrl", rappor::ETLD_PLUS_ONE_RAPPOR_TYPE,
net::registry_controlled_domains::GetDomainAndRegistry(
url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
ukm::builders::Plugins_FlashInstance(
ukm::GetSourceIdForWebContentsDocument(web_contents))
.Record(ukm::UkmRecorder::Get());
}
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);
}