// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/plugins_ui.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/prefs/pref_member.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/plugins/plugin_finder.h"
#include "chrome/browser/plugins/plugin_metadata.h"
#include "chrome/browser/plugins/plugin_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/ui/webui/chromeos/ui_account_tweaks.h"
#endif

using content::PluginService;
using content::WebContents;
using content::WebPluginInfo;
using content::WebUIMessageHandler;

namespace {

// Callback function to process result of EnablePlugin method.
void AssertPluginEnabled(bool did_enable) {
  DCHECK(did_enable);
}

content::WebUIDataSource* CreatePluginsUIHTMLSource() {
  content::WebUIDataSource* source =
      content::WebUIDataSource::Create(chrome::kChromeUIPluginsHost);
  source->SetUseJsonJSFormatV2();

  source->AddLocalizedString("pluginsTitle", IDS_PLUGINS_TITLE);
  source->AddLocalizedString("pluginsDetailsModeLink",
                             IDS_PLUGINS_DETAILS_MODE_LINK);
  source->AddLocalizedString("pluginsNoneInstalled",
                             IDS_PLUGINS_NONE_INSTALLED);
  source->AddLocalizedString("pluginDisabled", IDS_PLUGINS_DISABLED_PLUGIN);
  source->AddLocalizedString("pluginDisabledByPolicy",
                             IDS_PLUGINS_DISABLED_BY_POLICY_PLUGIN);
  source->AddLocalizedString("pluginEnabledByPolicy",
                             IDS_PLUGINS_ENABLED_BY_POLICY_PLUGIN);
  source->AddLocalizedString("pluginGroupManagedByPolicy",
                             IDS_PLUGINS_GROUP_MANAGED_BY_POLICY);
  source->AddLocalizedString("pluginDownload", IDS_PLUGINS_DOWNLOAD);
  source->AddLocalizedString("pluginName", IDS_PLUGINS_NAME);
  source->AddLocalizedString("pluginVersion", IDS_PLUGINS_VERSION);
  source->AddLocalizedString("pluginDescription", IDS_PLUGINS_DESCRIPTION);
  source->AddLocalizedString("pluginPath", IDS_PLUGINS_PATH);
  source->AddLocalizedString("pluginType", IDS_PLUGINS_TYPE);
  source->AddLocalizedString("pluginMimeTypes", IDS_PLUGINS_MIME_TYPES);
  source->AddLocalizedString("pluginMimeTypesMimeType",
                             IDS_PLUGINS_MIME_TYPES_MIME_TYPE);
  source->AddLocalizedString("pluginMimeTypesDescription",
                             IDS_PLUGINS_MIME_TYPES_DESCRIPTION);
  source->AddLocalizedString("pluginMimeTypesFileExtensions",
                             IDS_PLUGINS_MIME_TYPES_FILE_EXTENSIONS);
  source->AddLocalizedString("disable", IDS_PLUGINS_DISABLE);
  source->AddLocalizedString("enable", IDS_PLUGINS_ENABLE);
  source->AddLocalizedString("alwaysAllowed", IDS_PLUGINS_ALWAYS_ALLOWED);
  source->AddLocalizedString("noPlugins", IDS_PLUGINS_NO_PLUGINS);

  source->SetJsonPath("strings.js");
  source->AddResourcePath("plugins.js", IDR_PLUGINS_JS);
  source->SetDefaultResource(IDR_PLUGINS_HTML);
#if defined(OS_CHROMEOS)
  chromeos::AddAccountUITweaksLocalizedValues(source);
#endif
  return source;
}

base::string16 PluginTypeToString(int type) {
  // The type is stored as an |int|, but doing the switch on the right
  // enumeration type gives us better build-time error checking (if someone adds
  // a new type).
  switch (static_cast<WebPluginInfo::PluginType>(type)) {
    case WebPluginInfo::PLUGIN_TYPE_NPAPI:
      return l10n_util::GetStringUTF16(IDS_PLUGINS_NPAPI);
    case WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS:
      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_IN_PROCESS);
    case WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS:
      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_OUT_OF_PROCESS);
    case WebPluginInfo::PLUGIN_TYPE_PEPPER_UNSANDBOXED:
      return l10n_util::GetStringUTF16(IDS_PLUGINS_PPAPI_UNSANDBOXED);
  }
  NOTREACHED();
  return base::string16();
}

////////////////////////////////////////////////////////////////////////////////
//
// PluginsDOMHandler
//
////////////////////////////////////////////////////////////////////////////////

