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 48 static 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 60 static 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 78 static 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 173 static 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