1// Copyright (c) 2011 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/plugin_updater.h"
6
7#include <string>
8
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop.h"
11#include "base/path_service.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "base/version.h"
15#include "chrome/browser/prefs/pref_service.h"
16#include "chrome/browser/prefs/scoped_user_pref_update.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/chrome_content_client.h"
19#include "chrome/common/chrome_paths.h"
20#include "chrome/common/pref_names.h"
21#include "content/browser/browser_thread.h"
22#include "content/common/notification_service.h"
23#include "webkit/plugins/npapi/plugin_list.h"
24#include "webkit/plugins/npapi/webplugininfo.h"
25
26// How long to wait to save the plugin enabled information, which might need to
27// go to disk.
28#define kPluginUpdateDelayMs (60 * 1000)
29
30PluginUpdater::PluginUpdater()
31    : notify_pending_(false) {
32}
33
34DictionaryValue* PluginUpdater::CreatePluginFileSummary(
35    const webkit::npapi::WebPluginInfo& plugin) {
36  DictionaryValue* data = new DictionaryValue();
37  data->SetString("path", plugin.path.value());
38  data->SetString("name", plugin.name);
39  data->SetString("version", plugin.version);
40  data->SetBoolean("enabled", webkit::npapi::IsPluginEnabled(plugin));
41  return data;
42}
43
44// static
45ListValue* PluginUpdater::GetPluginGroupsData() {
46  std::vector<webkit::npapi::PluginGroup> plugin_groups;
47  webkit::npapi::PluginList::Singleton()->GetPluginGroups(true, &plugin_groups);
48
49  // Construct DictionaryValues to return to the UI
50  ListValue* plugin_groups_data = new ListValue();
51  for (size_t i = 0; i < plugin_groups.size(); ++i) {
52    plugin_groups_data->Append(plugin_groups[i].GetDataForUI());
53  }
54  return plugin_groups_data;
55}
56
57void PluginUpdater::EnablePluginGroup(bool enable, const string16& group_name) {
58  webkit::npapi::PluginList::Singleton()->EnableGroup(enable, group_name);
59  NotifyPluginStatusChanged();
60}
61
62void PluginUpdater::EnablePlugin(bool enable,
63                                 const FilePath::StringType& path) {
64  FilePath file_path(path);
65  if (enable)
66    webkit::npapi::PluginList::Singleton()->EnablePlugin(file_path);
67  else
68    webkit::npapi::PluginList::Singleton()->DisablePlugin(file_path);
69
70  NotifyPluginStatusChanged();
71}
72
73void PluginUpdater::Observe(NotificationType type,
74                            const NotificationSource& source,
75                            const NotificationDetails& details) {
76  DCHECK_EQ(NotificationType::PREF_CHANGED, type.value);
77  const std::string* pref_name = Details<std::string>(details).ptr();
78  if (!pref_name) {
79    NOTREACHED();
80    return;
81  }
82  if (*pref_name == prefs::kPluginsDisabledPlugins ||
83      *pref_name == prefs::kPluginsDisabledPluginsExceptions ||
84      *pref_name == prefs::kPluginsEnabledPlugins) {
85    PrefService* pref_service = Source<PrefService>(source).ptr();
86    const ListValue* disabled_list =
87        pref_service->GetList(prefs::kPluginsDisabledPlugins);
88    const ListValue* exceptions_list =
89        pref_service->GetList(prefs::kPluginsDisabledPluginsExceptions);
90    const ListValue* enabled_list =
91        pref_service->GetList(prefs::kPluginsEnabledPlugins);
92    UpdatePluginsStateFromPolicy(disabled_list, exceptions_list, enabled_list);
93  }
94}
95
96void PluginUpdater::UpdatePluginsStateFromPolicy(
97    const ListValue* disabled_list,
98    const ListValue* exceptions_list,
99    const ListValue* enabled_list) {
100  std::set<string16> disabled_plugin_patterns;
101  std::set<string16> disabled_plugin_exception_patterns;
102  std::set<string16> enabled_plugin_patterns;
103
104  ListValueToStringSet(disabled_list, &disabled_plugin_patterns);
105  ListValueToStringSet(exceptions_list, &disabled_plugin_exception_patterns);
106  ListValueToStringSet(enabled_list, &enabled_plugin_patterns);
107
108  webkit::npapi::PluginGroup::SetPolicyEnforcedPluginPatterns(
109      disabled_plugin_patterns,
110      disabled_plugin_exception_patterns,
111      enabled_plugin_patterns);
112
113  NotifyPluginStatusChanged();
114}
115
116void PluginUpdater::ListValueToStringSet(const ListValue* src,
117                                         std::set<string16>* dest) {
118  DCHECK(src);
119  DCHECK(dest);
120  ListValue::const_iterator end(src->end());
121  for (ListValue::const_iterator current(src->begin());
122       current != end; ++current) {
123    string16 plugin_name;
124    if ((*current)->GetAsString(&plugin_name)) {
125      dest->insert(plugin_name);
126    }
127  }
128}
129
130void PluginUpdater::UpdatePluginGroupsStateFromPrefs(Profile* profile) {
131  bool update_internal_dir = false;
132  FilePath last_internal_dir =
133  profile->GetPrefs()->GetFilePath(prefs::kPluginsLastInternalDirectory);
134  FilePath cur_internal_dir;
135  if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &cur_internal_dir) &&
136      cur_internal_dir != last_internal_dir) {
137    update_internal_dir = true;
138    profile->GetPrefs()->SetFilePath(
139        prefs::kPluginsLastInternalDirectory, cur_internal_dir);
140  }
141
142  bool force_enable_internal_pdf = false;
143  bool internal_pdf_enabled = false;
144  string16 pdf_group_name =
145      ASCIIToUTF16(chrome::ChromeContentClient::kPDFPluginName);
146  FilePath pdf_path;
147  PathService::Get(chrome::FILE_PDF_PLUGIN, &pdf_path);
148  FilePath::StringType pdf_path_str = pdf_path.value();
149  if (!profile->GetPrefs()->GetBoolean(prefs::kPluginsEnabledInternalPDF)) {
150    // We switched to the internal pdf plugin being on by default, and so we
151    // need to force it to be enabled.  We only want to do it this once though,
152    // i.e. we don't want to enable it again if the user disables it afterwards.
153    profile->GetPrefs()->SetBoolean(prefs::kPluginsEnabledInternalPDF, true);
154    force_enable_internal_pdf = true;
155  }
156
157  {  // Scoped update of prefs::kPluginsPluginsList.
158    ListPrefUpdate update(profile->GetPrefs(), prefs::kPluginsPluginsList);
159    ListValue* saved_plugins_list = update.Get();
160    if (saved_plugins_list) {
161      for (ListValue::const_iterator it = saved_plugins_list->begin();
162           it != saved_plugins_list->end();
163           ++it) {
164        if (!(*it)->IsType(Value::TYPE_DICTIONARY)) {
165          LOG(WARNING) << "Invalid entry in " << prefs::kPluginsPluginsList;
166          continue;  // Oops, don't know what to do with this item.
167        }
168
169        DictionaryValue* plugin = static_cast<DictionaryValue*>(*it);
170        string16 group_name;
171        bool enabled = true;
172        plugin->GetBoolean("enabled", &enabled);
173
174        FilePath::StringType path;
175        // The plugin list constains all the plugin files in addition to the
176        // plugin groups.
177        if (plugin->GetString("path", &path)) {
178          // Files have a path attribute, groups don't.
179          FilePath plugin_path(path);
180          if (update_internal_dir &&
181              FilePath::CompareIgnoreCase(plugin_path.DirName().value(),
182                  last_internal_dir.value()) == 0) {
183            // If the internal plugin directory has changed and if the plugin
184            // looks internal, update its path in the prefs.
185            plugin_path = cur_internal_dir.Append(plugin_path.BaseName());
186            path = plugin_path.value();
187            plugin->SetString("path", path);
188          }
189
190          if (FilePath::CompareIgnoreCase(path, pdf_path_str) == 0) {
191            if (!enabled && force_enable_internal_pdf) {
192              enabled = true;
193              plugin->SetBoolean("enabled", true);
194            }
195
196            internal_pdf_enabled = enabled;
197          }
198
199          if (!enabled)
200            webkit::npapi::PluginList::Singleton()->DisablePlugin(plugin_path);
201        } else if (!enabled && plugin->GetString("name", &group_name)) {
202          // Don't disable this group if it's for the pdf plugin and we just
203          // forced it on.
204          if (force_enable_internal_pdf && pdf_group_name == group_name)
205            continue;
206
207          // Otherwise this is a list of groups.
208          EnablePluginGroup(false, group_name);
209        }
210      }
211    }
212  }  // Scoped update of prefs::kPluginsPluginsList.
213
214  // Build the set of policy enabled/disabled plugin patterns once and cache it.
215  // Don't do this in the constructor, there's no profile available there.
216  const ListValue* disabled_plugins =
217      profile->GetPrefs()->GetList(prefs::kPluginsDisabledPlugins);
218  const ListValue* disabled_exception_plugins =
219      profile->GetPrefs()->GetList(prefs::kPluginsDisabledPluginsExceptions);
220  const ListValue* enabled_plugins =
221      profile->GetPrefs()->GetList(prefs::kPluginsEnabledPlugins);
222  UpdatePluginsStateFromPolicy(disabled_plugins,
223                               disabled_exception_plugins,
224                               enabled_plugins);
225
226  if (force_enable_internal_pdf || internal_pdf_enabled) {
227    // See http://crbug.com/50105 for background.
228    EnablePluginGroup(false, ASCIIToUTF16(
229        webkit::npapi::PluginGroup::kAdobeReaderGroupName));
230  }
231
232  if (force_enable_internal_pdf) {
233    // We want to save this, but doing so requires loading the list of plugins,
234    // so do it after a minute as to not impact startup performance.  Note that
235    // plugins are loaded after 30s by the metrics service.
236    UpdatePreferences(profile, kPluginUpdateDelayMs);
237  }
238}
239
240void PluginUpdater::UpdatePreferences(Profile* profile, int delay_ms) {
241  BrowserThread::PostDelayedTask(
242    BrowserThread::FILE,
243    FROM_HERE,
244    NewRunnableFunction(
245        &PluginUpdater::GetPreferencesDataOnFileThread, profile), delay_ms);
246}
247
248void PluginUpdater::GetPreferencesDataOnFileThread(void* profile) {
249  std::vector<webkit::npapi::WebPluginInfo> plugins;
250  webkit::npapi::PluginList::Singleton()->GetPlugins(false, &plugins);
251
252  std::vector<webkit::npapi::PluginGroup> groups;
253  webkit::npapi::PluginList::Singleton()->GetPluginGroups(false, &groups);
254
255  BrowserThread::PostTask(
256    BrowserThread::UI,
257    FROM_HERE,
258    NewRunnableFunction(&PluginUpdater::OnUpdatePreferences,
259                        static_cast<Profile*>(profile),
260                        plugins, groups));
261}
262
263void PluginUpdater::OnUpdatePreferences(
264    Profile* profile,
265    const std::vector<webkit::npapi::WebPluginInfo>& plugins,
266    const std::vector<webkit::npapi::PluginGroup>& groups) {
267  ListPrefUpdate update(profile->GetPrefs(), prefs::kPluginsPluginsList);
268  ListValue* plugins_list = update.Get();
269  plugins_list->Clear();
270
271  FilePath internal_dir;
272  if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &internal_dir))
273    profile->GetPrefs()->SetFilePath(prefs::kPluginsLastInternalDirectory,
274                                     internal_dir);
275
276  // Add the plugin files.
277  for (size_t i = 0; i < plugins.size(); ++i) {
278    DictionaryValue* summary = CreatePluginFileSummary(plugins[i]);
279    // If the plugin is managed by policy, store the user preferred state
280    // instead.
281    if (plugins[i].enabled & webkit::npapi::WebPluginInfo::MANAGED_MASK) {
282      bool user_enabled =
283          (plugins[i].enabled & webkit::npapi::WebPluginInfo::USER_MASK) ==
284              webkit::npapi::WebPluginInfo::USER_ENABLED;
285      summary->SetBoolean("enabled", user_enabled);
286    }
287    bool enabled_val;
288    summary->GetBoolean("enabled", &enabled_val);
289    plugins_list->Append(summary);
290  }
291
292  // Add the groups as well.
293  for (size_t i = 0; i < groups.size(); ++i) {
294      DictionaryValue* summary = groups[i].GetSummary();
295      // If the plugin is disabled only by policy don't store this state in the
296      // user pref store.
297      if (!groups[i].Enabled() &&
298          webkit::npapi::PluginGroup::IsPluginNameDisabledByPolicy(
299              groups[i].GetGroupName()))
300        summary->SetBoolean("enabled", true);
301      plugins_list->Append(summary);
302  }
303}
304
305void PluginUpdater::NotifyPluginStatusChanged() {
306  if (notify_pending_)
307    return;
308  notify_pending_ = true;
309  MessageLoop::current()->PostTask(
310      FROM_HERE,
311      NewRunnableFunction(&PluginUpdater::OnNotifyPluginStatusChanged));
312}
313
314void PluginUpdater::OnNotifyPluginStatusChanged() {
315  GetInstance()->notify_pending_ = false;
316  NotificationService::current()->Notify(
317      NotificationType::PLUGIN_ENABLE_STATUS_CHANGED,
318      Source<PluginUpdater>(GetInstance()),
319      NotificationService::NoDetails());
320}
321
322/*static*/
323PluginUpdater* PluginUpdater::GetInstance() {
324  return Singleton<PluginUpdater>::get();
325}
326