/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "PluginInfoStore.h"
#include "NetscapePluginModule.h"
#include <WebCore/FileSystem.h>
#include <WebCore/PathWalker.h>
#include <shlwapi.h>
using namespace WebCore;
namespace WebKit {
static inline Vector<int> parseVersionString(const String& versionString)
{
Vector<int> version;
unsigned startPos = 0;
unsigned endPos;
while (startPos < versionString.length()) {
for (endPos = startPos; endPos < versionString.length(); ++endPos)
if (versionString[endPos] == '.' || versionString[endPos] == '_')
break;
int versionComponent = versionString.substring(startPos, endPos - startPos).toInt();
version.append(versionComponent);
startPos = endPos + 1;
}
return version;
}
// This returns whether versionA is higher than versionB
static inline bool compareVersions(const Vector<int>& versionA, const Vector<int>& versionB)
{
for (unsigned i = 0; i < versionA.size(); i++) {
if (i >= versionB.size())
return true;
if (versionA[i] > versionB[i])
return true;
else if (versionA[i] < versionB[i])
return false;
}
// If we come here, the versions are either the same or versionB has an extra component, just return false
return false;
}
static inline String safariPluginsDirectory()
{
static String pluginsDirectory;
static bool cachedPluginDirectory = false;
if (!cachedPluginDirectory) {
cachedPluginDirectory = true;
WCHAR moduleFileNameStr[MAX_PATH];
int moduleFileNameLen = ::GetModuleFileNameW(0, moduleFileNameStr, WTF_ARRAY_LENGTH(moduleFileNameStr));
if (!moduleFileNameLen || moduleFileNameLen == WTF_ARRAY_LENGTH(moduleFileNameStr))
return pluginsDirectory;
if (!::PathRemoveFileSpecW(moduleFileNameStr))
return pluginsDirectory;
pluginsDirectory = String(moduleFileNameStr) + "\\Plugins";
}
return pluginsDirectory;
}
static inline void addMozillaPluginDirectories(Vector<String>& directories)
{
// Enumerate all Mozilla plugin directories in the registry
HKEY key;
LONG result = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Mozilla", 0, KEY_READ, &key);
if (result != ERROR_SUCCESS)
return;
WCHAR name[128];
FILETIME lastModified;
// Enumerate subkeys
for (int i = 0;; i++) {
DWORD nameLen = WTF_ARRAY_LENGTH(name);
result = ::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
if (result != ERROR_SUCCESS)
break;
String extensionsPath = String(name, nameLen) + "\\Extensions";
HKEY extensionsKey;
// Try opening the key
result = ::RegOpenKeyExW(key, extensionsPath.charactersWithNullTermination(), 0, KEY_READ, &extensionsKey);
if (result == ERROR_SUCCESS) {
// Now get the plugins directory
WCHAR pluginsDirectoryStr[MAX_PATH];
DWORD pluginsDirectorySize = sizeof(pluginsDirectoryStr);
DWORD type;
result = ::RegQueryValueExW(extensionsKey, L"Plugins", 0, &type, reinterpret_cast<LPBYTE>(&pluginsDirectoryStr), &pluginsDirectorySize);
if (result == ERROR_SUCCESS && type == REG_SZ)
directories.append(String(pluginsDirectoryStr, pluginsDirectorySize / sizeof(WCHAR) - 1));
::RegCloseKey(extensionsKey);
}
}
::RegCloseKey(key);
}
static inline void addWindowsMediaPlayerPluginDirectory(Vector<String>& directories)
{
// The new WMP Firefox plugin is installed in \PFiles\Plugins if it can't find any Firefox installs
WCHAR pluginDirectoryStr[MAX_PATH + 1];
DWORD pluginDirectorySize = ::ExpandEnvironmentStringsW(L"%SYSTEMDRIVE%\\PFiles\\Plugins", pluginDirectoryStr, WTF_ARRAY_LENGTH(pluginDirectoryStr));
if (pluginDirectorySize > 0 && pluginDirectorySize <= WTF_ARRAY_LENGTH(pluginDirectoryStr))
directories.append(String(pluginDirectoryStr, pluginDirectorySize - 1));
DWORD type;
WCHAR installationDirectoryStr[MAX_PATH];
DWORD installationDirectorySize = sizeof(installationDirectoryStr);
HRESULT result = ::SHGetValueW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\MediaPlayer", L"Installation Directory", &type, reinterpret_cast<LPBYTE>(&installationDirectoryStr), &installationDirectorySize);
if (result == ERROR_SUCCESS && type == REG_SZ)
directories.append(String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1));
}
static inline void addQuickTimePluginDirectory(Vector<String>& directories)
{
DWORD type;
WCHAR installationDirectoryStr[MAX_PATH];
DWORD installationDirectorySize = sizeof(installationDirectoryStr);
HRESULT result = ::SHGetValueW(HKEY_LOCAL_MACHINE, L"Software\\Apple Computer, Inc.\\QuickTime", L"InstallDir", &type, reinterpret_cast<LPBYTE>(&installationDirectoryStr), &installationDirectorySize);
if (result == ERROR_SUCCESS && type == REG_SZ) {
String pluginDir = String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1) + "\\plugins";
directories.append(pluginDir);
}
}
static inline void addAdobeAcrobatPluginDirectory(Vector<String>& directories)
{
HKEY key;
HRESULT result = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Adobe\\Acrobat Reader", 0, KEY_READ, &key);
if (result != ERROR_SUCCESS)
return;
WCHAR name[128];
FILETIME lastModified;
Vector<int> latestAcrobatVersion;
String latestAcrobatVersionString;
// Enumerate subkeys
for (int i = 0;; i++) {
DWORD nameLen = WTF_ARRAY_LENGTH(name);
result = ::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
if (result != ERROR_SUCCESS)
break;
Vector<int> acrobatVersion = parseVersionString(String(name, nameLen));
if (compareVersions(acrobatVersion, latestAcrobatVersion)) {
latestAcrobatVersion = acrobatVersion;
latestAcrobatVersionString = String(name, nameLen);
}
}
if (!latestAcrobatVersionString.isNull()) {
DWORD type;
WCHAR acrobatInstallPathStr[MAX_PATH];
DWORD acrobatInstallPathSize = sizeof(acrobatInstallPathStr);
String acrobatPluginKeyPath = "Software\\Adobe\\Acrobat Reader\\" + latestAcrobatVersionString + "\\InstallPath";
result = ::SHGetValueW(HKEY_LOCAL_MACHINE, acrobatPluginKeyPath.charactersWithNullTermination(), 0, &type, reinterpret_cast<LPBYTE>(acrobatInstallPathStr), &acrobatInstallPathSize);
if (result == ERROR_SUCCESS) {
String acrobatPluginDirectory = String(acrobatInstallPathStr, acrobatInstallPathSize / sizeof(WCHAR) - 1) + "\\browser";
directories.append(acrobatPluginDirectory);
}
}
::RegCloseKey(key);
}
static inline void addMacromediaPluginDirectories(Vector<String>& directories)
{
#if !OS(WINCE)
WCHAR systemDirectoryStr[MAX_PATH];
if (!::GetSystemDirectoryW(systemDirectoryStr, WTF_ARRAY_LENGTH(systemDirectoryStr)))
return;
WCHAR macromediaDirectoryStr[MAX_PATH];
if (!::PathCombineW(macromediaDirectoryStr, systemDirectoryStr, L"macromed\\Flash"))
return;
directories.append(macromediaDirectoryStr);
if (!::PathCombineW(macromediaDirectoryStr, systemDirectoryStr, L"macromed\\Shockwave 10"))
return;
directories.append(macromediaDirectoryStr);
#endif
}
Vector<String> PluginInfoStore::pluginsDirectories()
{
Vector<String> directories;
String ourDirectory = safariPluginsDirectory();
if (!ourDirectory.isNull())
directories.append(ourDirectory);
addQuickTimePluginDirectory(directories);
addAdobeAcrobatPluginDirectory(directories);
addMozillaPluginDirectories(directories);
addWindowsMediaPlayerPluginDirectory(directories);
addMacromediaPluginDirectories(directories);
return directories;
}
Vector<String> PluginInfoStore::pluginPathsInDirectory(const String& directory)
{
Vector<String> paths;
PathWalker walker(directory, "*");
if (!walker.isValid())
return paths;
do {
if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue;
String filename = walker.data().cFileName;
if ((!filename.startsWith("np", false) || !filename.endsWith("dll", false)) && (!equalIgnoringCase(filename, "Plugin.dll") || !directory.endsWith("Shockwave 10", false)))
continue;
paths.append(directory + "\\" + filename);
} while (walker.step());
return paths;
}
static void addPluginPathsFromRegistry(HKEY rootKey, Vector<String>& paths)
{
HKEY key;
if (::RegOpenKeyExW(rootKey, L"Software\\MozillaPlugins", 0, KEY_ENUMERATE_SUB_KEYS, &key) != ERROR_SUCCESS)
return;
for (size_t i = 0; ; ++i) {
// MSDN says that key names have a maximum length of 255 characters.
wchar_t name[256];
DWORD nameLen = WTF_ARRAY_LENGTH(name);
if (::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, 0) != ERROR_SUCCESS)
break;
wchar_t path[MAX_PATH];
DWORD pathSizeInBytes = sizeof(path);
DWORD type;
if (::SHGetValueW(key, name, L"Path", &type, path, &pathSizeInBytes) != ERROR_SUCCESS)
continue;
if (type != REG_SZ)
continue;
paths.append(path);
}
::RegCloseKey(key);
}
Vector<String> PluginInfoStore::individualPluginPaths()
{
Vector<String> paths;
addPluginPathsFromRegistry(HKEY_LOCAL_MACHINE, paths);
addPluginPathsFromRegistry(HKEY_CURRENT_USER, paths);
return paths;
}
static uint64_t fileVersion(DWORD leastSignificant, DWORD mostSignificant)
{
ULARGE_INTEGER version;
version.LowPart = leastSignificant;
version.HighPart = mostSignificant;
return version.QuadPart;
}
bool PluginInfoStore::getPluginInfo(const String& pluginPath, Plugin& plugin)
{
return NetscapePluginModule::getPluginInfo(pluginPath, plugin);
}
static bool isOldWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
{
return equalIgnoringCase(plugin.info.file, "npdsplay.dll");
}
static bool isNewWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
{
return equalIgnoringCase(plugin.info.file, "np-mswmp.dll");
}
bool PluginInfoStore::shouldUsePlugin(const Plugin& plugin)
{
if (plugin.info.name == "Citrix ICA Client") {
// The Citrix ICA Client plug-in requires a Mozilla-based browser; see <rdar://6418681>.
return false;
}
if (plugin.info.name == "Silverlight Plug-In") {
// workaround for <rdar://5557379> Crash in Silverlight when opening microsoft.com.
// the latest 1.0 version of Silverlight does not reproduce this crash, so allow it
// and any newer versions
static const uint64_t minimumRequiredVersion = fileVersion(0x51BE0000, 0x00010000);
return plugin.fileVersion >= minimumRequiredVersion;
}
if (equalIgnoringCase(plugin.info.file, "npmozax.dll")) {
// Bug 15217: Mozilla ActiveX control complains about missing xpcom_core.dll
return false;
}
if (equalIgnoringCase(plugin.info.file, "npwpf.dll")) {
// Bug 57119: Microsoft Windows Presentation Foundation (WPF) plug-in complains about missing xpcom.dll
return false;
}
if (plugin.info.name == "Yahoo Application State Plugin") {
// https://bugs.webkit.org/show_bug.cgi?id=26860
// Bug in Yahoo Application State plug-in earlier than 1.0.0.6 leads to heap corruption.
static const uint64_t minimumRequiredVersion = fileVersion(0x00000006, 0x00010000);
return plugin.fileVersion >= minimumRequiredVersion;
}
if (isOldWindowsMediaPlayerPlugin(plugin)) {
// Don't load the old Windows Media Player plugin if we've already loaded the new Windows
// Media Player plugin.
for (size_t i = 0; i < m_plugins.size(); ++i) {
if (!isNewWindowsMediaPlayerPlugin(m_plugins[i]))
continue;
return false;
}
return true;
}
if (isNewWindowsMediaPlayerPlugin(plugin)) {
// Unload the old Windows Media Player plugin if we've already loaded it.
for (size_t i = 0; i < m_plugins.size(); ++i) {
if (!isOldWindowsMediaPlayerPlugin(m_plugins[i]))
continue;
m_plugins.remove(i);
}
return true;
}
// FIXME: We should prefer a newer version of a plugin to an older version, rather than loading
// only the first. <http://webkit.org/b/58469>
String pluginFileName = pathGetFileName(plugin.path);
for (size_t i = 0; i < m_plugins.size(); ++i) {
Plugin& loadedPlugin = m_plugins[i];
// If a plug-in with the same filename already exists, we don't want to load it.
if (equalIgnoringCase(pluginFileName, pathGetFileName(loadedPlugin.path)))
return false;
}
return true;
}
} // namespace WebKit