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