1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PluginInfoStore.h"
28
29#include "NetscapePluginModule.h"
30#include <WebCore/FileSystem.h>
31#include <WebCore/PathWalker.h>
32#include <shlwapi.h>
33
34using namespace WebCore;
35
36namespace WebKit {
37
38static inline Vector<int> parseVersionString(const String& versionString)
39{
40    Vector<int> version;
41
42    unsigned startPos = 0;
43    unsigned endPos;
44
45    while (startPos < versionString.length()) {
46        for (endPos = startPos; endPos < versionString.length(); ++endPos)
47            if (versionString[endPos] == '.' || versionString[endPos] == '_')
48                break;
49
50        int versionComponent = versionString.substring(startPos, endPos - startPos).toInt();
51        version.append(versionComponent);
52
53        startPos = endPos + 1;
54    }
55
56    return version;
57}
58
59// This returns whether versionA is higher than versionB
60static inline bool compareVersions(const Vector<int>& versionA, const Vector<int>& versionB)
61{
62    for (unsigned i = 0; i < versionA.size(); i++) {
63        if (i >= versionB.size())
64            return true;
65
66        if (versionA[i] > versionB[i])
67            return true;
68        else if (versionA[i] < versionB[i])
69            return false;
70    }
71
72    // If we come here, the versions are either the same or versionB has an extra component, just return false
73    return false;
74}
75
76static inline String safariPluginsDirectory()
77{
78    static String pluginsDirectory;
79    static bool cachedPluginDirectory = false;
80
81    if (!cachedPluginDirectory) {
82        cachedPluginDirectory = true;
83
84        WCHAR moduleFileNameStr[MAX_PATH];
85        int moduleFileNameLen = ::GetModuleFileNameW(0, moduleFileNameStr, WTF_ARRAY_LENGTH(moduleFileNameStr));
86
87        if (!moduleFileNameLen || moduleFileNameLen == WTF_ARRAY_LENGTH(moduleFileNameStr))
88            return pluginsDirectory;
89
90        if (!::PathRemoveFileSpecW(moduleFileNameStr))
91            return pluginsDirectory;
92
93        pluginsDirectory = String(moduleFileNameStr) + "\\Plugins";
94    }
95
96    return pluginsDirectory;
97}
98
99static inline void addMozillaPluginDirectories(Vector<String>& directories)
100{
101    // Enumerate all Mozilla plugin directories in the registry
102    HKEY key;
103    LONG result = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Mozilla", 0, KEY_READ, &key);
104    if (result != ERROR_SUCCESS)
105        return;
106
107    WCHAR name[128];
108    FILETIME lastModified;
109
110    // Enumerate subkeys
111    for (int i = 0;; i++) {
112        DWORD nameLen = WTF_ARRAY_LENGTH(name);
113        result = ::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
114
115        if (result != ERROR_SUCCESS)
116            break;
117
118        String extensionsPath = String(name, nameLen) + "\\Extensions";
119        HKEY extensionsKey;
120
121        // Try opening the key
122        result = ::RegOpenKeyExW(key, extensionsPath.charactersWithNullTermination(), 0, KEY_READ, &extensionsKey);
123
124        if (result == ERROR_SUCCESS) {
125            // Now get the plugins directory
126            WCHAR pluginsDirectoryStr[MAX_PATH];
127            DWORD pluginsDirectorySize = sizeof(pluginsDirectoryStr);
128            DWORD type;
129
130            result = ::RegQueryValueExW(extensionsKey, L"Plugins", 0, &type, reinterpret_cast<LPBYTE>(&pluginsDirectoryStr), &pluginsDirectorySize);
131
132            if (result == ERROR_SUCCESS && type == REG_SZ)
133                directories.append(String(pluginsDirectoryStr, pluginsDirectorySize / sizeof(WCHAR) - 1));
134
135            ::RegCloseKey(extensionsKey);
136        }
137    }
138
139    ::RegCloseKey(key);
140}
141
142static inline void addWindowsMediaPlayerPluginDirectory(Vector<String>& directories)
143{
144    // The new WMP Firefox plugin is installed in \PFiles\Plugins if it can't find any Firefox installs
145    WCHAR pluginDirectoryStr[MAX_PATH + 1];
146    DWORD pluginDirectorySize = ::ExpandEnvironmentStringsW(L"%SYSTEMDRIVE%\\PFiles\\Plugins", pluginDirectoryStr, WTF_ARRAY_LENGTH(pluginDirectoryStr));
147
148    if (pluginDirectorySize > 0 && pluginDirectorySize <= WTF_ARRAY_LENGTH(pluginDirectoryStr))
149        directories.append(String(pluginDirectoryStr, pluginDirectorySize - 1));
150
151    DWORD type;
152    WCHAR installationDirectoryStr[MAX_PATH];
153    DWORD installationDirectorySize = sizeof(installationDirectoryStr);
154
155    HRESULT result = ::SHGetValueW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\MediaPlayer", L"Installation Directory", &type, reinterpret_cast<LPBYTE>(&installationDirectoryStr), &installationDirectorySize);
156
157    if (result == ERROR_SUCCESS && type == REG_SZ)
158        directories.append(String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1));
159}
160
161static inline void addQuickTimePluginDirectory(Vector<String>& directories)
162{
163    DWORD type;
164    WCHAR installationDirectoryStr[MAX_PATH];
165    DWORD installationDirectorySize = sizeof(installationDirectoryStr);
166
167    HRESULT result = ::SHGetValueW(HKEY_LOCAL_MACHINE, L"Software\\Apple Computer, Inc.\\QuickTime", L"InstallDir", &type, reinterpret_cast<LPBYTE>(&installationDirectoryStr), &installationDirectorySize);
168
169    if (result == ERROR_SUCCESS && type == REG_SZ) {
170        String pluginDir = String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1) + "\\plugins";
171        directories.append(pluginDir);
172    }
173}
174
175static inline void addAdobeAcrobatPluginDirectory(Vector<String>& directories)
176{
177    HKEY key;
178    HRESULT result = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Adobe\\Acrobat Reader", 0, KEY_READ, &key);
179    if (result != ERROR_SUCCESS)
180        return;
181
182    WCHAR name[128];
183    FILETIME lastModified;
184
185    Vector<int> latestAcrobatVersion;
186    String latestAcrobatVersionString;
187
188    // Enumerate subkeys
189    for (int i = 0;; i++) {
190        DWORD nameLen = WTF_ARRAY_LENGTH(name);
191        result = ::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
192
193        if (result != ERROR_SUCCESS)
194            break;
195
196        Vector<int> acrobatVersion = parseVersionString(String(name, nameLen));
197        if (compareVersions(acrobatVersion, latestAcrobatVersion)) {
198            latestAcrobatVersion = acrobatVersion;
199            latestAcrobatVersionString = String(name, nameLen);
200        }
201    }
202
203    if (!latestAcrobatVersionString.isNull()) {
204        DWORD type;
205        WCHAR acrobatInstallPathStr[MAX_PATH];
206        DWORD acrobatInstallPathSize = sizeof(acrobatInstallPathStr);
207
208        String acrobatPluginKeyPath = "Software\\Adobe\\Acrobat Reader\\" + latestAcrobatVersionString + "\\InstallPath";
209        result = ::SHGetValueW(HKEY_LOCAL_MACHINE, acrobatPluginKeyPath.charactersWithNullTermination(), 0, &type, reinterpret_cast<LPBYTE>(acrobatInstallPathStr), &acrobatInstallPathSize);
210
211        if (result == ERROR_SUCCESS) {
212            String acrobatPluginDirectory = String(acrobatInstallPathStr, acrobatInstallPathSize / sizeof(WCHAR) - 1) + "\\browser";
213            directories.append(acrobatPluginDirectory);
214        }
215    }
216
217    ::RegCloseKey(key);
218}
219
220static inline void addMacromediaPluginDirectories(Vector<String>& directories)
221{
222#if !OS(WINCE)
223    WCHAR systemDirectoryStr[MAX_PATH];
224
225    if (!::GetSystemDirectoryW(systemDirectoryStr, WTF_ARRAY_LENGTH(systemDirectoryStr)))
226        return;
227
228    WCHAR macromediaDirectoryStr[MAX_PATH];
229
230    if (!::PathCombineW(macromediaDirectoryStr, systemDirectoryStr, L"macromed\\Flash"))
231        return;
232
233    directories.append(macromediaDirectoryStr);
234
235    if (!::PathCombineW(macromediaDirectoryStr, systemDirectoryStr, L"macromed\\Shockwave 10"))
236        return;
237
238    directories.append(macromediaDirectoryStr);
239#endif
240}
241
242Vector<String> PluginInfoStore::pluginsDirectories()
243{
244    Vector<String> directories;
245
246    String ourDirectory = safariPluginsDirectory();
247    if (!ourDirectory.isNull())
248        directories.append(ourDirectory);
249
250    addQuickTimePluginDirectory(directories);
251    addAdobeAcrobatPluginDirectory(directories);
252    addMozillaPluginDirectories(directories);
253    addWindowsMediaPlayerPluginDirectory(directories);
254    addMacromediaPluginDirectories(directories);
255
256    return directories;
257}
258
259Vector<String> PluginInfoStore::pluginPathsInDirectory(const String& directory)
260{
261    Vector<String> paths;
262
263    PathWalker walker(directory, "*");
264    if (!walker.isValid())
265        return paths;
266
267    do {
268        if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
269            continue;
270
271        String filename = walker.data().cFileName;
272        if ((!filename.startsWith("np", false) || !filename.endsWith("dll", false)) && (!equalIgnoringCase(filename, "Plugin.dll") || !directory.endsWith("Shockwave 10", false)))
273            continue;
274
275        paths.append(directory + "\\" + filename);
276    } while (walker.step());
277
278    return paths;
279}
280
281static void addPluginPathsFromRegistry(HKEY rootKey, Vector<String>& paths)
282{
283    HKEY key;
284    if (::RegOpenKeyExW(rootKey, L"Software\\MozillaPlugins", 0, KEY_ENUMERATE_SUB_KEYS, &key) != ERROR_SUCCESS)
285        return;
286
287    for (size_t i = 0; ; ++i) {
288        // MSDN says that key names have a maximum length of 255 characters.
289        wchar_t name[256];
290        DWORD nameLen = WTF_ARRAY_LENGTH(name);
291        if (::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, 0) != ERROR_SUCCESS)
292            break;
293
294        wchar_t path[MAX_PATH];
295        DWORD pathSizeInBytes = sizeof(path);
296        DWORD type;
297        if (::SHGetValueW(key, name, L"Path", &type, path, &pathSizeInBytes) != ERROR_SUCCESS)
298            continue;
299        if (type != REG_SZ)
300            continue;
301
302        paths.append(path);
303    }
304
305    ::RegCloseKey(key);
306}
307
308Vector<String> PluginInfoStore::individualPluginPaths()
309{
310    Vector<String> paths;
311
312    addPluginPathsFromRegistry(HKEY_LOCAL_MACHINE, paths);
313    addPluginPathsFromRegistry(HKEY_CURRENT_USER, paths);
314
315    return paths;
316}
317
318static uint64_t fileVersion(DWORD leastSignificant, DWORD mostSignificant)
319{
320    ULARGE_INTEGER version;
321    version.LowPart = leastSignificant;
322    version.HighPart = mostSignificant;
323    return version.QuadPart;
324}
325
326bool PluginInfoStore::getPluginInfo(const String& pluginPath, Plugin& plugin)
327{
328    return NetscapePluginModule::getPluginInfo(pluginPath, plugin);
329}
330
331static bool isOldWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
332{
333    return equalIgnoringCase(plugin.info.file, "npdsplay.dll");
334}
335
336static bool isNewWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
337{
338    return equalIgnoringCase(plugin.info.file, "np-mswmp.dll");
339}
340
341bool PluginInfoStore::shouldUsePlugin(const Plugin& plugin)
342{
343    if (plugin.info.name == "Citrix ICA Client") {
344        // The Citrix ICA Client plug-in requires a Mozilla-based browser; see <rdar://6418681>.
345        return false;
346    }
347
348    if (plugin.info.name == "Silverlight Plug-In") {
349        // workaround for <rdar://5557379> Crash in Silverlight when opening microsoft.com.
350        // the latest 1.0 version of Silverlight does not reproduce this crash, so allow it
351        // and any newer versions
352        static const uint64_t minimumRequiredVersion = fileVersion(0x51BE0000, 0x00010000);
353        return plugin.fileVersion >= minimumRequiredVersion;
354    }
355
356    if (equalIgnoringCase(plugin.info.file, "npmozax.dll")) {
357        // Bug 15217: Mozilla ActiveX control complains about missing xpcom_core.dll
358        return false;
359    }
360
361    if (equalIgnoringCase(plugin.info.file, "npwpf.dll")) {
362        // Bug 57119: Microsoft Windows Presentation Foundation (WPF) plug-in complains about missing xpcom.dll
363        return false;
364    }
365
366    if (plugin.info.name == "Yahoo Application State Plugin") {
367        // https://bugs.webkit.org/show_bug.cgi?id=26860
368        // Bug in Yahoo Application State plug-in earlier than 1.0.0.6 leads to heap corruption.
369        static const uint64_t minimumRequiredVersion = fileVersion(0x00000006, 0x00010000);
370        return plugin.fileVersion >= minimumRequiredVersion;
371    }
372
373    if (isOldWindowsMediaPlayerPlugin(plugin)) {
374        // Don't load the old Windows Media Player plugin if we've already loaded the new Windows
375        // Media Player plugin.
376        for (size_t i = 0; i < m_plugins.size(); ++i) {
377            if (!isNewWindowsMediaPlayerPlugin(m_plugins[i]))
378                continue;
379            return false;
380        }
381        return true;
382    }
383
384    if (isNewWindowsMediaPlayerPlugin(plugin)) {
385        // Unload the old Windows Media Player plugin if we've already loaded it.
386        for (size_t i = 0; i < m_plugins.size(); ++i) {
387            if (!isOldWindowsMediaPlayerPlugin(m_plugins[i]))
388                continue;
389            m_plugins.remove(i);
390        }
391        return true;
392    }
393
394    // FIXME: We should prefer a newer version of a plugin to an older version, rather than loading
395    // only the first. <http://webkit.org/b/58469>
396    String pluginFileName = pathGetFileName(plugin.path);
397    for (size_t i = 0; i < m_plugins.size(); ++i) {
398        Plugin& loadedPlugin = m_plugins[i];
399
400        // If a plug-in with the same filename already exists, we don't want to load it.
401        if (equalIgnoringCase(pluginFileName, pathGetFileName(loadedPlugin.path)))
402            return false;
403    }
404
405    return true;
406}
407
408} // namespace WebKit
409