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