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