3be1f12a77fb802cc019e71d611a65f267b9f19d
[WebKit-https.git] / 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 + (WebPluginDatabase *)sharedDatabaseIfExists
76 {
77     return sharedDatabase;
78 }
79
80 + (void)closeSharedDatabase 
81 {
82     [sharedDatabase close];
83 }
84
85 static 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
96 struct 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
186 static 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