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