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