initial import
[vuplus_webkit] / Source / WebKit / mac / Plugins / WebPluginDatabase.mm
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