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