1 /*
2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "PluginDatabase.h"
29
30 #include "Frame.h"
31 #include "KURL.h"
32 #include "PluginPackage.h"
33 #include <stdlib.h>
34
35 #if PLATFORM(ANDROID)
36 #include "JavaSharedClient.h"
37 #include "PluginClient.h"
38 #endif
39
40 namespace WebCore {
41
42 typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap;
43
installedPlugins(bool populate)44 PluginDatabase* PluginDatabase::installedPlugins(bool populate)
45 {
46 static PluginDatabase* plugins = 0;
47
48 if (!plugins) {
49 plugins = new PluginDatabase;
50
51 if (populate) {
52 plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories());
53 plugins->refresh();
54 }
55 }
56
57 return plugins;
58 }
59
isMIMETypeRegistered(const String & mimeType)60 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType)
61 {
62 if (mimeType.isNull())
63 return false;
64 if (m_registeredMIMETypes.contains(mimeType))
65 return true;
66 // No plugin was found, try refreshing the database and searching again
67 return (refresh() && m_registeredMIMETypes.contains(mimeType));
68 }
69
addExtraPluginDirectory(const String & directory)70 void PluginDatabase::addExtraPluginDirectory(const String& directory)
71 {
72 m_pluginDirectories.append(directory);
73 refresh();
74 }
75
refresh()76 bool PluginDatabase::refresh()
77 {
78 bool pluginSetChanged = false;
79
80 if (!m_plugins.isEmpty()) {
81 PluginSet pluginsToUnload;
82 getDeletedPlugins(pluginsToUnload);
83
84 // Unload plugins
85 PluginSet::const_iterator end = pluginsToUnload.end();
86 for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it)
87 remove(it->get());
88
89 pluginSetChanged = !pluginsToUnload.isEmpty();
90 }
91
92 HashSet<String> paths;
93 getPluginPathsInDirectories(paths);
94
95 HashMap<String, time_t> pathsWithTimes;
96
97 // We should only skip unchanged files if we didn't remove any plugins above. If we did remove
98 // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions
99 // of RealPlayer installed and just removed the newer one, we'll pick up the older one.
100 bool shouldSkipUnchangedFiles = !pluginSetChanged;
101
102 HashSet<String>::const_iterator pathsEnd = paths.end();
103 for (HashSet<String>::const_iterator it = paths.begin(); it != pathsEnd; ++it) {
104 time_t lastModified;
105 if (!getFileModificationTime(*it, lastModified))
106 continue;
107
108 pathsWithTimes.add(*it, lastModified);
109
110 // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything.
111 if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified)
112 continue;
113
114 if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) {
115 ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified);
116 remove(oldPackage.get());
117 }
118
119 RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified);
120 if (package && add(package.release()))
121 pluginSetChanged = true;
122 }
123
124 // Cache all the paths we found with their timestamps for next time.
125 pathsWithTimes.swap(m_pluginPathsWithTimes);
126
127 if (!pluginSetChanged)
128 return false;
129
130 m_registeredMIMETypes.clear();
131
132 // Register plug-in MIME types
133 PluginSet::const_iterator end = m_plugins.end();
134 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
135 // Get MIME types
136 MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin();
137 MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end();
138 for (; map_it != map_end; ++map_it)
139 m_registeredMIMETypes.add(map_it->first);
140 }
141
142 return true;
143 }
144
plugins() const145 Vector<PluginPackage*> PluginDatabase::plugins() const
146 {
147 Vector<PluginPackage*> result;
148
149 PluginSet::const_iterator end = m_plugins.end();
150 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it)
151 result.append((*it).get());
152
153 return result;
154 }
155
preferredPluginCompare(const void * a,const void * b)156 int PluginDatabase::preferredPluginCompare(const void* a, const void* b)
157 {
158 PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a);
159 PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b);
160
161 return pluginA->compare(*pluginB);
162 }
163
pluginForMIMEType(const String & mimeType)164 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType)
165 {
166 if (mimeType.isEmpty())
167 return 0;
168
169 String key = mimeType.lower();
170 PluginSet::const_iterator end = m_plugins.end();
171 PluginPackage* preferredPlugin = m_preferredPlugins.get(key).get();
172 if (preferredPlugin
173 && preferredPlugin->isEnabled()
174 && preferredPlugin->mimeToDescriptions().contains(key)) {
175 return preferredPlugin;
176 }
177
178 Vector<PluginPackage*, 2> pluginChoices;
179
180 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
181 PluginPackage* plugin = (*it).get();
182
183 if (!plugin->isEnabled())
184 continue;
185
186 if (plugin->mimeToDescriptions().contains(key))
187 pluginChoices.append(plugin);
188 }
189
190 if (pluginChoices.isEmpty())
191 return 0;
192
193 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
194
195 return pluginChoices[0];
196 }
197
MIMETypeForExtension(const String & extension) const198 String PluginDatabase::MIMETypeForExtension(const String& extension) const
199 {
200 if (extension.isEmpty())
201 return String();
202
203 PluginSet::const_iterator end = m_plugins.end();
204 String mimeType;
205 Vector<PluginPackage*, 2> pluginChoices;
206 HashMap<PluginPackage*, String> mimeTypeForPlugin;
207
208 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
209 if (!(*it)->isEnabled())
210 continue;
211
212 MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end();
213
214 for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) {
215 mimeType = mime_it->first;
216 PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType).get();
217 const Vector<String>& extensions = mime_it->second;
218 bool foundMapping = false;
219 for (unsigned i = 0; i < extensions.size(); i++) {
220 if (equalIgnoringCase(extensions[i], extension)) {
221 PluginPackage* plugin = (*it).get();
222
223 if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin))
224 return mimeType;
225
226 pluginChoices.append(plugin);
227 mimeTypeForPlugin.add(plugin, mimeType);
228 foundMapping = true;
229 break;
230 }
231 }
232 if (foundMapping)
233 break;
234 }
235 }
236
237 if (pluginChoices.isEmpty())
238 return String();
239
240 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
241
242 return mimeTypeForPlugin.get(pluginChoices[0]);
243 }
244
findPlugin(const KURL & url,String & mimeType)245 PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType)
246 {
247 PluginPackage* plugin = pluginForMIMEType(mimeType);
248 String filename = url.string();
249
250 if (!plugin) {
251 String filename = url.lastPathComponent();
252 if (!filename.endsWith("/")) {
253 int extensionPos = filename.reverseFind('.');
254 if (extensionPos != -1) {
255 String extension = filename.substring(extensionPos + 1);
256
257 mimeType = MIMETypeForExtension(extension);
258 plugin = pluginForMIMEType(mimeType);
259 }
260 }
261 }
262
263 // FIXME: if no plugin could be found, query Windows for the mime type
264 // corresponding to the extension.
265
266 return plugin;
267 }
268
setPreferredPluginForMIMEType(const String & mimeType,PluginPackage * plugin)269 void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin)
270 {
271 if (!plugin || plugin->mimeToExtensions().contains(mimeType))
272 m_preferredPlugins.set(mimeType.lower(), plugin);
273 }
274
getDeletedPlugins(PluginSet & plugins) const275 void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const
276 {
277 PluginSet::const_iterator end = m_plugins.end();
278 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
279 if (!fileExists((*it)->path()))
280 plugins.add(*it);
281 }
282 }
283
add(PassRefPtr<PluginPackage> prpPackage)284 bool PluginDatabase::add(PassRefPtr<PluginPackage> prpPackage)
285 {
286 ASSERT_ARG(prpPackage, prpPackage);
287
288 RefPtr<PluginPackage> package = prpPackage;
289
290 if (!m_plugins.add(package).second)
291 return false;
292
293 m_pluginsByPath.add(package->path(), package);
294 return true;
295 }
296
remove(PluginPackage * package)297 void PluginDatabase::remove(PluginPackage* package)
298 {
299 MIMEToExtensionsMap::const_iterator it = package->mimeToExtensions().begin();
300 MIMEToExtensionsMap::const_iterator end = package->mimeToExtensions().end();
301 for ( ; it != end; ++it) {
302 PluginPackageByNameMap::iterator packageInMap = m_preferredPlugins.find(it->first);
303 if (packageInMap != m_preferredPlugins.end() && packageInMap->second == package)
304 m_preferredPlugins.remove(packageInMap);
305 }
306
307 m_plugins.remove(package);
308 m_pluginsByPath.remove(package->path());
309 }
310
clear()311 void PluginDatabase::clear()
312 {
313 m_plugins.clear();
314 m_pluginsByPath.clear();
315 m_pluginPathsWithTimes.clear();
316 m_registeredMIMETypes.clear();
317 m_preferredPlugins.clear();
318 }
319
320 #if !PLATFORM(WIN_OS) || PLATFORM(WX)
321 // For Safari/Win the following three methods are implemented
322 // in PluginDatabaseWin.cpp, but if we can use WebCore constructs
323 // for the logic we should perhaps move it here under XP_WIN?
324
defaultPluginDirectories()325 Vector<String> PluginDatabase::defaultPluginDirectories()
326 {
327 Vector<String> paths;
328
329 // Add paths specific to each platform
330 #if defined(XP_UNIX)
331 String userPluginPath = homeDirectoryPath();
332 userPluginPath.append(String("/.mozilla/plugins"));
333 paths.append(userPluginPath);
334
335 userPluginPath = homeDirectoryPath();
336 userPluginPath.append(String("/.netscape/plugins"));
337 paths.append(userPluginPath);
338
339 paths.append("/usr/lib/browser/plugins");
340 paths.append("/usr/local/lib/mozilla/plugins");
341 paths.append("/usr/lib/firefox/plugins");
342 paths.append("/usr/lib64/browser-plugins");
343 paths.append("/usr/lib/browser-plugins");
344 paths.append("/usr/lib/mozilla/plugins");
345 paths.append("/usr/local/netscape/plugins");
346 paths.append("/opt/mozilla/plugins");
347 paths.append("/opt/mozilla/lib/plugins");
348 paths.append("/opt/netscape/plugins");
349 paths.append("/opt/netscape/communicator/plugins");
350 paths.append("/usr/lib/netscape/plugins");
351 paths.append("/usr/lib/netscape/plugins-libc5");
352 paths.append("/usr/lib/netscape/plugins-libc6");
353 paths.append("/usr/lib64/netscape/plugins");
354 paths.append("/usr/lib64/mozilla/plugins");
355
356 String mozHome(getenv("MOZILLA_HOME"));
357 mozHome.append("/plugins");
358 paths.append(mozHome);
359
360 Vector<String> mozPaths;
361 String mozPath(getenv("MOZ_PLUGIN_PATH"));
362 mozPath.split(UChar(':'), /* allowEmptyEntries */ false, mozPaths);
363 paths.append(mozPaths);
364 #elif defined(XP_MACOSX)
365 String userPluginPath = homeDirectoryPath();
366 userPluginPath.append(String("/Library/Internet Plug-Ins"));
367 paths.append(userPluginPath);
368 paths.append("/Library/Internet Plug-Ins");
369 #elif defined(XP_WIN)
370 String userPluginPath = homeDirectoryPath();
371 userPluginPath.append(String("\\Application Data\\Mozilla\\plugins"));
372 paths.append(userPluginPath);
373 #endif
374
375 // Add paths specific to each port
376 #if PLATFORM(QT)
377 Vector<String> qtPaths;
378 String qtPath(getenv("QTWEBKIT_PLUGIN_PATH"));
379 qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths);
380 paths.append(qtPaths);
381 #endif
382
383 #if PLATFORM(ANDROID)
384 if (android::JavaSharedClient::GetPluginClient())
385 return android::JavaSharedClient::GetPluginClient()->getPluginDirectories();
386 #endif
387
388 return paths;
389 }
390
isPreferredPluginDirectory(const String & path)391 bool PluginDatabase::isPreferredPluginDirectory(const String& path)
392 {
393 String preferredPath = homeDirectoryPath();
394
395 #if defined(XP_UNIX)
396 preferredPath.append(String("/.mozilla/plugins"));
397 #elif defined(XP_MACOSX)
398 preferredPath.append(String("/Library/Internet Plug-Ins"));
399 #elif defined(XP_WIN)
400 preferredPath.append(String("\\Application Data\\Mozilla\\plugins"));
401 #endif
402
403 // TODO: We should normalize the path before doing a comparison.
404 return path == preferredPath;
405 }
406
getPluginPathsInDirectories(HashSet<String> & paths) const407 void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const
408 {
409 // FIXME: This should be a case insensitive set.
410 HashSet<String> uniqueFilenames;
411
412 #if defined(XP_UNIX) || defined(ANDROID)
413 String fileNameFilter("*.so");
414 #else
415 String fileNameFilter("");
416 #endif
417
418 Vector<String>::const_iterator dirsEnd = m_pluginDirectories.end();
419 for (Vector<String>::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) {
420 Vector<String> pluginPaths = listDirectory(*dIt, fileNameFilter);
421 Vector<String>::const_iterator pluginsEnd = pluginPaths.end();
422 for (Vector<String>::const_iterator pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) {
423 if (!fileExists(*pIt))
424 continue;
425
426 paths.add(*pIt);
427 }
428 }
429 }
430
431 #endif // !PLATFORM(WIN_OS)
432
433 }
434