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