diff options
Diffstat (limited to 'Source/WebCore/plugins/PluginDatabase.cpp')
-rw-r--r-- | Source/WebCore/plugins/PluginDatabase.cpp | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/Source/WebCore/plugins/PluginDatabase.cpp b/Source/WebCore/plugins/PluginDatabase.cpp new file mode 100644 index 0000000..b9e154a --- /dev/null +++ b/Source/WebCore/plugins/PluginDatabase.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2008 Collabora, Ltd. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "PluginDatabase.h" + +#include "Frame.h" +#include "KURL.h" +#include "PluginPackage.h" +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) +#include "FileSystem.h" +#endif +#include <stdlib.h> +#include <wtf/text/CString.h> + +#if PLATFORM(ANDROID) +#include "JavaSharedClient.h" +#include "PluginClient.h" +#endif + +namespace WebCore { + +typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap; + +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) +static const size_t maximumPersistentPluginMetadataCacheSize = 32768; + +static bool gPersistentPluginMetadataCacheIsEnabled; + +String& persistentPluginMetadataCachePath() +{ + DEFINE_STATIC_LOCAL(String, cachePath, ()); + return cachePath; +} +#endif + +PluginDatabase::PluginDatabase() +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + : m_persistentMetadataCacheIsLoaded(false) +#endif +{ +} + +PluginDatabase* PluginDatabase::installedPlugins(bool populate) +{ + static PluginDatabase* plugins = 0; + + if (!plugins) { + plugins = new PluginDatabase; + + if (populate) { + plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories()); + plugins->refresh(); + } + } + + return plugins; +} + +bool PluginDatabase::isMIMETypeRegistered(const String& mimeType) +{ + if (mimeType.isNull()) + return false; + if (m_registeredMIMETypes.contains(mimeType)) + return true; + // No plugin was found, try refreshing the database and searching again + return (refresh() && m_registeredMIMETypes.contains(mimeType)); +} + +void PluginDatabase::addExtraPluginDirectory(const String& directory) +{ + m_pluginDirectories.append(directory); + refresh(); +} + +bool PluginDatabase::refresh() +{ +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + if (!m_persistentMetadataCacheIsLoaded) + loadPersistentMetadataCache(); +#endif + bool pluginSetChanged = false; + + if (!m_plugins.isEmpty()) { + PluginSet pluginsToUnload; + getDeletedPlugins(pluginsToUnload); + + // Unload plugins + PluginSet::const_iterator end = pluginsToUnload.end(); + for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it) + remove(it->get()); + + pluginSetChanged = !pluginsToUnload.isEmpty(); + } + + HashSet<String> paths; + getPluginPathsInDirectories(paths); + + HashMap<String, time_t> pathsWithTimes; + + // We should only skip unchanged files if we didn't remove any plugins above. If we did remove + // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions + // of RealPlayer installed and just removed the newer one, we'll pick up the older one. + bool shouldSkipUnchangedFiles = !pluginSetChanged; + + HashSet<String>::const_iterator pathsEnd = paths.end(); + for (HashSet<String>::const_iterator it = paths.begin(); it != pathsEnd; ++it) { + time_t lastModified; + if (!getFileModificationTime(*it, lastModified)) + continue; + + pathsWithTimes.add(*it, lastModified); + + // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything. + if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified) + continue; + + if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) { + ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified); + remove(oldPackage.get()); + } + + RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified); + if (package && add(package.release())) + pluginSetChanged = true; + } + + // Cache all the paths we found with their timestamps for next time. + pathsWithTimes.swap(m_pluginPathsWithTimes); + + if (!pluginSetChanged) + return false; + +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + updatePersistentMetadataCache(); +#endif + + m_registeredMIMETypes.clear(); + + // Register plug-in MIME types + PluginSet::const_iterator end = m_plugins.end(); + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { + // Get MIME types + MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin(); + MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end(); + for (; map_it != map_end; ++map_it) + m_registeredMIMETypes.add(map_it->first); + } + + return true; +} + +Vector<PluginPackage*> PluginDatabase::plugins() const +{ + Vector<PluginPackage*> result; + + PluginSet::const_iterator end = m_plugins.end(); + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) + result.append((*it).get()); + + return result; +} + +int PluginDatabase::preferredPluginCompare(const void* a, const void* b) +{ + PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a); + PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b); + + return pluginA->compare(*pluginB); +} + +PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType) +{ + if (mimeType.isEmpty()) + return 0; + + String key = mimeType.lower(); + PluginSet::const_iterator end = m_plugins.end(); + PluginPackage* preferredPlugin = m_preferredPlugins.get(key).get(); + if (preferredPlugin + && preferredPlugin->isEnabled() + && preferredPlugin->mimeToDescriptions().contains(key)) { + return preferredPlugin; + } + + Vector<PluginPackage*, 2> pluginChoices; + + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { + PluginPackage* plugin = (*it).get(); + + if (!plugin->isEnabled()) + continue; + + if (plugin->mimeToDescriptions().contains(key)) { +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + if (!plugin->ensurePluginLoaded()) + continue; +#endif + pluginChoices.append(plugin); + } + } + + if (pluginChoices.isEmpty()) + return 0; + + qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare); + + return pluginChoices[0]; +} + +String PluginDatabase::MIMETypeForExtension(const String& extension) const +{ + if (extension.isEmpty()) + return String(); + + PluginSet::const_iterator end = m_plugins.end(); + String mimeType; + Vector<PluginPackage*, 2> pluginChoices; + HashMap<PluginPackage*, String> mimeTypeForPlugin; + + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { + if (!(*it)->isEnabled()) + continue; + + MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end(); + + for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) { + mimeType = mime_it->first; + PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType).get(); + const Vector<String>& extensions = mime_it->second; + bool foundMapping = false; + for (unsigned i = 0; i < extensions.size(); i++) { + if (equalIgnoringCase(extensions[i], extension)) { + PluginPackage* plugin = (*it).get(); + + if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin)) + return mimeType; + +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + if (!plugin->ensurePluginLoaded()) + continue; +#endif + pluginChoices.append(plugin); + mimeTypeForPlugin.add(plugin, mimeType); + foundMapping = true; + break; + } + } + if (foundMapping) + break; + } + } + + if (pluginChoices.isEmpty()) + return String(); + + qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare); + + return mimeTypeForPlugin.get(pluginChoices[0]); +} + +PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType) +{ + if (!mimeType.isEmpty()) + return pluginForMIMEType(mimeType); + + String filename = url.lastPathComponent(); + if (filename.endsWith("/")) + return 0; + + int extensionPos = filename.reverseFind('.'); + if (extensionPos == -1) + return 0; + + String mimeTypeForExtension = MIMETypeForExtension(filename.substring(extensionPos + 1)); + PluginPackage* plugin = pluginForMIMEType(mimeTypeForExtension); + if (!plugin) { + // FIXME: if no plugin could be found, query Windows for the mime type + // corresponding to the extension. + return 0; + } + + mimeType = mimeTypeForExtension; + return plugin; +} + +void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin) +{ + if (!plugin || plugin->mimeToExtensions().contains(mimeType)) + m_preferredPlugins.set(mimeType.lower(), plugin); +} + +void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const +{ + PluginSet::const_iterator end = m_plugins.end(); + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { + if (!fileExists((*it)->path())) + plugins.add(*it); + } +} + +bool PluginDatabase::add(PassRefPtr<PluginPackage> prpPackage) +{ + ASSERT_ARG(prpPackage, prpPackage); + + RefPtr<PluginPackage> package = prpPackage; + + if (!m_plugins.add(package).second) + return false; + + m_pluginsByPath.add(package->path(), package); + return true; +} + +void PluginDatabase::remove(PluginPackage* package) +{ + MIMEToExtensionsMap::const_iterator it = package->mimeToExtensions().begin(); + MIMEToExtensionsMap::const_iterator end = package->mimeToExtensions().end(); + for ( ; it != end; ++it) { + PluginPackageByNameMap::iterator packageInMap = m_preferredPlugins.find(it->first); + if (packageInMap != m_preferredPlugins.end() && packageInMap->second == package) + m_preferredPlugins.remove(packageInMap); + } + + m_plugins.remove(package); + m_pluginsByPath.remove(package->path()); +} + +void PluginDatabase::clear() +{ + m_plugins.clear(); + m_pluginsByPath.clear(); + m_pluginPathsWithTimes.clear(); + m_registeredMIMETypes.clear(); + m_preferredPlugins.clear(); +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + m_persistentMetadataCacheIsLoaded = false; +#endif +} + +#if (!OS(WINCE)) && (!OS(SYMBIAN)) && (!OS(WINDOWS) || !ENABLE(NETSCAPE_PLUGIN_API)) +// For Safari/Win the following three methods are implemented +// in PluginDatabaseWin.cpp, but if we can use WebCore constructs +// for the logic we should perhaps move it here under XP_WIN? + +Vector<String> PluginDatabase::defaultPluginDirectories() +{ + Vector<String> paths; + + // Add paths specific to each platform +#if defined(XP_UNIX) + String userPluginPath = homeDirectoryPath(); + userPluginPath.append(String("/.mozilla/plugins")); + paths.append(userPluginPath); + + userPluginPath = homeDirectoryPath(); + userPluginPath.append(String("/.netscape/plugins")); + paths.append(userPluginPath); + + paths.append("/usr/lib/browser/plugins"); + paths.append("/usr/local/lib/mozilla/plugins"); + paths.append("/usr/lib/firefox/plugins"); + paths.append("/usr/lib64/browser-plugins"); + paths.append("/usr/lib/browser-plugins"); + paths.append("/usr/lib/mozilla/plugins"); + paths.append("/usr/local/netscape/plugins"); + paths.append("/opt/mozilla/plugins"); + paths.append("/opt/mozilla/lib/plugins"); + paths.append("/opt/netscape/plugins"); + paths.append("/opt/netscape/communicator/plugins"); + paths.append("/usr/lib/netscape/plugins"); + paths.append("/usr/lib/netscape/plugins-libc5"); + paths.append("/usr/lib/netscape/plugins-libc6"); + paths.append("/usr/lib64/netscape/plugins"); + paths.append("/usr/lib64/mozilla/plugins"); + paths.append("/usr/lib/nsbrowser/plugins"); + paths.append("/usr/lib64/nsbrowser/plugins"); + + String mozHome(getenv("MOZILLA_HOME")); + mozHome.append("/plugins"); + paths.append(mozHome); + + Vector<String> mozPaths; + String mozPath(getenv("MOZ_PLUGIN_PATH")); + mozPath.split(UChar(':'), /* allowEmptyEntries */ false, mozPaths); + paths.append(mozPaths); +#elif defined(XP_MACOSX) + String userPluginPath = homeDirectoryPath(); + userPluginPath.append(String("/Library/Internet Plug-Ins")); + paths.append(userPluginPath); + paths.append("/Library/Internet Plug-Ins"); +#elif defined(XP_WIN) + String userPluginPath = homeDirectoryPath(); + userPluginPath.append(String("\\Application Data\\Mozilla\\plugins")); + paths.append(userPluginPath); +#endif + + // Add paths specific to each port +#if PLATFORM(QT) + Vector<String> qtPaths; + String qtPath(qgetenv("QTWEBKIT_PLUGIN_PATH").constData()); + qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths); + paths.append(qtPaths); +#endif + +#if PLATFORM(ANDROID) + if (android::JavaSharedClient::GetPluginClient()) + return android::JavaSharedClient::GetPluginClient()->getPluginDirectories(); +#endif + + return paths; +} + +bool PluginDatabase::isPreferredPluginDirectory(const String& path) +{ + String preferredPath = homeDirectoryPath(); + +#if defined(XP_UNIX) + preferredPath.append(String("/.mozilla/plugins")); +#elif defined(XP_MACOSX) + preferredPath.append(String("/Library/Internet Plug-Ins")); +#elif defined(XP_WIN) + preferredPath.append(String("\\Application Data\\Mozilla\\plugins")); +#endif + + // TODO: We should normalize the path before doing a comparison. + return path == preferredPath; +} + +void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const +{ + // FIXME: This should be a case insensitive set. + HashSet<String> uniqueFilenames; + +#if defined(XP_UNIX) || defined(ANDROID) + String fileNameFilter("*.so"); +#else + String fileNameFilter(""); +#endif + + Vector<String>::const_iterator dirsEnd = m_pluginDirectories.end(); + for (Vector<String>::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) { + Vector<String> pluginPaths = listDirectory(*dIt, fileNameFilter); + Vector<String>::const_iterator pluginsEnd = pluginPaths.end(); + for (Vector<String>::const_iterator pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) { + if (!fileExists(*pIt)) + continue; + + paths.add(*pIt); + } + } +} + +#endif // !OS(SYMBIAN) && !OS(WINDOWS) + +#if ENABLE(NETSCAPE_PLUGIN_METADATA_CACHE) + +static void fillBufferWithContentsOfFile(PlatformFileHandle file, Vector<char>& buffer) +{ + size_t bufferSize = 0; + size_t bufferCapacity = 1024; + buffer.resize(bufferCapacity); + + do { + bufferSize += readFromFile(file, buffer.data() + bufferSize, bufferCapacity - bufferSize); + if (bufferSize == bufferCapacity) { + if (bufferCapacity < maximumPersistentPluginMetadataCacheSize) { + bufferCapacity *= 2; + buffer.resize(bufferCapacity); + } else { + buffer.clear(); + return; + } + } else + break; + } while (true); + + buffer.shrink(bufferSize); +} + +static bool readUTF8String(String& resultString, char*& start, const char* end) +{ + if (start >= end) + return false; + + int len = strlen(start); + resultString = String::fromUTF8(start, len); + start += len + 1; + + return true; +} + +static bool readTime(time_t& resultTime, char*& start, const char* end) +{ + if (start + sizeof(time_t) >= end) + return false; + + resultTime = *reinterpret_cast_ptr<time_t*>(start); + start += sizeof(time_t); + + return true; +} + +static const char schemaVersion = '1'; +static const char persistentPluginMetadataCacheFilename[] = "PluginMetadataCache.bin"; + +void PluginDatabase::loadPersistentMetadataCache() +{ + if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty()) + return; + + PlatformFileHandle file; + String absoluteCachePath = pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename); + file = openFile(absoluteCachePath, OpenForRead); + + if (!isHandleValid(file)) + return; + + // Mark cache as loaded regardless of success or failure. If + // there's error in the cache, we won't try to load it anymore. + m_persistentMetadataCacheIsLoaded = true; + + Vector<char> fileContents; + fillBufferWithContentsOfFile(file, fileContents); + closeFile(file); + + if (fileContents.size() < 2 || fileContents.first() != schemaVersion || fileContents.last() != '\0') { + LOG_ERROR("Unable to read plugin metadata cache: corrupt schema"); + deleteFile(absoluteCachePath); + return; + } + + char* bufferPos = fileContents.data() + 1; + char* end = fileContents.data() + fileContents.size(); + + PluginSet cachedPlugins; + HashMap<String, time_t> cachedPluginPathsWithTimes; + HashMap<String, RefPtr<PluginPackage> > cachedPluginsByPath; + + while (bufferPos < end) { + String path; + time_t lastModified; + String name; + String desc; + String mimeDesc; + if (!(readUTF8String(path, bufferPos, end) + && readTime(lastModified, bufferPos, end) + && readUTF8String(name, bufferPos, end) + && readUTF8String(desc, bufferPos, end) + && readUTF8String(mimeDesc, bufferPos, end))) { + LOG_ERROR("Unable to read plugin metadata cache: corrupt data"); + deleteFile(absoluteCachePath); + return; + } + + // Skip metadata that points to plugins from directories that + // are not part of plugin directory list anymore. + String pluginDirectoryName = directoryName(path); + if (m_pluginDirectories.find(pluginDirectoryName) == WTF::notFound) + continue; + + RefPtr<PluginPackage> package = PluginPackage::createPackageFromCache(path, lastModified, name, desc, mimeDesc); + + if (package && cachedPlugins.add(package).second) { + cachedPluginPathsWithTimes.add(package->path(), package->lastModified()); + cachedPluginsByPath.add(package->path(), package); + } + } + + m_plugins.swap(cachedPlugins); + m_pluginsByPath.swap(cachedPluginsByPath); + m_pluginPathsWithTimes.swap(cachedPluginPathsWithTimes); +} + +static bool writeUTF8String(PlatformFileHandle file, const String& string) +{ + CString utf8String = string.utf8(); + int length = utf8String.length() + 1; + return writeToFile(file, utf8String.data(), length) == length; +} + +static bool writeTime(PlatformFileHandle file, const time_t& time) +{ + return writeToFile(file, reinterpret_cast<const char*>(&time), sizeof(time_t)) == sizeof(time_t); +} + +void PluginDatabase::updatePersistentMetadataCache() +{ + if (!isPersistentMetadataCacheEnabled() || persistentMetadataCachePath().isEmpty()) + return; + + makeAllDirectories(persistentMetadataCachePath()); + String absoluteCachePath = pathByAppendingComponent(persistentMetadataCachePath(), persistentPluginMetadataCacheFilename); + deleteFile(absoluteCachePath); + + if (m_plugins.isEmpty()) + return; + + PlatformFileHandle file; + file = openFile(absoluteCachePath, OpenForWrite); + + if (!isHandleValid(file)) { + LOG_ERROR("Unable to open plugin metadata cache for saving"); + return; + } + + char localSchemaVersion = schemaVersion; + if (writeToFile(file, &localSchemaVersion, 1) != 1) { + LOG_ERROR("Unable to write plugin metadata cache schema"); + closeFile(file); + deleteFile(absoluteCachePath); + return; + } + + PluginSet::const_iterator end = m_plugins.end(); + for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { + if (!(writeUTF8String(file, (*it)->path()) + && writeTime(file, (*it)->lastModified()) + && writeUTF8String(file, (*it)->name()) + && writeUTF8String(file, (*it)->description()) + && writeUTF8String(file, (*it)->fullMIMEDescription()))) { + LOG_ERROR("Unable to write plugin metadata to cache"); + closeFile(file); + deleteFile(absoluteCachePath); + return; + } + } + + closeFile(file); +} + +bool PluginDatabase::isPersistentMetadataCacheEnabled() +{ + return gPersistentPluginMetadataCacheIsEnabled; +} + +void PluginDatabase::setPersistentMetadataCacheEnabled(bool isEnabled) +{ + gPersistentPluginMetadataCacheIsEnabled = isEnabled; +} + +String PluginDatabase::persistentMetadataCachePath() +{ + return WebCore::persistentPluginMetadataCachePath(); +} + +void PluginDatabase::setPersistentMetadataCachePath(const String& persistentMetadataCachePath) +{ + WebCore::persistentPluginMetadataCachePath() = persistentMetadataCachePath; +} +#endif +} |