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