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 <algorithm>
32#import <WebCore/WebCoreObjCExtras.h>
33#import <WebKit/WebKitNSStringExtras.h>
34#import <WebKit/WebNSObjectExtras.h>
35#import <WebKit/WebNetscapePluginPackage.h>
36#import <WebKit/WebPluginPackage.h>
37#import <runtime/InitializeThreading.h>
38#import <wtf/Assertions.h>
39#import <wtf/Threading.h>
40#import <wtf/Vector.h>
41#import <wtf/text/CString.h>
42
43#import <WebKitSystemInterface.h>
44
45#import "WebKitLogging.h"
46#import "WebTypesInternal.h"
47
48#import <mach-o/arch.h>
49#import <mach-o/fat.h>
50#import <mach-o/loader.h>
51
52#define JavaCocoaPluginIdentifier   "com.apple.JavaPluginCocoa"
53#define JavaCarbonPluginIdentifier  "com.apple.JavaAppletPlugin"
54#define JavaCFMPluginFilename       "Java Applet Plugin Enabler"
55
56#define QuickTimeCarbonPluginIdentifier       "com.apple.QuickTime Plugin.plugin"
57#define QuickTimeCocoaPluginIdentifier        "com.apple.quicktime.webplugin"
58
59@interface NSArray (WebPluginExtensions)
60- (NSArray *)_web_lowercaseStrings;
61@end;
62
63using namespace std;
64using namespace WebCore;
65
66@implementation WebBasePluginPackage
67
68+ (void)initialize
69{
70    JSC::initializeThreading();
71    WTF::initializeMainThreadToProcessMainThread();
72#ifndef BUILDING_ON_TIGER
73    WebCoreObjCFinalizeOnMainThread(self);
74#endif
75}
76
77+ (WebBasePluginPackage *)pluginWithPath:(NSString *)pluginPath
78{
79
80    WebBasePluginPackage *pluginPackage = [[WebPluginPackage alloc] initWithPath:pluginPath];
81
82    if (!pluginPackage) {
83#if ENABLE(NETSCAPE_PLUGIN_API)
84        pluginPackage = [[WebNetscapePluginPackage alloc] initWithPath:pluginPath];
85#else
86        return nil;
87#endif
88    }
89
90    return [pluginPackage autorelease];
91}
92
93+ (NSString *)preferredLocalizationName
94{
95    return WebCFAutorelease(WKCopyCFLocalizationPreferredName(NULL));
96}
97
98static NSString *pathByResolvingSymlinksAndAliases(NSString *thePath)
99{
100    NSString *newPath = [thePath stringByResolvingSymlinksInPath];
101
102    FSRef fref;
103    OSStatus err;
104
105    err = FSPathMakeRef((const UInt8 *)[thePath fileSystemRepresentation], &fref, NULL);
106    if (err != noErr)
107        return newPath;
108
109    Boolean targetIsFolder;
110    Boolean wasAliased;
111    err = FSResolveAliasFileWithMountFlags(&fref, TRUE, &targetIsFolder, &wasAliased, kResolveAliasFileNoUI);
112    if (err != noErr)
113        return newPath;
114
115    if (wasAliased) {
116        CFURLRef URL = CFURLCreateFromFSRef(kCFAllocatorDefault, &fref);
117        newPath = [(NSURL *)URL path];
118        CFRelease(URL);
119    }
120
121    return newPath;
122}
123
124- (id)initWithPath:(NSString *)pluginPath
125{
126    if (!(self = [super init]))
127        return nil;
128
129    path = pathByResolvingSymlinksAndAliases(pluginPath);
130    cfBundle.adoptCF(CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:path]));
131
132#ifndef __ppc__
133    // 32-bit PowerPC is the only platform where non-bundled CFM plugins are supported
134    if (!cfBundle) {
135        [self release];
136        return nil;
137    }
138#endif
139
140    return self;
141}
142
143- (void)unload
144{
145}
146
147- (void)createPropertyListFile
148{
149    if ([self load] && BP_CreatePluginMIMETypesPreferences) {
150        BP_CreatePluginMIMETypesPreferences();
151        [self unload];
152    }
153}
154
155- (NSDictionary *)pListForPath:(NSString *)pListPath createFile:(BOOL)createFile
156{
157    if (createFile)
158        [self createPropertyListFile];
159
160    NSDictionary *pList = nil;
161    NSData *data = [NSData dataWithContentsOfFile:pListPath];
162    if (data) {
163        pList = [NSPropertyListSerialization propertyListFromData:data
164                                                 mutabilityOption:NSPropertyListImmutable
165                                                           format:nil
166                                                 errorDescription:nil];
167    }
168
169    return pList;
170}
171
172- (id)_objectForInfoDictionaryKey:(NSString *)key
173{
174    CFDictionaryRef bundleInfoDictionary = CFBundleGetInfoDictionary(cfBundle.get());
175    if (!bundleInfoDictionary)
176        return nil;
177
178    return (id)CFDictionaryGetValue(bundleInfoDictionary, key);
179}
180
181- (BOOL)getPluginInfoFromPLists
182{
183    if (!cfBundle)
184        return NO;
185
186    NSDictionary *MIMETypes = nil;
187    NSString *pListFilename = [self _objectForInfoDictionaryKey:WebPluginMIMETypesFilenameKey];
188
189    // Check if the MIME types are claimed in a plist in the user's preferences directory.
190    if (pListFilename) {
191        NSString *pListPath = [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), pListFilename];
192        NSDictionary *pList = [self pListForPath:pListPath createFile:NO];
193        if (pList) {
194            // If the plist isn't localized, have the plug-in recreate it in the preferred language.
195            NSString *localizationName = [pList objectForKey:WebPluginLocalizationNameKey];
196            if (![localizationName isEqualToString:[[self class] preferredLocalizationName]])
197                pList = [self pListForPath:pListPath createFile:YES];
198            MIMETypes = [pList objectForKey:WebPluginMIMETypesKey];
199        } else
200            // Plist doesn't exist, ask the plug-in to create it.
201            MIMETypes = [[self pListForPath:pListPath createFile:YES] objectForKey:WebPluginMIMETypesKey];
202    }
203
204    if (!MIMETypes) {
205        MIMETypes = [self _objectForInfoDictionaryKey:WebPluginMIMETypesKey];
206        if (!MIMETypes)
207            return NO;
208    }
209
210    NSEnumerator *keyEnumerator = [MIMETypes keyEnumerator];
211    NSDictionary *MIMEDictionary;
212    NSString *MIME, *description;
213    NSArray *extensions;
214
215    while ((MIME = [keyEnumerator nextObject]) != nil) {
216        MIMEDictionary = [MIMETypes objectForKey:MIME];
217
218        // FIXME: Consider storing disabled MIME types.
219        NSNumber *isEnabled = [MIMEDictionary objectForKey:WebPluginTypeEnabledKey];
220        if (isEnabled && [isEnabled boolValue] == NO)
221            continue;
222
223        MimeClassInfo mimeClassInfo;
224
225        extensions = [[MIMEDictionary objectForKey:WebPluginExtensionsKey] _web_lowercaseStrings];
226        for (NSUInteger i = 0; i < [extensions count]; ++i) {
227            // The DivX plug-in lists multiple extensions in a comma separated string instead of using
228            // multiple array elements in the property list. Work around this here by splitting the
229            // extension string into components.
230            NSArray *extensionComponents = [[extensions objectAtIndex:i] componentsSeparatedByString:@","];
231
232            for (NSString *extension in extensionComponents)
233                mimeClassInfo.extensions.append(extension);
234        }
235
236        if ([extensions count] == 0)
237            extensions = [NSArray arrayWithObject:@""];
238
239        mimeClassInfo.type = String(MIME).lower();
240
241        description = [MIMEDictionary objectForKey:WebPluginTypeDescriptionKey];
242        mimeClassInfo.desc = description;
243
244        pluginInfo.mimes.append(mimeClassInfo);
245        if (!description)
246            description = @"";
247    }
248
249    NSString *filename = [(NSString *)path lastPathComponent];
250    pluginInfo.file = filename;
251
252    NSString *theName = [self _objectForInfoDictionaryKey:WebPluginNameKey];
253    if (!theName)
254        theName = filename;
255    pluginInfo.name = theName;
256
257    description = [self _objectForInfoDictionaryKey:WebPluginDescriptionKey];
258    if (!description)
259        description = filename;
260    pluginInfo.desc = description;
261
262    return YES;
263}
264
265- (BOOL)load
266{
267    if (cfBundle && !BP_CreatePluginMIMETypesPreferences)
268        BP_CreatePluginMIMETypesPreferences = (BP_CreatePluginMIMETypesPreferencesFuncPtr)CFBundleGetFunctionPointerForName(cfBundle.get(), CFSTR("BP_CreatePluginMIMETypesPreferences"));
269
270    return YES;
271}
272
273- (void)dealloc
274{
275    ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
276    [pluginDatabases release];
277
278    [super dealloc];
279}
280
281- (void)finalize
282{
283    ASSERT_MAIN_THREAD();
284    ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
285    [pluginDatabases release];
286
287    [super finalize];
288}
289
290- (const String&)path
291{
292    return path;
293}
294
295- (const PluginInfo&)pluginInfo
296{
297    return pluginInfo;
298}
299
300- (BOOL)supportsExtension:(const String&)extension
301{
302    ASSERT(extension.lower() == extension);
303
304    for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) {
305        const Vector<String>& extensions = pluginInfo.mimes[i].extensions;
306
307        if (find(extensions.begin(), extensions.end(), extension) != extensions.end())
308            return YES;
309    }
310
311    return NO;
312}
313
314- (BOOL)supportsMIMEType:(const WTF::String&)mimeType
315{
316    ASSERT(mimeType.lower() == mimeType);
317
318    for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) {
319        if (pluginInfo.mimes[i].type == mimeType)
320            return YES;
321    }
322
323    return NO;
324}
325
326- (NSString *)MIMETypeForExtension:(const String&)extension
327{
328    ASSERT(extension.lower() == extension);
329
330    for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) {
331        const MimeClassInfo& mimeClassInfo = pluginInfo.mimes[i];
332        const Vector<String>& extensions = mimeClassInfo.extensions;
333
334        if (find(extensions.begin(), extensions.end(), extension) != extensions.end())
335            return mimeClassInfo.type;
336    }
337
338    return nil;
339}
340
341- (BOOL)isQuickTimePlugIn
342{
343    const String& bundleIdentifier = [self bundleIdentifier];
344    return bundleIdentifier == QuickTimeCocoaPluginIdentifier || bundleIdentifier == QuickTimeCocoaPluginIdentifier;
345}
346
347- (BOOL)isJavaPlugIn
348{
349    const String& bundleIdentifier = [self bundleIdentifier];
350    return bundleIdentifier == JavaCocoaPluginIdentifier || bundleIdentifier == JavaCarbonPluginIdentifier ||
351        equalIgnoringCase(pluginInfo.file, JavaCFMPluginFilename);
352}
353
354static inline void swapIntsInHeader(uint32_t* rawData, size_t length)
355{
356    for (size_t i = 0; i < length; ++i)
357        rawData[i] = OSSwapInt32(rawData[i]);
358}
359
360- (BOOL)isNativeLibraryData:(NSData *)data
361{
362    NSUInteger sizeInBytes = [data length];
363    Vector<uint32_t, 128> rawData((sizeInBytes + 3) / 4);
364    memcpy(rawData.data(), [data bytes], sizeInBytes);
365
366    unsigned numArchs = 0;
367    struct fat_arch singleArch = { 0, 0, 0, 0, 0 };
368    struct fat_arch* archs = 0;
369
370    if (sizeInBytes >= sizeof(struct mach_header_64)) {
371        uint32_t magic = *rawData.data();
372
373        if (magic == MH_MAGIC || magic == MH_CIGAM) {
374            // We have a 32-bit thin binary
375            struct mach_header* header = (struct mach_header*)rawData.data();
376
377            // Check if we need to swap the bytes
378            if (magic == MH_CIGAM)
379                swapIntsInHeader(rawData.data(), rawData.size());
380
381            singleArch.cputype = header->cputype;
382            singleArch.cpusubtype = header->cpusubtype;
383
384            archs = &singleArch;
385            numArchs = 1;
386        } else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
387            // We have a 64-bit thin binary
388            struct mach_header_64* header = (struct mach_header_64*)rawData.data();
389
390            // Check if we need to swap the bytes
391            if (magic == MH_CIGAM_64)
392                swapIntsInHeader(rawData.data(), rawData.size());
393
394            singleArch.cputype = header->cputype;
395            singleArch.cpusubtype = header->cpusubtype;
396
397            archs = &singleArch;
398            numArchs = 1;
399        } else if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
400            // We have a fat (universal) binary
401
402            // Check if we need to swap the bytes
403            if (magic == FAT_CIGAM)
404                swapIntsInHeader(rawData.data(), rawData.size());
405
406            COMPILE_ASSERT(sizeof(struct fat_header) % sizeof(uint32_t) == 0, struct_fat_header_must_be_integral_size_of_uint32_t);
407            archs = reinterpret_cast<struct fat_arch*>(rawData.data() + sizeof(struct fat_header) / sizeof(uint32_t));
408            numArchs = reinterpret_cast<struct fat_header*>(rawData.data())->nfat_arch;
409
410            unsigned maxArchs = (sizeInBytes - sizeof(struct fat_header)) / sizeof(struct fat_arch);
411            if (numArchs > maxArchs)
412                numArchs = maxArchs;
413        }
414    }
415
416    if (!archs || !numArchs)
417        return NO;
418
419    const NXArchInfo* localArch = NXGetLocalArchInfo();
420    if (!localArch)
421        return NO;
422
423    cpu_type_t cputype = localArch->cputype;
424    cpu_subtype_t cpusubtype = localArch->cpusubtype;
425
426#ifdef __x86_64__
427    // NXGetLocalArchInfo returns CPU_TYPE_X86 even when running in 64-bit.
428    // See <rdar://problem/4996965> for more information.
429    cputype = CPU_TYPE_X86_64;
430#endif
431
432    return NXFindBestFatArch(cputype, cpusubtype, archs, numArchs) != 0;
433}
434
435- (UInt32)versionNumber
436{
437    // CFBundleGetVersionNumber doesn't work with all possible versioning schemes, but we think for now it's good enough for us.
438    return CFBundleGetVersionNumber(cfBundle.get());
439}
440
441- (void)wasAddedToPluginDatabase:(WebPluginDatabase *)database
442{
443    if (!pluginDatabases)
444        pluginDatabases = [[NSMutableSet alloc] init];
445
446    ASSERT(![pluginDatabases containsObject:database]);
447    [pluginDatabases addObject:database];
448}
449
450- (void)wasRemovedFromPluginDatabase:(WebPluginDatabase *)database
451{
452    ASSERT(pluginDatabases);
453    ASSERT([pluginDatabases containsObject:database]);
454
455    [pluginDatabases removeObject:database];
456}
457
458- (WTF::String)bundleIdentifier
459{
460    return CFBundleGetIdentifier(cfBundle.get());
461}
462
463@end
464
465@implementation NSArray (WebPluginExtensions)
466
467- (NSArray *)_web_lowercaseStrings
468{
469    NSMutableArray *lowercaseStrings = [NSMutableArray arrayWithCapacity:[self count]];
470    NSEnumerator *strings = [self objectEnumerator];
471    NSString *string;
472
473    while ((string = [strings nextObject]) != nil) {
474        if ([string isKindOfClass:[NSString class]])
475            [lowercaseStrings addObject:[string lowercaseString]];
476    }
477
478    return lowercaseStrings;
479}
480
481@end
482