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/ui/webui/plugins_ui.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/files/file_path.h"
14#include "base/memory/ref_counted_memory.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/memory/singleton.h"
17#include "base/memory/weak_ptr.h"
18#include "base/message_loop/message_loop.h"
19#include "base/path_service.h"
20#include "base/prefs/pref_member.h"
21#include "base/prefs/pref_service.h"
22#include "base/prefs/scoped_user_pref_update.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/values.h"
25#include "chrome/browser/chrome_notification_types.h"
26#include "chrome/browser/content_settings/host_content_settings_map.h"
27#include "chrome/browser/plugins/plugin_finder.h"
28#include "chrome/browser/plugins/plugin_metadata.h"
29#include "chrome/browser/plugins/plugin_prefs.h"
30#include "chrome/browser/profiles/profile.h"
31#include "chrome/browser/ui/browser.h"
32#include "chrome/browser/ui/browser_window.h"
33#include "chrome/common/chrome_content_client.h"
34#include "chrome/common/chrome_paths.h"
35#include "chrome/common/pref_names.h"
36#include "chrome/common/url_constants.h"
37#include "chrome/grit/generated_resources.h"
38#include "components/pref_registry/pref_registry_syncable.h"
39#include "content/public/browser/notification_source.h"
40#include "content/public/browser/plugin_service.h"
41#include "content/public/browser/web_contents.h"
42#include "content/public/browser/web_ui.h"
43#include "content/public/browser/web_ui_data_source.h"
44#include "content/public/browser/web_ui_message_handler.h"
45#include "content/public/common/content_constants.h"
46#include "grit/browser_resources.h"
47#include "grit/theme_resources.h"
48#include "ui/base/l10n/l10n_util.h"
49#include "ui/base/resource/resource_bundle.h"
50
51#if defined(OS_CHROMEOS)
52#include "chrome/browser/ui/webui/chromeos/ui_account_tweaks.h"
53#endif
54
55using content::PluginService;
56using content::WebContents;
57using content::WebPluginInfo;
58using content::WebUIMessageHandler;
59
60namespace {
61
62// Callback function to process result of EnablePlugin method.
63void AssertPluginEnabled(bool did_enable) {
64  DCHECK(did_enable);
65}
66
67content::WebUIDataSource* CreatePluginsUIHTMLSource(Profile* profile) {
68  content::WebUIDataSource* source =
69      content::WebUIDataSource::Create(chrome::kChromeUIPluginsHost);
70  source->SetUseJsonJSFormatV2();
71
72  source->AddLocalizedString("pluginsTitle", IDS_PLUGINS_TITLE);
73  source->AddLocalizedString("pluginsDetailsModeLink",
74                             IDS_PLUGINS_DETAILS_MODE_LINK);
75  source->AddLocalizedString("pluginsNoneInstalled",
76                             IDS_PLUGINS_NONE_INSTALLED);
77  source->AddLocalizedString("pluginDisabled", IDS_PLUGINS_DISABLED_PLUGIN);
78  source->AddLocalizedString("pluginDisabledByPolicy",
79                             IDS_PLUGINS_DISABLED_BY_POLICY_PLUGIN);
80  source->AddLocalizedString("pluginEnabledByPolicy",
81                             IDS_PLUGINS_ENABLED_BY_POLICY_PLUGIN);
82  source->AddLocalizedString("pluginGroupManagedByPolicy",
83                             IDS_PLUGINS_GROUP_MANAGED_BY_POLICY);
84  source->AddLocalizedString("pluginDownload", IDS_PLUGINS_DOWNLOAD);
85  source->AddLocalizedString("pluginName", IDS_PLUGINS_NAME);
86  source->AddLocalizedString("pluginVersion", IDS_PLUGINS_VERSION);
87  source->AddLocalizedString("pluginDescription", IDS_PLUGINS_DESCRIPTION);
88  source->AddLocalizedString("pluginPath", IDS_PLUGINS_PATH);
89  source->AddLocalizedString("pluginType", IDS_PLUGINS_TYPE);
90  source->AddLocalizedString("pluginMimeTypes", IDS_PLUGINS_MIME_TYPES);
91  source->AddLocalizedString("pluginMimeTypesMimeType",
92                             IDS_PLUGINS_MIME_TYPES_MIME_TYPE);
93  source->AddLocalizedString("pluginMimeTypesDescription",
94                             IDS_PLUGINS_MIME_TYPES_DESCRIPTION);
95  source->AddLocalizedString("pluginMimeTypesFileExtensions",
96                             IDS_PLUGINS_MIME_TYPES_FILE_EXTENSIONS);
97  source->AddLocalizedString("disable", IDS_PLUGINS_DISABLE);
98  source->AddLocalizedString("enable", IDS_PLUGINS_ENABLE);
99  source->AddLocalizedString("alwaysAllowed", IDS_PLUGINS_ALWAYS_ALLOWED);
100  source->AddLocalizedString("noPlugins", IDS_PLUGINS_NO_PLUGINS);
101
102  source->SetJsonPath("strings.js");
103  source->AddResourcePath("plugins.js", IDR_PLUGINS_JS);
104  source->SetDefaultResource(IDR_PLUGINS_HTML);
105#if defined(OS_CHROMEOS)
106  chromeos::AddAccountUITweaksLocalizedValues(source, profile);
107#endif
108  return source;
109}
110
111base::string16 PluginTypeToString(int type) {
112  // The type is stored as an |int|, but doing the switch on the right
113  // enumeration type gives us better build-time error checking (if someone adds
114  // a new type).
115  switch (static_cast<WebPluginInfo::PluginType>(type)) {
116    case WebPluginInfo::PLUGIN_TYPE_NPAPI:
117      return l10n_util::GetStringUTF16(IDS_PLUGINS_NPAPI);
118    case WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS:
119      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_IN_PROCESS);
120    case WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS:
121      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_OUT_OF_PROCESS);
122    case WebPluginInfo::PLUGIN_TYPE_PEPPER_UNSANDBOXED:
123      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_UNSANDBOXED);
124    case WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN:
125      return l10n_util::GetStringUTF16(IDS_PLUGINS_BROWSER_PLUGIN);
126  }
127  NOTREACHED();
128  return base::string16();
129}
130
131////////////////////////////////////////////////////////////////////////////////
132//
133// PluginsDOMHandler
134//
135////////////////////////////////////////////////////////////////////////////////
136
137// The handler for Javascript messages for the chrome://plugins/ page.
138// TODO(viettrungluu): Make plugin list updates notify, and then observe
139// changes; maybe replumb plugin list through plugin service?
140// <http://crbug.com/39101>
141class PluginsDOMHandler : public WebUIMessageHandler,
142                          public content::NotificationObserver {
143 public:
144  PluginsDOMHandler();
145  virtual ~PluginsDOMHandler() {}
146
147  // WebUIMessageHandler implementation.
148  virtual void RegisterMessages() OVERRIDE;
149
150  // Callback for the "requestPluginsData" message.
151  void HandleRequestPluginsData(const base::ListValue* args);
152
153  // Callback for the "enablePlugin" message.
154  void HandleEnablePluginMessage(const base::ListValue* args);
155
156  // Callback for the "saveShowDetailsToPrefs" message.
157  void HandleSaveShowDetailsToPrefs(const base::ListValue* args);
158
159  // Calback for the "getShowDetails" message.
160  void HandleGetShowDetails(const base::ListValue* args);
161
162  // Callback for the "setPluginAlwaysAllowed" message.
163  void HandleSetPluginAlwaysAllowed(const base::ListValue* args);
164
165  // content::NotificationObserver method overrides
166  virtual void Observe(int type,
167                       const content::NotificationSource& source,
168                       const content::NotificationDetails& details) OVERRIDE;
169
170 private:
171  void LoadPlugins();
172
173  // Called on the UI thread when the plugin information is ready.
174  void PluginsLoaded(const std::vector<WebPluginInfo>& plugins);
175
176  content::NotificationRegistrar registrar_;
177
178  // Holds grouped plug-ins. The key is the group identifier and
179  // the value is the list of plug-ins belonging to the group.
180  typedef base::hash_map<std::string, std::vector<const WebPluginInfo*> >
181      PluginGroups;
182
183  // This pref guards the value whether about:plugins is in the details mode or
184  // not.
185  BooleanPrefMember show_details_;
186
187  base::WeakPtrFactory<PluginsDOMHandler> weak_ptr_factory_;
188
189  DISALLOW_COPY_AND_ASSIGN(PluginsDOMHandler);
190};
191
192PluginsDOMHandler::PluginsDOMHandler()
193    : weak_ptr_factory_(this) {
194}
195
196void PluginsDOMHandler::RegisterMessages() {
197  Profile* profile = Profile::FromWebUI(web_ui());
198
199  PrefService* prefs = profile->GetPrefs();
200  show_details_.Init(prefs::kPluginsShowDetails, prefs);
201
202  registrar_.Add(this,
203                 chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED,
204                 content::Source<Profile>(profile));
205
206  web_ui()->RegisterMessageCallback("requestPluginsData",
207      base::Bind(&PluginsDOMHandler::HandleRequestPluginsData,
208                 base::Unretained(this)));
209  web_ui()->RegisterMessageCallback("enablePlugin",
210      base::Bind(&PluginsDOMHandler::HandleEnablePluginMessage,
211                 base::Unretained(this)));
212  web_ui()->RegisterMessageCallback("setPluginAlwaysAllowed",
213      base::Bind(&PluginsDOMHandler::HandleSetPluginAlwaysAllowed,
214                 base::Unretained(this)));
215  web_ui()->RegisterMessageCallback("saveShowDetailsToPrefs",
216      base::Bind(&PluginsDOMHandler::HandleSaveShowDetailsToPrefs,
217                 base::Unretained(this)));
218  web_ui()->RegisterMessageCallback("getShowDetails",
219      base::Bind(&PluginsDOMHandler::HandleGetShowDetails,
220                 base::Unretained(this)));
221}
222
223void PluginsDOMHandler::HandleRequestPluginsData(const base::ListValue* args) {
224  LoadPlugins();
225}
226
227void PluginsDOMHandler::HandleEnablePluginMessage(const base::ListValue* args) {
228  Profile* profile = Profile::FromWebUI(web_ui());
229
230  // Be robust in accepting badness since plug-ins display HTML (hence
231  // JavaScript).
232  if (args->GetSize() != 3) {
233    NOTREACHED();
234    return;
235  }
236
237  std::string enable_str;
238  std::string is_group_str;
239  if (!args->GetString(1, &enable_str) || !args->GetString(2, &is_group_str)) {
240    NOTREACHED();
241    return;
242  }
243  bool enable = enable_str == "true";
244
245  PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile).get();
246  if (is_group_str == "true") {
247    base::string16 group_name;
248    if (!args->GetString(0, &group_name)) {
249      NOTREACHED();
250      return;
251    }
252
253    plugin_prefs->EnablePluginGroup(enable, group_name);
254    if (enable) {
255      // See http://crbug.com/50105 for background.
256      base::string16 adobereader = base::ASCIIToUTF16(
257          PluginMetadata::kAdobeReaderGroupName);
258      base::string16 internalpdf =
259          base::ASCIIToUTF16(ChromeContentClient::kPDFPluginName);
260      if (group_name == adobereader)
261        plugin_prefs->EnablePluginGroup(false, internalpdf);
262      else if (group_name == internalpdf)
263        plugin_prefs->EnablePluginGroup(false, adobereader);
264    }
265  } else {
266    base::FilePath::StringType file_path;
267    if (!args->GetString(0, &file_path)) {
268      NOTREACHED();
269      return;
270    }
271
272    plugin_prefs->EnablePlugin(enable, base::FilePath(file_path),
273                               base::Bind(&AssertPluginEnabled));
274  }
275}
276
277void PluginsDOMHandler::HandleSaveShowDetailsToPrefs(
278    const base::ListValue* args) {
279  std::string details_mode;
280  if (!args->GetString(0, &details_mode)) {
281    NOTREACHED();
282    return;
283  }
284  show_details_.SetValue(details_mode == "true");
285}
286
287void PluginsDOMHandler::HandleGetShowDetails(const base::ListValue* args) {
288  base::FundamentalValue show_details(show_details_.GetValue());
289  web_ui()->CallJavascriptFunction("loadShowDetailsFromPrefs", show_details);
290}
291
292void PluginsDOMHandler::HandleSetPluginAlwaysAllowed(
293    const base::ListValue* args) {
294  // Be robust in the input parameters, but crash in a Debug build.
295  if (args->GetSize() != 2) {
296    NOTREACHED();
297    return;
298  }
299
300  std::string plugin;
301  bool allowed = false;
302  if (!args->GetString(0, &plugin) || !args->GetBoolean(1, &allowed)) {
303    NOTREACHED();
304    return;
305  }
306  Profile* profile = Profile::FromWebUI(web_ui());
307  profile->GetHostContentSettingsMap()->SetContentSetting(
308      ContentSettingsPattern::Wildcard(),
309      ContentSettingsPattern::Wildcard(),
310      CONTENT_SETTINGS_TYPE_PLUGINS,
311      plugin,
312      allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_DEFAULT);
313
314  // Keep track of the whitelist separately, so that we can distinguish plug-ins
315  // whitelisted by the user from automatically whitelisted ones.
316  DictionaryPrefUpdate update(profile->GetPrefs(),
317                              prefs::kContentSettingsPluginWhitelist);
318  update->SetBoolean(plugin, allowed);
319}
320
321void PluginsDOMHandler::Observe(int type,
322                                const content::NotificationSource& source,
323                                const content::NotificationDetails& details) {
324  DCHECK_EQ(chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED, type);
325  LoadPlugins();
326}
327
328void PluginsDOMHandler::LoadPlugins() {
329  if (weak_ptr_factory_.HasWeakPtrs())
330    return;
331
332  PluginService::GetInstance()->GetPlugins(
333      base::Bind(&PluginsDOMHandler::PluginsLoaded,
334                 weak_ptr_factory_.GetWeakPtr()));
335}
336
337void PluginsDOMHandler::PluginsLoaded(
338    const std::vector<WebPluginInfo>& plugins) {
339  Profile* profile = Profile::FromWebUI(web_ui());
340  PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile).get();
341
342  ContentSettingsPattern wildcard = ContentSettingsPattern::Wildcard();
343
344  PluginFinder* plugin_finder = PluginFinder::GetInstance();
345  // Group plug-ins by identifier. This is done to be able to display
346  // the plug-ins in UI in a grouped fashion.
347  PluginGroups groups;
348  for (size_t i = 0; i < plugins.size(); ++i) {
349    scoped_ptr<PluginMetadata> plugin(
350        plugin_finder->GetPluginMetadata(plugins[i]));
351    groups[plugin->identifier()].push_back(&plugins[i]);
352  }
353
354  // Construct DictionaryValues to return to UI.
355  base::ListValue* plugin_groups_data = new base::ListValue();
356  for (PluginGroups::const_iterator it = groups.begin();
357      it != groups.end(); ++it) {
358    const std::vector<const WebPluginInfo*>& group_plugins = it->second;
359    base::ListValue* plugin_files = new base::ListValue();
360    scoped_ptr<PluginMetadata> plugin_metadata(
361        plugin_finder->GetPluginMetadata(*group_plugins[0]));
362    base::string16 group_name = plugin_metadata->name();
363    std::string group_identifier = plugin_metadata->identifier();
364    bool group_enabled = false;
365    bool all_plugins_enabled_by_policy = true;
366    bool all_plugins_disabled_by_policy = true;
367    bool all_plugins_managed_by_policy = true;
368    const WebPluginInfo* active_plugin = NULL;
369    for (size_t j = 0; j < group_plugins.size(); ++j) {
370      const WebPluginInfo& group_plugin = *group_plugins[j];
371
372      base::DictionaryValue* plugin_file = new base::DictionaryValue();
373      plugin_file->SetString("name", group_plugin.name);
374
375      // If this plugin is Pepper Flash, and the plugin path is the same as the
376      // path for the Pepper Flash Debugger plugin, then mark this plugin
377      // description as the debugger plugin to help the user disambiguate the
378      // two plugins.
379      base::string16 desc = group_plugin.desc;
380      if (group_plugin.is_pepper_plugin() &&
381          group_plugin.name == base::ASCIIToUTF16(content::kFlashPluginName)) {
382        base::FilePath debug_path;
383        PathService::Get(chrome::DIR_PEPPER_FLASH_DEBUGGER_PLUGIN, &debug_path);
384        if (group_plugin.path.DirName() == debug_path)
385          desc += base::ASCIIToUTF16(" Debug");
386      }
387      plugin_file->SetString("description", desc);
388
389      plugin_file->SetString("path", group_plugin.path.value());
390      plugin_file->SetString("version", group_plugin.version);
391      plugin_file->SetString("type", PluginTypeToString(group_plugin.type));
392
393      base::ListValue* mime_types = new base::ListValue();
394      const std::vector<content::WebPluginMimeType>& plugin_mime_types =
395          group_plugin.mime_types;
396      for (size_t k = 0; k < plugin_mime_types.size(); ++k) {
397        base::DictionaryValue* mime_type = new base::DictionaryValue();
398        mime_type->SetString("mimeType", plugin_mime_types[k].mime_type);
399        mime_type->SetString("description", plugin_mime_types[k].description);
400
401        base::ListValue* file_extensions = new base::ListValue();
402        const std::vector<std::string>& mime_file_extensions =
403            plugin_mime_types[k].file_extensions;
404        for (size_t l = 0; l < mime_file_extensions.size(); ++l) {
405          file_extensions->Append(
406              new base::StringValue(mime_file_extensions[l]));
407        }
408        mime_type->Set("fileExtensions", file_extensions);
409
410        mime_types->Append(mime_type);
411      }
412      plugin_file->Set("mimeTypes", mime_types);
413
414      bool plugin_enabled = plugin_prefs->IsPluginEnabled(group_plugin);
415
416      if (!active_plugin || (plugin_enabled && !group_enabled))
417        active_plugin = &group_plugin;
418      group_enabled = plugin_enabled || group_enabled;
419
420      std::string enabled_mode;
421      PluginPrefs::PolicyStatus plugin_status =
422          plugin_prefs->PolicyStatusForPlugin(group_plugin.name);
423      PluginPrefs::PolicyStatus group_status =
424          plugin_prefs->PolicyStatusForPlugin(group_name);
425      if (plugin_status == PluginPrefs::POLICY_ENABLED ||
426          group_status == PluginPrefs::POLICY_ENABLED) {
427        enabled_mode = "enabledByPolicy";
428        all_plugins_disabled_by_policy = false;
429      } else {
430        all_plugins_enabled_by_policy = false;
431        if (plugin_status == PluginPrefs::POLICY_DISABLED ||
432            group_status == PluginPrefs::POLICY_DISABLED) {
433          enabled_mode = "disabledByPolicy";
434        } else {
435          all_plugins_disabled_by_policy = false;
436          all_plugins_managed_by_policy = false;
437          if (plugin_enabled) {
438            enabled_mode = "enabledByUser";
439          } else {
440            enabled_mode = "disabledByUser";
441          }
442        }
443      }
444      plugin_file->SetString("enabledMode", enabled_mode);
445
446      plugin_files->Append(plugin_file);
447    }
448    base::DictionaryValue* group_data = new base::DictionaryValue();
449
450    group_data->Set("plugin_files", plugin_files);
451    group_data->SetString("name", group_name);
452    group_data->SetString("id", group_identifier);
453    group_data->SetString("description", active_plugin->desc);
454    group_data->SetString("version", active_plugin->version);
455
456#if defined(ENABLE_PLUGIN_INSTALLATION)
457    bool out_of_date = plugin_metadata->GetSecurityStatus(*active_plugin) ==
458        PluginMetadata::SECURITY_STATUS_OUT_OF_DATE;
459    group_data->SetBoolean("critical", out_of_date);
460    group_data->SetString("update_url", plugin_metadata->plugin_url().spec());
461#endif
462
463    std::string enabled_mode;
464    if (all_plugins_enabled_by_policy) {
465      enabled_mode = "enabledByPolicy";
466    } else if (all_plugins_disabled_by_policy) {
467      enabled_mode = "disabledByPolicy";
468    } else if (all_plugins_managed_by_policy) {
469      enabled_mode = "managedByPolicy";
470    } else if (group_enabled) {
471      enabled_mode = "enabledByUser";
472    } else {
473      enabled_mode = "disabledByUser";
474    }
475    group_data->SetString("enabledMode", enabled_mode);
476
477    bool always_allowed = false;
478    if (group_enabled) {
479      const base::DictionaryValue* whitelist =
480          profile->GetPrefs()->GetDictionary(
481              prefs::kContentSettingsPluginWhitelist);
482      whitelist->GetBoolean(group_identifier, &always_allowed);
483    }
484    group_data->SetBoolean("alwaysAllowed", always_allowed);
485
486    plugin_groups_data->Append(group_data);
487  }
488  base::DictionaryValue results;
489  results.Set("plugins", plugin_groups_data);
490  web_ui()->CallJavascriptFunction("returnPluginsData", results);
491}
492
493}  // namespace
494
495///////////////////////////////////////////////////////////////////////////////
496//
497// PluginsUI
498//
499///////////////////////////////////////////////////////////////////////////////
500
501PluginsUI::PluginsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
502  web_ui->AddMessageHandler(new PluginsDOMHandler());
503
504  // Set up the chrome://plugins/ source.
505  Profile* profile = Profile::FromWebUI(web_ui);
506  content::WebUIDataSource::Add(profile, CreatePluginsUIHTMLSource(profile));
507}
508
509// static
510base::RefCountedMemory* PluginsUI::GetFaviconResourceBytes(
511      ui::ScaleFactor scale_factor) {
512  return ResourceBundle::GetSharedInstance().
513      LoadDataResourceBytesForScale(IDR_PLUGINS_FAVICON, scale_factor);
514}
515
516// static
517void PluginsUI::RegisterProfilePrefs(
518    user_prefs::PrefRegistrySyncable* registry) {
519  registry->RegisterBooleanPref(
520      prefs::kPluginsShowDetails,
521      false,
522      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
523  registry->RegisterDictionaryPref(
524      prefs::kContentSettingsPluginWhitelist,
525      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
526}
527