1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/plugins/plugin_prefs.h"
10 #include "base/command_line.h"
11 #include "base/location.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/path_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "base/values.h"
20 #include "build/build_config.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/plugins/plugin_installer.h"
24 #include "chrome/browser/plugins/plugin_metadata.h"
25 #include "chrome/browser/plugins/plugin_prefs_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/common/chrome_constants.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/pref_names.h"
31 #include "components/keyed_service/core/keyed_service.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/notification_service.h"
34 #include "content/public/browser/plugin_service.h"
35 #include "content/public/common/webplugininfo.h"
37 #if !defined(DISABLE_NACL)
38 #include "components/nacl/common/nacl_constants.h"
41 using content::BrowserThread
;
42 using content::PluginService
;
46 bool IsComponentUpdatedPepperFlash(const base::FilePath
& plugin
) {
47 if (plugin
.BaseName().value() == chrome::kPepperFlashPluginFilename
) {
48 base::FilePath component_updated_pepper_flash_dir
;
49 if (PathService::Get(chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN
,
50 &component_updated_pepper_flash_dir
) &&
51 component_updated_pepper_flash_dir
.IsParent(plugin
)) {
61 PluginPrefs::PluginState::PluginState() {
64 PluginPrefs::PluginState::~PluginState() {
67 bool PluginPrefs::PluginState::Get(const base::FilePath
& plugin
,
68 bool* enabled
) const {
69 base::FilePath key
= ConvertMapKey(plugin
);
70 std::map
<base::FilePath
, bool>::const_iterator iter
= state_
.find(key
);
71 if (iter
!= state_
.end()) {
72 *enabled
= iter
->second
;
78 void PluginPrefs::PluginState::Set(const base::FilePath
& plugin
, bool enabled
) {
79 state_
[ConvertMapKey(plugin
)] = enabled
;
82 base::FilePath
PluginPrefs::PluginState::ConvertMapKey(
83 const base::FilePath
& plugin
) const {
84 // Keep the state of component-updated and bundled Pepper Flash in sync.
85 if (IsComponentUpdatedPepperFlash(plugin
)) {
86 base::FilePath bundled_pepper_flash
;
87 if (PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN
,
88 &bundled_pepper_flash
)) {
89 return bundled_pepper_flash
;
97 scoped_refptr
<PluginPrefs
> PluginPrefs::GetForProfile(Profile
* profile
) {
98 return PluginPrefsFactory::GetPrefsForProfile(profile
);
102 scoped_refptr
<PluginPrefs
> PluginPrefs::GetForTestingProfile(
104 return static_cast<PluginPrefs
*>(
105 PluginPrefsFactory::GetInstance()->SetTestingFactoryAndUse(
106 profile
, &PluginPrefsFactory::CreateForTestingProfile
).get());
109 void PluginPrefs::EnablePluginGroup(bool enabled
,
110 const base::string16
& group_name
) {
111 PluginService::GetInstance()->GetPlugins(
112 base::Bind(&PluginPrefs::EnablePluginGroupInternal
,
113 this, enabled
, group_name
));
116 void PluginPrefs::EnablePluginGroupInternal(
118 const base::string16
& group_name
,
119 const std::vector
<content::WebPluginInfo
>& plugins
) {
120 base::AutoLock
auto_lock(lock_
);
121 PluginFinder
* finder
= PluginFinder::GetInstance();
123 // Set the desired state for the group.
124 plugin_group_state_
[group_name
] = enabled
;
126 // Update the state for all plugins in the group.
127 for (size_t i
= 0; i
< plugins
.size(); ++i
) {
128 scoped_ptr
<PluginMetadata
> plugin(finder
->GetPluginMetadata(plugins
[i
]));
129 if (group_name
!= plugin
->name())
131 plugin_state_
.Set(plugins
[i
].path
, enabled
);
134 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
135 base::Bind(&PluginPrefs::OnUpdatePreferences
, this, plugins
));
136 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
137 base::Bind(&PluginPrefs::NotifyPluginStatusChanged
, this));
140 void PluginPrefs::EnablePlugin(
141 bool enabled
, const base::FilePath
& path
,
142 const base::Callback
<void(bool)>& callback
) {
143 PluginFinder
* finder
= PluginFinder::GetInstance();
144 content::WebPluginInfo plugin
;
145 bool can_enable
= true;
146 if (PluginService::GetInstance()->GetPluginInfoByPath(path
, &plugin
)) {
147 scoped_ptr
<PluginMetadata
> plugin_metadata(
148 finder
->GetPluginMetadata(plugin
));
149 PolicyStatus plugin_status
= PolicyStatusForPlugin(plugin
.name
);
150 PolicyStatus group_status
= PolicyStatusForPlugin(plugin_metadata
->name());
152 if (plugin_status
== POLICY_DISABLED
|| group_status
== POLICY_DISABLED
)
155 if (plugin_status
== POLICY_ENABLED
|| group_status
== POLICY_ENABLED
)
163 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE
,
164 base::Bind(callback
, false));
168 PluginService::GetInstance()->GetPlugins(
169 base::Bind(&PluginPrefs::EnablePluginInternal
, this,
170 enabled
, path
, finder
, callback
));
173 void PluginPrefs::EnablePluginInternal(
175 const base::FilePath
& path
,
176 PluginFinder
* plugin_finder
,
177 const base::Callback
<void(bool)>& callback
,
178 const std::vector
<content::WebPluginInfo
>& plugins
) {
180 // Set the desired state for the plugin.
181 base::AutoLock
auto_lock(lock_
);
182 plugin_state_
.Set(path
, enabled
);
185 base::string16 group_name
;
186 for (size_t i
= 0; i
< plugins
.size(); ++i
) {
187 if (plugins
[i
].path
== path
) {
188 scoped_ptr
<PluginMetadata
> plugin_metadata(
189 plugin_finder
->GetPluginMetadata(plugins
[i
]));
190 // set the group name for this plugin.
191 group_name
= plugin_metadata
->name();
192 DCHECK_EQ(enabled
, IsPluginEnabled(plugins
[i
]));
197 bool all_disabled
= true;
198 for (size_t i
= 0; i
< plugins
.size(); ++i
) {
199 scoped_ptr
<PluginMetadata
> plugin_metadata(
200 plugin_finder
->GetPluginMetadata(plugins
[i
]));
201 DCHECK(!plugin_metadata
->name().empty());
202 if (group_name
== plugin_metadata
->name()) {
203 all_disabled
= all_disabled
&& !IsPluginEnabled(plugins
[i
]);
207 if (!group_name
.empty()) {
208 // Update the state for the corresponding plugin group.
209 base::AutoLock
auto_lock(lock_
);
210 plugin_group_state_
[group_name
] = !all_disabled
;
213 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
214 base::Bind(&PluginPrefs::OnUpdatePreferences
, this, plugins
));
215 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
216 base::Bind(&PluginPrefs::NotifyPluginStatusChanged
, this));
220 PluginPrefs::PolicyStatus
PluginPrefs::PolicyStatusForPlugin(
221 const base::string16
& name
) const {
222 base::AutoLock
auto_lock(lock_
);
223 if (IsStringMatchedInSet(name
, policy_enabled_plugin_patterns_
)) {
224 return POLICY_ENABLED
;
225 } else if (IsStringMatchedInSet(name
, policy_disabled_plugin_patterns_
) &&
226 !IsStringMatchedInSet(
227 name
, policy_disabled_plugin_exception_patterns_
)) {
228 return POLICY_DISABLED
;
234 bool PluginPrefs::IsPluginEnabled(const content::WebPluginInfo
& plugin
) const {
235 scoped_ptr
<PluginMetadata
> plugin_metadata(
236 PluginFinder::GetInstance()->GetPluginMetadata(plugin
));
237 base::string16 group_name
= plugin_metadata
->name();
239 // Check if the plugin or its group is enabled by policy.
240 PolicyStatus plugin_status
= PolicyStatusForPlugin(plugin
.name
);
241 PolicyStatus group_status
= PolicyStatusForPlugin(group_name
);
242 if (plugin_status
== POLICY_ENABLED
|| group_status
== POLICY_ENABLED
)
245 // Check if the plugin or its group is disabled by policy.
246 if (plugin_status
== POLICY_DISABLED
|| group_status
== POLICY_DISABLED
)
249 #if !defined(DISABLE_NACL)
250 // If enabling NaCl, make sure the plugin is also enabled. See bug
251 // http://code.google.com/p/chromium/issues/detail?id=81010 for more
253 // TODO(dspringer): When NaCl is on by default, remove this code.
254 if ((plugin
.name
== base::ASCIIToUTF16(nacl::kNaClPluginName
)) &&
255 base::CommandLine::ForCurrentProcess()->HasSwitch(
256 switches::kEnableNaCl
)) {
261 base::AutoLock
auto_lock(lock_
);
262 // Check user preferences for the plugin.
263 bool plugin_enabled
= false;
264 if (plugin_state_
.Get(plugin
.path
, &plugin_enabled
))
265 return plugin_enabled
;
267 // Check user preferences for the plugin group.
268 std::map
<base::string16
, bool>::const_iterator
group_it(
269 plugin_group_state_
.find(group_name
));
270 if (group_it
!= plugin_group_state_
.end())
271 return group_it
->second
;
273 // Default to enabled.
277 void PluginPrefs::UpdatePatternsAndNotify(std::set
<base::string16
>* patterns
,
278 const std::string
& pref_name
) {
279 base::AutoLock
auto_lock(lock_
);
280 ListValueToStringSet(prefs_
->GetList(pref_name
.c_str()), patterns
);
282 NotifyPluginStatusChanged();
286 bool PluginPrefs::IsStringMatchedInSet(
287 const base::string16
& name
,
288 const std::set
<base::string16
>& pattern_set
) {
289 std::set
<base::string16
>::const_iterator
pattern(pattern_set
.begin());
290 while (pattern
!= pattern_set
.end()) {
291 if (MatchPattern(name
, *pattern
))
300 void PluginPrefs::ListValueToStringSet(const base::ListValue
* src
,
301 std::set
<base::string16
>* dest
) {
305 base::ListValue::const_iterator
end(src
->end());
306 for (base::ListValue::const_iterator
current(src
->begin());
307 current
!= end
; ++current
) {
308 base::string16 plugin_name
;
309 if ((*current
)->GetAsString(&plugin_name
)) {
310 dest
->insert(plugin_name
);
315 void PluginPrefs::SetPrefs(PrefService
* prefs
) {
317 bool update_internal_dir
= false;
318 base::FilePath last_internal_dir
=
319 prefs_
->GetFilePath(prefs::kPluginsLastInternalDirectory
);
320 base::FilePath cur_internal_dir
;
321 if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS
, &cur_internal_dir
) &&
322 cur_internal_dir
!= last_internal_dir
) {
323 update_internal_dir
= true;
325 prefs::kPluginsLastInternalDirectory
, cur_internal_dir
);
328 bool migrate_to_pepper_flash
= false;
329 #if defined(OS_WIN) || defined(OS_MACOSX)
330 // If bundled NPAPI Flash is enabled while Pepper Flash is disabled, we
331 // would like to turn Pepper Flash on. And we only want to do it once.
332 // TODO(yzshen): Remove all |migrate_to_pepper_flash|-related code after it
333 // has been run once by most users. (Maybe Chrome 24 or Chrome 25.)
334 // NOTE(shess): Keep in mind that Mac is on a different schedule.
335 if (!prefs_
->GetBoolean(prefs::kPluginsMigratedToPepperFlash
)) {
336 prefs_
->SetBoolean(prefs::kPluginsMigratedToPepperFlash
, true);
337 migrate_to_pepper_flash
= true;
341 bool remove_component_pepper_flash_settings
= false;
342 // If component-updated Pepper Flash is disabled, we would like to remove that
343 // settings item. And we only want to do it once. (Please see the comments of
344 // kPluginsRemovedOldComponentPepperFlashSettings for why.)
345 // TODO(yzshen): Remove all |remove_component_pepper_flash_settings|-related
346 // code after it has been run once by most users.
347 if (!prefs_
->GetBoolean(
348 prefs::kPluginsRemovedOldComponentPepperFlashSettings
)) {
349 prefs_
->SetBoolean(prefs::kPluginsRemovedOldComponentPepperFlashSettings
,
351 remove_component_pepper_flash_settings
= true;
354 { // Scoped update of prefs::kPluginsPluginsList.
355 ListPrefUpdate
update(prefs_
, prefs::kPluginsPluginsList
);
356 base::ListValue
* saved_plugins_list
= update
.Get();
357 if (saved_plugins_list
&& !saved_plugins_list
->empty()) {
358 // The following four variables are only valid when
359 // |migrate_to_pepper_flash| is set to true.
360 base::FilePath npapi_flash
;
361 base::FilePath pepper_flash
;
362 base::DictionaryValue
* pepper_flash_node
= NULL
;
363 bool npapi_flash_enabled
= false;
364 if (migrate_to_pepper_flash
) {
365 PathService::Get(chrome::FILE_FLASH_PLUGIN
, &npapi_flash
);
366 PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN
, &pepper_flash
);
369 // Used when |remove_component_pepper_flash_settings| is set to true.
370 base::ListValue::iterator component_pepper_flash_node
=
371 saved_plugins_list
->end();
373 for (base::ListValue::iterator it
= saved_plugins_list
->begin();
374 it
!= saved_plugins_list
->end();
376 if (!(*it
)->IsType(base::Value::TYPE_DICTIONARY
)) {
377 LOG(WARNING
) << "Invalid entry in " << prefs::kPluginsPluginsList
;
378 continue; // Oops, don't know what to do with this item.
381 base::DictionaryValue
* plugin
=
382 static_cast<base::DictionaryValue
*>(*it
);
383 base::string16 group_name
;
385 if (!plugin
->GetBoolean("enabled", &enabled
))
388 base::FilePath::StringType path
;
389 // The plugin list constains all the plugin files in addition to the
391 if (plugin
->GetString("path", &path
)) {
392 // Files have a path attribute, groups don't.
393 base::FilePath
plugin_path(path
);
395 // The path to the intenral plugin directory changes everytime Chrome
396 // is auto-updated, since it contains the current version number. For
397 // example, it changes from foobar\Chrome\Application\21.0.1180.83 to
398 // foobar\Chrome\Application\21.0.1180.89.
399 // However, we would like the settings of internal plugins to persist
400 // across Chrome updates. Therefore, we need to recognize those paths
401 // that are within the previous internal plugin directory, and update
402 // them in the prefs accordingly.
403 if (update_internal_dir
) {
404 base::FilePath relative_path
;
406 // Extract the part of |plugin_path| that is relative to
407 // |last_internal_dir|. For example, |relative_path| will be
408 // foo\bar.dll if |plugin_path| is <last_internal_dir>\foo\bar.dll.
410 // Every iteration the last path component from |plugin_path| is
411 // removed and prepended to |relative_path| until we get up to
412 // |last_internal_dir|.
413 while (last_internal_dir
.IsParent(plugin_path
)) {
414 relative_path
= plugin_path
.BaseName().Append(relative_path
);
416 base::FilePath old_path
= plugin_path
;
417 plugin_path
= plugin_path
.DirName();
418 // To be extra sure that we won't end up in an infinite loop.
419 if (old_path
== plugin_path
) {
425 // If |relative_path| is empty, |plugin_path| is not within
426 // |last_internal_dir|. We don't need to update it.
427 if (!relative_path
.empty()) {
428 plugin_path
= cur_internal_dir
.Append(relative_path
);
429 path
= plugin_path
.value();
430 plugin
->SetString("path", path
);
434 if (migrate_to_pepper_flash
&&
435 base::FilePath::CompareEqualIgnoreCase(
436 path
, npapi_flash
.value())) {
437 npapi_flash_enabled
= enabled
;
438 } else if (migrate_to_pepper_flash
&&
439 base::FilePath::CompareEqualIgnoreCase(
440 path
, pepper_flash
.value())) {
442 pepper_flash_node
= plugin
;
443 } else if (remove_component_pepper_flash_settings
&&
444 IsComponentUpdatedPepperFlash(plugin_path
)) {
446 component_pepper_flash_node
= it
;
447 // Skip setting |enabled| into |plugin_state_|.
452 plugin_state_
.Set(plugin_path
, enabled
);
453 } else if (!enabled
&& plugin
->GetString("name", &group_name
)) {
454 // Otherwise this is a list of groups.
455 plugin_group_state_
[group_name
] = false;
459 if (npapi_flash_enabled
&& pepper_flash_node
) {
460 DCHECK(migrate_to_pepper_flash
);
461 pepper_flash_node
->SetBoolean("enabled", true);
462 plugin_state_
.Set(pepper_flash
, true);
465 if (component_pepper_flash_node
!= saved_plugins_list
->end()) {
466 DCHECK(remove_component_pepper_flash_settings
);
467 saved_plugins_list
->Erase(component_pepper_flash_node
, NULL
);
470 // If the saved plugin list is empty, then the call to UpdatePreferences()
471 // below failed in an earlier run, possibly because the user closed the
472 // browser too quickly.
474 // Only want one PDF plugin enabled at a time. See http://crbug.com/50105
476 plugin_group_state_
[base::ASCIIToUTF16(
477 PluginMetadata::kAdobeReaderGroupName
)] = false;
479 } // Scoped update of prefs::kPluginsPluginsList.
481 // Build the set of policy enabled/disabled plugin patterns once and cache it.
482 // Don't do this in the constructor, there's no profile available there.
483 ListValueToStringSet(prefs_
->GetList(prefs::kPluginsDisabledPlugins
),
484 &policy_disabled_plugin_patterns_
);
485 ListValueToStringSet(
486 prefs_
->GetList(prefs::kPluginsDisabledPluginsExceptions
),
487 &policy_disabled_plugin_exception_patterns_
);
488 ListValueToStringSet(prefs_
->GetList(prefs::kPluginsEnabledPlugins
),
489 &policy_enabled_plugin_patterns_
);
491 registrar_
.Init(prefs_
);
493 // Because pointers to our own members will remain unchanged for the
494 // lifetime of |registrar_| (which we also own), we can bind their
495 // pointer values directly in the callbacks to avoid string-based
496 // lookups at notification time.
497 registrar_
.Add(prefs::kPluginsDisabledPlugins
,
498 base::Bind(&PluginPrefs::UpdatePatternsAndNotify
,
499 base::Unretained(this),
500 &policy_disabled_plugin_patterns_
));
501 registrar_
.Add(prefs::kPluginsDisabledPluginsExceptions
,
502 base::Bind(&PluginPrefs::UpdatePatternsAndNotify
,
503 base::Unretained(this),
504 &policy_disabled_plugin_exception_patterns_
));
505 registrar_
.Add(prefs::kPluginsEnabledPlugins
,
506 base::Bind(&PluginPrefs::UpdatePatternsAndNotify
,
507 base::Unretained(this),
508 &policy_enabled_plugin_patterns_
));
510 NotifyPluginStatusChanged();
513 void PluginPrefs::ShutdownOnUIThread() {
515 registrar_
.RemoveAll();
518 PluginPrefs::PluginPrefs() : profile_(NULL
),
522 PluginPrefs::~PluginPrefs() {
525 void PluginPrefs::SetPolicyEnforcedPluginPatterns(
526 const std::set
<base::string16
>& disabled_patterns
,
527 const std::set
<base::string16
>& disabled_exception_patterns
,
528 const std::set
<base::string16
>& enabled_patterns
) {
529 policy_disabled_plugin_patterns_
= disabled_patterns
;
530 policy_disabled_plugin_exception_patterns_
= disabled_exception_patterns
;
531 policy_enabled_plugin_patterns_
= enabled_patterns
;
534 void PluginPrefs::OnUpdatePreferences(
535 const std::vector
<content::WebPluginInfo
>& plugins
) {
539 PluginFinder
* finder
= PluginFinder::GetInstance();
540 ListPrefUpdate
update(prefs_
, prefs::kPluginsPluginsList
);
541 base::ListValue
* plugins_list
= update
.Get();
542 plugins_list
->Clear();
544 base::FilePath internal_dir
;
545 if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS
, &internal_dir
))
546 prefs_
->SetFilePath(prefs::kPluginsLastInternalDirectory
, internal_dir
);
548 base::AutoLock
auto_lock(lock_
);
550 // Add the plugin files.
551 std::set
<base::string16
> group_names
;
552 for (size_t i
= 0; i
< plugins
.size(); ++i
) {
553 base::DictionaryValue
* summary
= new base::DictionaryValue();
554 summary
->SetString("path", plugins
[i
].path
.value());
555 summary
->SetString("name", plugins
[i
].name
);
556 summary
->SetString("version", plugins
[i
].version
);
558 plugin_state_
.Get(plugins
[i
].path
, &enabled
);
559 summary
->SetBoolean("enabled", enabled
);
560 plugins_list
->Append(summary
);
562 scoped_ptr
<PluginMetadata
> plugin_metadata(
563 finder
->GetPluginMetadata(plugins
[i
]));
564 // Insert into a set of all group names.
565 group_names
.insert(plugin_metadata
->name());
568 // Add the plugin groups.
569 for (std::set
<base::string16
>::const_iterator it
= group_names
.begin();
570 it
!= group_names
.end(); ++it
) {
571 base::DictionaryValue
* summary
= new base::DictionaryValue();
572 summary
->SetString("name", *it
);
574 std::map
<base::string16
, bool>::iterator gstate_it
=
575 plugin_group_state_
.find(*it
);
576 if (gstate_it
!= plugin_group_state_
.end())
577 enabled
= gstate_it
->second
;
578 summary
->SetBoolean("enabled", enabled
);
579 plugins_list
->Append(summary
);
583 void PluginPrefs::NotifyPluginStatusChanged() {
584 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
585 content::NotificationService::current()->Notify(
586 chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED
,
587 content::Source
<Profile
>(profile_
),
588 content::NotificationService::NoDetails());