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