Implement PluginInfoStore::shouldUsePlugin on Windows
[WebKit-https.git] / WebKit2 / UIProcess / Plugins / win / PluginInfoStoreWin.cpp
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 "PluginInfoStore.h"
27
28 #define DISABLE_NOT_IMPLEMENTED_WARNINGS 1
29 #include "NotImplemented.h"
30 #include <WebCore/FileSystem.h>
31 #include <shlwapi.h>
32 #include <wtf/OwnArrayPtr.h>
33
34 using namespace WebCore;
35
36 namespace WebKit {
37
38 static 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
60 static 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
76 static 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
99 static 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
142 static 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
161 static 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
175 static 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
220 static 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
242 Vector<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
259 class PathWalker : public Noncopyable {
260 public:
261     PathWalker(const String& directory)
262     {
263         String pattern = directory + "\\*";
264         m_handle = ::FindFirstFileW(pattern.charactersWithNullTermination(), &m_data);
265     }
266
267     ~PathWalker()
268     {
269         if (!isValid())
270             return;
271         ::FindClose(m_handle);
272     }
273
274     bool isValid() const { return m_handle != INVALID_HANDLE_VALUE; }
275     const WIN32_FIND_DATAW& data() const { return m_data; }
276
277     bool step() { return ::FindNextFileW(m_handle, &m_data); }
278
279 private:
280     HANDLE m_handle;
281     WIN32_FIND_DATAW m_data;
282 };
283
284 Vector<String> PluginInfoStore::pluginPathsInDirectory(const String& directory)
285 {
286     Vector<String> paths;
287
288     PathWalker walker(directory);
289     if (!walker.isValid())
290         return paths;
291
292     do {
293         if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
294             continue;
295
296         String filename = walker.data().cFileName;
297         if ((!filename.startsWith("np", false) || !filename.endsWith("dll", false)) && (!equalIgnoringCase(filename, "Plugin.dll") || !directory.endsWith("Shockwave 10", false)))
298             continue;
299
300         paths.append(directory + "\\" + filename);
301     } while (walker.step());
302
303     return paths;
304 }
305
306 static void addPluginPathsFromRegistry(HKEY rootKey, Vector<String>& paths)
307 {
308     HKEY key;
309     if (::RegOpenKeyExW(rootKey, L"Software\\MozillaPlugins", 0, KEY_ENUMERATE_SUB_KEYS, &key) != ERROR_SUCCESS)
310         return;
311
312     for (size_t i = 0; ; ++i) {
313         // MSDN says that key names have a maximum length of 255 characters.
314         wchar_t name[256];
315         DWORD nameLen = WTF_ARRAY_LENGTH(name);
316         if (::RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, 0) != ERROR_SUCCESS)
317             break;
318
319         wchar_t path[MAX_PATH];
320         DWORD pathSizeInBytes = sizeof(path);
321         DWORD type;
322         if (::SHGetValueW(key, name, L"Path", &type, path, &pathSizeInBytes) != ERROR_SUCCESS)
323             continue;
324         if (type != REG_SZ)
325             continue;
326
327         paths.append(path);
328     }
329
330     ::RegCloseKey(key);
331 }
332
333 Vector<String> PluginInfoStore::individualPluginPaths()
334 {
335     Vector<String> paths;
336
337     addPluginPathsFromRegistry(HKEY_LOCAL_MACHINE, paths);
338     addPluginPathsFromRegistry(HKEY_CURRENT_USER, paths);
339
340     return paths;
341 }
342
343 static String getVersionInfo(const LPVOID versionInfoData, const String& info)
344 {
345     LPVOID buffer;
346     UINT bufferLength;
347     String subInfo = "\\StringfileInfo\\040904E4\\" + info;
348     if (!::VerQueryValueW(versionInfoData, const_cast<UChar*>(subInfo.charactersWithNullTermination()), &buffer, &bufferLength) || !bufferLength)
349         return String();
350
351     // Subtract 1 from the length; we don't want the trailing null character.
352     return String(reinterpret_cast<UChar*>(buffer), bufferLength - 1);
353 }
354
355 static uint64_t fileVersion(DWORD leastSignificant, DWORD mostSignificant)
356 {
357     ULARGE_INTEGER version;
358     version.LowPart = leastSignificant;
359     version.HighPart = mostSignificant;
360     return version.QuadPart;
361 }
362
363 bool PluginInfoStore::getPluginInfo(const String& pluginPath, Plugin& plugin)
364 {
365     String pathCopy = pluginPath;
366     DWORD versionInfoSize = ::GetFileVersionInfoSizeW(pathCopy.charactersWithNullTermination(), 0);
367     if (!versionInfoSize)
368         return false;
369
370     OwnArrayPtr<char> versionInfoData(new char[versionInfoSize]);
371     if (!::GetFileVersionInfoW(pathCopy.charactersWithNullTermination(), 0, versionInfoSize, versionInfoData.get()))
372         return false;
373
374     String name = getVersionInfo(versionInfoData.get(), "ProductName");
375     String description = getVersionInfo(versionInfoData.get(), "FileDescription");
376     if (name.isNull() || description.isNull())
377         return false;
378
379     VS_FIXEDFILEINFO* info;
380     UINT infoSize;
381     if (!::VerQueryValueW(versionInfoData.get(), L"\\", reinterpret_cast<void**>(&info), &infoSize) || infoSize < sizeof(VS_FIXEDFILEINFO))
382         return false;
383
384     Vector<String> types;
385     getVersionInfo(versionInfoData.get(), "MIMEType").split('|', types);
386     Vector<String> extensionLists;
387     getVersionInfo(versionInfoData.get(), "FileExtents").split('|', extensionLists);
388     Vector<String> descriptions;
389     getVersionInfo(versionInfoData.get(), "FileOpenName").split('|', descriptions);
390
391     Vector<MimeClassInfo> mimes(types.size());
392     for (size_t i = 0; i < types.size(); i++) {
393         String type = types[i].lower();
394         String description = i < descriptions.size() ? descriptions[i] : "";
395         String extensionList = i < extensionLists.size() ? extensionLists[i] : "";
396
397         Vector<String> extensionsVector;
398         extensionList.split(',', extensionsVector);
399
400         // Get rid of the extension list that may be at the end of the description string.
401         int pos = description.find("(*");
402         if (pos != -1) {
403             // There might be a space that we need to get rid of.
404             if (pos > 1 && description[pos - 1] == ' ')
405                 pos--;
406             description = description.left(pos);
407         }
408
409         mimes[i].type = type;
410         mimes[i].desc = description;
411         mimes[i].extensions.swap(extensionsVector);
412     }
413
414     plugin.path = pluginPath;
415     plugin.info.desc = description;
416     plugin.info.name = name;
417     plugin.info.file = pathGetFileName(pluginPath);
418     plugin.info.mimes.swap(mimes);
419     plugin.fileVersion = fileVersion(info->dwFileVersionLS, info->dwFileVersionMS);
420
421     return true;
422 }
423
424 static bool isOldWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
425 {
426     return equalIgnoringCase(plugin.info.file, "npdsplay.dll");
427 }
428
429 static bool isNewWindowsMediaPlayerPlugin(const PluginInfoStore::Plugin& plugin)
430 {
431     return equalIgnoringCase(plugin.info.file, "np-mswmp.dll");
432 }
433
434 bool PluginInfoStore::shouldUsePlugin(const Plugin& plugin)
435 {
436     // FIXME: We should prefer a newer version of a plugin to an older version, rather than loading
437     // both. <http://webkit.org/b/49075>
438
439     if (plugin.info.name == "Citrix ICA Client") {
440         // The Citrix ICA Client plug-in requires a Mozilla-based browser; see <rdar://6418681>.
441         return false;
442     }
443
444     if (plugin.info.name == "Silverlight Plug-In") {
445         // workaround for <rdar://5557379> Crash in Silverlight when opening microsoft.com.
446         // the latest 1.0 version of Silverlight does not reproduce this crash, so allow it
447         // and any newer versions
448         static const uint64_t minimumRequiredVersion = fileVersion(0x51BE0000, 0x00010000);
449         return plugin.fileVersion >= minimumRequiredVersion;
450     }
451
452     if (equalIgnoringCase(plugin.info.file, "npmozax.dll")) {
453         // Bug 15217: Mozilla ActiveX control complains about missing xpcom_core.dll
454         return false;
455     }
456
457     if (plugin.info.name == "Yahoo Application State Plugin") {
458         // https://bugs.webkit.org/show_bug.cgi?id=26860
459         // Bug in Yahoo Application State plug-in earlier than 1.0.0.6 leads to heap corruption.
460         static const uint64_t minimumRequiredVersion = fileVersion(0x00000006, 0x00010000);
461         return plugin.fileVersion >= minimumRequiredVersion;
462     }
463
464     if (isOldWindowsMediaPlayerPlugin(plugin)) {
465         // Don't load the old Windows Media Player plugin if we've already loaded the new Windows
466         // Media Player plugin.
467         for (size_t i = 0; i < m_plugins.size(); ++i) {
468             if (!isNewWindowsMediaPlayerPlugin(m_plugins[i]))
469                 continue;
470             return false;
471         }
472         return true;
473     }
474
475     if (isNewWindowsMediaPlayerPlugin(plugin)) {
476         // Unload the old Windows Media Player plugin if we've already loaded it.
477         for (size_t i = 0; i < m_plugins.size(); ++i) {
478             if (!isOldWindowsMediaPlayerPlugin(m_plugins[i]))
479                 continue;
480             m_plugins.remove(i);
481         }
482         return true;
483     }
484
485     return true;
486 }
487
488 } // namespace WebKit