/* * 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 #include #if PLATFORM(ANDROID) #include "JavaSharedClient.h" #include "PluginClient.h" #endif namespace WebCore { typedef HashMap > 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 paths; getPluginPathsInDirectories(paths); HashMap 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::const_iterator pathsEnd = paths.end(); for (HashSet::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 oldPackage = m_pluginsByPath.get(*it)) { ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified); remove(oldPackage.get()); } RefPtr 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 PluginDatabase::plugins() const { Vector 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(a); PluginPackage* pluginB = *static_cast(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 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 pluginChoices; HashMap 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& 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 prpPackage) { ASSERT_ARG(prpPackage, prpPackage); RefPtr 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 PluginDatabase::defaultPluginDirectories() { Vector 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 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 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& paths) const { // FIXME: This should be a case insensitive set. HashSet uniqueFilenames; #if defined(XP_UNIX) || defined(ANDROID) String fileNameFilter("*.so"); #else String fileNameFilter(""); #endif Vector::const_iterator dirsEnd = m_pluginDirectories.end(); for (Vector::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) { Vector pluginPaths = listDirectory(*dIt, fileNameFilter); Vector::const_iterator pluginsEnd = pluginPaths.end(); for (Vector::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& 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(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 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 cachedPluginPathsWithTimes; HashMap > 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 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(&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 }