Reviewed by Darin Adler.
[WebKit-https.git] / WebKit / Plugins / WebPluginDatabase.m
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 <WebKit/WebPluginDatabase.h>
30
31 #import <JavaScriptCore/Assertions.h>
32 #import <WebKit/WebBasePluginPackage.h>
33 #import <WebKit/WebDataSourcePrivate.h>
34 #import <WebKit/WebFrame.h>
35 #import <WebKit/WebFrameViewInternal.h>
36 #import <WebKit/WebHTMLRepresentation.h>
37 #import <WebKit/WebHTMLView.h>
38 #import <WebKit/WebKitLogging.h>
39 #import <WebKit/WebNetscapePluginPackage.h>
40 #import <WebKit/WebPluginPackage.h>
41 #import <WebKit/WebViewPrivate.h>
42 #import <WebKitSystemInterface.h>
43
44 @interface WebPluginDatabase (Internal)
45 + (NSArray *)_defaultPlugInPaths;
46 - (NSArray *)_plugInPaths;
47 - (void)_addPlugin:(WebBasePluginPackage *)plugin;
48 - (void)_removePlugin:(WebBasePluginPackage *)plugin;
49 - (NSMutableSet *)_scanForNewPlugins;
50 - (void)_applicationWillTerminate;
51 @end
52
53 @implementation WebPluginDatabase
54
55 static WebPluginDatabase *database = nil;
56
57 + (WebPluginDatabase *)installedPlugins 
58 {
59     if (!database) {
60         database = [[WebPluginDatabase alloc] init];
61         [database setPlugInPaths:[self _defaultPlugInPaths]];
62         [database refresh];
63         
64         // Clear the global plug-in database on app exit
65         [[NSNotificationCenter defaultCenter] addObserver:database
66                                                  selector:@selector(_applicationWillTerminate)
67                                                      name:NSApplicationWillTerminateNotification
68                                                    object:NSApp];
69     }
70     
71     return database;
72 }
73
74 - (WebBasePluginPackage *)pluginForKey:(NSString *)key withEnumeratorSelector:(SEL)enumeratorSelector
75 {
76     WebBasePluginPackage *plugin, *CFMPlugin=nil, *machoPlugin=nil, *webPlugin=nil;
77     NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
78     key = [key lowercaseString];
79
80     while ((plugin = [pluginEnumerator nextObject]) != nil) {
81         if ([[[plugin performSelector:enumeratorSelector] allObjects] containsObject:key]) {
82             if ([plugin isKindOfClass:[WebPluginPackage class]]) {
83                 if (!webPlugin)
84                     webPlugin = plugin;
85             } else if([plugin isKindOfClass:[WebNetscapePluginPackage class]]) {
86                 WebExecutableType executableType = [(WebNetscapePluginPackage *)plugin executableType];
87                 if (executableType == WebCFMExecutableType) {
88                     if (!CFMPlugin)
89                         CFMPlugin = plugin;
90                 } else if (executableType == WebMachOExecutableType) {
91                     if (!machoPlugin)
92                         machoPlugin = plugin;
93                 } else {
94                     ASSERT_NOT_REACHED();
95                 }
96             } else {
97                 ASSERT_NOT_REACHED();
98             }
99         }
100     }
101
102     // Allow other plug-ins to win over QT because if the user has installed a plug-in that can handle a type
103     // that the QT plug-in can handle, they probably intended to override QT.
104     if (webPlugin && ![webPlugin isQuickTimePlugIn])
105         return webPlugin;
106     else if (machoPlugin && ![machoPlugin isQuickTimePlugIn])
107         return machoPlugin;
108     else if (CFMPlugin && ![CFMPlugin isQuickTimePlugIn])
109         return CFMPlugin;
110     else if (webPlugin)
111         return webPlugin;
112     else if (machoPlugin)
113         return machoPlugin;
114     else if (CFMPlugin)
115         return CFMPlugin;
116     return nil;
117 }
118
119 - (WebBasePluginPackage *)pluginForMIMEType:(NSString *)MIMEType
120 {
121     return [self pluginForKey:[MIMEType lowercaseString]
122        withEnumeratorSelector:@selector(MIMETypeEnumerator)];
123 }
124
125 - (WebBasePluginPackage *)pluginForExtension:(NSString *)extension
126 {
127     WebBasePluginPackage *plugin = [self pluginForKey:[extension lowercaseString]
128                                withEnumeratorSelector:@selector(extensionEnumerator)];
129     if (!plugin) {
130         // If no plug-in was found from the extension, attempt to map from the extension to a MIME type
131         // and find the a plug-in from the MIME type. This is done in case the plug-in has not fully specified
132         // an extension <-> MIME type mapping.
133         NSString *MIMEType = WKGetMIMETypeForExtension(extension);
134         if ([MIMEType length] > 0)
135             plugin = [self pluginForMIMEType:MIMEType];
136     }
137     return plugin;
138 }
139
140 - (NSArray *)plugins
141 {
142     return [plugins allValues];
143 }
144
145 static NSArray *additionalWebPlugInPaths;
146
147 + (void)setAdditionalWebPlugInPaths:(NSArray *)additionalPaths
148 {
149     if (additionalPaths == additionalWebPlugInPaths)
150         return;
151     
152     [additionalWebPlugInPaths release];
153     additionalWebPlugInPaths = [additionalPaths copy];
154
155     // One might be tempted to add additionalWebPlugInPaths to the global WebPluginDatabase here.
156     // For backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
157     // we need to save a copy of the additional paths and not cause a refresh of the plugin DB
158     // at this time.
159     // See Radars 4608487 and 4609047.
160 }
161
162 - (void)setPlugInPaths:(NSArray *)newPaths
163 {
164     if (plugInPaths == newPaths)
165         return;
166         
167     [plugInPaths release];
168     plugInPaths = [newPaths copy];
169 }
170
171 - (void)close
172 {
173     NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator];
174     WebBasePluginPackage *plugin;
175     while ((plugin = [pluginEnumerator nextObject]) != nil)
176         [self _removePlugin:plugin];
177     [plugins release];
178     plugins = nil;
179 }
180
181 - (id)init
182 {
183     if (!(self = [super init]))
184         return nil;
185         
186     registeredMIMETypes = [[NSMutableSet alloc] init];
187     
188     return self;
189 }
190
191 - (void)dealloc
192 {
193     [plugInPaths release];
194     [plugins release];
195     [registeredMIMETypes release];
196     
197     [super dealloc];
198 }
199
200 - (void)refresh
201 {
202     // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling
203     // -refresh multiple times does not bloat the default pool.
204     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
205     
206     // Create map from plug-in path to WebBasePluginPackage
207     if (!plugins)
208         plugins = [[NSMutableDictionary alloc] initWithCapacity:12];
209
210     // Find all plug-ins on disk
211     NSMutableSet *newPlugins = [self _scanForNewPlugins];
212
213     // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk)
214     NSMutableSet *pluginsToRemove = [NSMutableSet set];
215     NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
216     WebBasePluginPackage *plugin;
217     while ((plugin = [pluginEnumerator nextObject]) != nil) {
218         // Any plug-ins that were removed from disk since the last refresh should be removed from
219         // the database.
220         if (![newPlugins containsObject:plugin])
221             [pluginsToRemove addObject:plugin];
222             
223         // Remove every member of 'plugins' from 'newPlugins'.  After this loop exits, 'newPlugins'
224         // will be the set of new plug-ins that should be added to the database.
225         [newPlugins removeObject:plugin];
226     }
227
228 #if !LOG_DISABLED
229     if ([newPlugins count] > 0)
230         LOG(Plugins, "New plugins:\n%@", newPlugins);
231     if ([pluginsToRemove count] > 0)
232         LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove);
233 #endif
234
235     // Remove plugins from database
236     pluginEnumerator = [pluginsToRemove objectEnumerator];
237     while ((plugin = [pluginEnumerator nextObject]) != nil) 
238         [self _removePlugin:plugin];
239     
240     // Add new plugins to database
241     pluginEnumerator = [newPlugins objectEnumerator];
242     while ((plugin = [pluginEnumerator nextObject]) != nil)
243         [self _addPlugin:plugin];
244
245     // Build a list of MIME types.
246     NSMutableSet *MIMETypes = [[NSMutableSet alloc] init];
247     pluginEnumerator = [plugins objectEnumerator];
248     while ((plugin = [pluginEnumerator nextObject]) != nil)
249         [MIMETypes addObjectsFromArray:[[plugin MIMETypeEnumerator] allObjects]];
250     
251     // Register plug-in views and representations.
252     NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator];
253     NSString *MIMEType;
254     while ((MIMEType = [MIMEEnumerator nextObject]) != nil) {
255         [registeredMIMETypes addObject:MIMEType];
256
257         if ([WebView canShowMIMETypeAsHTML:MIMEType])
258             // Don't allow plug-ins to override our core HTML types.
259             continue;
260         plugin = [self pluginForMIMEType:MIMEType];
261         if ([plugin isJavaPlugIn])
262             // Don't register the Java plug-in for a document view since Java files should be downloaded when not embedded.
263             continue;
264         if ([plugin isQuickTimePlugIn] && [[WebFrameView _viewTypesAllowImageTypeOmission:NO] objectForKey:MIMEType])
265             // Don't allow the QT plug-in to override any types because it claims many that we can handle ourselves.
266             continue;
267         
268         if (self == database)
269             [WebView registerViewClass:[WebHTMLView class] representationClass:[WebHTMLRepresentation class] forMIMEType:MIMEType];
270     }
271     [MIMETypes release];
272     
273     [pool release];
274 }
275
276 - (BOOL)isMIMETypeRegistered:(NSString *)MIMEType
277 {
278     return [registeredMIMETypes containsObject:MIMEType];
279 }
280
281 @end
282
283 @implementation WebPluginDatabase (Internal)
284
285 + (NSArray *)_defaultPlugInPaths
286 {
287     // Plug-ins are found in order of precedence.
288     // If there are duplicates, the first found plug-in is used.
289     // For example, if there is a QuickTime.plugin in the users's home directory
290     // that is used instead of the /Library/Internet Plug-ins version.
291     // The purpose is to allow non-admin users to update their plug-ins.
292     return [NSArray arrayWithObjects:
293         [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Internet Plug-Ins"],
294         @"/Library/Internet Plug-Ins",
295         [[NSBundle mainBundle] builtInPlugInsPath],
296         nil];
297 }
298
299 - (NSArray *)_plugInPaths
300 {
301     if (self == database && additionalWebPlugInPaths) {
302         // Add additionalWebPlugInPaths to the global WebPluginDatabase.  We do this here for
303         // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
304         // which simply saved a copy of the additional paths and did not cause the plugin DB to 
305         // refresh.  See Radars 4608487 and 4609047.
306         NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease];
307         [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths];
308         return modifiedPlugInPaths;
309     } else
310         return plugInPaths;
311 }
312
313 - (void)_addPlugin:(WebBasePluginPackage *)plugin
314 {
315     ASSERT(plugin);
316     NSString *pluginPath = [plugin path];
317     ASSERT(pluginPath);
318     [plugins setObject:plugin forKey:pluginPath];
319     [plugin wasAddedToPluginDatabase:self];
320 }
321
322 - (void)_removePlugin:(WebBasePluginPackage *)plugin
323 {    
324     ASSERT(plugin);
325
326     // Unregister plug-in's MIME type registrations
327     NSEnumerator *MIMETypeEnumerator = [plugin MIMETypeEnumerator];
328     NSString *MIMEType;
329     while ((MIMEType = [MIMETypeEnumerator nextObject])) {
330         if ([registeredMIMETypes containsObject:MIMEType]) {
331             if (self == database)
332                 [WebView _unregisterViewClassAndRepresentationClassForMIMEType:MIMEType];
333             [registeredMIMETypes removeObject:MIMEType];
334         }
335     }
336
337     // Remove plug-in from database
338     NSString *pluginPath = [plugin path];
339     ASSERT(pluginPath);
340     [plugin retain];
341     [plugins removeObjectForKey:pluginPath];
342     [plugin wasRemovedFromPluginDatabase:self];
343     [plugin release];
344 }
345
346 - (NSMutableSet *)_scanForNewPlugins
347 {
348     NSMutableSet *newPlugins = [NSMutableSet set];
349     NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator];
350     NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init];
351     NSFileManager *fileManager = [NSFileManager defaultManager];
352     NSString *pluginDirectory;
353     while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) {
354         // Get contents of each plug-in directory
355         NSEnumerator *filenameEnumerator = [[fileManager directoryContentsAtPath:pluginDirectory] objectEnumerator];
356         NSString *filename;
357         while ((filename = [filenameEnumerator nextObject]) != nil) {
358             // Unique plug-ins by filename
359             if ([uniqueFilenames containsObject:filename])
360                 continue;
361             [uniqueFilenames addObject:filename];
362             
363             // Create a plug-in package for this path
364             NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename];
365             WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath];
366             if (!pluginPackage)
367                 pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath];
368             if (pluginPackage)
369                 [newPlugins addObject:pluginPackage];
370         }
371     }
372     [uniqueFilenames release];
373     
374     return newPlugins;
375 }
376
377 - (void)_applicationWillTerminate
378 {
379     ASSERT(self == database);
380     // Remove all plug-ins from database.  Netscape plug-ins have "destructor functions" that should be called
381     // when the browser unloads the plug-in.  These functions can do important things, such as closing/deleting files,
382     // so it is important to ensure that they are properly called when the application terminates.
383     [self close];
384 }
385
386 @end