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