// 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/plugin_prefs.h" #include #include "base/bind.h" #include "base/command_line.h" #include "base/lazy_instance.h" #include "base/memory/scoped_ptr.h" #include "base/memory/singleton.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "base/version.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/plugin_prefs_factory.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_keyed_service.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_content_client.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/plugin_service.h" #include "webkit/plugins/npapi/plugin_group.h" #include "webkit/plugins/npapi/plugin_list.h" #include "webkit/plugins/webplugininfo.h" using content::BrowserThread; using content::PluginService; namespace { // Default state for a plug-in (not state of the default plug-in!). // Accessed only on the UI thread. base::LazyInstance > g_default_plugin_state = LAZY_INSTANCE_INITIALIZER; class CallbackBarrier : public base::RefCountedThreadSafe { public: explicit CallbackBarrier(const base::Closure& callback) : callback_(callback), outstanding_callbacks_(0) { DCHECK(!callback_.is_null()); } base::Closure CreateCallback() { outstanding_callbacks_++; return base::Bind(&CallbackBarrier::MaybeRunCallback, this); } private: friend class base::RefCountedThreadSafe; ~CallbackBarrier() { DCHECK(callback_.is_null()); } void MaybeRunCallback() { DCHECK_GT(outstanding_callbacks_, 0); if (--outstanding_callbacks_ == 0) { callback_.Run(); callback_.Reset(); } } base::Closure callback_; int outstanding_callbacks_; }; } // namespace // How long to wait to save the plugin enabled information, which might need to // go to disk. #define kPluginUpdateDelayMs (60 * 1000) // static scoped_refptr PluginPrefs::GetForProfile(Profile* profile) { return PluginPrefsFactory::GetPrefsForProfile(profile); } // static scoped_refptr PluginPrefs::GetForTestingProfile( Profile* profile) { return static_cast( PluginPrefsFactory::GetInstance()->SetTestingFactoryAndUse( profile, &PluginPrefsFactory::CreateForTestingProfile).get()); } void PluginPrefs::SetPluginListForTesting( webkit::npapi::PluginList* plugin_list) { plugin_list_ = plugin_list; } void PluginPrefs::EnablePluginGroup(bool enabled, const string16& group_name) { PluginService::GetInstance()->GetPluginGroups( base::Bind(&PluginPrefs::EnablePluginGroupInternal, this, enabled, group_name)); } void PluginPrefs::EnablePluginGroupInternal( bool enabled, const string16& group_name, const std::vector& groups) { base::AutoLock auto_lock(lock_); // Set the desired state for the group. plugin_group_state_[group_name] = enabled; // Update the state for all plug-ins in the group. for (size_t i = 0; i < groups.size(); ++i) { if (groups[i].GetGroupName() != group_name) continue; const std::vector& plugins = groups[i].web_plugin_infos(); for (size_t j = 0; j < plugins.size(); ++j) plugin_state_[plugins[j].path] = enabled; break; } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PluginPrefs::OnUpdatePreferences, this, groups)); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this)); } bool PluginPrefs::CanEnablePlugin(bool enabled, const FilePath& path) { webkit::npapi::PluginList* plugin_list = GetPluginList(); webkit::WebPluginInfo plugin; if (PluginService::GetInstance()->GetPluginInfoByPath(path, &plugin)) { scoped_ptr group( plugin_list->GetPluginGroup(plugin)); PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name); PolicyStatus group_status = PolicyStatusForPlugin(group->GetGroupName()); if (enabled) { if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED) return false; } else { if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED) return false; } } else { NOTREACHED(); } return true; } void PluginPrefs::EnablePlugin(bool enabled, const FilePath& path, const base::Closure& callback) { PluginService::GetInstance()->GetPluginGroups( base::Bind(&PluginPrefs::EnablePluginInternal, this, enabled, path, callback)); } void PluginPrefs::EnablePluginInternal( bool enabled, const FilePath& path, const base::Closure& callback, const std::vector& groups) { { // Set the desired state for the plug-in. base::AutoLock auto_lock(lock_); plugin_state_[path] = enabled; } bool found_group = false; for (size_t i = 0; i < groups.size(); ++i) { bool all_disabled = true; const std::vector& plugins = groups[i].web_plugin_infos(); for (size_t j = 0; j < plugins.size(); ++j) { all_disabled = all_disabled && !IsPluginEnabled(plugins[j]); if (plugins[j].path == path) { found_group = true; DCHECK_EQ(enabled, IsPluginEnabled(plugins[j])); } } if (found_group) { // Update the state for the corresponding plug-in group. base::AutoLock auto_lock(lock_); plugin_group_state_[groups[i].GetGroupName()] = !all_disabled; break; } } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PluginPrefs::OnUpdatePreferences, this, groups)); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this)); callback.Run(); } // static void PluginPrefs::EnablePluginGlobally(bool enable, const FilePath& file_path, const base::Closure& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); g_default_plugin_state.Get()[file_path] = enable; std::vector profiles = g_browser_process->profile_manager()->GetLoadedProfiles(); scoped_refptr barrier = new CallbackBarrier(callback); for (std::vector::iterator it = profiles.begin(); it != profiles.end(); ++it) { PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(*it); DCHECK(plugin_prefs); plugin_prefs->EnablePlugin(enable, file_path, barrier->CreateCallback()); } } PluginPrefs::PolicyStatus PluginPrefs::PolicyStatusForPlugin( const string16& name) const { base::AutoLock auto_lock(lock_); if (IsStringMatchedInSet(name, policy_enabled_plugin_patterns_)) { return POLICY_ENABLED; } else if (IsStringMatchedInSet(name, policy_disabled_plugin_patterns_) && !IsStringMatchedInSet( name, policy_disabled_plugin_exception_patterns_)) { return POLICY_DISABLED; } else { return NO_POLICY; } } bool PluginPrefs::IsPluginEnabled(const webkit::WebPluginInfo& plugin) const { scoped_ptr group( GetPluginList()->GetPluginGroup(plugin)); string16 group_name = group->GetGroupName(); // Check if the plug-in or its group is enabled by policy. PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name); PolicyStatus group_status = PolicyStatusForPlugin(group_name); if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED) return true; // Check if the plug-in or its group is disabled by policy. if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED) return false; // If enabling NaCl, make sure the plugin is also enabled. See bug // http://code.google.com/p/chromium/issues/detail?id=81010 for more // information. // TODO(dspringer): When NaCl is on by default, remove this code. if ((plugin.name == ASCIIToUTF16(chrome::ChromeContentClient::kNaClPluginName)) && CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableNaCl)) { return true; } base::AutoLock auto_lock(lock_); // Check user preferences for the plug-in. std::map::const_iterator plugin_it = plugin_state_.find(plugin.path); if (plugin_it != plugin_state_.end()) return plugin_it->second; // Check user preferences for the plug-in group. std::map::const_iterator group_it( plugin_group_state_.find(plugin.name)); if (group_it != plugin_group_state_.end()) return group_it->second; // Default to enabled. return true; } void PluginPrefs::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { DCHECK_EQ(chrome::NOTIFICATION_PREF_CHANGED, type); const std::string* pref_name = content::Details(details).ptr(); if (!pref_name) { NOTREACHED(); return; } DCHECK_EQ(prefs_, content::Source(source).ptr()); if (*pref_name == prefs::kPluginsDisabledPlugins) { base::AutoLock auto_lock(lock_); ListValueToStringSet(prefs_->GetList(prefs::kPluginsDisabledPlugins), &policy_disabled_plugin_patterns_); } else if (*pref_name == prefs::kPluginsDisabledPluginsExceptions) { base::AutoLock auto_lock(lock_); ListValueToStringSet( prefs_->GetList(prefs::kPluginsDisabledPluginsExceptions), &policy_disabled_plugin_exception_patterns_); } else if (*pref_name == prefs::kPluginsEnabledPlugins) { base::AutoLock auto_lock(lock_); ListValueToStringSet(prefs_->GetList(prefs::kPluginsEnabledPlugins), &policy_enabled_plugin_patterns_); } else { NOTREACHED(); } NotifyPluginStatusChanged(); } /*static*/ bool PluginPrefs::IsStringMatchedInSet(const string16& name, const std::set& pattern_set) { std::set::const_iterator pattern(pattern_set.begin()); while (pattern != pattern_set.end()) { if (MatchPattern(name, *pattern)) return true; ++pattern; } return false; } /* static */ void PluginPrefs::ListValueToStringSet(const ListValue* src, std::set* dest) { DCHECK(src); DCHECK(dest); ListValue::const_iterator end(src->end()); for (ListValue::const_iterator current(src->begin()); current != end; ++current) { string16 plugin_name; if ((*current)->GetAsString(&plugin_name)) { dest->insert(plugin_name); } } } void PluginPrefs::SetPrefs(PrefService* prefs) { prefs_ = prefs; bool update_internal_dir = false; FilePath last_internal_dir = prefs_->GetFilePath(prefs::kPluginsLastInternalDirectory); FilePath cur_internal_dir; if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &cur_internal_dir) && cur_internal_dir != last_internal_dir) { update_internal_dir = true; prefs_->SetFilePath( prefs::kPluginsLastInternalDirectory, cur_internal_dir); } bool force_enable_internal_pdf = false; bool internal_pdf_enabled = false; string16 pdf_group_name = ASCIIToUTF16(chrome::ChromeContentClient::kPDFPluginName); FilePath pdf_path; PathService::Get(chrome::FILE_PDF_PLUGIN, &pdf_path); FilePath::StringType pdf_path_str = pdf_path.value(); if (!prefs_->GetBoolean(prefs::kPluginsEnabledInternalPDF)) { // We switched to the internal pdf plugin being on by default, and so we // need to force it to be enabled. We only want to do it this once though, // i.e. we don't want to enable it again if the user disables it afterwards. prefs_->SetBoolean(prefs::kPluginsEnabledInternalPDF, true); force_enable_internal_pdf = true; } bool force_enable_nacl = false; string16 nacl_group_name = ASCIIToUTF16(chrome::ChromeContentClient::kNaClPluginName); // Since the NaCl Plugin changed names between Chrome 13 and 14, we need to // check for both because either could be stored as the plugin group name. string16 old_nacl_group_name = ASCIIToUTF16(chrome::ChromeContentClient::kNaClOldPluginName); FilePath nacl_path; PathService::Get(chrome::FILE_NACL_PLUGIN, &nacl_path); FilePath::StringType nacl_path_str = nacl_path.value(); if (!prefs_->GetBoolean(prefs::kPluginsEnabledNaCl)) { // We switched to the nacl plugin being on by default, and so we need to // force it to be enabled. We only want to do it this once though, i.e. // we don't want to enable it again if the user disables it afterwards. prefs_->SetBoolean(prefs::kPluginsEnabledNaCl, true); force_enable_nacl = true; } { // Scoped update of prefs::kPluginsPluginsList. ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList); ListValue* saved_plugins_list = update.Get(); if (saved_plugins_list && !saved_plugins_list->empty()) { for (ListValue::const_iterator it = saved_plugins_list->begin(); it != saved_plugins_list->end(); ++it) { if (!(*it)->IsType(Value::TYPE_DICTIONARY)) { LOG(WARNING) << "Invalid entry in " << prefs::kPluginsPluginsList; continue; // Oops, don't know what to do with this item. } DictionaryValue* plugin = static_cast(*it); string16 group_name; bool enabled; if (!plugin->GetBoolean("enabled", &enabled)) enabled = true; FilePath::StringType path; // The plugin list constains all the plugin files in addition to the // plugin groups. if (plugin->GetString("path", &path)) { // Files have a path attribute, groups don't. FilePath plugin_path(path); if (update_internal_dir && FilePath::CompareIgnoreCase(plugin_path.DirName().value(), last_internal_dir.value()) == 0) { // If the internal plugin directory has changed and if the plugin // looks internal, update its path in the prefs. plugin_path = cur_internal_dir.Append(plugin_path.BaseName()); path = plugin_path.value(); plugin->SetString("path", path); } if (FilePath::CompareIgnoreCase(path, pdf_path_str) == 0) { if (!enabled && force_enable_internal_pdf) { enabled = true; plugin->SetBoolean("enabled", true); } internal_pdf_enabled = enabled; } else if (FilePath::CompareIgnoreCase(path, nacl_path_str) == 0) { if (!enabled && force_enable_nacl) { enabled = true; plugin->SetBoolean("enabled", true); } } plugin_state_[plugin_path] = enabled; } else if (!enabled && plugin->GetString("name", &group_name)) { // Don't disable this group if it's for the pdf or nacl plugins and // we just forced it on. if (force_enable_internal_pdf && pdf_group_name == group_name) continue; if (force_enable_nacl && (nacl_group_name == group_name || old_nacl_group_name == group_name)) continue; // Otherwise this is a list of groups. plugin_group_state_[group_name] = false; } } } else { // If the saved plugin list is empty, then the call to UpdatePreferences() // below failed in an earlier run, possibly because the user closed the // browser too quickly. Try to force enable the internal PDF and nacl // plugins again. force_enable_internal_pdf = true; force_enable_nacl = true; } } // Scoped update of prefs::kPluginsPluginsList. // Build the set of policy enabled/disabled plugin patterns once and cache it. // Don't do this in the constructor, there's no profile available there. ListValueToStringSet(prefs_->GetList(prefs::kPluginsDisabledPlugins), &policy_disabled_plugin_patterns_); ListValueToStringSet( prefs_->GetList(prefs::kPluginsDisabledPluginsExceptions), &policy_disabled_plugin_exception_patterns_); ListValueToStringSet(prefs_->GetList(prefs::kPluginsEnabledPlugins), &policy_enabled_plugin_patterns_); registrar_.Init(prefs_); registrar_.Add(prefs::kPluginsDisabledPlugins, this); registrar_.Add(prefs::kPluginsDisabledPluginsExceptions, this); registrar_.Add(prefs::kPluginsEnabledPlugins, this); if (force_enable_internal_pdf || internal_pdf_enabled) { // See http://crbug.com/50105 for background. plugin_group_state_[ASCIIToUTF16( webkit::npapi::PluginGroup::kAdobeReaderGroupName)] = false; } if (force_enable_internal_pdf || force_enable_nacl) { // We want to save this, but doing so requires loading the list of plugins, // so do it after a minute as to not impact startup performance. Note that // plugins are loaded after 30s by the metrics service. BrowserThread::PostDelayedTask( BrowserThread::FILE, FROM_HERE, base::Bind(&PluginPrefs::GetPreferencesDataOnFileThread, this), base::TimeDelta::FromMilliseconds(kPluginUpdateDelayMs)); } NotifyPluginStatusChanged(); } void PluginPrefs::ShutdownOnUIThread() { prefs_ = NULL; registrar_.RemoveAll(); } PluginPrefs::PluginPrefs() : plugin_state_(g_default_plugin_state.Get()), profile_(NULL), prefs_(NULL), plugin_list_(NULL) { } PluginPrefs::~PluginPrefs() { } void PluginPrefs::SetPolicyEnforcedPluginPatterns( const std::set& disabled_patterns, const std::set& disabled_exception_patterns, const std::set& enabled_patterns) { policy_disabled_plugin_patterns_ = disabled_patterns; policy_disabled_plugin_exception_patterns_ = disabled_exception_patterns; policy_enabled_plugin_patterns_ = enabled_patterns; } webkit::npapi::PluginList* PluginPrefs::GetPluginList() const { if (plugin_list_) return plugin_list_; return PluginService::GetInstance()->GetPluginList(); } void PluginPrefs::GetPreferencesDataOnFileThread() { std::vector groups; webkit::npapi::PluginList* plugin_list = GetPluginList(); plugin_list->GetPluginGroups(false, &groups); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&PluginPrefs::OnUpdatePreferences, this, groups)); } void PluginPrefs::OnUpdatePreferences( const std::vector& groups) { if (!prefs_) return; ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList); ListValue* plugins_list = update.Get(); plugins_list->Clear(); FilePath internal_dir; if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &internal_dir)) prefs_->SetFilePath(prefs::kPluginsLastInternalDirectory, internal_dir); base::AutoLock auto_lock(lock_); // Add the plug-in groups. for (size_t i = 0; i < groups.size(); ++i) { // Add the plugin files to the same list. const std::vector& plugins = groups[i].web_plugin_infos(); for (size_t j = 0; j < plugins.size(); ++j) { DictionaryValue* summary = new DictionaryValue(); summary->SetString("path", plugins[j].path.value()); summary->SetString("name", plugins[j].name); summary->SetString("version", plugins[j].version); bool enabled = true; std::map::iterator it = plugin_state_.find(plugins[j].path); if (it != plugin_state_.end()) enabled = it->second; summary->SetBoolean("enabled", enabled); plugins_list->Append(summary); } DictionaryValue* summary = new DictionaryValue(); string16 name = groups[i].GetGroupName(); summary->SetString("name", name); bool enabled = true; std::map::iterator it = plugin_group_state_.find(name); if (it != plugin_group_state_.end()) enabled = it->second; summary->SetBoolean("enabled", enabled); plugins_list->Append(summary); } } void PluginPrefs::NotifyPluginStatusChanged() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); content::NotificationService::current()->Notify( chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED, content::Source(profile_), content::NotificationService::NoDetails()); }