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