Home | History | Annotate | Download | only in common
      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 "content/common/plugin_list.h"
      6 
      7 #include <algorithm>
      8 #include <dlfcn.h>
      9 #if defined(OS_OPENBSD)
     10 #include <sys/exec_elf.h>
     11 #else
     12 #include <elf.h>
     13 #endif
     14 #include <fcntl.h>
     15 #include <sys/stat.h>
     16 #include <sys/types.h>
     17 #include <unistd.h>
     18 
     19 #include "base/cpu.h"
     20 #include "base/file_util.h"
     21 #include "base/files/file_enumerator.h"
     22 #include "base/native_library.h"
     23 #include "base/path_service.h"
     24 #include "base/posix/eintr_wrapper.h"
     25 #include "base/sha1.h"
     26 #include "base/strings/string_split.h"
     27 #include "base/strings/string_util.h"
     28 #include "base/strings/stringprintf.h"
     29 #include "base/strings/sys_string_conversions.h"
     30 #include "base/strings/utf_string_conversions.h"
     31 #include "build/build_config.h"
     32 #include "third_party/npapi/bindings/nphostapi.h"
     33 
     34 // These headers must be included in this order to make the declaration gods
     35 // happy.
     36 #include "base/third_party/nspr/prcpucfg_linux.h"
     37 
     38 namespace content {
     39 
     40 namespace {
     41 
     42 // We build up a list of files and mtimes so we can sort them.
     43 typedef std::pair<base::FilePath, base::Time> FileAndTime;
     44 typedef std::vector<FileAndTime> FileTimeList;
     45 
     46 enum PluginQuirk {
     47   // No quirks - plugin is outright banned.
     48   PLUGIN_QUIRK_NONE = 0,
     49   // Plugin is using SSE2 instructions without checking for SSE2 instruction
     50   // support. Ban the plugin if the system has no SSE2 support.
     51   PLUGIN_QUIRK_MISSING_SSE2_CHECK = 1 << 0,
     52 };
     53 
     54 // Copied from nsplugindefs.h instead of including the file since it has a bunch
     55 // of dependencies.
     56 enum nsPluginVariable {
     57   nsPluginVariable_NameString        = 1,
     58   nsPluginVariable_DescriptionString = 2
     59 };
     60 
     61 // Comparator used to sort by descending mtime then ascending filename.
     62 bool CompareTime(const FileAndTime& a, const FileAndTime& b) {
     63   if (a.second == b.second) {
     64     // Fall back on filename sorting, just to make the predicate valid.
     65     return a.first < b.first;
     66   }
     67 
     68   // Sort by mtime, descending.
     69   return a.second > b.second;
     70 }
     71 
     72 // Checks to see if the current environment meets any of the condtions set in
     73 // |quirks|. Returns true if any of the conditions are met, or if |quirks| is
     74 // PLUGIN_QUIRK_NONE.
     75 bool CheckQuirks(PluginQuirk quirks) {
     76   if (quirks == PLUGIN_QUIRK_NONE)
     77     return true;
     78 
     79   if ((quirks & PLUGIN_QUIRK_MISSING_SSE2_CHECK) != 0) {
     80     base::CPU cpu;
     81     if (!cpu.has_sse2())
     82       return true;
     83   }
     84 
     85   return false;
     86 }
     87 
     88 // Return true if |path| matches a known (file size, sha1sum) pair.
     89 // Also check against any PluginQuirks the bad plugin may have.
     90 // The use of the file size is an optimization so we don't have to read in
     91 // the entire file unless we have to.
     92 bool IsBlacklistedBySha1sumAndQuirks(const base::FilePath& path) {
     93   const struct BadEntry {
     94     int64 size;
     95     std::string sha1;
     96     PluginQuirk quirks;
     97   } bad_entries[] = {
     98     // Flash 9 r31 - http://crbug.com/29237
     99     { 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb", PLUGIN_QUIRK_NONE },
    100     // Flash 9 r48 - http://crbug.com/29237
    101     { 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b", PLUGIN_QUIRK_NONE },
    102     // Flash 11.2.202.236, 32-bit - http://crbug.com/140086
    103     { 17406436, "1e07eac912faf9426c52a288c76c3b6238f90b6b",
    104       PLUGIN_QUIRK_MISSING_SSE2_CHECK },
    105     // Flash 11.2.202.238, 32-bit - http://crbug.com/140086
    106     { 17410532, "e9401097e97c8443a7d9156be62184ffe1addd5c",
    107       PLUGIN_QUIRK_MISSING_SSE2_CHECK },
    108   };
    109 
    110   int64 size;
    111   if (!file_util::GetFileSize(path, &size))
    112     return false;
    113   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) {
    114     if (bad_entries[i].size != size)
    115       continue;
    116 
    117     std::string file_content;
    118     if (!file_util::ReadFileToString(path, &file_content))
    119       continue;
    120     std::string sha1 = base::SHA1HashString(file_content);
    121     std::string sha1_readable;
    122     for (size_t j = 0; j < sha1.size(); j++)
    123       base::StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF);
    124     if (bad_entries[i].sha1 == sha1_readable)
    125       return CheckQuirks(bad_entries[i].quirks);
    126   }
    127   return false;
    128 }
    129 
    130 // Some plugins are shells around other plugins; we prefer to use the
    131 // real plugin directly, if it's available.  This function returns
    132 // true if we should prefer other plugins over this one.  We'll still
    133 // use a "undesirable" plugin if no other option is available.
    134 bool IsUndesirablePlugin(const WebPluginInfo& info) {
    135   std::string filename = info.path.BaseName().value();
    136   const char* kUndesiredPlugins[] = {
    137     "npcxoffice",  // Crossover
    138     "npwrapper",   // nspluginwrapper
    139   };
    140   for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) {
    141     if (filename.find(kUndesiredPlugins[i]) != std::string::npos) {
    142       return true;
    143     }
    144   }
    145   return false;
    146 }
    147 
    148 // Return true if we shouldn't load a plugin at all.
    149 // This is an ugly hack to blacklist Adobe Acrobat due to not supporting
    150 // its Xt-based mainloop.
    151 // http://code.google.com/p/chromium/issues/detail?id=38229
    152 bool IsBlacklistedPlugin(const base::FilePath& path) {
    153   const char* kBlackListedPlugins[] = {
    154     "nppdf.so",           // Adobe PDF
    155   };
    156   std::string filename = path.BaseName().value();
    157   for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) {
    158     if (filename.find(kBlackListedPlugins[i]) != std::string::npos) {
    159       return true;
    160     }
    161   }
    162   return IsBlacklistedBySha1sumAndQuirks(path);
    163 }
    164 
    165 // Read the ELF header and return true if it is usable on
    166 // the current architecture (e.g. 32-bit ELF on 32-bit build).
    167 // Returns false on other errors as well.
    168 bool ELFMatchesCurrentArchitecture(const base::FilePath& filename) {
    169   // First make sure we can open the file and it is in fact, a regular file.
    170   struct stat stat_buf;
    171   // Open with O_NONBLOCK so we don't block on pipes.
    172   int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK);
    173   if (fd < 0)
    174     return false;
    175   bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode));
    176   if (HANDLE_EINTR(close(fd)) < 0)
    177     return false;
    178   if (!ret)
    179     return false;
    180 
    181   const size_t kELFBufferSize = 5;
    182   char buffer[kELFBufferSize];
    183   if (!file_util::ReadFile(filename, buffer, kELFBufferSize))
    184     return false;
    185 
    186   if (buffer[0] != ELFMAG0 ||
    187       buffer[1] != ELFMAG1 ||
    188       buffer[2] != ELFMAG2 ||
    189       buffer[3] != ELFMAG3) {
    190     // Not an ELF file, perhaps?
    191     return false;
    192   }
    193 
    194   int elf_class = buffer[EI_CLASS];
    195 #if defined(ARCH_CPU_32_BITS)
    196   if (elf_class == ELFCLASS32)
    197     return true;
    198 #elif defined(ARCH_CPU_64_BITS)
    199   if (elf_class == ELFCLASS64)
    200     return true;
    201 #endif
    202 
    203   return false;
    204 }
    205 
    206 // This structure matches enough of nspluginwrapper's NPW_PluginInfo
    207 // for us to extract the real plugin path.
    208 struct __attribute__((packed)) NSPluginWrapperInfo {
    209   char ident[32];  // NSPluginWrapper magic identifier (includes version).
    210   char path[PATH_MAX];  // Path to wrapped plugin.
    211 };
    212 
    213 // Test a plugin for whether it's been wrapped by NSPluginWrapper, and
    214 // if so attempt to unwrap it.  Pass in an opened plugin handle; on
    215 // success, |dl| and |unwrapped_path| will be filled in with the newly
    216 // opened plugin.  On failure, params are left unmodified.
    217 void UnwrapNSPluginWrapper(void **dl, base::FilePath* unwrapped_path) {
    218   NSPluginWrapperInfo* info =
    219       reinterpret_cast<NSPluginWrapperInfo*>(dlsym(*dl, "NPW_Plugin"));
    220   if (!info)
    221     return;  // Not a NSPW plugin.
    222 
    223   // Here we could check the NSPW ident field for the versioning
    224   // information, but the path field is available in all versions
    225   // anyway.
    226 
    227   // Grab the path to the wrapped plugin.  Just in case the structure
    228   // format changes, protect against the path not being null-terminated.
    229   char* path_end = static_cast<char*>(memchr(info->path, '\0',
    230                                              sizeof(info->path)));
    231   if (!path_end)
    232     path_end = info->path + sizeof(info->path);
    233   base::FilePath path = base::FilePath(
    234       std::string(info->path, path_end - info->path));
    235 
    236   if (!ELFMatchesCurrentArchitecture(path)) {
    237     LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a "
    238                  << "plugin for a different architecture; it will "
    239                  << "work better if you instead use a native plugin.";
    240     return;
    241   }
    242 
    243   std::string error;
    244   void* newdl = base::LoadNativeLibrary(path, &error);
    245   if (!newdl) {
    246     // We couldn't load the unwrapped plugin for some reason, despite
    247     // being able to load the wrapped one.  Just use the wrapped one.
    248     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    249         << "Could not use unwrapped nspluginwrapper plugin "
    250         << unwrapped_path->value() << " (" << error << "), "
    251         << "using the wrapped one.";
    252     return;
    253   }
    254 
    255   // Unload the wrapped plugin, and use the wrapped plugin instead.
    256   LOG_IF(ERROR, PluginList::DebugPluginLoading())
    257       << "Using unwrapped version " << unwrapped_path->value()
    258       << " of nspluginwrapper-wrapped plugin.";
    259   base::UnloadNativeLibrary(*dl);
    260   *dl = newdl;
    261   *unwrapped_path = path;
    262 }
    263 
    264 }  // namespace
    265 
    266 bool PluginList::ReadWebPluginInfo(const base::FilePath& filename,
    267                                    WebPluginInfo* info) {
    268   // The file to reference is:
    269   // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUnix.cpp
    270 
    271   // Skip files that aren't appropriate for our architecture.
    272   if (!ELFMatchesCurrentArchitecture(filename)) {
    273     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    274         << "Skipping plugin " << filename.value()
    275         << " because it doesn't match the current architecture.";
    276     return false;
    277   }
    278 
    279   std::string error;
    280   void* dl = base::LoadNativeLibrary(filename, &error);
    281   if (!dl) {
    282     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    283         << "While reading plugin info, unable to load library "
    284         << filename.value() << " (" << error << "), skipping.";
    285     return false;
    286   }
    287 
    288   info->path = filename;
    289 
    290   // Attempt to swap in the wrapped plugin if this is nspluginwrapper.
    291   UnwrapNSPluginWrapper(&dl, &info->path);
    292 
    293   // See comments in plugin_lib_mac regarding this symbol.
    294   typedef const char* (*NP_GetMimeDescriptionType)();
    295   NP_GetMimeDescriptionType NP_GetMIMEDescription =
    296       reinterpret_cast<NP_GetMimeDescriptionType>(
    297           dlsym(dl, "NP_GetMIMEDescription"));
    298   const char* mime_description = NULL;
    299   if (!NP_GetMIMEDescription) {
    300     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    301         << "Plugin " << filename.value() << " doesn't have a "
    302         << "NP_GetMIMEDescription symbol";
    303     return false;
    304   }
    305   mime_description = NP_GetMIMEDescription();
    306 
    307   if (!mime_description) {
    308     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    309         << "MIME description for " << filename.value() << " is empty";
    310     return false;
    311   }
    312   ParseMIMEDescription(mime_description, &info->mime_types);
    313 
    314   // The plugin name and description live behind NP_GetValue calls.
    315   typedef NPError (*NP_GetValueType)(void* unused,
    316                                      nsPluginVariable variable,
    317                                      void* value_out);
    318   NP_GetValueType NP_GetValue =
    319       reinterpret_cast<NP_GetValueType>(dlsym(dl, "NP_GetValue"));
    320   if (NP_GetValue) {
    321     const char* name = NULL;
    322     NP_GetValue(NULL, nsPluginVariable_NameString, &name);
    323     if (name) {
    324       info->name = UTF8ToUTF16(name);
    325       ExtractVersionString(name, info);
    326     }
    327 
    328     const char* description = NULL;
    329     NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description);
    330     if (description) {
    331       info->desc = UTF8ToUTF16(description);
    332       if (info->version.empty())
    333         ExtractVersionString(description, info);
    334     }
    335 
    336     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    337         << "Got info for plugin " << filename.value()
    338         << " Name = \"" << UTF16ToUTF8(info->name)
    339         << "\", Description = \"" << UTF16ToUTF8(info->desc)
    340         << "\", Version = \"" << UTF16ToUTF8(info->version)
    341         << "\".";
    342   } else {
    343     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    344         << "Plugin " << filename.value()
    345         << " has no GetValue() and probably won't work.";
    346   }
    347 
    348   // Intentionally not unloading the plugin here, it can lead to crashes.
    349 
    350   return true;
    351 }
    352 
    353 // static
    354 void PluginList::ParseMIMEDescription(
    355     const std::string& description,
    356     std::vector<WebPluginMimeType>* mime_types) {
    357   // We parse the description here into WebPluginMimeType structures.
    358   // Naively from the NPAPI docs you'd think you could use
    359   // string-splitting, but the Firefox parser turns out to do something
    360   // different: find the first colon, then the second, then a semi.
    361   //
    362   // See ParsePluginMimeDescription near
    363   // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53
    364 
    365   std::string::size_type ofs = 0;
    366   for (;;) {
    367     WebPluginMimeType mime_type;
    368 
    369     std::string::size_type end = description.find(':', ofs);
    370     if (end == std::string::npos)
    371       break;
    372     mime_type.mime_type = description.substr(ofs, end - ofs);
    373     ofs = end + 1;
    374 
    375     end = description.find(':', ofs);
    376     if (end == std::string::npos)
    377       break;
    378     const std::string extensions = description.substr(ofs, end - ofs);
    379     base::SplitString(extensions, ',', &mime_type.file_extensions);
    380     ofs = end + 1;
    381 
    382     end = description.find(';', ofs);
    383     // It's ok for end to run off the string here.  If there's no
    384     // trailing semicolon we consume the remainder of the string.
    385     if (end != std::string::npos) {
    386       mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs));
    387     } else {
    388       mime_type.description = UTF8ToUTF16(description.substr(ofs));
    389     }
    390     mime_types->push_back(mime_type);
    391     if (end == std::string::npos)
    392       break;
    393     ofs = end + 1;
    394   }
    395 }
    396 
    397 // static
    398 void PluginList::ExtractVersionString(const std::string& desc,
    399                                       WebPluginInfo* info) {
    400   // This matching works by extracting a version substring, along the lines of:
    401   // No postfix: second match in .*<prefix>.*$
    402   // With postfix: second match .*<prefix>.*<postfix>
    403   static const struct {
    404     const char* kPrefix;
    405     const char* kPostfix;
    406   } kPrePostFixes[] = {
    407     { "Shockwave Flash ", 0 },
    408     { "Java(TM) Plug-in ", 0 },
    409     { "(using IcedTea-Web ", " " },
    410     { 0, 0 }
    411   };
    412   std::string version;
    413   for (size_t i = 0; kPrePostFixes[i].kPrefix; ++i) {
    414     size_t pos;
    415     if ((pos = desc.find(kPrePostFixes[i].kPrefix)) != std::string::npos) {
    416       version = desc.substr(pos + strlen(kPrePostFixes[i].kPrefix));
    417       pos = std::string::npos;
    418       if (kPrePostFixes[i].kPostfix)
    419         pos = version.find(kPrePostFixes[i].kPostfix);
    420       if (pos != std::string::npos)
    421         version = version.substr(0, pos);
    422       break;
    423     }
    424   }
    425   if (!version.empty()) {
    426     info->version = UTF8ToUTF16(version);
    427   }
    428 }
    429 
    430 void PluginList::GetPluginDirectories(std::vector<base::FilePath>* plugin_dirs) {
    431   // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9
    432   // for discussion.
    433   // We first consult Chrome-specific dirs, then fall back on the logic
    434   // Mozilla uses.
    435 
    436   if (PluginList::plugins_discovery_disabled_)
    437     return;
    438 
    439   // Note: "extra" plugin dirs and paths are examined before these.
    440   // "Extra" are those added by PluginList::AddExtraPluginDir() and
    441   // PluginList::AddExtraPluginPath().
    442 
    443   // The Chrome binary dir + "plugins/".
    444   base::FilePath dir;
    445   PathService::Get(base::DIR_EXE, &dir);
    446   plugin_dirs->push_back(dir.Append("plugins"));
    447 
    448   // Chrome OS only loads plugins from /opt/google/chrome/plugins.
    449 #if !defined(OS_CHROMEOS)
    450   // Mozilla code to reference:
    451   // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST
    452   // and tens of accompanying files (mxr is very helpful).
    453   // This code carefully matches their behavior for compat reasons.
    454 
    455   // 1) MOZ_PLUGIN_PATH env variable.
    456   const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH");
    457   if (moz_plugin_path) {
    458     std::vector<std::string> paths;
    459     base::SplitString(moz_plugin_path, ':', &paths);
    460     for (size_t i = 0; i < paths.size(); ++i)
    461       plugin_dirs->push_back(base::FilePath(paths[i]));
    462   }
    463 
    464   // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins.
    465   // This is a de-facto standard, so even though we're not Mozilla, let's
    466   // look in there too.
    467   base::FilePath home = file_util::GetHomeDir();
    468   if (!home.empty())
    469     plugin_dirs->push_back(home.Append(".mozilla/plugins"));
    470 
    471   // 3) NS_SYSTEM_PLUGINS_DIR:
    472   // This varies across different browsers and versions, so check 'em all.
    473   plugin_dirs->push_back(base::FilePath("/usr/lib/browser-plugins"));
    474   plugin_dirs->push_back(base::FilePath("/usr/lib/mozilla/plugins"));
    475   plugin_dirs->push_back(base::FilePath("/usr/lib/firefox/plugins"));
    476   plugin_dirs->push_back(base::FilePath("/usr/lib/xulrunner-addons/plugins"));
    477 
    478 #if defined(ARCH_CPU_64_BITS)
    479   // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib.
    480   // But a user reported on their Fedora system they are separate.
    481   plugin_dirs->push_back(base::FilePath("/usr/lib64/browser-plugins"));
    482   plugin_dirs->push_back(base::FilePath("/usr/lib64/mozilla/plugins"));
    483   plugin_dirs->push_back(base::FilePath("/usr/lib64/firefox/plugins"));
    484   plugin_dirs->push_back(base::FilePath("/usr/lib64/xulrunner-addons/plugins"));
    485 #endif  // defined(ARCH_CPU_64_BITS)
    486 #endif  // !defined(OS_CHROMEOS)
    487 }
    488 
    489 void PluginList::GetPluginsInDir(
    490     const base::FilePath& dir_path, std::vector<base::FilePath>* plugins) {
    491   // See ScanPluginsDirectory near
    492   // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052
    493 
    494   // Construct and stat a list of all filenames under consideration, for
    495   // later sorting by mtime.
    496   FileTimeList files;
    497   base::FileEnumerator enumerator(dir_path,
    498                                   false,  // not recursive
    499                                   base::FileEnumerator::FILES);
    500   for (base::FilePath path = enumerator.Next(); !path.value().empty();
    501        path = enumerator.Next()) {
    502     // Skip over Mozilla .xpt files.
    503     if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt")))
    504       continue;
    505 
    506     // Java doesn't like being loaded through a symlink, since it uses
    507     // its path to find dependent data files.
    508     // MakeAbsoluteFilePath calls through to realpath(), which resolves
    509     // symlinks.
    510     base::FilePath orig_path = path;
    511     path = base::MakeAbsoluteFilePath(path);
    512     if (path.empty())
    513       path = orig_path;
    514     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    515         << "Resolved " << orig_path.value() << " -> " << path.value();
    516 
    517     if (std::find(plugins->begin(), plugins->end(), path) != plugins->end()) {
    518       LOG_IF(ERROR, PluginList::DebugPluginLoading())
    519           << "Skipping duplicate instance of " << path.value();
    520       continue;
    521     }
    522 
    523     if (IsBlacklistedPlugin(path)) {
    524       LOG_IF(ERROR, PluginList::DebugPluginLoading())
    525           << "Skipping blacklisted plugin " << path.value();
    526       continue;
    527     }
    528 
    529     // Flash stops working if the containing directory involves 'netscape'.
    530     // No joke.  So use the other path if it's better.
    531     static const char kFlashPlayerFilename[] = "libflashplayer.so";
    532     static const char kNetscapeInPath[] = "/netscape/";
    533     if (path.BaseName().value() == kFlashPlayerFilename &&
    534         path.value().find(kNetscapeInPath) != std::string::npos) {
    535       if (orig_path.value().find(kNetscapeInPath) == std::string::npos) {
    536         // Go back to the old path.
    537         path = orig_path;
    538       } else {
    539         LOG_IF(ERROR, PluginList::DebugPluginLoading())
    540             << "Flash misbehaves when used from a directory containing "
    541             << kNetscapeInPath << ", so skipping " << orig_path.value();
    542         continue;
    543       }
    544     }
    545 
    546     // Get mtime.
    547     base::PlatformFileInfo info;
    548     if (!file_util::GetFileInfo(path, &info))
    549       continue;
    550 
    551     files.push_back(std::make_pair(path, info.last_modified));
    552   }
    553 
    554   // Sort the file list by time (and filename).
    555   std::sort(files.begin(), files.end(), CompareTime);
    556 
    557   // Load the files in order.
    558   for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) {
    559     plugins->push_back(i->first);
    560   }
    561 }
    562 
    563 bool PluginList::ShouldLoadPluginUsingPluginList(
    564     const WebPluginInfo& info, std::vector<WebPluginInfo>* plugins) {
    565   LOG_IF(ERROR, PluginList::DebugPluginLoading())
    566       << "Considering " << info.path.value() << " (" << info.name << ")";
    567 
    568   if (IsUndesirablePlugin(info)) {
    569     LOG_IF(ERROR, PluginList::DebugPluginLoading())
    570         << info.path.value() << " is undesirable.";
    571 
    572     // See if we have a better version of this plugin.
    573     for (size_t j = 0; j < plugins->size(); ++j) {
    574       if ((*plugins)[j].name == info.name &&
    575           !IsUndesirablePlugin((*plugins)[j])) {
    576         // Skip the current undesirable one so we can use the better one
    577         // we just found.
    578         LOG_IF(ERROR, PluginList::DebugPluginLoading())
    579             << "Skipping " << info.path.value() << ", preferring "
    580             << (*plugins)[j].path.value();
    581         return false;
    582       }
    583     }
    584   }
    585 
    586   // TODO(evanm): prefer the newest version of flash, etc. here?
    587 
    588   VLOG_IF(1, PluginList::DebugPluginLoading()) << "Using " << info.path.value();
    589 
    590   return true;
    591 }
    592 
    593 }  // namespace content
    594