// The handler for Javascript messages for the chrome://plugins/ page.
// TODO(viettrungluu): Make plugin list updates notify, and then observe
// changes; maybe replumb plugin list through plugin service?
// <http://crbug.com/39101>
class PluginsDOMHandler : public WebUIMessageHandler,
                          public content::NotificationObserver {
 public:
  explicit PluginsDOMHandler();
  virtual ~PluginsDOMHandler() {}

  // WebUIMessageHandler implementation.
  virtual void RegisterMessages() OVERRIDE;

  // Callback for the "requestPluginsData" message.
  void HandleRequestPluginsData(const ListValue* args);

  // Callback for the "enablePlugin" message.
  void HandleEnablePluginMessage(const ListValue* args);

  // Callback for the "saveShowDetailsToPrefs" message.
  void HandleSaveShowDetailsToPrefs(const ListValue* args);

  // Calback for the "getShowDetails" message.
  void HandleGetShowDetails(const ListValue* args);

  // Callback for the "setPluginAlwaysAllowed" message.
  void HandleSetPluginAlwaysAllowed(const ListValue* args);

  // content::NotificationObserver method overrides
  virtual void Observe(int type,
                       const content::NotificationSource& source,
                       const content::NotificationDetails& details) OVERRIDE;

 private:
  void LoadPlugins();

  // Called on the UI thread when the plugin information is ready.
  void PluginsLoaded(const std::vector<WebPluginInfo>& plugins);

  content::NotificationRegistrar registrar_;

  // Holds grouped plug-ins. The key is the group identifier and
  // the value is the list of plug-ins belonging to the group.
  typedef base::hash_map<std::string, std::vector<const WebPluginInfo*> >
      PluginGroups;

  // This pref guards the value whether about:plugins is in the details mode or
  // not.
  BooleanPrefMember show_details_;

  base::WeakPtrFactory<PluginsDOMHandler> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(PluginsDOMHandler);
};

PluginsDOMHandler::PluginsDOMHandler()
    : weak_ptr_factory_(this) {
}

