blob: 974a42361aee4da66698527728f96aeb29cd0e54 [file] [log] [blame]
// Copyright (c) 2011 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 "webkit/plugins/npapi/plugin_list.h"
#include <algorithm>
#include "base/cpu.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/sha1.h"
#include "base/stringprintf.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "build/build_config.h"
namespace webkit {
namespace npapi {
namespace {
// We build up a list of files and mtimes so we can sort them.
typedef std::pair<FilePath, base::Time> FileAndTime;
typedef std::vector<FileAndTime> FileTimeList;
enum PluginQuirk {
// No quirks - plugin is outright banned.
PLUGIN_QUIRK_NONE = 0,
// Plugin is using SSE2 instructions without checking for SSE2 instruction
// support. Ban the plugin if the system has no SSE2 support.
PLUGIN_QUIRK_MISSING_SSE2_CHECK = 1 << 0,
};
// Comparator used to sort by descending mtime then ascending filename.
bool CompareTime(const FileAndTime& a, const FileAndTime& b) {
if (a.second == b.second) {
// Fall back on filename sorting, just to make the predicate valid.
return a.first < b.first;
}
// Sort by mtime, descending.
return a.second > b.second;
}
// Checks to see if the current environment meets any of the condtions set in
// |quirks|. Returns true if any of the conditions are met, or if |quirks| is
// PLUGIN_QUIRK_NONE.
bool CheckQuirks(PluginQuirk quirks) {
if (quirks == PLUGIN_QUIRK_NONE)
return true;
if ((quirks & PLUGIN_QUIRK_MISSING_SSE2_CHECK) != 0) {
base::CPU cpu;
if (!cpu.has_sse2())
return true;
}
return false;
}
// Return true if |path| matches a known (file size, sha1sum) pair.
// Also check against any PluginQuirks the bad plugin may have.
// The use of the file size is an optimization so we don't have to read in
// the entire file unless we have to.
bool IsBlacklistedBySha1sumAndQuirks(const FilePath& path) {
const struct BadEntry {
int64 size;
std::string sha1;
PluginQuirk quirks;
} bad_entries[] = {
// Flash 9 r31 - http://crbug.com/29237
{ 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb", PLUGIN_QUIRK_NONE },
// Flash 9 r48 - http://crbug.com/29237
{ 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b", PLUGIN_QUIRK_NONE },
// Flash 11.2.202.236, 32-bit - http://crbug.com/140086
{ 17406436, "1e07eac912faf9426c52a288c76c3b6238f90b6b",
PLUGIN_QUIRK_MISSING_SSE2_CHECK },
};
int64 size;
if (!file_util::GetFileSize(path, &size))
return false;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) {
if (bad_entries[i].size != size)
continue;
std::string file_content;
if (!file_util::ReadFileToString(path, &file_content))
continue;
std::string sha1 = base::SHA1HashString(file_content);
std::string sha1_readable;
for (size_t j = 0; j < sha1.size(); j++)
base::StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF);
if (bad_entries[i].sha1 == sha1_readable)
return CheckQuirks(bad_entries[i].quirks);
}
return false;
}
// Some plugins are shells around other plugins; we prefer to use the
// real plugin directly, if it's available. This function returns
// true if we should prefer other plugins over this one. We'll still
// use a "undesirable" plugin if no other option is available.
bool IsUndesirablePlugin(const WebPluginInfo& info) {
std::string filename = info.path.BaseName().value();
const char* kUndesiredPlugins[] = {
"npcxoffice", // Crossover
"npwrapper", // nspluginwrapper
};
for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) {
if (filename.find(kUndesiredPlugins[i]) != std::string::npos) {
return true;
}
}
return false;
}
// Return true if we shouldn't load a plugin at all.
// This is an ugly hack to blacklist Adobe Acrobat due to not supporting
// its Xt-based mainloop.
// http://code.google.com/p/chromium/issues/detail?id=38229
bool IsBlacklistedPlugin(const FilePath& path) {
const char* kBlackListedPlugins[] = {
"nppdf.so", // Adobe PDF
};
std::string filename = path.BaseName().value();
for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) {
if (filename.find(kBlackListedPlugins[i]) != std::string::npos) {
return true;
}
}
return IsBlacklistedBySha1sumAndQuirks(path);
}
} // namespace
void PluginList::PlatformInit() {
}
void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) {
// See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9
// for discussion.
// We first consult Chrome-specific dirs, then fall back on the logic
// Mozilla uses.
// Note: "extra" plugin dirs, including the Plugins subdirectory of
// your Chrome config, are examined before these. See the logic
// related to extra_plugin_dirs in plugin_list.cc.
// The Chrome binary dir + "plugins/".
FilePath dir;
PathService::Get(base::DIR_EXE, &dir);
plugin_dirs->push_back(dir.Append("plugins"));
// Chrome OS only loads plugins from /opt/google/chrome/plugins.
#if !defined(OS_CHROMEOS)
// Mozilla code to reference:
// http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST
// and tens of accompanying files (mxr is very helpful).
// This code carefully matches their behavior for compat reasons.
// 1) MOZ_PLUGIN_PATH env variable.
const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH");
if (moz_plugin_path) {
std::vector<std::string> paths;
base::SplitString(moz_plugin_path, ':', &paths);
for (size_t i = 0; i < paths.size(); ++i)
plugin_dirs->push_back(FilePath(paths[i]));
}
// 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins.
// This is a de-facto standard, so even though we're not Mozilla, let's
// look in there too.
FilePath home = file_util::GetHomeDir();
if (!home.empty())
plugin_dirs->push_back(home.Append(".mozilla/plugins"));
// 3) NS_SYSTEM_PLUGINS_DIR:
// This varies across different browsers and versions, so check 'em all.
plugin_dirs->push_back(FilePath("/usr/lib/browser-plugins"));
plugin_dirs->push_back(FilePath("/usr/lib/mozilla/plugins"));
plugin_dirs->push_back(FilePath("/usr/lib/firefox/plugins"));
plugin_dirs->push_back(FilePath("/usr/lib/xulrunner-addons/plugins"));
#if defined(ARCH_CPU_64_BITS)
// On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib.
// But a user reported on their Fedora system they are separate.
plugin_dirs->push_back(FilePath("/usr/lib64/browser-plugins"));
plugin_dirs->push_back(FilePath("/usr/lib64/mozilla/plugins"));
plugin_dirs->push_back(FilePath("/usr/lib64/firefox/plugins"));
plugin_dirs->push_back(FilePath("/usr/lib64/xulrunner-addons/plugins"));
#endif // defined(ARCH_CPU_64_BITS)
#endif // !defined(OS_CHROMEOS)
}
void PluginList::GetPluginsInDir(
const FilePath& dir_path, std::vector<FilePath>* plugins) {
// See ScanPluginsDirectory near
// http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052
// Construct and stat a list of all filenames under consideration, for
// later sorting by mtime.
FileTimeList files;
file_util::FileEnumerator enumerator(dir_path,
false, // not recursive
file_util::FileEnumerator::FILES);
for (FilePath path = enumerator.Next(); !path.value().empty();
path = enumerator.Next()) {
// Skip over Mozilla .xpt files.
if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt")))
continue;
// Java doesn't like being loaded through a symlink, since it uses
// its path to find dependent data files.
// file_util::AbsolutePath calls through to realpath(), which resolves
// symlinks.
FilePath orig_path = path;
file_util::AbsolutePath(&path);
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Resolved " << orig_path.value() << " -> " << path.value();
if (std::find(plugins->begin(), plugins->end(), path) != plugins->end()) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Skipping duplicate instance of " << path.value();
continue;
}
if (IsBlacklistedPlugin(path)) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Skipping blacklisted plugin " << path.value();
continue;
}
// Flash stops working if the containing directory involves 'netscape'.
// No joke. So use the other path if it's better.
static const char kFlashPlayerFilename[] = "libflashplayer.so";
static const char kNetscapeInPath[] = "/netscape/";
if (path.BaseName().value() == kFlashPlayerFilename &&
path.value().find(kNetscapeInPath) != std::string::npos) {
if (orig_path.value().find(kNetscapeInPath) == std::string::npos) {
// Go back to the old path.
path = orig_path;
} else {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Flash misbehaves when used from a directory containing "
<< kNetscapeInPath << ", so skipping " << orig_path.value();
continue;
}
}
// Get mtime.
base::PlatformFileInfo info;
if (!file_util::GetFileInfo(path, &info))
continue;
files.push_back(std::make_pair(path, info.last_modified));
}
// Sort the file list by time (and filename).
std::sort(files.begin(), files.end(), CompareTime);
// Load the files in order.
for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) {
plugins->push_back(i->first);
}
}
// TODO(ibraaaa): DELETE. http://crbug.com/124396
bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info,
ScopedVector<PluginGroup>* plugin_groups) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Considering " << info.path.value() << " (" << info.name << ")";
if (IsUndesirablePlugin(info)) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< info.path.value() << " is undesirable.";
// See if we have a better version of this plugin.
for (size_t i = 0; i < plugin_groups->size(); ++i) {
const std::vector<WebPluginInfo>& plugins =
(*plugin_groups)[i]->web_plugin_infos();
for (size_t j = 0; j < plugins.size(); ++j) {
if (plugins[j].name == info.name &&
!IsUndesirablePlugin(plugins[j])) {
// Skip the current undesirable one so we can use the better one
// we just found.
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Skipping " << info.path.value() << ", preferring "
<< plugins[j].path.value();
return false;
}
}
}
}
// TODO(evanm): prefer the newest version of flash, etc. here?
VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value();
return true;
}
bool PluginList::ShouldLoadPluginUsingPluginList(
const WebPluginInfo& info, std::vector<webkit::WebPluginInfo>* plugins) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Considering " << info.path.value() << " (" << info.name << ")";
if (IsUndesirablePlugin(info)) {
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< info.path.value() << " is undesirable.";
// See if we have a better version of this plugin.
for (size_t j = 0; j < plugins->size(); ++j) {
if ((*plugins)[j].name == info.name &&
!IsUndesirablePlugin((*plugins)[j])) {
// Skip the current undesirable one so we can use the better one
// we just found.
LOG_IF(ERROR, PluginList::DebugPluginLoading())
<< "Skipping " << info.path.value() << ", preferring "
<< (*plugins)[j].path.value();
return false;
}
}
}
// TODO(evanm): prefer the newest version of flash, etc. here?
VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value();
return true;
}
} // namespace npapi
} // namespace webkit