be8a2534bec44bec965376584f64ad28738db896
[WebKit-https.git] / WebCore / plugins / win / PluginDatabaseWin.cpp
1 /*
2  * Copyright (C) 2006, 2007 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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "PluginDatabase.h"
28
29 #include "PluginPackage.h"
30 #include "PluginView.h"
31 #include "Frame.h"
32 #include <windows.h>
33 #include <shlwapi.h>
34
35 namespace WebCore {
36
37 PluginDatabase* PluginDatabase::installedPlugins()
38 {
39     static PluginDatabase* plugins = 0;
40     
41     if (!plugins) {
42         plugins = new PluginDatabase;
43         plugins->setPluginPaths(PluginDatabase::defaultPluginPaths());
44         plugins->refresh();
45     }
46
47     return plugins;
48 }
49
50 void PluginDatabase::addExtraPluginPath(const String& path)
51 {
52     m_pluginPaths.append(path);
53     refresh();
54 }
55
56 bool PluginDatabase::refresh()
57 {   
58     PluginSet newPlugins;
59
60     bool pluginSetChanged = false;
61
62     // Create a new set of plugins
63     newPlugins = getPluginsInPaths();
64
65     if (!m_plugins.isEmpty()) {
66         m_registeredMIMETypes.clear();
67
68         PluginSet pluginsToUnload = m_plugins;
69
70         PluginSet::const_iterator end = newPlugins.end();
71         for (PluginSet::const_iterator it = newPlugins.begin(); it != end; ++it)
72             pluginsToUnload.remove(*it);
73
74         end = m_plugins.end();
75         for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it)
76             newPlugins.remove(*it);
77
78         // Unload plugins
79         end = pluginsToUnload.end();
80         for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it)
81             m_plugins.remove(*it);
82
83         // Add new plugins
84         end = newPlugins.end();
85         for (PluginSet::const_iterator it = newPlugins.begin(); it != end; ++it)
86             m_plugins.add(*it);
87
88         pluginSetChanged = !pluginsToUnload.isEmpty() || !newPlugins.isEmpty();
89     } else {
90         m_plugins = newPlugins;
91         PluginSet::const_iterator end = newPlugins.end();
92         for (PluginSet::const_iterator it = newPlugins.begin(); it != end; ++it)
93             m_plugins.add(*it);
94
95         pluginSetChanged = !newPlugins.isEmpty();
96     }
97
98     // Register plug-in MIME types
99     PluginSet::const_iterator end = m_plugins.end();
100     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
101         // Get MIME types
102         MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end();
103         for (MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin(); map_it != map_end; ++map_it) {
104             m_registeredMIMETypes.add(map_it->first);
105         }
106     }
107
108     return pluginSetChanged;
109 }
110
111 Vector<PluginPackage*> PluginDatabase::plugins() const
112 {
113     Vector<PluginPackage*> result;
114
115     PluginSet::const_iterator end = m_plugins.end();
116     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it)
117         result.append((*it).get());
118
119     return result;
120 }
121
122 static inline void addPluginsFromRegistry(HKEY rootKey, PluginSet& plugins)
123 {
124     HKEY key;
125     HRESULT result = RegOpenKeyExW(rootKey, L"Software\\MozillaPlugins", 0, KEY_ENUMERATE_SUB_KEYS, &key);
126
127     if (result != ERROR_SUCCESS)
128         return;
129
130     wchar_t name[128];
131     FILETIME lastModified;
132
133     // Enumerate subkeys
134     for (int i = 0;; i++) {
135         DWORD nameLen = _countof(name);
136         result = RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
137
138         if (result != ERROR_SUCCESS)
139             break;
140
141         WCHAR pathStr[_MAX_PATH];
142         DWORD pathStrSize = sizeof(pathStr);
143         DWORD type;
144
145         result = SHGetValue(key, name, TEXT("Path"), &type, (LPBYTE)pathStr, &pathStrSize);
146         if (result != ERROR_SUCCESS || type != REG_SZ)
147             continue;
148
149         WIN32_FILE_ATTRIBUTE_DATA attributes;
150         if (GetFileAttributesEx(pathStr, GetFileExInfoStandard, &attributes) == 0)
151             continue;
152
153         PluginPackage* package = PluginPackage::createPackage(String(pathStr, pathStrSize / sizeof(WCHAR) - 1), attributes.ftLastWriteTime);
154
155         if (package)
156             plugins.add(package);
157     }
158
159     RegCloseKey(key);
160 }
161
162 PluginSet PluginDatabase::getPluginsInPaths() const
163 {
164     // FIXME: This should be a case insensitive set.
165     HashSet<String> uniqueFilenames;
166     PluginSet plugins;
167
168     HANDLE hFind = INVALID_HANDLE_VALUE;
169     WIN32_FIND_DATAW findFileData;
170
171     PluginPackage* oldWMPPlugin = 0;
172     PluginPackage* newWMPPlugin = 0;
173
174     Vector<String>::const_iterator end = m_pluginPaths.end();
175     for (Vector<String>::const_iterator it = m_pluginPaths.begin(); it != end; ++it) {
176         String pattern = *it + "\\*";
177
178         hFind = FindFirstFileW(pattern.charactersWithNullTermination(), &findFileData);
179
180         if (hFind == INVALID_HANDLE_VALUE)
181             continue;
182
183         do {
184             if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
185                 continue;
186
187             String filename = String(findFileData.cFileName, wcslen(findFileData.cFileName));
188             if ((!filename.startsWith("np", false) || !filename.endsWith("dll", false)) &&
189                 (!equalIgnoringCase(filename, "Plugin.dll") || !it->endsWith("Shockwave 10", false)))
190                 continue;
191
192             String fullPath = *it + "\\" + filename;
193             if (!uniqueFilenames.add(fullPath).second)
194                 continue;
195         
196             PluginPackage* pluginPackage = PluginPackage::createPackage(fullPath, findFileData.ftLastWriteTime);
197
198             if (pluginPackage) {
199                 plugins.add(pluginPackage);
200
201                 if (equalIgnoringCase(filename, "npdsplay.dll"))
202                     oldWMPPlugin = pluginPackage;
203                 else if (equalIgnoringCase(filename, "np-mswmp.dll"))
204                     newWMPPlugin = pluginPackage;
205             }
206
207         } while (FindNextFileW(hFind, &findFileData) != 0);
208
209         FindClose(hFind);
210     }
211
212     addPluginsFromRegistry(HKEY_LOCAL_MACHINE, plugins);
213     addPluginsFromRegistry(HKEY_CURRENT_USER, plugins);
214
215     // If both the old and new WMP plugin are present in the plugins set, 
216     // we remove the old one so we don't end up choosing the old one.
217     if (oldWMPPlugin && newWMPPlugin)
218         plugins.remove(oldWMPPlugin);
219
220     return plugins;
221 }
222
223 static inline Vector<int> parseVersionString(const String& versionString)
224 {
225     Vector<int> version;
226
227     unsigned startPos = 0;
228     unsigned endPos;
229     
230     while (startPos < versionString.length()) {
231         for (endPos = startPos; endPos < versionString.length(); ++endPos)
232             if (versionString[endPos] == '.' || versionString[endPos] == '_')
233                 break;
234
235         int versionComponent = versionString.substring(startPos, endPos - startPos).toInt();
236         version.append(versionComponent);
237
238         startPos = endPos + 1;
239     }
240
241     return version;
242 }
243
244 // This returns whether versionA is higher than versionB
245 static inline bool compareVersions(const Vector<int>& versionA, const Vector<int>& versionB)
246 {
247     for (unsigned i = 0; i < versionA.size(); i++) {
248         if (i >= versionB.size())
249             return true;
250
251         if (versionA[i] > versionB[i])
252             return true;
253         else if (versionA[i] < versionB[i])
254             return false;
255     }
256
257     // If we come here, the versions are either the same or versionB has an extra component, just return false
258     return false;
259 }
260
261 static inline void addMozillaPluginPaths(Vector<String>& paths)
262 {
263     // Enumerate all Mozilla plugin directories in the registry
264     HKEY key;
265     LONG result;
266     
267     result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Mozilla"), 0, KEY_READ, &key);
268     if (result == ERROR_SUCCESS) {
269         WCHAR name[128];
270         FILETIME lastModified;
271
272         // Enumerate subkeys
273         for (int i = 0;; i++) {
274             DWORD nameLen = sizeof(name) / sizeof(WCHAR);
275             result = RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
276
277             if (result != ERROR_SUCCESS)
278                 break;
279
280             String extensionsPath = String(name, nameLen) + "\\Extensions";
281             HKEY extensionsKey;
282
283             // Try opening the key
284             result = RegOpenKeyEx(key, extensionsPath.charactersWithNullTermination(), 0, KEY_READ, &extensionsKey);
285
286             if (result == ERROR_SUCCESS) {
287                 // Now get the plugins path
288                 WCHAR pluginsPathStr[_MAX_PATH];
289                 DWORD pluginsPathSize = sizeof(pluginsPathStr);
290                 DWORD type;
291
292                 result = RegQueryValueEx(extensionsKey, TEXT("Plugins"), 0, &type, (LPBYTE)&pluginsPathStr, &pluginsPathSize);
293
294                 if (result == ERROR_SUCCESS && type == REG_SZ)
295                     paths.append(String(pluginsPathStr, pluginsPathSize / sizeof(WCHAR) - 1));
296
297                 RegCloseKey(extensionsKey);
298             }
299         }
300         
301         RegCloseKey(key);
302     }
303 }
304
305 static inline void addWindowsMediaPlayerPluginPath(Vector<String>& paths)
306 {
307     // The new WMP Firefox plugin is installed in \PFiles\Plugins if it can't find any Firefox installs
308     WCHAR pluginDirectoryStr[_MAX_PATH + 1];
309     DWORD pluginDirectorySize = ::ExpandEnvironmentStringsW(TEXT("%SYSTEMDRIVE%\\PFiles\\Plugins"), pluginDirectoryStr, _countof(pluginDirectoryStr));
310
311     if (pluginDirectorySize > 0 && pluginDirectorySize <= _countof(pluginDirectoryStr))
312         paths.append(String(pluginDirectoryStr, pluginDirectorySize - 1));
313
314     DWORD type;
315     WCHAR installationDirectoryStr[_MAX_PATH];
316     DWORD installationDirectorySize = sizeof(installationDirectoryStr);
317
318     HRESULT result = SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\MediaPlayer"), TEXT("Installation Directory"), &type, (LPBYTE)&installationDirectoryStr, &installationDirectorySize);
319
320     if (result == ERROR_SUCCESS && type == REG_SZ)
321         paths.append(String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1));
322 }
323
324 static inline void addQuickTimePluginPath(Vector<String>& paths)
325 {
326     DWORD type;
327     WCHAR installationDirectoryStr[_MAX_PATH];
328     DWORD installationDirectorySize = sizeof(installationDirectoryStr);
329
330     HRESULT result = SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Apple Computer, Inc.\\QuickTime"), TEXT("InstallDir"), &type, (LPBYTE)&installationDirectoryStr, &installationDirectorySize);
331
332     if (result == ERROR_SUCCESS && type == REG_SZ) {
333         String pluginDir = String(installationDirectoryStr, installationDirectorySize / sizeof(WCHAR) - 1) + "\\plugins";
334         paths.append(pluginDir);
335     }
336 }
337
338 static inline void addAdobeAcrobatPluginPath(Vector<String>& paths)
339 {
340     HKEY key;
341     HRESULT result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Adobe\\Acrobat Reader"), 0, KEY_READ, &key);
342     if (result != ERROR_SUCCESS)
343         return;
344
345     WCHAR name[128];
346     FILETIME lastModified;
347
348     Vector<int> latestAcrobatVersion;
349     String latestAcrobatVersionString;
350
351     // Enumerate subkeys
352     for (int i = 0;; i++) {
353         DWORD nameLen = sizeof(name) / sizeof(WCHAR);
354         result = RegEnumKeyExW(key, i, name, &nameLen, 0, 0, 0, &lastModified);
355
356         if (result != ERROR_SUCCESS)
357             break;
358
359         Vector<int> acrobatVersion = parseVersionString(String(name, nameLen));
360         if (compareVersions(acrobatVersion, latestAcrobatVersion)) {
361             latestAcrobatVersion = acrobatVersion;
362             latestAcrobatVersionString = String(name, nameLen);
363         }
364     }
365
366     if (!latestAcrobatVersionString.isNull()) {
367         DWORD type;
368         WCHAR acrobatInstallPathStr[_MAX_PATH];
369         DWORD acrobatInstallPathSize = sizeof(acrobatInstallPathStr);
370
371         String acrobatPluginKeyPath = "Software\\Adobe\\Acrobat Reader\\" + latestAcrobatVersionString + "\\InstallPath";
372         result = SHGetValue(HKEY_LOCAL_MACHINE, acrobatPluginKeyPath.charactersWithNullTermination(), 0, &type, (LPBYTE)acrobatInstallPathStr, &acrobatInstallPathSize);
373
374         if (result == ERROR_SUCCESS) {
375             String acrobatPluginPath = String(acrobatInstallPathStr, acrobatInstallPathSize / sizeof(WCHAR) - 1) + "\\browser";
376             paths.append(acrobatPluginPath);
377         }
378     }
379
380     RegCloseKey(key);
381 }
382
383 static inline String safariPluginsPath()
384 {
385     WCHAR moduleFileNameStr[_MAX_PATH];
386     static String pluginsPath;
387     static bool cachedPluginPath = false;
388
389     if (!cachedPluginPath) {
390         cachedPluginPath = true;
391
392         int moduleFileNameLen = GetModuleFileName(0, moduleFileNameStr, _MAX_PATH);
393
394         if (!moduleFileNameLen || moduleFileNameLen == _MAX_PATH)
395             goto exit;
396
397         if (!PathRemoveFileSpec(moduleFileNameStr))
398             goto exit;
399
400         pluginsPath = String(moduleFileNameStr) + "\\Plugins";
401     }
402 exit:
403     return pluginsPath;
404 }
405
406 static inline void addMacromediaPluginPaths(Vector<String>& paths)
407 {
408     WCHAR systemDirectoryStr[MAX_PATH];
409
410     if (GetSystemDirectory(systemDirectoryStr, _countof(systemDirectoryStr)) == 0)
411         return;
412
413     WCHAR macromediaDirectoryStr[MAX_PATH];
414
415     PathCombine(macromediaDirectoryStr, systemDirectoryStr, TEXT("macromed\\Flash"));
416     paths.append(macromediaDirectoryStr);
417
418     PathCombine(macromediaDirectoryStr, systemDirectoryStr, TEXT("macromed\\Shockwave 10"));
419     paths.append(macromediaDirectoryStr);
420 }
421
422 Vector<String> PluginDatabase::defaultPluginPaths()
423 {
424     Vector<String> paths;
425     String ourPath = safariPluginsPath();
426
427     if (!ourPath.isNull())
428         paths.append(ourPath);
429     addQuickTimePluginPath(paths);
430     addAdobeAcrobatPluginPath(paths);
431     addMozillaPluginPaths(paths);
432     addWindowsMediaPlayerPluginPath(paths);
433     addMacromediaPluginPaths(paths);
434
435     return paths;
436 }
437
438 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType)
439 {
440     if (mimeType.isNull())
441         return false;
442     if (m_registeredMIMETypes.contains(mimeType))
443         return true;
444     // No plugin was found, try refreshing the database and searching again
445     return (refresh() && m_registeredMIMETypes.contains(mimeType));
446 }
447
448 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType)
449 {
450     if (mimeType.isEmpty())
451         return 0;
452
453     String key = mimeType.lower();
454     String ourPath = safariPluginsPath();
455     PluginPackage* plugin = 0;
456     PluginSet::const_iterator end = m_plugins.end();
457
458     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
459         if ((*it)->mimeToDescriptions().contains(key)) {
460             plugin = (*it).get();
461             // prefer plugins in our own plugins directory
462             if (plugin->parentDirectory() == ourPath)
463                 break;
464         }
465     }
466
467     return plugin;
468 }
469
470 String PluginDatabase::MIMETypeForExtension(const String& extension) const
471 {
472     if (extension.isEmpty())
473         return String();
474
475     PluginSet::const_iterator end = m_plugins.end();
476     String ourPath = safariPluginsPath();
477     String mimeType;
478     PluginPackage* plugin = 0;
479
480     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
481         MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end();
482
483         for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) {
484             const Vector<String>& extensions = mime_it->second;
485             for (unsigned i = 0; i < extensions.size(); i++) {
486                 if (equalIgnoringCase(extensions[i], extension)) {
487                     mimeType = mime_it->first;
488                     plugin = (*it).get();
489                     // prefer plugins in our own plugins directory
490                     if (plugin->parentDirectory() == ourPath)
491                         break;
492                 }
493             }
494         }
495     }
496
497     return mimeType;
498 }
499
500 PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType)
501 {   
502     PluginPackage* plugin = pluginForMIMEType(mimeType);
503     String filename = url.string();
504     
505     if (!plugin) {
506         String filename = url.lastPathComponent();
507         if (!filename.endsWith("/")) {
508             int extensionPos = filename.reverseFind('.');
509             if (extensionPos != -1) {
510                 String extension = filename.substring(extensionPos + 1);
511
512                 mimeType = MIMETypeForExtension(extension);
513                 plugin = pluginForMIMEType(mimeType);
514             }
515         }
516     }
517
518     // FIXME: if no plugin could be found, query Windows for the mime type 
519     // corresponding to the extension.
520
521     return plugin;
522 }
523
524 PluginView* PluginDatabase::createPluginView(Frame* parentFrame, const IntSize& size, Element* element, const KURL& url, const Vector<String>& paramNames, const Vector<String>& paramValues, const String& mimeType, bool loadManually)
525 {
526     // if we fail to find a plugin for this MIME type, findPlugin will search for
527     // a plugin by the file extension and update the MIME type, so pass a mutable String
528     String mimeTypeCopy = mimeType;
529     PluginPackage* plugin = findPlugin(url, mimeTypeCopy);
530     
531     // No plugin was found, try refreshing the database and searching again
532     if (!plugin && refresh()) {
533         mimeTypeCopy = mimeType;
534         plugin = findPlugin(url, mimeTypeCopy);
535     }
536         
537     return new PluginView(parentFrame, size, plugin, element, url, paramNames, paramValues, mimeTypeCopy, loadManually);
538 }
539
540 }