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.
4
5#include "chrome/browser/plugins/plugin_prefs.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/message_loop/message_loop.h"
13#include "base/path_service.h"
14#include "base/prefs/scoped_user_pref_update.h"
15#include "base/strings/string_util.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/values.h"
18#include "build/build_config.h"
19#include "chrome/browser/browser_process.h"
20#include "chrome/browser/chrome_notification_types.h"
21#include "chrome/browser/plugins/plugin_installer.h"
22#include "chrome/browser/plugins/plugin_metadata.h"
23#include "chrome/browser/plugins/plugin_prefs_factory.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/common/chrome_constants.h"
26#include "chrome/common/chrome_paths.h"
27#include "chrome/common/chrome_switches.h"
28#include "chrome/common/pref_names.h"
29#include "components/keyed_service/core/keyed_service.h"
30#include "content/public/browser/browser_thread.h"
31#include "content/public/browser/notification_service.h"
32#include "content/public/browser/plugin_service.h"
33#include "content/public/common/webplugininfo.h"
34
35#if !defined(DISABLE_NACL)
36#include "components/nacl/common/nacl_constants.h"
37#endif
38
39using content::BrowserThread;
40using content::PluginService;
41
42namespace {
43
44bool IsComponentUpdatedPepperFlash(const base::FilePath& plugin) {
45  if (plugin.BaseName().value() == chrome::kPepperFlashPluginFilename) {
46    base::FilePath component_updated_pepper_flash_dir;
47    if (PathService::Get(chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN,
48                         &component_updated_pepper_flash_dir) &&
49        component_updated_pepper_flash_dir.IsParent(plugin)) {
50      return true;
51    }
52  }
53
54  return false;
55}
56
57}  // namespace
58
59PluginPrefs::PluginState::PluginState() {
60}
61
62PluginPrefs::PluginState::~PluginState() {
63}
64
65bool PluginPrefs::PluginState::Get(const base::FilePath& plugin,
66                                   bool* enabled) const {
67  base::FilePath key = ConvertMapKey(plugin);
68  std::map<base::FilePath, bool>::const_iterator iter = state_.find(key);
69  if (iter != state_.end()) {
70    *enabled = iter->second;
71    return true;
72  }
73  return false;
74}
75
76void PluginPrefs::PluginState::Set(const base::FilePath& plugin, bool enabled) {
77  state_[ConvertMapKey(plugin)] = enabled;
78}
79
80base::FilePath PluginPrefs::PluginState::ConvertMapKey(
81    const base::FilePath& plugin) const {
82  // Keep the state of component-updated and bundled Pepper Flash in sync.
83  if (IsComponentUpdatedPepperFlash(plugin)) {
84    base::FilePath bundled_pepper_flash;
85    if (PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN,
86                         &bundled_pepper_flash)) {
87      return bundled_pepper_flash;
88    }
89  }
90
91  return plugin;
92}
93
94// static
95scoped_refptr<PluginPrefs> PluginPrefs::GetForProfile(Profile* profile) {
96  return PluginPrefsFactory::GetPrefsForProfile(profile);
97}
98
99// static
100scoped_refptr<PluginPrefs> PluginPrefs::GetForTestingProfile(
101    Profile* profile) {
102  return static_cast<PluginPrefs*>(
103      PluginPrefsFactory::GetInstance()->SetTestingFactoryAndUse(
104          profile, &PluginPrefsFactory::CreateForTestingProfile).get());
105}
106
107void PluginPrefs::EnablePluginGroup(bool enabled,
108                                    const base::string16& group_name) {
109  PluginService::GetInstance()->GetPlugins(
110      base::Bind(&PluginPrefs::EnablePluginGroupInternal,
111                 this, enabled, group_name));
112}
113
114void PluginPrefs::EnablePluginGroupInternal(
115    bool enabled,
116    const base::string16& group_name,
117    const std::vector<content::WebPluginInfo>& plugins) {
118  base::AutoLock auto_lock(lock_);
119  PluginFinder* finder = PluginFinder::GetInstance();
120
121  // Set the desired state for the group.
122  plugin_group_state_[group_name] = enabled;
123
124  // Update the state for all plug-ins in the group.
125  for (size_t i = 0; i < plugins.size(); ++i) {
126    scoped_ptr<PluginMetadata> plugin(finder->GetPluginMetadata(plugins[i]));
127    if (group_name != plugin->name())
128      continue;
129    plugin_state_.Set(plugins[i].path, enabled);
130  }
131
132  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
133      base::Bind(&PluginPrefs::OnUpdatePreferences, this, plugins));
134  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
135      base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this));
136}
137
138void PluginPrefs::EnablePlugin(
139    bool enabled, const base::FilePath& path,
140    const base::Callback<void(bool)>& callback) {
141  PluginFinder* finder = PluginFinder::GetInstance();
142  content::WebPluginInfo plugin;
143  bool can_enable = true;
144  if (PluginService::GetInstance()->GetPluginInfoByPath(path, &plugin)) {
145    scoped_ptr<PluginMetadata> plugin_metadata(
146        finder->GetPluginMetadata(plugin));
147    PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name);
148    PolicyStatus group_status = PolicyStatusForPlugin(plugin_metadata->name());
149    if (enabled) {
150      if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED)
151        can_enable = false;
152    } else {
153      if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED)
154        can_enable = false;
155    }
156  } else {
157    NOTREACHED();
158  }
159
160  if (!can_enable) {
161    base::MessageLoop::current()->PostTask(FROM_HERE,
162                                           base::Bind(callback, false));
163    return;
164  }
165
166  PluginService::GetInstance()->GetPlugins(
167      base::Bind(&PluginPrefs::EnablePluginInternal, this,
168                 enabled, path, finder, callback));
169}
170
171void PluginPrefs::EnablePluginInternal(
172    bool enabled,
173    const base::FilePath& path,
174    PluginFinder* plugin_finder,
175    const base::Callback<void(bool)>& callback,
176    const std::vector<content::WebPluginInfo>& plugins) {
177  {
178    // Set the desired state for the plug-in.
179    base::AutoLock auto_lock(lock_);
180    plugin_state_.Set(path, enabled);
181  }
182
183  base::string16 group_name;
184  for (size_t i = 0; i < plugins.size(); ++i) {
185    if (plugins[i].path == path) {
186      scoped_ptr<PluginMetadata> plugin_metadata(
187          plugin_finder->GetPluginMetadata(plugins[i]));
188      // set the group name for this plug-in.
189      group_name = plugin_metadata->name();
190      DCHECK_EQ(enabled, IsPluginEnabled(plugins[i]));
191      break;
192    }
193  }
194
195  bool all_disabled = true;
196  for (size_t i = 0; i < plugins.size(); ++i) {
197    scoped_ptr<PluginMetadata> plugin_metadata(
198        plugin_finder->GetPluginMetadata(plugins[i]));
199    DCHECK(!plugin_metadata->name().empty());
200    if (group_name == plugin_metadata->name()) {
201      all_disabled = all_disabled && !IsPluginEnabled(plugins[i]);
202    }
203  }
204
205  if (!group_name.empty()) {
206    // Update the state for the corresponding plug-in group.
207    base::AutoLock auto_lock(lock_);
208    plugin_group_state_[group_name] = !all_disabled;
209  }
210
211  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
212      base::Bind(&PluginPrefs::OnUpdatePreferences, this, plugins));
213  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
214      base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this));
215  callback.Run(true);
216}
217
218PluginPrefs::PolicyStatus PluginPrefs::PolicyStatusForPlugin(
219    const base::string16& name) const {
220  base::AutoLock auto_lock(lock_);
221  if (IsStringMatchedInSet(name, policy_enabled_plugin_patterns_)) {
222    return POLICY_ENABLED;
223  } else if (IsStringMatchedInSet(name, policy_disabled_plugin_patterns_) &&
224             !IsStringMatchedInSet(
225                 name, policy_disabled_plugin_exception_patterns_)) {
226    return POLICY_DISABLED;
227  } else {
228    return NO_POLICY;
229  }
230}
231
232bool PluginPrefs::IsPluginEnabled(const content::WebPluginInfo& plugin) const {
233  scoped_ptr<PluginMetadata> plugin_metadata(
234      PluginFinder::GetInstance()->GetPluginMetadata(plugin));
235  base::string16 group_name = plugin_metadata->name();
236
237  // Check if the plug-in or its group is enabled by policy.
238  PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name);
239  PolicyStatus group_status = PolicyStatusForPlugin(group_name);
240  if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED)
241    return true;
242
243  // Check if the plug-in or its group is disabled by policy.
244  if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED)
245    return false;
246
247#if !defined(DISABLE_NACL)
248  // If enabling NaCl, make sure the plugin is also enabled. See bug
249  // http://code.google.com/p/chromium/issues/detail?id=81010 for more
250  // information.
251  // TODO(dspringer): When NaCl is on by default, remove this code.
252  if ((plugin.name == base::ASCIIToUTF16(nacl::kNaClPluginName)) &&
253      CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableNaCl)) {
254    return true;
255  }
256#endif
257
258  base::AutoLock auto_lock(lock_);
259  // Check user preferences for the plug-in.
260  bool plugin_enabled = false;
261  if (plugin_state_.Get(plugin.path, &plugin_enabled))
262    return plugin_enabled;
263
264  // Check user preferences for the plug-in group.
265  std::map<base::string16, bool>::const_iterator group_it(
266      plugin_group_state_.find(group_name));
267  if (group_it != plugin_group_state_.end())
268    return group_it->second;
269
270  // Default to enabled.
271  return true;
272}
273
274void PluginPrefs::UpdatePatternsAndNotify(std::set<base::string16>* patterns,
275                                          const std::string& pref_name) {
276  base::AutoLock auto_lock(lock_);
277  ListValueToStringSet(prefs_->GetList(pref_name.c_str()), patterns);
278
279  NotifyPluginStatusChanged();
280}
281
282/*static*/
283bool PluginPrefs::IsStringMatchedInSet(
284    const base::string16& name,
285    const std::set<base::string16>& pattern_set) {
286  std::set<base::string16>::const_iterator pattern(pattern_set.begin());
287  while (pattern != pattern_set.end()) {
288    if (MatchPattern(name, *pattern))
289      return true;
290    ++pattern;
291  }
292
293  return false;
294}
295
296/* static */
297void PluginPrefs::ListValueToStringSet(const base::ListValue* src,
298                                       std::set<base::string16>* dest) {
299  DCHECK(src);
300  DCHECK(dest);
301  dest->clear();
302  base::ListValue::const_iterator end(src->end());
303  for (base::ListValue::const_iterator current(src->begin());
304       current != end; ++current) {
305    base::string16 plugin_name;
306    if ((*current)->GetAsString(&plugin_name)) {
307      dest->insert(plugin_name);
308    }
309  }
310}
311
312void PluginPrefs::SetPrefs(PrefService* prefs) {
313  prefs_ = prefs;
314  bool update_internal_dir = false;
315  base::FilePath last_internal_dir =
316      prefs_->GetFilePath(prefs::kPluginsLastInternalDirectory);
317  base::FilePath cur_internal_dir;
318  if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &cur_internal_dir) &&
319      cur_internal_dir != last_internal_dir) {
320    update_internal_dir = true;
321    prefs_->SetFilePath(
322        prefs::kPluginsLastInternalDirectory, cur_internal_dir);
323  }
324
325  bool migrate_to_pepper_flash = false;
326#if defined(OS_WIN) || defined(OS_MACOSX)
327  // If bundled NPAPI Flash is enabled while Pepper Flash is disabled, we
328  // would like to turn Pepper Flash on. And we only want to do it once.
329  // TODO(yzshen): Remove all |migrate_to_pepper_flash|-related code after it
330  // has been run once by most users. (Maybe Chrome 24 or Chrome 25.)
331  // NOTE(shess): Keep in mind that Mac is on a different schedule.
332  if (!prefs_->GetBoolean(prefs::kPluginsMigratedToPepperFlash)) {
333    prefs_->SetBoolean(prefs::kPluginsMigratedToPepperFlash, true);
334    migrate_to_pepper_flash = true;
335  }
336#endif
337
338  bool remove_component_pepper_flash_settings = false;
339  // If component-updated Pepper Flash is disabled, we would like to remove that
340  // settings item. And we only want to do it once. (Please see the comments of
341  // kPluginsRemovedOldComponentPepperFlashSettings for why.)
342  // TODO(yzshen): Remove all |remove_component_pepper_flash_settings|-related
343  // code after it has been run once by most users.
344  if (!prefs_->GetBoolean(
345          prefs::kPluginsRemovedOldComponentPepperFlashSettings)) {
346    prefs_->SetBoolean(prefs::kPluginsRemovedOldComponentPepperFlashSettings,
347                       true);
348    remove_component_pepper_flash_settings = true;
349  }
350
351  {  // Scoped update of prefs::kPluginsPluginsList.
352    ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList);
353    base::ListValue* saved_plugins_list = update.Get();
354    if (saved_plugins_list && !saved_plugins_list->empty()) {
355      // The following four variables are only valid when
356      // |migrate_to_pepper_flash| is set to true.
357      base::FilePath npapi_flash;
358      base::FilePath pepper_flash;
359      base::DictionaryValue* pepper_flash_node = NULL;
360      bool npapi_flash_enabled = false;
361      if (migrate_to_pepper_flash) {
362        PathService::Get(chrome::FILE_FLASH_PLUGIN, &npapi_flash);
363        PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, &pepper_flash);
364      }
365
366      // Used when |remove_component_pepper_flash_settings| is set to true.
367      base::ListValue::iterator component_pepper_flash_node =
368          saved_plugins_list->end();
369
370      for (base::ListValue::iterator it = saved_plugins_list->begin();
371           it != saved_plugins_list->end();
372           ++it) {
373        if (!(*it)->IsType(base::Value::TYPE_DICTIONARY)) {
374          LOG(WARNING) << "Invalid entry in " << prefs::kPluginsPluginsList;
375          continue;  // Oops, don't know what to do with this item.
376        }
377
378        base::DictionaryValue* plugin =
379            static_cast<base::DictionaryValue*>(*it);
380        base::string16 group_name;
381        bool enabled;
382        if (!plugin->GetBoolean("enabled", &enabled))
383          enabled = true;
384
385        base::FilePath::StringType path;
386        // The plugin list constains all the plugin files in addition to the
387        // plugin groups.
388        if (plugin->GetString("path", &path)) {
389          // Files have a path attribute, groups don't.
390          base::FilePath plugin_path(path);
391
392          // The path to the intenral plugin directory changes everytime Chrome
393          // is auto-updated, since it contains the current version number. For
394          // example, it changes from foobar\Chrome\Application\21.0.1180.83 to
395          // foobar\Chrome\Application\21.0.1180.89.
396          // However, we would like the settings of internal plugins to persist
397          // across Chrome updates. Therefore, we need to recognize those paths
398          // that are within the previous internal plugin directory, and update
399          // them in the prefs accordingly.
400          if (update_internal_dir) {
401            base::FilePath relative_path;
402
403            // Extract the part of |plugin_path| that is relative to
404            // |last_internal_dir|. For example, |relative_path| will be
405            // foo\bar.dll if |plugin_path| is <last_internal_dir>\foo\bar.dll.
406            //
407            // Every iteration the last path component from |plugin_path| is
408            // removed and prepended to |relative_path| until we get up to
409            // |last_internal_dir|.
410            while (last_internal_dir.IsParent(plugin_path)) {
411              relative_path = plugin_path.BaseName().Append(relative_path);
412
413              base::FilePath old_path = plugin_path;
414              plugin_path = plugin_path.DirName();
415              // To be extra sure that we won't end up in an infinite loop.
416              if (old_path == plugin_path) {
417                NOTREACHED();
418                break;
419              }
420            }
421
422            // If |relative_path| is empty, |plugin_path| is not within
423            // |last_internal_dir|. We don't need to update it.
424            if (!relative_path.empty()) {
425              plugin_path = cur_internal_dir.Append(relative_path);
426              path = plugin_path.value();
427              plugin->SetString("path", path);
428            }
429          }
430
431          if (migrate_to_pepper_flash &&
432                     base::FilePath::CompareEqualIgnoreCase(
433                         path, npapi_flash.value())) {
434            npapi_flash_enabled = enabled;
435          } else if (migrate_to_pepper_flash &&
436                     base::FilePath::CompareEqualIgnoreCase(
437                         path, pepper_flash.value())) {
438            if (!enabled)
439              pepper_flash_node = plugin;
440          } else if (remove_component_pepper_flash_settings &&
441                     IsComponentUpdatedPepperFlash(plugin_path)) {
442            if (!enabled) {
443              component_pepper_flash_node = it;
444              // Skip setting |enabled| into |plugin_state_|.
445              continue;
446            }
447          }
448
449          plugin_state_.Set(plugin_path, enabled);
450        } else if (!enabled && plugin->GetString("name", &group_name)) {
451          // Otherwise this is a list of groups.
452          plugin_group_state_[group_name] = false;
453        }
454      }
455
456      if (npapi_flash_enabled && pepper_flash_node) {
457        DCHECK(migrate_to_pepper_flash);
458        pepper_flash_node->SetBoolean("enabled", true);
459        plugin_state_.Set(pepper_flash, true);
460      }
461
462      if (component_pepper_flash_node != saved_plugins_list->end()) {
463        DCHECK(remove_component_pepper_flash_settings);
464        saved_plugins_list->Erase(component_pepper_flash_node, NULL);
465      }
466    } else {
467      // If the saved plugin list is empty, then the call to UpdatePreferences()
468      // below failed in an earlier run, possibly because the user closed the
469      // browser too quickly.
470
471      // Only want one PDF plugin enabled at a time. See http://crbug.com/50105
472      // for background.
473      plugin_group_state_[base::ASCIIToUTF16(
474          PluginMetadata::kAdobeReaderGroupName)] = false;
475    }
476  }  // Scoped update of prefs::kPluginsPluginsList.
477
478  // Build the set of policy enabled/disabled plugin patterns once and cache it.
479  // Don't do this in the constructor, there's no profile available there.
480  ListValueToStringSet(prefs_->GetList(prefs::kPluginsDisabledPlugins),
481                       &policy_disabled_plugin_patterns_);
482  ListValueToStringSet(
483      prefs_->GetList(prefs::kPluginsDisabledPluginsExceptions),
484      &policy_disabled_plugin_exception_patterns_);
485  ListValueToStringSet(prefs_->GetList(prefs::kPluginsEnabledPlugins),
486                       &policy_enabled_plugin_patterns_);
487
488  registrar_.Init(prefs_);
489
490  // Because pointers to our own members will remain unchanged for the
491  // lifetime of |registrar_| (which we also own), we can bind their
492  // pointer values directly in the callbacks to avoid string-based
493  // lookups at notification time.
494  registrar_.Add(prefs::kPluginsDisabledPlugins,
495                 base::Bind(&PluginPrefs::UpdatePatternsAndNotify,
496                            base::Unretained(this),
497                            &policy_disabled_plugin_patterns_));
498  registrar_.Add(prefs::kPluginsDisabledPluginsExceptions,
499                 base::Bind(&PluginPrefs::UpdatePatternsAndNotify,
500                            base::Unretained(this),
501                            &policy_disabled_plugin_exception_patterns_));
502  registrar_.Add(prefs::kPluginsEnabledPlugins,
503                 base::Bind(&PluginPrefs::UpdatePatternsAndNotify,
504                            base::Unretained(this),
505                            &policy_enabled_plugin_patterns_));
506
507  NotifyPluginStatusChanged();
508}
509
510void PluginPrefs::ShutdownOnUIThread() {
511  prefs_ = NULL;
512  registrar_.RemoveAll();
513}
514
515PluginPrefs::PluginPrefs() : profile_(NULL),
516                             prefs_(NULL) {
517}
518
519PluginPrefs::~PluginPrefs() {
520}
521
522void PluginPrefs::SetPolicyEnforcedPluginPatterns(
523    const std::set<base::string16>& disabled_patterns,
524    const std::set<base::string16>& disabled_exception_patterns,
525    const std::set<base::string16>& enabled_patterns) {
526  policy_disabled_plugin_patterns_ = disabled_patterns;
527  policy_disabled_plugin_exception_patterns_ = disabled_exception_patterns;
528  policy_enabled_plugin_patterns_ = enabled_patterns;
529}
530
531void PluginPrefs::OnUpdatePreferences(
532    const std::vector<content::WebPluginInfo>& plugins) {
533  if (!prefs_)
534    return;
535
536  PluginFinder* finder = PluginFinder::GetInstance();
537  ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList);
538  base::ListValue* plugins_list = update.Get();
539  plugins_list->Clear();
540
541  base::FilePath internal_dir;
542  if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &internal_dir))
543    prefs_->SetFilePath(prefs::kPluginsLastInternalDirectory, internal_dir);
544
545  base::AutoLock auto_lock(lock_);
546
547  // Add the plugin files.
548  std::set<base::string16> group_names;
549  for (size_t i = 0; i < plugins.size(); ++i) {
550    base::DictionaryValue* summary = new base::DictionaryValue();
551    summary->SetString("path", plugins[i].path.value());
552    summary->SetString("name", plugins[i].name);
553    summary->SetString("version", plugins[i].version);
554    bool enabled = true;
555    plugin_state_.Get(plugins[i].path, &enabled);
556    summary->SetBoolean("enabled", enabled);
557    plugins_list->Append(summary);
558
559    scoped_ptr<PluginMetadata> plugin_metadata(
560        finder->GetPluginMetadata(plugins[i]));
561    // Insert into a set of all group names.
562    group_names.insert(plugin_metadata->name());
563  }
564
565  // Add the plug-in groups.
566  for (std::set<base::string16>::const_iterator it = group_names.begin();
567      it != group_names.end(); ++it) {
568    base::DictionaryValue* summary = new base::DictionaryValue();
569    summary->SetString("name", *it);
570    bool enabled = true;
571    std::map<base::string16, bool>::iterator gstate_it =
572        plugin_group_state_.find(*it);
573    if (gstate_it != plugin_group_state_.end())
574      enabled = gstate_it->second;
575    summary->SetBoolean("enabled", enabled);
576    plugins_list->Append(summary);
577  }
578}
579
580void PluginPrefs::NotifyPluginStatusChanged() {
581  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
582  content::NotificationService::current()->Notify(
583      chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED,
584      content::Source<Profile>(profile_),
585      content::NotificationService::NoDetails());
586}
587