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