| /* |
| * 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" |
| #include <stdlib.h> |
| |
| namespace WebCore { |
| |
| PluginDatabase* PluginDatabase::installedPlugins() |
| { |
| static PluginDatabase* plugins = 0; |
| |
| if (!plugins) { |
| plugins = new PluginDatabase; |
| 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() |
| { |
| 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; |
| |
| 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_end = (*it)->mimeToDescriptions().end(); |
| for (MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin(); 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(); |
| |
| Vector<PluginPackage*, 2> pluginChoices; |
| |
| for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) { |
| if ((*it)->mimeToDescriptions().contains(key)) |
| pluginChoices.append((*it).get()); |
| } |
| |
| 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) { |
| MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end(); |
| |
| for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) { |
| 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(); |
| pluginChoices.append(plugin); |
| mimeTypeForPlugin.add(plugin, mime_it->first); |
| 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) |
| { |
| PluginPackage* plugin = pluginForMIMEType(mimeType); |
| String filename = url.string(); |
| |
| if (!plugin) { |
| String filename = url.lastPathComponent(); |
| if (!filename.endsWith("/")) { |
| int extensionPos = filename.reverseFind('.'); |
| if (extensionPos != -1) { |
| String extension = filename.substring(extensionPos + 1); |
| |
| mimeType = MIMETypeForExtension(extension); |
| plugin = pluginForMIMEType(mimeType); |
| } |
| } |
| } |
| |
| // FIXME: if no plugin could be found, query Windows for the mime type |
| // corresponding to the extension. |
| |
| return 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) |
| { |
| m_plugins.remove(package); |
| m_pluginsByPath.remove(package->path()); |
| } |
| |
| #if !PLATFORM(WIN_OS) || PLATFORM(WX) |
| // 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"); |
| |
| 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(getenv("QTWEBKIT_PLUGIN_PATH")); |
| qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths); |
| paths.append(qtPaths); |
| #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) |
| 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 // !PLATFORM(WIN_OS) |
| |
| } |