/* * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import #import #import #import #import #import #import #import #import #import #import #import #import static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin); @interface WebPluginDatabase (Internal) + (NSArray *)_defaultPlugInPaths; - (NSArray *)_plugInPaths; - (void)_addPlugin:(WebBasePluginPackage *)plugin; - (void)_removePlugin:(WebBasePluginPackage *)plugin; - (NSMutableSet *)_scanForNewPlugins; @end @implementation WebPluginDatabase static WebPluginDatabase *sharedDatabase = nil; + (WebPluginDatabase *)sharedDatabase { if (!sharedDatabase) { sharedDatabase = [[WebPluginDatabase alloc] init]; [sharedDatabase setPlugInPaths:[self _defaultPlugInPaths]]; [sharedDatabase refresh]; } return sharedDatabase; } + (void)closeSharedDatabase { [sharedDatabase close]; } static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin) { if (!*currentPlugin) { *currentPlugin = *candidatePlugin; return; } if ([[[*currentPlugin bundle] bundleIdentifier] isEqualToString:[[*candidatePlugin bundle] bundleIdentifier]] && [*candidatePlugin versionNumber] > [*currentPlugin versionNumber]) *currentPlugin = *candidatePlugin; } - (WebBasePluginPackage *)pluginForKey:(NSString *)key withEnumeratorSelector:(SEL)enumeratorSelector { WebBasePluginPackage *plugin = nil; WebBasePluginPackage *webPlugin = nil; #ifndef __LP64__ WebBasePluginPackage *CFMPlugin = nil; WebBasePluginPackage *machoPlugin = nil; #endif NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; key = [key lowercaseString]; while ((plugin = [pluginEnumerator nextObject]) != nil) { if ([[[plugin performSelector:enumeratorSelector] allObjects] containsObject:key]) { if ([plugin isKindOfClass:[WebPluginPackage class]]) checkCandidate(&webPlugin, &plugin); #ifndef __LP64__ else if([plugin isKindOfClass:[WebNetscapePluginPackage class]]) { WebExecutableType executableType = [(WebNetscapePluginPackage *)plugin executableType]; if (executableType == WebCFMExecutableType) { checkCandidate(&CFMPlugin, &plugin); } else if (executableType == WebMachOExecutableType) { checkCandidate(&machoPlugin, &plugin); } else { ASSERT_NOT_REACHED(); } } else { ASSERT_NOT_REACHED(); } #endif } } // Allow other plug-ins to win over QT because if the user has installed a plug-in that can handle a type // that the QT plug-in can handle, they probably intended to override QT. if (webPlugin && ![webPlugin isQuickTimePlugIn]) return webPlugin; #ifndef __LP64__ else if (machoPlugin && ![machoPlugin isQuickTimePlugIn]) return machoPlugin; else if (CFMPlugin && ![CFMPlugin isQuickTimePlugIn]) return CFMPlugin; #endif else if (webPlugin) return webPlugin; #ifndef __LP64__ else if (machoPlugin) return machoPlugin; else if (CFMPlugin) return CFMPlugin; #endif return nil; } - (WebBasePluginPackage *)pluginForMIMEType:(NSString *)MIMEType { return [self pluginForKey:[MIMEType lowercaseString] withEnumeratorSelector:@selector(MIMETypeEnumerator)]; } - (WebBasePluginPackage *)pluginForExtension:(NSString *)extension { WebBasePluginPackage *plugin = [self pluginForKey:[extension lowercaseString] withEnumeratorSelector:@selector(extensionEnumerator)]; if (!plugin) { // If no plug-in was found from the extension, attempt to map from the extension to a MIME type // and find the a plug-in from the MIME type. This is done in case the plug-in has not fully specified // an extension <-> MIME type mapping. NSString *MIMEType = WKGetMIMETypeForExtension(extension); if ([MIMEType length] > 0) plugin = [self pluginForMIMEType:MIMEType]; } return plugin; } - (NSArray *)plugins { return [plugins allValues]; } static NSArray *additionalWebPlugInPaths; + (void)setAdditionalWebPlugInPaths:(NSArray *)additionalPaths { if (additionalPaths == additionalWebPlugInPaths) return; [additionalWebPlugInPaths release]; additionalWebPlugInPaths = [additionalPaths copy]; // One might be tempted to add additionalWebPlugInPaths to the global WebPluginDatabase here. // For backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, // we need to save a copy of the additional paths and not cause a refresh of the plugin DB // at this time. // See Radars 4608487 and 4609047. } - (void)setPlugInPaths:(NSArray *)newPaths { if (plugInPaths == newPaths) return; [plugInPaths release]; plugInPaths = [newPaths copy]; } - (void)close { NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator]; WebBasePluginPackage *plugin; while ((plugin = [pluginEnumerator nextObject]) != nil) [self _removePlugin:plugin]; [plugins release]; plugins = nil; } - (id)init { if (!(self = [super init])) return nil; registeredMIMETypes = [[NSMutableSet alloc] init]; return self; } - (void)dealloc { [plugInPaths release]; [plugins release]; [registeredMIMETypes release]; [super dealloc]; } - (void)refresh { // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling // -refresh multiple times does not bloat the default pool. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Create map from plug-in path to WebBasePluginPackage if (!plugins) plugins = [[NSMutableDictionary alloc] initWithCapacity:12]; // Find all plug-ins on disk NSMutableSet *newPlugins = [self _scanForNewPlugins]; // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk) NSMutableSet *pluginsToRemove = [NSMutableSet set]; NSEnumerator *pluginEnumerator = [plugins objectEnumerator]; WebBasePluginPackage *plugin; while ((plugin = [pluginEnumerator nextObject]) != nil) { // Any plug-ins that were removed from disk since the last refresh should be removed from // the database. if (![newPlugins containsObject:plugin]) [pluginsToRemove addObject:plugin]; // Remove every member of 'plugins' from 'newPlugins'. After this loop exits, 'newPlugins' // will be the set of new plug-ins that should be added to the database. [newPlugins removeObject:plugin]; } #if !LOG_DISABLED if ([newPlugins count] > 0) LOG(Plugins, "New plugins:\n%@", newPlugins); if ([pluginsToRemove count] > 0) LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove); #endif // Remove plugins from database pluginEnumerator = [pluginsToRemove objectEnumerator]; while ((plugin = [pluginEnumerator nextObject]) != nil) [self _removePlugin:plugin]; // Add new plugins to database pluginEnumerator = [newPlugins objectEnumerator]; while ((plugin = [pluginEnumerator nextObject]) != nil) [self _addPlugin:plugin]; // Build a list of MIME types. NSMutableSet *MIMETypes = [[NSMutableSet alloc] init]; pluginEnumerator = [plugins objectEnumerator]; while ((plugin = [pluginEnumerator nextObject]) != nil) [MIMETypes addObjectsFromArray:[[plugin MIMETypeEnumerator] allObjects]]; // Register plug-in views and representations. NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator]; NSString *MIMEType; while ((MIMEType = [MIMEEnumerator nextObject]) != nil) { [registeredMIMETypes addObject:MIMEType]; if ([WebView canShowMIMETypeAsHTML:MIMEType]) // Don't allow plug-ins to override our core HTML types. continue; plugin = [self pluginForMIMEType:MIMEType]; if ([plugin isJavaPlugIn]) // Don't register the Java plug-in for a document view since Java files should be downloaded when not embedded. continue; if ([plugin isQuickTimePlugIn] && [[WebFrameView _viewTypesAllowImageTypeOmission:NO] objectForKey:MIMEType]) // Don't allow the QT plug-in to override any types because it claims many that we can handle ourselves. continue; if (self == sharedDatabase) [WebView registerViewClass:[WebHTMLView class] representationClass:[WebHTMLRepresentation class] forMIMEType:MIMEType]; } [MIMETypes release]; [pool drain]; } - (BOOL)isMIMETypeRegistered:(NSString *)MIMEType { return [registeredMIMETypes containsObject:MIMEType]; } @end @implementation WebPluginDatabase (Internal) + (NSArray *)_defaultPlugInPaths { // Plug-ins are found in order of precedence. // If there are duplicates, the first found plug-in is used. // For example, if there is a QuickTime.plugin in the users's home directory // that is used instead of the /Library/Internet Plug-ins version. // The purpose is to allow non-admin users to update their plug-ins. return [NSArray arrayWithObjects: [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Internet Plug-Ins"], @"/Library/Internet Plug-Ins", [[NSBundle mainBundle] builtInPlugInsPath], nil]; } - (NSArray *)_plugInPaths { if (self == sharedDatabase && additionalWebPlugInPaths) { // Add additionalWebPlugInPaths to the global WebPluginDatabase. We do this here for // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI, // which simply saved a copy of the additional paths and did not cause the plugin DB to // refresh. See Radars 4608487 and 4609047. NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease]; [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths]; return modifiedPlugInPaths; } else return plugInPaths; } - (void)_addPlugin:(WebBasePluginPackage *)plugin { ASSERT(plugin); NSString *pluginPath = [plugin path]; ASSERT(pluginPath); [plugins setObject:plugin forKey:pluginPath]; [plugin wasAddedToPluginDatabase:self]; } - (void)_removePlugin:(WebBasePluginPackage *)plugin { ASSERT(plugin); // Unregister plug-in's MIME type registrations NSEnumerator *MIMETypeEnumerator = [plugin MIMETypeEnumerator]; NSString *MIMEType; while ((MIMEType = [MIMETypeEnumerator nextObject])) { if ([registeredMIMETypes containsObject:MIMEType]) { if (self == sharedDatabase) [WebView _unregisterViewClassAndRepresentationClassForMIMEType:MIMEType]; [registeredMIMETypes removeObject:MIMEType]; } } // Remove plug-in from database NSString *pluginPath = [plugin path]; ASSERT(pluginPath); [plugin retain]; [plugins removeObjectForKey:pluginPath]; [plugin wasRemovedFromPluginDatabase:self]; [plugin release]; } - (NSMutableSet *)_scanForNewPlugins { NSMutableSet *newPlugins = [NSMutableSet set]; NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator]; NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *pluginDirectory; while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) { // Get contents of each plug-in directory NSEnumerator *filenameEnumerator = [[fileManager directoryContentsAtPath:pluginDirectory] objectEnumerator]; NSString *filename; while ((filename = [filenameEnumerator nextObject]) != nil) { // Unique plug-ins by filename if ([uniqueFilenames containsObject:filename]) continue; [uniqueFilenames addObject:filename]; // Create a plug-in package for this path NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename]; WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath]; if (!pluginPackage) pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath]; if (pluginPackage) [newPlugins addObject:pluginPackage]; } } [uniqueFilenames release]; return newPlugins; } @end