1 /* 2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #import "WebPluginDatabase.h" 30 31 #import "WebBaseNetscapePluginView.h" 32 #import "WebBasePluginPackage.h" 33 #import "WebDataSourcePrivate.h" 34 #import "WebFrame.h" 35 #import "WebFrameViewInternal.h" 36 #import "WebHTMLRepresentation.h" 37 #import "WebHTMLView.h" 38 #import "WebKitLogging.h" 39 #import "WebNSFileManagerExtras.h" 40 #import "WebNetscapePluginPackage.h" 41 #import "WebPluginController.h" 42 #import "WebPluginPackage.h" 43 #import "WebViewPrivate.h" 44 #import "WebViewInternal.h" 45 #import <WebKitSystemInterface.h> 46 #import <wtf/Assertions.h> 47 48 using namespace WebCore; 49 50 static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin); 51 52 @interface WebPluginDatabase (Internal) 53 + (NSArray *)_defaultPlugInPaths; 54 - (NSArray *)_plugInPaths; 55 - (void)_addPlugin:(WebBasePluginPackage *)plugin; 56 - (void)_removePlugin:(WebBasePluginPackage *)plugin; 57 - (NSMutableSet *)_scanForNewPlugins; 58 @end 59 60 @implementation WebPluginDatabase 61 62 static WebPluginDatabase *sharedDatabase = nil; 63 64 + (WebPluginDatabase *)sharedDatabase 65 { 66 if (!sharedDatabase) { 67 sharedDatabase = [[WebPluginDatabase alloc] init]; 68 [sharedDatabase setPlugInPaths:[self _defaultPlugInPaths]]; 69 [sharedDatabase refresh]; 70 } 71 72 return sharedDatabase; 73 } 74 75 + (void)closeSharedDatabase 76 { 77 [sharedDatabase close]; 78 } 79 80 static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin) 81 { 82 if (!*currentPlugin) { 83 *currentPlugin = *candidatePlugin; 84 return; 85 } 86 87 if ([*currentPlugin bundleIdentifier] == [*candidatePlugin bundleIdentifier] && [*candidatePlugin versionNumber] > [*currentPlugin versionNumber]) 88 *currentPlugin = *candidatePlugin; 89 } 90 91 struct PluginPackageCandidates { 92 PluginPackageCandidates() 93 : webPlugin(nil) 94 , machoPlugin(nil) 95 #ifdef SUPPORT_CFM 96 , CFMPlugin(nil) 97 #endif 98 { 99 } 100 101 void update(WebBasePluginPackage *plugin) 102 { 103 if ([plugin isKindOfClass:[WebPluginPackage class]]) { 104 checkCandidate(&webPlugin, &plugin); 105 return; 106 } 107 108 #if ENABLE(NETSCAPE_PLUGIN_API) 109 if([plugin isKindOfClass:[WebNetscapePluginPackage class]]) { 110 WebExecutableType executableType = [(WebNetscapePluginPackage *)plugin executableType]; 111 #ifdef SUPPORT_CFM 112 if (executableType == WebCFMExecutableType) { 113 checkCandidate(&CFMPlugin, &plugin); 114 return; 115 } 116 #endif // SUPPORT_CFM 117 if (executableType == WebMachOExecutableType) { 118 checkCandidate(&machoPlugin, &plugin); 119 return; 120 } 121 } 122 #endif 123 ASSERT_NOT_REACHED(); 124 } 125 126 WebBasePluginPackage *bestCandidate() 127 { 128 // Allow other plug-ins to win over QT because if the user has installed a plug-in that can handle a type 129 // that the QT plug-in can handle, they probably intended to override QT. 130 if (webPlugin && ![webPlugin isQuickTimePlugIn]) 131 return webPlugin; 132 133 if (machoPlugin && ![machoPlugin isQuickTimePlugIn]) 134 return machoPlugin; 135 136 #ifdef SUPPORT_CFM 137 if (CFMPlugin && ![CFMPlugin isQuickTimePlugIn]) 138 return CFMPlugin; 139 #endif // SUPPORT_CFM 140 141 if (webPlugin) 142 return webPlugin; 143 if (machoPlugin) 144 return machoPlugin; 145 #ifdef SUPPORT_CFM 146 if (CFMPlugin) 147 return CFMPlugin; 148 #endif 149 return nil; 150 } 151 152 WebBasePluginPackage *webPlugin; 153 WebBasePluginPackage *machoPlugin; 154 #ifdef SUPPORT_CFM 155 WebBasePluginPackage *CFMPlugin; 156 #endif 157 }; 158 159 - (WebBasePluginPackage *)pluginForMIMEType:(NSString *)MIMEType 160 { 161 PluginPackageCandidates candidates; 162 163 MIMEType = [MIMEType lowercaseString]; 164 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 165 166 while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) { 167 if ([plugin supportsMIMEType:MIMEType]) 168 candidates.update(plugin); 169 } 170 171 return candidates.bestCandidate(); 172 } 173 174 - (WebBasePluginPackage *)pluginForExtension:(NSString *)extension 175 { 176 PluginPackageCandidates candidates; 177 178 extension = [extension lowercaseString]; 179 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 180 181 while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) { 182 if ([plugin supportsExtension:extension]) 183 candidates.update(plugin); 184 } 185 186 WebBasePluginPackage *plugin = candidates.bestCandidate(); 187 188 if (!plugin) { 189 // If no plug-in was found from the extension, attempt to map from the extension to a MIME type 190 // and find the a plug-in from the MIME type. This is done in case the plug-in has not fully specified 191 // an extension <-> MIME type mapping. 192 NSString *MIMEType = WKGetMIMETypeForExtension(extension); 193 if ([MIMEType length] > 0) 194 plugin = [self pluginForMIMEType:MIMEType]; 195 } 196 return plugin; 197 } 198 199 - (NSArray *)plugins 200 { 201 return [plugins allValues]; 202 } 203 204 static NSArray *additionalWebPlugInPaths; 205 206 + (void)setAdditionalWebPlugInPaths:(NSArray *)additionalPaths 207 { 208 if (additionalPaths == additionalWebPlugInPaths) 209 return; 210 211 [additionalWebPlugInPaths release]; 212 additionalWebPlugInPaths = [additionalPaths copy]; 213 214 // One might be tempted to add additionalWebPlugInPaths to the global WebPluginDatabase here. 215 // For backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, 216 // we need to save a copy of the additional paths and not cause a refresh of the plugin DB 217 // at this time. 218 // See Radars 4608487 and 4609047. 219 } 220 221 - (void)setPlugInPaths:(NSArray *)newPaths 222 { 223 if (plugInPaths == newPaths) 224 return; 225 226 [plugInPaths release]; 227 plugInPaths = [newPaths copy]; 228 } 229 230 - (void)close 231 { 232 NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator]; 233 WebBasePluginPackage *plugin; 234 while ((plugin = [pluginEnumerator nextObject]) != nil) 235 [self _removePlugin:plugin]; 236 [plugins release]; 237 plugins = nil; 238 } 239 240 - (id)init 241 { 242 if (!(self = [super init])) 243 return nil; 244 245 registeredMIMETypes = [[NSMutableSet alloc] init]; 246 pluginInstanceViews = [[NSMutableSet alloc] init]; 247 248 return self; 249 } 250 251 - (void)dealloc 252 { 253 [plugInPaths release]; 254 [plugins release]; 255 [registeredMIMETypes release]; 256 [pluginInstanceViews release]; 257 258 [super dealloc]; 259 } 260 261 - (void)refresh 262 { 263 // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling 264 // -refresh multiple times does not bloat the default pool. 265 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 266 267 // Create map from plug-in path to WebBasePluginPackage 268 if (!plugins) 269 plugins = [[NSMutableDictionary alloc] initWithCapacity:12]; 270 271 // Find all plug-ins on disk 272 NSMutableSet *newPlugins = [self _scanForNewPlugins]; 273 274 // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk) 275 NSMutableSet *pluginsToRemove = [NSMutableSet set]; 276 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 277 WebBasePluginPackage *plugin; 278 while ((plugin = [pluginEnumerator nextObject]) != nil) { 279 // Any plug-ins that were removed from disk since the last refresh should be removed from 280 // the database. 281 if (![newPlugins containsObject:plugin]) 282 [pluginsToRemove addObject:plugin]; 283 284 // Remove every member of 'plugins' from 'newPlugins'. After this loop exits, 'newPlugins' 285 // will be the set of new plug-ins that should be added to the database. 286 [newPlugins removeObject:plugin]; 287 } 288 289 #if !LOG_DISABLED 290 if ([newPlugins count] > 0) 291 LOG(Plugins, "New plugins:\n%@", newPlugins); 292 if ([pluginsToRemove count] > 0) 293 LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove); 294 #endif 295 296 // Remove plugins from database 297 pluginEnumerator = [pluginsToRemove objectEnumerator]; 298 while ((plugin = [pluginEnumerator nextObject]) != nil) 299 [self _removePlugin:plugin]; 300 301 // Add new plugins to database 302 pluginEnumerator = [newPlugins objectEnumerator]; 303 while ((plugin = [pluginEnumerator nextObject]) != nil) 304 [self _addPlugin:plugin]; 305 306 // Build a list of MIME types. 307 NSMutableSet *MIMETypes = [[NSMutableSet alloc] init]; 308 pluginEnumerator = [plugins objectEnumerator]; 309 while ((plugin = [pluginEnumerator nextObject])) { 310 const PluginInfo& pluginInfo = [plugin pluginInfo]; 311 for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) 312 [MIMETypes addObject:pluginInfo.mimes[i].type]; 313 } 314 315 // Register plug-in views and representations. 316 NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator]; 317 NSString *MIMEType; 318 while ((MIMEType = [MIMEEnumerator nextObject]) != nil) { 319 [registeredMIMETypes addObject:MIMEType]; 320 321 if ([WebView canShowMIMETypeAsHTML:MIMEType]) 322 // Don't allow plug-ins to override our core HTML types. 323 continue; 324 plugin = [self pluginForMIMEType:MIMEType]; 325 if ([plugin isJavaPlugIn]) 326 // Don't register the Java plug-in for a document view since Java files should be downloaded when not embedded. 327 continue; 328 if ([plugin isQuickTimePlugIn] && [[WebFrameView _viewTypesAllowImageTypeOmission:NO] objectForKey:MIMEType]) 329 // Don't allow the QT plug-in to override any types because it claims many that we can handle ourselves. 330 continue; 331 332 if (self == sharedDatabase) 333 [WebView _registerPluginMIMEType:MIMEType]; 334 } 335 [MIMETypes release]; 336 337 [pool drain]; 338 } 339 340 - (BOOL)isMIMETypeRegistered:(NSString *)MIMEType 341 { 342 return [registeredMIMETypes containsObject:MIMEType]; 343 } 344 345 - (void)addPluginInstanceView:(NSView *)view 346 { 347 [pluginInstanceViews addObject:view]; 348 } 349 350 - (void)removePluginInstanceView:(NSView *)view 351 { 352 [pluginInstanceViews removeObject:view]; 353 } 354 355 - (void)removePluginInstanceViewsFor:(WebFrame*)webFrame 356 { 357 // This handles handles the case where a frame or view is being destroyed and the plugin needs to be removed from the list first 358 359 if( [pluginInstanceViews count] == 0 ) 360 return; 361 362 NSView <WebDocumentView> *documentView = [[webFrame frameView] documentView]; 363 if ([documentView isKindOfClass:[WebHTMLView class]]) { 364 NSArray *subviews = [documentView subviews]; 365 unsigned int subviewCount = [subviews count]; 366 unsigned int subviewIndex; 367 368 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { 369 NSView *subview = [subviews objectAtIndex:subviewIndex]; 370 #if ENABLE(NETSCAPE_PLUGIN_API) 371 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]] || [WebPluginController isPlugInView:subview]) 372 #else 373 if ([WebPluginController isPlugInView:subview]) 374 #endif 375 [pluginInstanceViews removeObject:subview]; 376 } 377 } 378 } 379 380 - (void)destroyAllPluginInstanceViews 381 { 382 NSView *view; 383 NSArray *pli = [pluginInstanceViews allObjects]; 384 NSEnumerator *enumerator = [pli objectEnumerator]; 385 while ((view = [enumerator nextObject]) != nil) { 386 #if ENABLE(NETSCAPE_PLUGIN_API) 387 if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) { 388 ASSERT([view respondsToSelector:@selector(stop)]); 389 [view performSelector:@selector(stop)]; 390 } else 391 #endif 392 if ([WebPluginController isPlugInView:view]) { 393 ASSERT([[view superview] isKindOfClass:[WebHTMLView class]]); 394 ASSERT([[view superview] respondsToSelector:@selector(_destroyAllWebPlugins)]); 395 // this will actually destroy all plugin instances for a webHTMLView and remove them from this list 396 [[view superview] performSelector:@selector(_destroyAllWebPlugins)]; 397 } 398 } 399 } 400 401 @end 402 403 @implementation WebPluginDatabase (Internal) 404 405 + (NSArray *)_defaultPlugInPaths 406 { 407 // Plug-ins are found in order of precedence. 408 // If there are duplicates, the first found plug-in is used. 409 // For example, if there is a QuickTime.plugin in the users's home directory 410 // that is used instead of the /Library/Internet Plug-ins version. 411 // The purpose is to allow non-admin users to update their plug-ins. 412 return [NSArray arrayWithObjects: 413 [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Internet Plug-Ins"], 414 @"/Library/Internet Plug-Ins", 415 [[NSBundle mainBundle] builtInPlugInsPath], 416 nil]; 417 } 418 419 - (NSArray *)_plugInPaths 420 { 421 if (self == sharedDatabase && additionalWebPlugInPaths) { 422 // Add additionalWebPlugInPaths to the global WebPluginDatabase. We do this here for 423 // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, 424 // which simply saved a copy of the additional paths and did not cause the plugin DB to 425 // refresh. See Radars 4608487 and 4609047. 426 NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease]; 427 [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths]; 428 return modifiedPlugInPaths; 429 } else 430 return plugInPaths; 431 } 432 433 - (void)_addPlugin:(WebBasePluginPackage *)plugin 434 { 435 ASSERT(plugin); 436 NSString *pluginPath = [plugin path]; 437 ASSERT(pluginPath); 438 [plugins setObject:plugin forKey:pluginPath]; 439 [plugin wasAddedToPluginDatabase:self]; 440 } 441 442 - (void)_removePlugin:(WebBasePluginPackage *)plugin 443 { 444 ASSERT(plugin); 445 446 // Unregister plug-in's MIME type registrations 447 const PluginInfo& pluginInfo = [plugin pluginInfo]; 448 for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) { 449 NSString *MIMEType = pluginInfo.mimes[i].type; 450 451 if ([registeredMIMETypes containsObject:MIMEType]) { 452 if (self == sharedDatabase) 453 [WebView _unregisterPluginMIMEType:MIMEType]; 454 [registeredMIMETypes removeObject:MIMEType]; 455 } 456 } 457 458 // Remove plug-in from database 459 NSString *pluginPath = [plugin path]; 460 ASSERT(pluginPath); 461 [plugin retain]; 462 [plugins removeObjectForKey:pluginPath]; 463 [plugin wasRemovedFromPluginDatabase:self]; 464 [plugin release]; 465 } 466 467 - (NSMutableSet *)_scanForNewPlugins 468 { 469 NSMutableSet *newPlugins = [NSMutableSet set]; 470 NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator]; 471 NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init]; 472 NSFileManager *fileManager = [NSFileManager defaultManager]; 473 NSString *pluginDirectory; 474 while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) { 475 // Get contents of each plug-in directory 476 NSEnumerator *filenameEnumerator = [[fileManager contentsOfDirectoryAtPath:pluginDirectory error:NULL] objectEnumerator]; 477 NSString *filename; 478 while ((filename = [filenameEnumerator nextObject]) != nil) { 479 // Unique plug-ins by filename 480 if ([uniqueFilenames containsObject:filename]) 481 continue; 482 [uniqueFilenames addObject:filename]; 483 484 // Create a plug-in package for this path 485 NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename]; 486 WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath]; 487 if (!pluginPackage) 488 pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath]; 489 if (pluginPackage) 490 [newPlugins addObject:pluginPackage]; 491 } 492 } 493 [uniqueFilenames release]; 494 495 return newPlugins; 496 } 497 498 @end 499