2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "PluginDatabase.h"
32 #include "PluginPackage.h"
33 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
34 #include "FileSystem.h"
37 #include <wtf/text/CString.h>
41 typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap;
43 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
44 static const size_t maximumPersistentPluginMetadataCacheSize = 32768;
46 static bool gPersistentPluginMetadataCacheIsEnabled;
48 String& persistentPluginMetadataCachePath()
50 DEFINE_STATIC_LOCAL(String, cachePath, ());
55 PluginDatabase::PluginDatabase()
56 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
57 : m_persistentMetadataCacheIsLoaded(false)
62 PluginDatabase* PluginDatabase::installedPlugins(bool populate)
64 static PluginDatabase* plugins = 0;
67 plugins = new PluginDatabase;
70 plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories());
78 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType)
80 if (mimeType.isNull())
82 if (m_registeredMIMETypes.contains(mimeType))
84 // No plugin was found, try refreshing the database and searching again
85 return (refresh() && m_registeredMIMETypes.contains(mimeType));
88 void PluginDatabase::addExtraPluginDirectory(const String& directory)
90 m_pluginDirectories.append(directory);
94 bool PluginDatabase::refresh()
96 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
97 if (!m_persistentMetadataCacheIsLoaded)
98 loadPersistentMetadataCache();
100 bool pluginSetChanged = false;
102 if (!m_plugins.isEmpty()) {
103 PluginSet pluginsToUnload;
104 getDeletedPlugins(pluginsToUnload);
107 PluginSet::const_iterator end = pluginsToUnload.end();
108 for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it)
111 pluginSetChanged = !pluginsToUnload.isEmpty();
114 HashSet<String> paths;
115 getPluginPathsInDirectories(paths);
117 HashMap<String, time_t> pathsWithTimes;
119 // We should only skip unchanged files if we didn't remove any plugins above. If we did remove
120 // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions
121 // of RealPlayer installed and just removed the newer one, we'll pick up the older one.
122 bool shouldSkipUnchangedFiles = !pluginSetChanged;
124 HashSet<String>::const_iterator pathsEnd = paths.end();
125 for (HashSet<String>::const_iterator it = paths.begin(); it != pathsEnd; ++it) {
127 if (!getFileModificationTime(*it, lastModified))
130 pathsWithTimes.add(*it, lastModified);
132 // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything.
133 if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified)
136 if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) {
137 ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified);
138 remove(oldPackage.get());
141 RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified);
142 if (package && add(package.release()))
143 pluginSetChanged = true;
146 // Cache all the paths we found with their timestamps for next time.
147 pathsWithTimes.swap(m_pluginPathsWithTimes);
149 if (!pluginSetChanged)
152 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
153 updatePersistentMetadataCache();
156 m_registeredMIMETypes.clear();
158 // Register plug-in MIME types
159 PluginSet::const_iterator end = m_plugins.end();
160 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
162 MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin();
163 MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end();
164 for (; map_it != map_end; ++map_it)
165 m_registeredMIMETypes.add(map_it->first);
171 Vector<PluginPackage*> PluginDatabase::plugins() const
173 Vector<PluginPackage*> result;
175 PluginSet::const_iterator end = m_plugins.end();
176 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it)
177 result.append((*it).get());
182 int PluginDatabase::preferredPluginCompare(const void* a, const void* b)
184 PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a);
185 PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b);
187 return pluginA->compare(*pluginB);
190 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType)
192 if (mimeType.isEmpty())
195 String key = mimeType.lower();
196 PluginSet::const_iterator end = m_plugins.end();
197 PluginPackage* preferredPlugin = m_preferredPlugins.get(key).get();
199 && preferredPlugin->isEnabled()
200 && preferredPlugin->mimeToDescriptions().contains(key)) {
201 return preferredPlugin;
204 Vector<PluginPackage*, 2> pluginChoices;
206 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
207 PluginPackage* plugin = (*it).get();
209 if (!plugin->isEnabled())
212 if (plugin->mimeToDescriptions().contains(key)) {
213 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
214 if (!plugin->ensurePluginLoaded())
217 pluginChoices.append(plugin);
221 if (pluginChoices.isEmpty())
224 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
226 return pluginChoices[0];
229 String PluginDatabase::MIMETypeForExtension(const String& extension) const
231 if (extension.isEmpty())
234 PluginSet::const_iterator end = m_plugins.end();
236 Vector<PluginPackage*, 2> pluginChoices;
237 HashMap<PluginPackage*, String> mimeTypeForPlugin;
239 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
240 if (!(*it)->isEnabled())
243 MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end();
245 for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) {
246 mimeType = mime_it->first;
247 PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType).get();
248 const Vector<String>& extensions = mime_it->second;
249 bool foundMapping = false;
250 for (unsigned i = 0; i < extensions.size(); i++) {
251 if (equalIgnoringCase(extensions[i], extension)) {
252 PluginPackage* plugin = (*it).get();
254 if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin))
257 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
258 if (!plugin->ensurePluginLoaded())
261 pluginChoices.append(plugin);
262 mimeTypeForPlugin.add(plugin, mimeType);
272 if (pluginChoices.isEmpty())
275 qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
277 return mimeTypeForPlugin.get(pluginChoices[0]);
280 PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType)
282 if (!mimeType.isEmpty())
283 return pluginForMIMEType(mimeType);
285 String filename = url.lastPathComponent();
286 if (filename.endsWith("/"))
289 int extensionPos = filename.reverseFind('.');
290 if (extensionPos == -1)
293 String mimeTypeForExtension = MIMETypeForExtension(filename.substring(extensionPos + 1));
294 PluginPackage* plugin = pluginForMIMEType(mimeTypeForExtension);
296 // FIXME: if no plugin could be found, query Windows for the mime type
297 // corresponding to the extension.
301 mimeType = mimeTypeForExtension;
305 void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin)
307 if (!plugin || plugin->mimeToExtensions().contains(mimeType))
308 m_preferredPlugins.set(mimeType.lower(), plugin);
311 void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const
313 PluginSet::const_iterator end = m_plugins.end();
314 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
315 if (!fileExists((*it)->path()))
320 bool PluginDatabase::add(PassRefPtr<PluginPackage> prpPackage)
322 ASSERT_ARG(prpPackage, prpPackage);
324 RefPtr<PluginPackage> package = prpPackage;
326 if (!m_plugins.add(package).second)
329 m_pluginsByPath.add(package->path(), package);
333 void PluginDatabase::remove(PluginPackage* package)
335 MIMEToExtensionsMap::const_iterator it = package->mimeToExtensions().begin();
336 MIMEToExtensionsMap::const_iterator end = package->mimeToExtensions().end();
337 for ( ; it != end; ++it) {
338 PluginPackageByNameMap::iterator packageInMap = m_preferredPlugins.find(it->first);
339 if (packageInMap != m_preferredPlugins.end() && packageInMap->second == package)
340 m_preferredPlugins.remove(packageInMap);
343 m_plugins.remove(package);
344 m_pluginsByPath.remove(package->path());
347 void PluginDatabase::clear()
350 m_pluginsByPath.clear();
351 m_pluginPathsWithTimes.clear();
352 m_registeredMIMETypes.clear();
353 m_preferredPlugins.clear();
354 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
355 m_persistentMetadataCacheIsLoaded = false;
359 #if (!OS(WINCE)) && (!OS(SYMBIAN)) && (!OS(WINDOWS) || !ENABLE(NETSCAPE_PLUGIN_API))
360 // For Safari/Win the following three methods are implemented
361 // in PluginDatabaseWin.cpp, but if we can use WebCore constructs
362 // for the logic we should perhaps move it here under XP_WIN?
364 Vector<String> PluginDatabase::defaultPluginDirectories()
366 Vector<String> paths;
368 // Add paths specific to each platform
370 String userPluginPath = homeDirectoryPath();
371 userPluginPath.append(String("/.mozilla/plugins"));
372 paths.append(userPluginPath);
374 userPluginPath = homeDirectoryPath();
375 userPluginPath.append(String("/.netscape/plugins"));
376 paths.append(userPluginPath);
378 paths.append("/usr/lib/browser/plugins");
379 paths.append("/usr/local/lib/mozilla/plugins");
380 paths.append("/usr/lib/firefox/plugins");
381 paths.append("/usr/lib64/browser-plugins");
382 paths.append("/usr/lib/browser-plugins");
383 paths.append("/usr/lib/mozilla/plugins");
384 paths.append("/usr/local/netscape/plugins");
385 paths.append("/opt/mozilla/plugins");
386 paths.append("/opt/mozilla/lib/plugins");
387 paths.append("/opt/netscape/plugins");
388 paths.append("/opt/netscape/communicator/plugins");
389 paths.append("/usr/lib/netscape/plugins");
390 paths.append("/usr/lib/netscape/plugins-libc5");
391 paths.append("/usr/lib/netscape/plugins-libc6");
392 paths.append("/usr/lib64/netscape/plugins");
393 paths.append("/usr/lib64/mozilla/plugins");
394 paths.append("/usr/lib/nsbrowser/plugins");
395 paths.append("/usr/lib64/nsbrowser/plugins");
397 String mozHome(getenv("MOZILLA_HOME"));
398 mozHome.append("/plugins");
399 paths.append(mozHome);
401 Vector<String> mozPaths;
402 String mozPath(getenv("MOZ_PLUGIN_PATH"));
403 mozPath.split(UChar(':'), /* allowEmptyEntries */ false, mozPaths);
404 paths.append(mozPaths);
405 #elif defined(XP_MACOSX)
406 String userPluginPath = homeDirectoryPath();
407 userPluginPath.append(String("/Library/Internet Plug-Ins"));
408 paths.append(userPluginPath);
409 paths.append("/Library/Internet Plug-Ins");
410 #elif defined(XP_WIN)
411 String userPluginPath = homeDirectoryPath();
412 userPluginPath.append(String("\\Application Data\\Mozilla\\plugins"));
413 paths.append(userPluginPath);
416 // Add paths specific to each port
418 Vector<String> qtPaths;
419 String qtPath(qgetenv("QTWEBKIT_PLUGIN_PATH").constData());
420 qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths);
421 paths.append(qtPaths);
427 bool PluginDatabase::isPreferredPluginDirectory(const String& path)
429 String preferredPath = homeDirectoryPath();
432 preferredPath.append(String("/.mozilla/plugins"));
433 #elif defined(XP_MACOSX)
434 preferredPath.append(String("/Library/Internet Plug-Ins"));
435 #elif defined(XP_WIN)
436 preferredPath.append(String("\\Application Data\\Mozilla\\plugins"));
439 // TODO: We should normalize the path before doing a comparison.
440 return path == preferredPath;
443 void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const
445 // FIXME: This should be a case insensitive set.
446 HashSet<String> uniqueFilenames;
449 String fileNameFilter("*.so");
451 String fileNameFilter("");
454 Vector<String>::const_iterator dirsEnd = m_pluginDirectories.end();
455 for (Vector<String>::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) {
456 Vector<String> pluginPaths = listDirectory(*dIt, fileNameFilter);
457 Vector<String>::const_iterator pluginsEnd = pluginPaths.end();
458 for (Vector<String>::const_iterator pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) {
459 if (!fileExists(*pIt))
467 #endif // !OS(SYMBIAN) && !OS(WINDOWS)
469 #if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE)
471 static void fillBufferWithContentsOfFile(PlatformFileHandle file, Vector<char>& buffer)
473 size_t bufferSize = 0;
474 size_t bufferCapacity = 1024;
475 buffer.resize(bufferCapacity);
478 bufferSize += readFromFile(file, buffer.data() + bufferSize, bufferCapacity - bufferSize);
479 if (bufferSize == bufferCapacity) {
480 if (bufferCapacity < maximumPersistentPluginMetadataCacheSize) {
482 buffer.resize(bufferCapacity);
491 buffer.shrink(bufferSize);
494 static bool readUTF8String(String& resultString, char*& start, const char* end)
499 int len = strlen(start);
500 resultString = String::fromUTF8(start, len);
506 static bool readTime(time_t& resultTime, char*& start, const char* end)
508 if (start + sizeof(time_t) >= end)
511 resultTime = *reinterpret_cast_ptr<time_t*>(start);
512 start += sizeof(time_t);
517 static const char schemaVersion = '1';
518 static const char persistentPluginMetadataCacheFilename[] = "PluginMetadataCache.bin";
520 void PluginDatabase::loadPersistentMetadataCache()
522 if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty())
525 PlatformFileHandle file;
526 String absoluteCachePath = pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename);
527 file = openFile(absoluteCachePath, OpenForRead);
529 if (!isHandleValid(file))
532 // Mark cache as loaded regardless of success or failure. If
533 // there's error in the cache, we won't try to load it anymore.
534 m_persistentMetadataCacheIsLoaded = true;
536 Vector<char> fileContents;
537 fillBufferWithContentsOfFile(file, fileContents);
540 if (fileContents.size() < 2 || fileContents.first() != schemaVersion || fileContents.last() != '\0') {
541 LOG_ERROR("Unable to read plugin metadata cache: corrupt schema");
542 deleteFile(absoluteCachePath);
546 char* bufferPos = fileContents.data() + 1;
547 char* end = fileContents.data() + fileContents.size();
549 PluginSet cachedPlugins;
550 HashMap<String, time_t> cachedPluginPathsWithTimes;
551 HashMap<String, RefPtr<PluginPackage> > cachedPluginsByPath;
553 while (bufferPos < end) {
559 if (!(readUTF8String(path, bufferPos, end)
560 && readTime(lastModified, bufferPos, end)
561 && readUTF8String(name, bufferPos, end)
562 && readUTF8String(desc, bufferPos, end)
563 && readUTF8String(mimeDesc, bufferPos, end))) {
564 LOG_ERROR("Unable to read plugin metadata cache: corrupt data");
565 deleteFile(absoluteCachePath);
569 // Skip metadata that points to plugins from directories that
570 // are not part of plugin directory list anymore.
571 String pluginDirectoryName = directoryName(path);
572 if (m_pluginDirectories.find(pluginDirectoryName) == WTF::notFound)
575 RefPtr<PluginPackage> package = PluginPackage::createPackageFromCache(path, lastModified, name, desc, mimeDesc);
577 if (package && cachedPlugins.add(package).second) {
578 cachedPluginPathsWithTimes.add(package->path(), package->lastModified());
579 cachedPluginsByPath.add(package->path(), package);
583 m_plugins.swap(cachedPlugins);
584 m_pluginsByPath.swap(cachedPluginsByPath);
585 m_pluginPathsWithTimes.swap(cachedPluginPathsWithTimes);
588 static bool writeUTF8String(PlatformFileHandle file, const String& string)
590 CString utf8String = string.utf8();
591 int length = utf8String.length() + 1;
592 return writeToFile(file, utf8String.data(), length) == length;
595 static bool writeTime(PlatformFileHandle file, const time_t& time)
597 return writeToFile(file, reinterpret_cast<const char*>(&time), sizeof(time_t)) == sizeof(time_t);
600 void PluginDatabase::updatePersistentMetadataCache()
602 if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty())
605 makeAllDirectories(persistentMetadataCachePath());
606 String absoluteCachePath = pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename);
607 deleteFile(absoluteCachePath);
609 if (m_plugins.isEmpty())
612 PlatformFileHandle file;
613 file = openFile(absoluteCachePath, OpenForWrite);
615 if (!isHandleValid(file)) {
616 LOG_ERROR("Unable to open plugin metadata cache for saving");
620 char localSchemaVersion = schemaVersion;
621 if (writeToFile(file, &localSchemaVersion, 1) != 1) {
622 LOG_ERROR("Unable to write plugin metadata cache schema");
624 deleteFile(absoluteCachePath);
628 PluginSet::const_iterator end = m_plugins.end();
629 for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
630 if (!(writeUTF8String(file, (*it)->path())
631 && writeTime(file, (*it)->lastModified())
632 && writeUTF8String(file, (*it)->name())
633 && writeUTF8String(file, (*it)->description())
634 && writeUTF8String(file, (*it)->fullMIMEDescription()))) {
635 LOG_ERROR("Unable to write plugin metadata to cache");
637 deleteFile(absoluteCachePath);
645 bool PluginDatabase::isPersistentMetadataCacheEnabled()
647 return gPersistentPluginMetadataCacheIsEnabled;
650 void PluginDatabase::setPersistentMetadataCacheEnabled(bool isEnabled)
652 gPersistentPluginMetadataCacheIsEnabled = isEnabled;
655 String PluginDatabase::persistentMetadataCachePath()
657 return WebCore::persistentPluginMetadataCachePath();
660 void PluginDatabase::setPersistentMetadataCachePath(const String& persistentMetadataCachePath)
662 WebCore::persistentPluginMetadataCachePath() = persistentMetadataCachePath;