Home | History | Annotate | Download | only in plugins
      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 "PluginDatabaseClient.h"
     33 #include "PluginPackage.h"
     34 #include <stdlib.h>
     35 
     36 #if PLATFORM(ANDROID)
     37 #include "JavaSharedClient.h"
     38 #include "PluginClient.h"
     39 #endif
     40 
     41 namespace WebCore {
     42 
     43 typedef HashMap<String, RefPtr<PluginPackage> > PluginPackageByNameMap;
     44 
     45 PluginDatabase::PluginDatabase()
     46     : m_client(0)
     47 {
     48 }
     49 
     50 PluginDatabase* PluginDatabase::installedPlugins(bool populate)
     51 {
     52     static PluginDatabase* plugins = 0;
     53 
     54     if (!plugins) {
     55         plugins = new PluginDatabase;
     56 
     57         if (populate) {
     58             plugins->setPluginDirectories(PluginDatabase::defaultPluginDirectories());
     59             plugins->refresh();
     60         }
     61     }
     62 
     63     return plugins;
     64 }
     65 
     66 bool PluginDatabase::isMIMETypeRegistered(const String& mimeType)
     67 {
     68     if (mimeType.isNull())
     69         return false;
     70     if (m_registeredMIMETypes.contains(mimeType))
     71         return true;
     72     // No plugin was found, try refreshing the database and searching again
     73     return (refresh() && m_registeredMIMETypes.contains(mimeType));
     74 }
     75 
     76 void PluginDatabase::addExtraPluginDirectory(const String& directory)
     77 {
     78     m_pluginDirectories.append(directory);
     79     refresh();
     80 }
     81 
     82 bool PluginDatabase::refresh()
     83 {
     84     bool pluginSetChanged = false;
     85 
     86     if (!m_plugins.isEmpty()) {
     87         PluginSet pluginsToUnload;
     88         getDeletedPlugins(pluginsToUnload);
     89 
     90         // Unload plugins
     91         PluginSet::const_iterator end = pluginsToUnload.end();
     92         for (PluginSet::const_iterator it = pluginsToUnload.begin(); it != end; ++it)
     93             remove(it->get());
     94 
     95         pluginSetChanged = !pluginsToUnload.isEmpty();
     96     }
     97 
     98     HashSet<String> paths;
     99     getPluginPathsInDirectories(paths);
    100 
    101     HashMap<String, time_t> pathsWithTimes;
    102 
    103     // We should only skip unchanged files if we didn't remove any plugins above. If we did remove
    104     // any plugins, we need to look at every plugin file so that, e.g., if the user has two versions
    105     // of RealPlayer installed and just removed the newer one, we'll pick up the older one.
    106     bool shouldSkipUnchangedFiles = !pluginSetChanged;
    107 
    108     HashSet<String>::const_iterator pathsEnd = paths.end();
    109     for (HashSet<String>::const_iterator it = paths.begin(); it != pathsEnd; ++it) {
    110         time_t lastModified;
    111         if (!getFileModificationTime(*it, lastModified))
    112             continue;
    113 
    114         pathsWithTimes.add(*it, lastModified);
    115 
    116         // If the path's timestamp hasn't changed since the last time we ran refresh(), we don't have to do anything.
    117         if (shouldSkipUnchangedFiles && m_pluginPathsWithTimes.get(*it) == lastModified)
    118             continue;
    119 
    120         if (RefPtr<PluginPackage> oldPackage = m_pluginsByPath.get(*it)) {
    121             ASSERT(!shouldSkipUnchangedFiles || oldPackage->lastModified() != lastModified);
    122             remove(oldPackage.get());
    123         }
    124 
    125         if (!m_client || m_client->shouldLoadPluginAtPath(*it)) {
    126             RefPtr<PluginPackage> package = PluginPackage::createPackage(*it, lastModified);
    127             if (package && (!m_client || m_client->shouldLoadPluginPackage(package.get())) && add(package.release()))
    128                 pluginSetChanged = true;
    129         }
    130     }
    131 
    132     // Cache all the paths we found with their timestamps for next time.
    133     pathsWithTimes.swap(m_pluginPathsWithTimes);
    134 
    135     if (!pluginSetChanged)
    136         return false;
    137 
    138     m_registeredMIMETypes.clear();
    139 
    140     // Register plug-in MIME types
    141     PluginSet::const_iterator end = m_plugins.end();
    142     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
    143         // Get MIME types
    144         MIMEToDescriptionsMap::const_iterator map_it = (*it)->mimeToDescriptions().begin();
    145         MIMEToDescriptionsMap::const_iterator map_end = (*it)->mimeToDescriptions().end();
    146         for (; map_it != map_end; ++map_it)
    147             m_registeredMIMETypes.add(map_it->first);
    148     }
    149 
    150     return true;
    151 }
    152 
    153 Vector<PluginPackage*> PluginDatabase::plugins() const
    154 {
    155     Vector<PluginPackage*> result;
    156 
    157     PluginSet::const_iterator end = m_plugins.end();
    158     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it)
    159         result.append((*it).get());
    160 
    161     return result;
    162 }
    163 
    164 int PluginDatabase::preferredPluginCompare(const void* a, const void* b)
    165 {
    166     PluginPackage* pluginA = *static_cast<PluginPackage* const*>(a);
    167     PluginPackage* pluginB = *static_cast<PluginPackage* const*>(b);
    168 
    169     return pluginA->compare(*pluginB);
    170 }
    171 
    172 PluginPackage* PluginDatabase::pluginForMIMEType(const String& mimeType)
    173 {
    174     if (mimeType.isEmpty())
    175         return 0;
    176 
    177     String key = mimeType.lower();
    178     PluginSet::const_iterator end = m_plugins.end();
    179     PluginPackage* preferredPlugin = m_preferredPlugins.get(key).get();
    180     if (preferredPlugin
    181         && preferredPlugin->isEnabled()
    182         && preferredPlugin->mimeToDescriptions().contains(key)) {
    183         return preferredPlugin;
    184     }
    185 
    186     Vector<PluginPackage*, 2> pluginChoices;
    187 
    188     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
    189         PluginPackage* plugin = (*it).get();
    190 
    191         if (!plugin->isEnabled())
    192             continue;
    193 
    194         if (plugin->mimeToDescriptions().contains(key))
    195             pluginChoices.append(plugin);
    196     }
    197 
    198     if (pluginChoices.isEmpty())
    199         return 0;
    200 
    201     qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
    202 
    203     return pluginChoices[0];
    204 }
    205 
    206 String PluginDatabase::MIMETypeForExtension(const String& extension) const
    207 {
    208     if (extension.isEmpty())
    209         return String();
    210 
    211     PluginSet::const_iterator end = m_plugins.end();
    212     String mimeType;
    213     Vector<PluginPackage*, 2> pluginChoices;
    214     HashMap<PluginPackage*, String> mimeTypeForPlugin;
    215 
    216     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
    217         if (!(*it)->isEnabled())
    218             continue;
    219 
    220         MIMEToExtensionsMap::const_iterator mime_end = (*it)->mimeToExtensions().end();
    221 
    222         for (MIMEToExtensionsMap::const_iterator mime_it = (*it)->mimeToExtensions().begin(); mime_it != mime_end; ++mime_it) {
    223             mimeType = mime_it->first;
    224             PluginPackage* preferredPlugin = m_preferredPlugins.get(mimeType).get();
    225             const Vector<String>& extensions = mime_it->second;
    226             bool foundMapping = false;
    227             for (unsigned i = 0; i < extensions.size(); i++) {
    228                 if (equalIgnoringCase(extensions[i], extension)) {
    229                     PluginPackage* plugin = (*it).get();
    230 
    231                     if (preferredPlugin && PluginPackage::equal(*plugin, *preferredPlugin))
    232                         return mimeType;
    233 
    234                     pluginChoices.append(plugin);
    235                     mimeTypeForPlugin.add(plugin, mimeType);
    236                     foundMapping = true;
    237                     break;
    238                 }
    239             }
    240             if (foundMapping)
    241                 break;
    242         }
    243     }
    244 
    245     if (pluginChoices.isEmpty())
    246         return String();
    247 
    248     qsort(pluginChoices.data(), pluginChoices.size(), sizeof(PluginPackage*), PluginDatabase::preferredPluginCompare);
    249 
    250     return mimeTypeForPlugin.get(pluginChoices[0]);
    251 }
    252 
    253 PluginPackage* PluginDatabase::findPlugin(const KURL& url, String& mimeType)
    254 {
    255     PluginPackage* plugin = pluginForMIMEType(mimeType);
    256     String filename = url.string();
    257 
    258     if (!plugin) {
    259         String filename = url.lastPathComponent();
    260         if (!filename.endsWith("/")) {
    261             int extensionPos = filename.reverseFind('.');
    262             if (extensionPos != -1) {
    263                 String extension = filename.substring(extensionPos + 1);
    264 
    265                 mimeType = MIMETypeForExtension(extension);
    266                 plugin = pluginForMIMEType(mimeType);
    267             }
    268         }
    269     }
    270 
    271     // FIXME: if no plugin could be found, query Windows for the mime type
    272     // corresponding to the extension.
    273 
    274     return plugin;
    275 }
    276 
    277 void PluginDatabase::setPreferredPluginForMIMEType(const String& mimeType, PluginPackage* plugin)
    278 {
    279     if (!plugin || plugin->mimeToExtensions().contains(mimeType))
    280         m_preferredPlugins.set(mimeType.lower(), plugin);
    281 }
    282 
    283 void PluginDatabase::getDeletedPlugins(PluginSet& plugins) const
    284 {
    285     PluginSet::const_iterator end = m_plugins.end();
    286     for (PluginSet::const_iterator it = m_plugins.begin(); it != end; ++it) {
    287         if (!fileExists((*it)->path()))
    288             plugins.add(*it);
    289     }
    290 }
    291 
    292 bool PluginDatabase::add(PassRefPtr<PluginPackage> prpPackage)
    293 {
    294     ASSERT_ARG(prpPackage, prpPackage);
    295 
    296     RefPtr<PluginPackage> package = prpPackage;
    297 
    298     if (!m_plugins.add(package).second)
    299         return false;
    300 
    301     m_pluginsByPath.add(package->path(), package);
    302     return true;
    303 }
    304 
    305 void PluginDatabase::remove(PluginPackage* package)
    306 {
    307     MIMEToExtensionsMap::const_iterator it = package->mimeToExtensions().begin();
    308     MIMEToExtensionsMap::const_iterator end = package->mimeToExtensions().end();
    309     for ( ; it != end; ++it) {
    310         PluginPackageByNameMap::iterator packageInMap = m_preferredPlugins.find(it->first);
    311         if (packageInMap != m_preferredPlugins.end() && packageInMap->second == package)
    312             m_preferredPlugins.remove(packageInMap);
    313     }
    314 
    315     m_plugins.remove(package);
    316     m_pluginsByPath.remove(package->path());
    317 }
    318 
    319 void PluginDatabase::clear()
    320 {
    321     m_plugins.clear();
    322     m_pluginsByPath.clear();
    323     m_pluginPathsWithTimes.clear();
    324     m_registeredMIMETypes.clear();
    325     m_preferredPlugins.clear();
    326 }
    327 
    328 #if (!OS(WINCE)) && (!OS(SYMBIAN)) && (!OS(WINDOWS) || !ENABLE(NETSCAPE_PLUGIN_API))
    329 // For Safari/Win the following three methods are implemented
    330 // in PluginDatabaseWin.cpp, but if we can use WebCore constructs
    331 // for the logic we should perhaps move it here under XP_WIN?
    332 
    333 Vector<String> PluginDatabase::defaultPluginDirectories()
    334 {
    335     Vector<String> paths;
    336 
    337     // Add paths specific to each platform
    338 #if defined(XP_UNIX)
    339     String userPluginPath = homeDirectoryPath();
    340     userPluginPath.append(String("/.mozilla/plugins"));
    341     paths.append(userPluginPath);
    342 
    343     userPluginPath = homeDirectoryPath();
    344     userPluginPath.append(String("/.netscape/plugins"));
    345     paths.append(userPluginPath);
    346 
    347     paths.append("/usr/lib/browser/plugins");
    348     paths.append("/usr/local/lib/mozilla/plugins");
    349     paths.append("/usr/lib/firefox/plugins");
    350     paths.append("/usr/lib64/browser-plugins");
    351     paths.append("/usr/lib/browser-plugins");
    352     paths.append("/usr/lib/mozilla/plugins");
    353     paths.append("/usr/local/netscape/plugins");
    354     paths.append("/opt/mozilla/plugins");
    355     paths.append("/opt/mozilla/lib/plugins");
    356     paths.append("/opt/netscape/plugins");
    357     paths.append("/opt/netscape/communicator/plugins");
    358     paths.append("/usr/lib/netscape/plugins");
    359     paths.append("/usr/lib/netscape/plugins-libc5");
    360     paths.append("/usr/lib/netscape/plugins-libc6");
    361     paths.append("/usr/lib64/netscape/plugins");
    362     paths.append("/usr/lib64/mozilla/plugins");
    363     paths.append("/usr/lib/nsbrowser/plugins");
    364     paths.append("/usr/lib64/nsbrowser/plugins");
    365 
    366     String mozHome(getenv("MOZILLA_HOME"));
    367     mozHome.append("/plugins");
    368     paths.append(mozHome);
    369 
    370     Vector<String> mozPaths;
    371     String mozPath(getenv("MOZ_PLUGIN_PATH"));
    372     mozPath.split(UChar(':'), /* allowEmptyEntries */ false, mozPaths);
    373     paths.append(mozPaths);
    374 #elif defined(XP_MACOSX)
    375     String userPluginPath = homeDirectoryPath();
    376     userPluginPath.append(String("/Library/Internet Plug-Ins"));
    377     paths.append(userPluginPath);
    378     paths.append("/Library/Internet Plug-Ins");
    379 #elif defined(XP_WIN)
    380     String userPluginPath = homeDirectoryPath();
    381     userPluginPath.append(String("\\Application Data\\Mozilla\\plugins"));
    382     paths.append(userPluginPath);
    383 #endif
    384 
    385     // Add paths specific to each port
    386 #if PLATFORM(QT)
    387     Vector<String> qtPaths;
    388     String qtPath(qgetenv("QTWEBKIT_PLUGIN_PATH").constData());
    389     qtPath.split(UChar(':'), /* allowEmptyEntries */ false, qtPaths);
    390     paths.append(qtPaths);
    391 #endif
    392 
    393 #if PLATFORM(ANDROID)
    394     if (android::JavaSharedClient::GetPluginClient())
    395         return android::JavaSharedClient::GetPluginClient()->getPluginDirectories();
    396 #endif
    397 
    398     return paths;
    399 }
    400 
    401 bool PluginDatabase::isPreferredPluginDirectory(const String& path)
    402 {
    403     String preferredPath = homeDirectoryPath();
    404 
    405 #if defined(XP_UNIX)
    406     preferredPath.append(String("/.mozilla/plugins"));
    407 #elif defined(XP_MACOSX)
    408     preferredPath.append(String("/Library/Internet Plug-Ins"));
    409 #elif defined(XP_WIN)
    410     preferredPath.append(String("\\Application Data\\Mozilla\\plugins"));
    411 #endif
    412 
    413     // TODO: We should normalize the path before doing a comparison.
    414     return path == preferredPath;
    415 }
    416 
    417 void PluginDatabase::getPluginPathsInDirectories(HashSet<String>& paths) const
    418 {
    419     // FIXME: This should be a case insensitive set.
    420     HashSet<String> uniqueFilenames;
    421 
    422 #if defined(XP_UNIX) || defined(ANDROID)
    423     String fileNameFilter("*.so");
    424 #else
    425     String fileNameFilter("");
    426 #endif
    427 
    428     Vector<String>::const_iterator dirsEnd = m_pluginDirectories.end();
    429     for (Vector<String>::const_iterator dIt = m_pluginDirectories.begin(); dIt != dirsEnd; ++dIt) {
    430         Vector<String> pluginPaths = listDirectory(*dIt, fileNameFilter);
    431         Vector<String>::const_iterator pluginsEnd = pluginPaths.end();
    432         for (Vector<String>::const_iterator pIt = pluginPaths.begin(); pIt != pluginsEnd; ++pIt) {
    433             if (!fileExists(*pIt))
    434                 continue;
    435 
    436             paths.add(*pIt);
    437         }
    438     }
    439 }
    440 
    441 #endif // !OS(SYMBIAN) && !OS(WINDOWS)
    442 
    443 }
    444