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