Reviewed by Darin, landed by Anders.
[WebKit-https.git] / WebKit / mac / Plugins / WebBasePluginPackage.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/WebBasePluginPackage.h>
30
31 #import <JavaScriptCore/Assertions.h>
32 #import <WebKit/WebKitNSStringExtras.h>
33 #import <WebKit/WebNetscapePluginPackage.h>
34 #import <WebKit/WebNSObjectExtras.h>
35 #import <WebKit/WebPluginPackage.h>
36 #import <WebCore/WebCoreObjCExtras.h>
37
38 #import <WebKitSystemInterface.h>
39
40 #import "WebKitLogging.h"
41 #import "WebTypesInternal.h"
42
43 #import <mach-o/arch.h>
44 #import <mach-o/loader.h>
45
46 #define JavaCocoaPluginIdentifier   @"com.apple.JavaPluginCocoa"
47 #define JavaCarbonPluginIdentifier  @"com.apple.JavaAppletPlugin"
48 #define JavaCFMPluginFilename       @"Java Applet Plugin Enabler"
49
50 #define QuickTimeCarbonPluginIdentifier       @"com.apple.QuickTime Plugin.plugin"
51 #define QuickTimeCocoaPluginIdentifier        @"com.apple.quicktime.webplugin"
52
53 @interface NSArray (WebPluginExtensions)
54 - (NSArray *)_web_lowercaseStrings;
55 @end;
56
57 @implementation WebBasePluginPackage
58
59 #ifndef BUILDING_ON_TIGER
60 + (void)initialize
61 {
62     WebCoreObjCFinalizeOnMainThread(self);
63 }
64 #endif
65
66 + (WebBasePluginPackage *)pluginWithPath:(NSString *)pluginPath
67 {
68     
69     WebBasePluginPackage *pluginPackage = [[WebPluginPackage alloc] initWithPath:pluginPath];
70
71     if (!pluginPackage) {
72 #ifdef __LP64__
73         return nil;
74 #else
75         pluginPackage = [[WebNetscapePluginPackage alloc] initWithPath:pluginPath];
76 #endif
77     }
78
79     return [pluginPackage autorelease];
80 }
81
82 + (NSString *)preferredLocalizationName
83 {
84     return WebCFAutorelease(WKCopyCFLocalizationPreferredName(NULL));
85 }
86
87 - (NSString *)pathByResolvingSymlinksAndAliasesInPath:(NSString *)thePath
88 {
89     NSString *newPath = [thePath stringByResolvingSymlinksInPath];
90
91     FSRef fref;
92     OSStatus err;
93
94     err = FSPathMakeRef((const UInt8 *)[thePath fileSystemRepresentation], &fref, NULL);
95     if (err != noErr)
96         return newPath;
97
98     Boolean targetIsFolder;
99     Boolean wasAliased;
100     err = FSResolveAliasFileWithMountFlags(&fref, TRUE, &targetIsFolder, &wasAliased, kResolveAliasFileNoUI);
101     if (err != noErr)
102         return newPath;
103
104     if (wasAliased) {
105         CFURLRef URL = CFURLCreateFromFSRef(kCFAllocatorDefault, &fref);
106         newPath = [(NSURL *)URL path];
107         CFRelease(URL);
108     }
109
110     return newPath;
111 }
112
113 - (id)initWithPath:(NSString *)pluginPath
114 {
115     if (!(self = [super init]))
116         return nil;
117         
118     path = [[self pathByResolvingSymlinksAndAliasesInPath:pluginPath] retain];
119     bundle = [[NSBundle alloc] initWithPath:path];
120 #ifndef __ppc__
121     // 32-bit PowerPC is the only platform where non-bundled CFM plugins are supported
122     if (!bundle) {
123         [self release];
124         return nil;
125     }
126 #endif
127     cfBundle = CFBundleCreate(NULL, (CFURLRef)[NSURL fileURLWithPath:path]);
128     extensionToMIME = [[NSMutableDictionary alloc] init];
129     
130     return self;
131 }
132
133 - (BOOL)getPluginInfoFromBundleAndMIMEDictionary:(NSDictionary *)MIMETypes
134 {
135     if (!bundle)
136         return NO;
137     
138     if (!MIMETypes) {
139         MIMETypes = [bundle objectForInfoDictionaryKey:WebPluginMIMETypesKey];
140         if (!MIMETypes)
141             return NO;
142     }
143
144     NSMutableDictionary *MIMEToExtensionsDictionary = [NSMutableDictionary dictionary];
145     NSMutableDictionary *MIMEToDescriptionDictionary = [NSMutableDictionary dictionary];
146     NSEnumerator *keyEnumerator = [MIMETypes keyEnumerator];
147     NSDictionary *MIMEDictionary;
148     NSString *MIME, *description;
149     NSArray *extensions;
150
151     while ((MIME = [keyEnumerator nextObject]) != nil) {
152         MIMEDictionary = [MIMETypes objectForKey:MIME];
153         
154         // FIXME: Consider storing disabled MIME types.
155         NSNumber *isEnabled = [MIMEDictionary objectForKey:WebPluginTypeEnabledKey];
156         if (isEnabled && [isEnabled boolValue] == NO)
157             continue;
158
159         extensions = [[MIMEDictionary objectForKey:WebPluginExtensionsKey] _web_lowercaseStrings];
160         if ([extensions count] == 0)
161             extensions = [NSArray arrayWithObject:@""];
162
163         MIME = [MIME lowercaseString];
164
165         [MIMEToExtensionsDictionary setObject:extensions forKey:MIME];
166
167         description = [MIMEDictionary objectForKey:WebPluginTypeDescriptionKey];
168         if (!description)
169             description = @"";
170
171         [MIMEToDescriptionDictionary setObject:description forKey:MIME];
172     }
173
174     [self setMIMEToExtensionsDictionary:MIMEToExtensionsDictionary];
175     [self setMIMEToDescriptionDictionary:MIMEToDescriptionDictionary];
176
177     NSString *filename = [self filename];
178
179     NSString *theName = [bundle objectForInfoDictionaryKey:WebPluginNameKey];
180     if (!theName)
181         theName = filename;
182     [self setName:theName];
183
184     description = [bundle objectForInfoDictionaryKey:WebPluginDescriptionKey];
185     if (!description)
186         description = filename;
187     [self setPluginDescription:description];
188
189     return YES;
190 }
191
192 - (NSDictionary *)pListForPath:(NSString *)pListPath createFile:(BOOL)createFile
193 {
194     if (createFile && [self load] && BP_CreatePluginMIMETypesPreferences)
195         BP_CreatePluginMIMETypesPreferences();
196     
197     NSDictionary *pList = nil;
198     NSData *data = [NSData dataWithContentsOfFile:pListPath];
199     if (data) {
200         pList = [NSPropertyListSerialization propertyListFromData:data
201                                                  mutabilityOption:NSPropertyListImmutable
202                                                            format:nil
203                                                  errorDescription:nil];
204     }
205     
206     return pList;
207 }
208
209 - (BOOL)getPluginInfoFromPLists
210 {
211     if (!bundle)
212         return NO;
213     
214     NSDictionary *MIMETypes = nil;
215     NSString *pListFilename = [bundle objectForInfoDictionaryKey:WebPluginMIMETypesFilenameKey];
216     
217     // Check if the MIME types are claimed in a plist in the user's preferences directory.
218     if (pListFilename) {
219         NSString *pListPath = [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), pListFilename];
220         NSDictionary *pList = [self pListForPath:pListPath createFile:NO];
221         if (pList) {
222             // If the plist isn't localized, have the plug-in recreate it in the preferred language.
223             NSString *localizationName = [pList objectForKey:WebPluginLocalizationNameKey];
224             if (![localizationName isEqualToString:[[self class] preferredLocalizationName]])
225                 pList = [self pListForPath:pListPath createFile:YES];
226             MIMETypes = [pList objectForKey:WebPluginMIMETypesKey];
227         } else
228             // Plist doesn't exist, ask the plug-in to create it.
229             MIMETypes = [[self pListForPath:pListPath createFile:YES] objectForKey:WebPluginMIMETypesKey];
230     }
231     
232     // Pass the MIME dictionary to the superclass to parse it.
233     return [self getPluginInfoFromBundleAndMIMEDictionary:MIMETypes];
234 }
235
236 - (BOOL)load
237 {
238     if (bundle && !BP_CreatePluginMIMETypesPreferences)
239         BP_CreatePluginMIMETypesPreferences = (BP_CreatePluginMIMETypesPreferencesFuncPtr)CFBundleGetFunctionPointerForName(cfBundle, CFSTR("BP_CreatePluginMIMETypesPreferences"));
240     
241     return YES;
242 }
243
244 - (void)dealloc
245 {
246     ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
247     [pluginDatabases release];
248     
249     [name release];
250     [path release];
251     [pluginDescription release];
252
253     [MIMEToDescription release];
254     [MIMEToExtensions release];
255     [extensionToMIME release];
256
257     [bundle release];
258     if (cfBundle)
259         CFRelease(cfBundle);
260     
261     [super dealloc];
262 }
263
264 - (void)finalize
265 {
266     ASSERT_MAIN_THREAD();
267     ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
268     [pluginDatabases release];
269
270     if (cfBundle)
271         CFRelease(cfBundle);
272
273     [super finalize];
274 }
275
276 - (NSString *)name
277 {
278     return name;
279 }
280
281 - (NSString *)path
282 {
283     return path;
284 }
285
286 - (NSString *)filename
287 {
288     return [path lastPathComponent];
289 }
290
291 - (NSString *)pluginDescription
292 {
293     return pluginDescription;
294 }
295
296 - (NSEnumerator *)extensionEnumerator
297 {
298     return [extensionToMIME keyEnumerator];
299 }
300
301 - (NSEnumerator *)MIMETypeEnumerator
302 {
303     return [MIMEToExtensions keyEnumerator];
304 }
305
306 - (NSString *)descriptionForMIMEType:(NSString *)MIMEType
307 {
308     return [MIMEToDescription objectForKey:MIMEType];
309 }
310
311 - (NSString *)MIMETypeForExtension:(NSString *)extension
312 {
313     return [extensionToMIME objectForKey:extension];
314 }
315
316 - (NSArray *)extensionsForMIMEType:(NSString *)MIMEType
317 {
318     return [MIMEToExtensions objectForKey:MIMEType];
319 }
320
321 - (NSBundle *)bundle
322 {
323     return bundle;
324 }
325
326 - (void)setName:(NSString *)theName
327 {
328     [name release];
329     name = [theName retain];
330 }
331
332 - (void)setPath:(NSString *)thePath
333 {
334     [path release];
335     path = [thePath retain];
336 }
337
338 - (void)setPluginDescription:(NSString *)description
339 {
340     [pluginDescription release];
341     pluginDescription = [description retain];
342 }
343
344 - (void)setMIMEToDescriptionDictionary:(NSDictionary *)MIMEToDescriptionDictionary
345 {
346     [MIMEToDescription release];
347     MIMEToDescription = [MIMEToDescriptionDictionary retain];
348 }
349
350 - (void)setMIMEToExtensionsDictionary:(NSDictionary *)MIMEToExtensionsDictionary
351 {
352     [MIMEToExtensions release];
353     MIMEToExtensions = [MIMEToExtensionsDictionary retain];
354
355     // Reverse the mapping
356     [extensionToMIME removeAllObjects];
357
358     NSEnumerator *MIMEEnumerator = [MIMEToExtensions keyEnumerator], *extensionEnumerator;
359     NSString *MIME, *extension;
360     NSArray *extensions;
361     
362     while ((MIME = [MIMEEnumerator nextObject]) != nil) {
363         extensions = [MIMEToExtensions objectForKey:MIME];
364         extensionEnumerator = [extensions objectEnumerator];
365
366         while ((extension = [extensionEnumerator nextObject]) != nil) {
367             if (![extension isEqualToString:@""])
368                 [extensionToMIME setObject:MIME forKey:extension];
369         }
370     }
371 }
372
373 - (NSString *)description
374 {
375     return [NSString stringWithFormat:@"name: %@\npath: %@\nmimeTypes:\n%@\npluginDescription:%@",
376         name, path, [MIMEToExtensions description], [MIMEToDescription description], pluginDescription];
377 }
378
379 - (BOOL)isQuickTimePlugIn
380 {
381     NSString *bundleIdentifier = [[self bundle] bundleIdentifier];
382     return [bundleIdentifier _webkit_isCaseInsensitiveEqualToString:QuickTimeCarbonPluginIdentifier] || 
383         [bundleIdentifier _webkit_isCaseInsensitiveEqualToString:QuickTimeCocoaPluginIdentifier];
384 }
385
386 - (BOOL)isJavaPlugIn
387 {
388     NSString *bundleIdentifier = [[self bundle] bundleIdentifier];
389     return [bundleIdentifier _webkit_isCaseInsensitiveEqualToString:JavaCocoaPluginIdentifier] || 
390         [bundleIdentifier _webkit_isCaseInsensitiveEqualToString:JavaCarbonPluginIdentifier] ||
391         [[path lastPathComponent] _webkit_isCaseInsensitiveEqualToString:JavaCFMPluginFilename];
392 }
393
394 - (BOOL)isNativeLibraryData:(NSData *)data
395 {  
396     // If we have a 32-bit thin Mach-O file, see if we have an i386 binary.  If not, don't load it.
397     // This is designed to be the safest possible test for now.  We'll only reject files that we
398     // can easily tell are wrong.
399     if ([data length] >= sizeof(struct mach_header)) {
400         const NXArchInfo *localArch = NXGetLocalArchInfo();
401         if (localArch != NULL) {
402             struct mach_header *header = (struct mach_header *)[data bytes];
403             if (header->magic == MH_MAGIC)
404                 return (header->cputype == localArch->cputype);
405             if (header->magic == MH_CIGAM)
406                 return ((cpu_type_t) OSSwapInt32(header->cputype) == localArch->cputype);
407         }
408     }
409     return YES;
410 }
411
412 - (UInt32)versionNumber
413 {
414     // CFBundleGetVersionNumber doesn't work with all possible versioning schemes, but we think for now it's good enough for us.
415     return CFBundleGetVersionNumber(cfBundle);
416 }
417
418 - (void)wasAddedToPluginDatabase:(WebPluginDatabase *)database
419 {    
420     if (!pluginDatabases)
421         pluginDatabases = [[NSMutableSet alloc] init];
422         
423     ASSERT(![pluginDatabases containsObject:database]);
424     [pluginDatabases addObject:database];
425 }
426
427 - (void)wasRemovedFromPluginDatabase:(WebPluginDatabase *)database
428 {
429     ASSERT(pluginDatabases);
430     ASSERT([pluginDatabases containsObject:database]);
431
432     [pluginDatabases removeObject:database];
433 }
434
435 @end
436
437 @implementation NSArray (WebPluginExtensions)
438
439 - (NSArray *)_web_lowercaseStrings
440 {
441     NSMutableArray *lowercaseStrings = [NSMutableArray arrayWithCapacity:[self count]];
442     NSEnumerator *strings = [self objectEnumerator];
443     NSString *string;
444
445     while ((string = [strings nextObject]) != nil) {
446         if ([string isKindOfClass:[NSString class]])
447             [lowercaseStrings addObject:[string lowercaseString]];
448     }
449
450     return lowercaseStrings;
451 }
452
453 @end