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 48using namespace WebCore; 49 50static 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 62static 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+ (WebPluginDatabase *)sharedDatabaseIfExists 76{ 77 return sharedDatabase; 78} 79 80+ (void)closeSharedDatabase 81{ 82 [sharedDatabase close]; 83} 84 85static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin) 86{ 87 if (!*currentPlugin) { 88 *currentPlugin = *candidatePlugin; 89 return; 90 } 91 92 if ([*currentPlugin bundleIdentifier] == [*candidatePlugin bundleIdentifier] && [*candidatePlugin versionNumber] > [*currentPlugin versionNumber]) 93 *currentPlugin = *candidatePlugin; 94} 95 96struct PluginPackageCandidates { 97 PluginPackageCandidates() 98 : webPlugin(nil) 99 , netscapePlugin(nil) 100 { 101 } 102 103 void update(WebBasePluginPackage *plugin) 104 { 105 if ([plugin isKindOfClass:[WebPluginPackage class]]) { 106 checkCandidate(&webPlugin, &plugin); 107 return; 108 } 109 110#if ENABLE(NETSCAPE_PLUGIN_API) 111 if([plugin isKindOfClass:[WebNetscapePluginPackage class]]) { 112 checkCandidate(&netscapePlugin, &plugin); 113 return; 114 } 115#endif 116 ASSERT_NOT_REACHED(); 117 } 118 119 WebBasePluginPackage *bestCandidate() 120 { 121 // Allow other plug-ins to win over QT because if the user has installed a plug-in that can handle a type 122 // that the QT plug-in can handle, they probably intended to override QT. 123 if (webPlugin && ![webPlugin isQuickTimePlugIn]) 124 return webPlugin; 125 126 if (netscapePlugin && ![netscapePlugin isQuickTimePlugIn]) 127 return netscapePlugin; 128 129 if (webPlugin) 130 return webPlugin; 131 if (netscapePlugin) 132 return netscapePlugin; 133 134 return nil; 135 } 136 137 WebBasePluginPackage *webPlugin; 138 WebBasePluginPackage *netscapePlugin; 139}; 140 141- (WebBasePluginPackage *)pluginForMIMEType:(NSString *)MIMEType 142{ 143 PluginPackageCandidates candidates; 144 145 MIMEType = [MIMEType lowercaseString]; 146 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 147 148 while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) { 149 if ([plugin supportsMIMEType:MIMEType]) 150 candidates.update(plugin); 151 } 152 153 return candidates.bestCandidate(); 154} 155 156- (WebBasePluginPackage *)pluginForExtension:(NSString *)extension 157{ 158 PluginPackageCandidates candidates; 159 160 extension = [extension lowercaseString]; 161 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 162 163 while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) { 164 if ([plugin supportsExtension:extension]) 165 candidates.update(plugin); 166 } 167 168 WebBasePluginPackage *plugin = candidates.bestCandidate(); 169 170 if (!plugin) { 171 // If no plug-in was found from the extension, attempt to map from the extension to a MIME type 172 // and find the a plug-in from the MIME type. This is done in case the plug-in has not fully specified 173 // an extension <-> MIME type mapping. 174 NSString *MIMEType = WKGetMIMETypeForExtension(extension); 175 if ([MIMEType length] > 0) 176 plugin = [self pluginForMIMEType:MIMEType]; 177 } 178 return plugin; 179} 180 181- (NSArray *)plugins 182{ 183 return [plugins allValues]; 184} 185 186static NSArray *additionalWebPlugInPaths; 187 188+ (void)setAdditionalWebPlugInPaths:(NSArray *)additionalPaths 189{ 190 if (additionalPaths == additionalWebPlugInPaths) 191 return; 192 193 [additionalWebPlugInPaths release]; 194 additionalWebPlugInPaths = [additionalPaths copy]; 195 196 // One might be tempted to add additionalWebPlugInPaths to the global WebPluginDatabase here. 197 // For backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, 198 // we need to save a copy of the additional paths and not cause a refresh of the plugin DB 199 // at this time. 200 // See Radars 4608487 and 4609047. 201} 202 203- (void)setPlugInPaths:(NSArray *)newPaths 204{ 205 if (plugInPaths == newPaths) 206 return; 207 208 [plugInPaths release]; 209 plugInPaths = [newPaths copy]; 210} 211 212- (void)close 213{ 214 NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator]; 215 WebBasePluginPackage *plugin; 216 while ((plugin = [pluginEnumerator nextObject]) != nil) 217 [self _removePlugin:plugin]; 218 [plugins release]; 219 plugins = nil; 220} 221 222- (id)init 223{ 224 if (!(self = [super init])) 225 return nil; 226 227 registeredMIMETypes = [[NSMutableSet alloc] init]; 228 pluginInstanceViews = [[NSMutableSet alloc] init]; 229 230 return self; 231} 232 233- (void)dealloc 234{ 235 [plugInPaths release]; 236 [plugins release]; 237 [registeredMIMETypes release]; 238 [pluginInstanceViews release]; 239 240 [super dealloc]; 241} 242 243- (void)refresh 244{ 245 // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling 246 // -refresh multiple times does not bloat the default pool. 247 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 248 249 // Create map from plug-in path to WebBasePluginPackage 250 if (!plugins) 251 plugins = [[NSMutableDictionary alloc] initWithCapacity:12]; 252 253 // Find all plug-ins on disk 254 NSMutableSet *newPlugins = [self _scanForNewPlugins]; 255 256 // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk) 257 NSMutableSet *pluginsToRemove = [NSMutableSet set]; 258 NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; 259 WebBasePluginPackage *plugin; 260 while ((plugin = [pluginEnumerator nextObject]) != nil) { 261 // Any plug-ins that were removed from disk since the last refresh should be removed from 262 // the database. 263 if (![newPlugins containsObject:plugin]) 264 [pluginsToRemove addObject:plugin]; 265 266 // Remove every member of 'plugins' from 'newPlugins'. After this loop exits, 'newPlugins' 267 // will be the set of new plug-ins that should be added to the database. 268 [newPlugins removeObject:plugin]; 269 } 270 271#if !LOG_DISABLED 272 if ([newPlugins count] > 0) 273 LOG(Plugins, "New plugins:\n%@", newPlugins); 274 if ([pluginsToRemove count] > 0) 275 LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove); 276#endif 277 278 // Remove plugins from database 279 pluginEnumerator = [pluginsToRemove objectEnumerator]; 280 while ((plugin = [pluginEnumerator nextObject]) != nil) 281 [self _removePlugin:plugin]; 282 283 // Add new plugins to database 284 pluginEnumerator = [newPlugins objectEnumerator]; 285 while ((plugin = [pluginEnumerator nextObject]) != nil) 286 [self _addPlugin:plugin]; 287 288 // Build a list of MIME types. 289 NSMutableSet *MIMETypes = [[NSMutableSet alloc] init]; 290 pluginEnumerator = [plugins objectEnumerator]; 291 while ((plugin = [pluginEnumerator nextObject])) { 292 const PluginInfo& pluginInfo = [plugin pluginInfo]; 293 for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) 294 [MIMETypes addObject:pluginInfo.mimes[i].type]; 295 } 296 297 // Register plug-in views and representations. 298 NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator]; 299 NSString *MIMEType; 300 while ((MIMEType = [MIMEEnumerator nextObject]) != nil) { 301 [registeredMIMETypes addObject:MIMEType]; 302 303 if ([WebView canShowMIMETypeAsHTML:MIMEType]) 304 // Don't allow plug-ins to override our core HTML types. 305 continue; 306 plugin = [self pluginForMIMEType:MIMEType]; 307 if ([plugin isJavaPlugIn]) 308 // Don't register the Java plug-in for a document view since Java files should be downloaded when not embedded. 309 continue; 310 if ([plugin isQuickTimePlugIn] && [[WebFrameView _viewTypesAllowImageTypeOmission:NO] objectForKey:MIMEType]) 311 // Don't allow the QT plug-in to override any types because it claims many that we can handle ourselves. 312 continue; 313 314 if (self == sharedDatabase) 315 [WebView _registerPluginMIMEType:MIMEType]; 316 } 317 [MIMETypes release]; 318 319 [pool drain]; 320} 321 322- (BOOL)isMIMETypeRegistered:(NSString *)MIMEType 323{ 324 return [registeredMIMETypes containsObject:MIMEType]; 325} 326 327- (void)addPluginInstanceView:(NSView *)view 328{ 329 [pluginInstanceViews addObject:view]; 330} 331 332- (void)removePluginInstanceView:(NSView *)view 333{ 334 [pluginInstanceViews removeObject:view]; 335} 336 337- (void)removePluginInstanceViewsFor:(WebFrame*)webFrame 338{ 339 // This handles handles the case where a frame or view is being destroyed and the plugin needs to be removed from the list first 340 341 if( [pluginInstanceViews count] == 0 ) 342 return; 343 344 NSView <WebDocumentView> *documentView = [[webFrame frameView] documentView]; 345 if ([documentView isKindOfClass:[WebHTMLView class]]) { 346 NSArray *subviews = [documentView subviews]; 347 unsigned int subviewCount = [subviews count]; 348 unsigned int subviewIndex; 349 350 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { 351 NSView *subview = [subviews objectAtIndex:subviewIndex]; 352#if ENABLE(NETSCAPE_PLUGIN_API) 353 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]] || [WebPluginController isPlugInView:subview]) 354#else 355 if ([WebPluginController isPlugInView:subview]) 356#endif 357 [pluginInstanceViews removeObject:subview]; 358 } 359 } 360} 361 362- (void)destroyAllPluginInstanceViews 363{ 364 NSView *view; 365 NSArray *pli = [pluginInstanceViews allObjects]; 366 NSEnumerator *enumerator = [pli objectEnumerator]; 367 while ((view = [enumerator nextObject]) != nil) { 368#if ENABLE(NETSCAPE_PLUGIN_API) 369 if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) { 370 ASSERT([view respondsToSelector:@selector(stop)]); 371 [view performSelector:@selector(stop)]; 372 } else 373#endif 374 if ([WebPluginController isPlugInView:view]) { 375 ASSERT([[view superview] isKindOfClass:[WebHTMLView class]]); 376 ASSERT([[view superview] respondsToSelector:@selector(_destroyAllWebPlugins)]); 377 // this will actually destroy all plugin instances for a webHTMLView and remove them from this list 378 [[view superview] performSelector:@selector(_destroyAllWebPlugins)]; 379 } 380 } 381} 382 383@end 384 385@implementation WebPluginDatabase (Internal) 386 387+ (NSArray *)_defaultPlugInPaths 388{ 389 // Plug-ins are found in order of precedence. 390 // If there are duplicates, the first found plug-in is used. 391 // For example, if there is a QuickTime.plugin in the users's home directory 392 // that is used instead of the /Library/Internet Plug-ins version. 393 // The purpose is to allow non-admin users to update their plug-ins. 394 return [NSArray arrayWithObjects: 395 [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Internet Plug-Ins"], 396 @"/Library/Internet Plug-Ins", 397 [[NSBundle mainBundle] builtInPlugInsPath], 398 nil]; 399} 400 401- (NSArray *)_plugInPaths 402{ 403 if (self == sharedDatabase && additionalWebPlugInPaths) { 404 // Add additionalWebPlugInPaths to the global WebPluginDatabase. We do this here for 405 // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, 406 // which simply saved a copy of the additional paths and did not cause the plugin DB to 407 // refresh. See Radars 4608487 and 4609047. 408 NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease]; 409 [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths]; 410 return modifiedPlugInPaths; 411 } else 412 return plugInPaths; 413} 414 415- (void)_addPlugin:(WebBasePluginPackage *)plugin 416{ 417 ASSERT(plugin); 418 NSString *pluginPath = [plugin path]; 419 ASSERT(pluginPath); 420 [plugins setObject:plugin forKey:pluginPath]; 421 [plugin wasAddedToPluginDatabase:self]; 422} 423 424- (void)_removePlugin:(WebBasePluginPackage *)plugin 425{ 426 ASSERT(plugin); 427 428 // Unregister plug-in's MIME type registrations 429 const PluginInfo& pluginInfo = [plugin pluginInfo]; 430 for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) { 431 NSString *MIMEType = pluginInfo.mimes[i].type; 432 433 if ([registeredMIMETypes containsObject:MIMEType]) { 434 if (self == sharedDatabase) 435 [WebView _unregisterPluginMIMEType:MIMEType]; 436 [registeredMIMETypes removeObject:MIMEType]; 437 } 438 } 439 440 // Remove plug-in from database 441 NSString *pluginPath = [plugin path]; 442 ASSERT(pluginPath); 443 [plugin retain]; 444 [plugins removeObjectForKey:pluginPath]; 445 [plugin wasRemovedFromPluginDatabase:self]; 446 [plugin release]; 447} 448 449- (NSMutableSet *)_scanForNewPlugins 450{ 451 NSMutableSet *newPlugins = [NSMutableSet set]; 452 NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator]; 453 NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init]; 454 NSFileManager *fileManager = [NSFileManager defaultManager]; 455 NSString *pluginDirectory; 456 while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) { 457 // Get contents of each plug-in directory 458 NSEnumerator *filenameEnumerator = [[fileManager contentsOfDirectoryAtPath:pluginDirectory error:NULL] objectEnumerator]; 459 NSString *filename; 460 while ((filename = [filenameEnumerator nextObject]) != nil) { 461 // Unique plug-ins by filename 462 if ([uniqueFilenames containsObject:filename]) 463 continue; 464 [uniqueFilenames addObject:filename]; 465 466 // Create a plug-in package for this path 467 NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename]; 468 WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath]; 469 if (!pluginPackage) 470 pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath]; 471 if (pluginPackage) 472 [newPlugins addObject:pluginPackage]; 473 } 474 } 475 [uniqueFilenames release]; 476 477 return newPlugins; 478} 479 480@end 481