void PluginsDOMHandler::RegisterMessages() {
  Profile* profile = Profile::FromWebUI(web_ui());

  PrefService* prefs = profile->GetPrefs();
  show_details_.Init(prefs::kPluginsShowDetails, prefs);

  registrar_.Add(this,
                 chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED,
                 content::Source<Profile>(profile));

  web_ui()->RegisterMessageCallback("requestPluginsData",
      base::Bind(&PluginsDOMHandler::HandleRequestPluginsData,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("enablePlugin",
      base::Bind(&PluginsDOMHandler::HandleEnablePluginMessage,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("setPluginAlwaysAllowed",
      base::Bind(&PluginsDOMHandler::HandleSetPluginAlwaysAllowed,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("saveShowDetailsToPrefs",
      base::Bind(&PluginsDOMHandler::HandleSaveShowDetailsToPrefs,
                 base::Unretained(this)));
  web_ui()->RegisterMessageCallback("getShowDetails",
      base::Bind(&PluginsDOMHandler::HandleGetShowDetails,
                 base::Unretained(this)));
}

void PluginsDOMHandler::HandleRequestPluginsData(const ListValue* args) {
  LoadPlugins();
}

void PluginsDOMHandler::HandleEnablePluginMessage(const ListValue* args) {
  Profile* profile = Profile::FromWebUI(web_ui());

  // Be robust in accepting badness since plug-ins display HTML (hence
  // JavaScript).
  if (args->GetSize() != 3) {
    NOTREACHED();
    return;
  }

  std::string enable_str;
  std::string is_group_str;
  if (!args->GetString(1, &enable_str) || !args->GetString(2, &is_group_str)) {
    NOTREACHED();
    return;
  }
  bool enable = enable_str == "true";

  PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile).get();
  if (is_group_str == "true") {
    base::string16 group_name;
    if (!args->GetString(0, &group_name)) {
      NOTREACHED();
      return;
    }

    plugin_prefs->EnablePluginGroup(enable, group_name);
    if (enable) {
      // See http://crbug.com/50105 for background.
      base::string16 adobereader = ASCIIToUTF16(
          PluginMetadata::kAdobeReaderGroupName);
      base::string16 internalpdf =
          ASCIIToUTF16(ChromeContentClient::kPDFPluginName);
      if (group_name == adobereader)
        plugin_prefs->EnablePluginGroup(false, internalpdf);
      else if (group_name == internalpdf)
        plugin_prefs->EnablePluginGroup(false, adobereader);
    }
  } else {
    base::FilePath::StringType file_path;
    if (!args->GetString(0, &file_path)) {
      NOTREACHED();
      return;
    }

    plugin_prefs->EnablePlugin(enable, base::FilePath(file_path),
                               base::Bind(&AssertPluginEnabled));
  }
}

void PluginsDOMHandler::HandleSaveShowDetailsToPrefs(const ListValue* args) {
  std::string details_mode;
  if (!args->GetString(0, &details_mode)) {
    NOTREACHED();
    return;
  }
  show_details_.SetValue(details_mode == "true");
}

void PluginsDOMHandler::HandleGetShowDetails(const ListValue* args) {
  base::FundamentalValue show_details(show_details_.GetValue());
  web_ui()->CallJavascriptFunction("loadShowDetailsFromPrefs", show_details);
}

void PluginsDOMHandler::HandleSetPluginAlwaysAllowed(const ListValue* args) {
  // Be robust in the input parameters, but crash in a Debug build.
  if (args->GetSize() != 2) {
    NOTREACHED();
    return;
  }

  std::string plugin;
  bool allowed = false;
  if (!args->GetString(0, &plugin) || !args->GetBoolean(1, &allowed)) {
    NOTREACHED();
    return;
  }
  Profile* profile = Profile::FromWebUI(web_ui());
  profile->GetHostContentSettingsMap()->SetContentSetting(
      ContentSettingsPattern::Wildcard(),
      ContentSettingsPattern::Wildcard(),
      CONTENT_SETTINGS_TYPE_PLUGINS,
      plugin,
      allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_DEFAULT);

  // Keep track of the whitelist separately, so that we can distinguish plug-ins
  // whitelisted by the user from automatically whitelisted ones.
  DictionaryPrefUpdate update(profile->GetPrefs(),
                              prefs::kContentSettingsPluginWhitelist);
  update->SetBoolean(plugin, allowed);
}

void PluginsDOMHandler::Observe(int type,
                                const content::NotificationSource& source,
                                const content::NotificationDetails& details) {
  DCHECK_EQ(chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED, type);
  LoadPlugins();
}

void PluginsDOMHandler::LoadPlugins() {
  if (weak_ptr_factory_.HasWeakPtrs())
    return;

  PluginService::GetInstance()->GetPlugins(
      base::Bind(&PluginsDOMHandler::PluginsLoaded,
                 weak_ptr_factory_.GetWeakPtr()));
}

void PluginsDOMHandler::PluginsLoaded(
    const std::vector<WebPluginInfo>& plugins) {
  Profile* profile = Profile::FromWebUI(web_ui());
  PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile).get();

  ContentSettingsPattern wildcard = ContentSettingsPattern::Wildcard();

  PluginFinder* plugin_finder = PluginFinder::GetInstance();
  // Group plug-ins by identifier. This is done to be able to display
  // the plug-ins in UI in a grouped fashion.
  PluginGroups groups;
  for (size_t i = 0; i < plugins.size(); ++i) {
    scoped_ptr<PluginMetadata> plugin(
        plugin_finder->GetPluginMetadata(plugins[i]));
    groups[plugin->identifier()].push_back(&plugins[i]);
  }

  // Construct DictionaryValues to return to UI.
  ListValue* plugin_groups_data = new ListValue();
  for (PluginGroups::const_iterator it = groups.begin();
      it != groups.end(); ++it) {
    const std::vector<const WebPluginInfo*>& group_plugins = it->second;
    ListValue* plugin_files = new ListValue();
    scoped_ptr<PluginMetadata> plugin_metadata(
        plugin_finder->GetPluginMetadata(*group_plugins[0]));
    base::string16 group_name = plugin_metadata->name();
    std::string group_identifier = plugin_metadata->identifier();
    bool group_enabled = false;
    bool all_plugins_enabled_by_policy = true;
    bool all_plugins_disabled_by_policy = true;
    bool all_plugins_managed_by_policy = true;
    const WebPluginInfo* active_plugin = NULL;
    for (size_t j = 0; j < group_plugins.size(); ++j) {
      const WebPluginInfo& group_plugin = *group_plugins[j];

      DictionaryValue* plugin_file = new DictionaryValue();
      plugin_file->SetString("name", group_plugin.name);
      plugin_file->SetString("description", group_plugin.desc);
      plugin_file->SetString("path", group_plugin.path.value());
      plugin_file->SetString("version", group_plugin.version);
      plugin_file->SetString("type", PluginTypeToString(group_plugin.type));

      ListValue* mime_types = new ListValue();
      const std::vector<content::WebPluginMimeType>& plugin_mime_types =
          group_plugin.mime_types;
      for (size_t k = 0; k < plugin_mime_types.size(); ++k) {
        DictionaryValue* mime_type = new DictionaryValue();
        mime_type->SetString("mimeType", plugin_mime_types[k].mime_type);
        mime_type->SetString("description", plugin_mime_types[k].description);

        ListValue* file_extensions = new ListValue();
        const std::vector<std::string>& mime_file_extensions =
            plugin_mime_types[k].file_extensions;
        for (size_t l = 0; l < mime_file_extensions.size(); ++l)
          file_extensions->Append(new StringValue(mime_file_extensions[l]));
        mime_type->Set("fileExtensions", file_extensions);

        mime_types->Append(mime_type);
      }
      plugin_file->Set("mimeTypes", mime_types);

      bool plugin_enabled = plugin_prefs->IsPluginEnabled(group_plugin);

      if (!active_plugin || (plugin_enabled && !group_enabled))
        active_plugin = &group_plugin;
      group_enabled = plugin_enabled || group_enabled;

      std::string enabled_mode;
      PluginPrefs::PolicyStatus plugin_status =
          plugin_prefs->PolicyStatusForPlugin(group_plugin.name);
      PluginPrefs::PolicyStatus group_status =
          plugin_prefs->PolicyStatusForPlugin(group_name);
      if (plugin_status == PluginPrefs::POLICY_ENABLED ||
          group_status == PluginPrefs::POLICY_ENABLED) {
        enabled_mode = "enabledByPolicy";
        all_plugins_disabled_by_policy = false;
      } else {
        all_plugins_enabled_by_policy = false;
        if (plugin_status == PluginPrefs::POLICY_DISABLED ||
            group_status == PluginPrefs::POLICY_DISABLED) {
          enabled_mode = "disabledByPolicy";
        } else {
          all_plugins_disabled_by_policy = false;
          all_plugins_managed_by_policy = false;
          if (plugin_enabled) {
            enabled_mode = "enabledByUser";
          } else {
            enabled_mode = "disabledByUser";
          }
        }
      }
      plugin_file->SetString("enabledMode", enabled_mode);

      plugin_files->Append(plugin_file);
    }
    DictionaryValue* group_data = new DictionaryValue();

    group_data->Set("plugin_files", plugin_files);
    group_data->SetString("name", group_name);
    group_data->SetString("id", group_identifier);
    group_data->SetString("description", active_plugin->desc);
    group_data->SetString("version", active_plugin->version);

#if defined(ENABLE_PLUGIN_INSTALLATION)
    bool out_of_date = plugin_metadata->GetSecurityStatus(*active_plugin) ==
        PluginMetadata::SECURITY_STATUS_OUT_OF_DATE;
    group_data->SetBoolean("critical", out_of_date);
    group_data->SetString("update_url", plugin_metadata->plugin_url().spec());
#endif

    std::string enabled_mode;
    if (all_plugins_enabled_by_policy) {
      enabled_mode = "enabledByPolicy";
    } else if (all_plugins_disabled_by_policy) {
      enabled_mode = "disabledByPolicy";
    } else if (all_plugins_managed_by_policy) {
      enabled_mode = "managedByPolicy";
    } else if (group_enabled) {
      enabled_mode = "enabledByUser";
    } else {
      enabled_mode = "disabledByUser";
    }
    group_data->SetString("enabledMode", enabled_mode);

    bool always_allowed = false;
    if (group_enabled) {
      const DictionaryValue* whitelist = profile->GetPrefs()->GetDictionary(
          prefs::kContentSettingsPluginWhitelist);
      whitelist->GetBoolean(group_identifier, &always_allowed);
    }
    group_data->SetBoolean("alwaysAllowed", always_allowed);

    plugin_groups_data->Append(group_data);
  }
  DictionaryValue results;
  results.Set("plugins", plugin_groups_data);
  web_ui()->CallJavascriptFunction("returnPluginsData", results);
}

}  // namespace

///////////////////////////////////////////////////////////////////////////////
//
// PluginsUI
//
///////////////////////////////////////////////////////////////////////////////

PluginsUI::PluginsUI(content::WebUI* web_ui) : WebUIController(web_ui) {
  web_ui->AddMessageHandler(new PluginsDOMHandler());

  // Set up the chrome://plugins/ source.
  Profile* profile = Profile::FromWebUI(web_ui);
  content::WebUIDataSource::Add(profile, CreatePluginsUIHTMLSource());
}

// static
base::RefCountedMemory* PluginsUI::GetFaviconResourceBytes(
      ui::ScaleFactor scale_factor) {
  return ResourceBundle::GetSharedInstance().
      LoadDataResourceBytesForScale(IDR_PLUGINS_FAVICON, scale_factor);
}

// static
void PluginsUI::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(
      prefs::kPluginsShowDetails,
      false,
      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
  registry->RegisterDictionaryPref(
      prefs::kContentSettingsPluginWhitelist,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}