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