1/*
2 * Copyright (C) 2010 Apple 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 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "NetscapePluginModule.h"
28
29#import <WebCore/WebCoreNSStringExtras.h>
30#import <wtf/HashSet.h>
31
32using namespace WebCore;
33
34namespace WebKit {
35
36static bool getPluginArchitecture(CFBundleRef bundle, cpu_type_t& pluginArchitecture)
37{
38    RetainPtr<CFArrayRef> pluginArchitecturesArray(AdoptCF, CFBundleCopyExecutableArchitectures(bundle));
39    if (!pluginArchitecturesArray)
40        return false;
41
42    // Turn the array into a set.
43    HashSet<unsigned> architectures;
44    for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) {
45        CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i));
46
47        SInt32 architecture;
48        if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture))
49            continue;
50        architectures.add(architecture);
51    }
52
53#ifdef __x86_64__
54    // We only support 64-bit Intel plug-ins on 64-bit Intel.
55    if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) {
56        pluginArchitecture = CPU_TYPE_X86_64;
57        return true;
58    }
59
60    // We also support 32-bit Intel plug-ins on 64-bit Intel.
61    if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
62        pluginArchitecture = CPU_TYPE_X86;
63        return true;
64    }
65#elif defined(__i386__)
66    // We only support 32-bit Intel plug-ins on 32-bit Intel.
67    if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
68        pluginArchitecture = CPU_TYPE_X86;
69        return true;
70    }
71#elif defined(__ppc64__)
72    // We only support 64-bit PPC plug-ins on 64-bit PPC.
73    if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) {
74        pluginArchitecture = CPU_TYPE_POWERPC64;
75        return true;
76    }
77#elif defined(__ppc__)
78    // We only support 32-bit PPC plug-ins on 32-bit PPC.
79    if (architectures.contains(kCFBundleExecutableArchitecturePPC)) {
80        pluginArchitecture = CPU_TYPE_POWERPC;
81        return true;
82    }
83#else
84#error "Unhandled architecture"
85#endif
86
87    return false;
88}
89
90static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle)
91{
92    CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")));
93    if (propertyListFilename) {
94        RetainPtr<CFStringRef> propertyListPath(AdoptCF, CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename));
95        RetainPtr<CFURLRef> propertyListURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE));
96
97        CFDataRef propertyListData;
98        CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, propertyListURL.get(), &propertyListData, 0, 0, 0);
99        RetainPtr<CFPropertyListRef> propertyList(AdoptCF, CFPropertyListCreateWithData(kCFAllocatorDefault, propertyListData, kCFPropertyListImmutable, 0, 0));
100        if (propertyListData)
101            CFRelease(propertyListData);
102
103        // FIXME: Have the plug-in create the MIME types property list if it doesn't exist.
104        // https://bugs.webkit.org/show_bug.cgi?id=57204
105        if (!propertyList || CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID())
106            return 0;
107
108        return static_cast<CFDictionaryRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList.get()), CFSTR("WebPluginMIMETypes")));
109    }
110
111    return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")));
112}
113
114static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginInfo& pluginInfo)
115{
116    RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle);
117    if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID())
118        return false;
119
120    // Get the plug-in name.
121    CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")));
122    if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID())
123        pluginInfo.name = pluginName;
124
125    // Get the plug-in description.
126    CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")));
127    if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID())
128        pluginInfo.desc = pluginDescription;
129
130    // Get the MIME type mapping dictionary.
131    CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get());
132    Vector<CFStringRef> mimeTypesVector(numMimeTypes);
133    Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes);
134    CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data()));
135
136    for (CFIndex i = 0; i < numMimeTypes; ++i) {
137        MimeClassInfo mimeClassInfo;
138
139        // If this MIME type is invalid, ignore it.
140        CFStringRef mimeType = mimeTypesVector[i];
141        if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0)
142            continue;
143
144        // If this MIME type doesn't have a valid info dictionary, ignore it.
145        CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i];
146        if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID())
147            continue;
148
149        // Get the MIME type description.
150        CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription")));
151        if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID())
152            mimeTypeDescription = 0;
153
154        mimeClassInfo.type = String(mimeType).lower();
155        mimeClassInfo.desc = mimeTypeDescription;
156
157        // Now get the extensions for this MIME type.
158        CFIndex numExtensions = 0;
159        CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions")));
160        if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID())
161            numExtensions = CFArrayGetCount(extensionsArray);
162
163        for (CFIndex i = 0; i < numExtensions; ++i) {
164            CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i));
165            if (!extension || CFGetTypeID(extension) != CFStringGetTypeID())
166                continue;
167
168            // The DivX plug-in lists multiple extensions in a comma separated string instead of using
169            // multiple array elements in the property list. Work around this here by splitting the
170            // extension string into components.
171            Vector<String> extensionComponents;
172            String(extension).lower().split(',', extensionComponents);
173
174            for (size_t i = 0; i < extensionComponents.size(); ++i)
175                mimeClassInfo.extensions.append(extensionComponents[i]);
176        }
177
178        // Add this MIME type.
179        pluginInfo.mimes.append(mimeClassInfo);
180    }
181
182    return true;
183}
184
185class ResourceMap {
186public:
187    explicit ResourceMap(CFBundleRef bundle)
188        : m_bundle(bundle)
189        , m_currentResourceFile(CurResFile())
190        , m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle))
191    {
192        UseResFile(m_bundleResourceMap);
193    }
194
195    ~ResourceMap()
196    {
197        // Close the resource map.
198        CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap);
199
200        // And restore the old resource.
201        UseResFile(m_currentResourceFile);
202    }
203
204    bool isValid() const { return m_bundleResourceMap != -1; }
205
206private:
207    CFBundleRef m_bundle;
208    ResFileRefNum m_currentResourceFile;
209    ResFileRefNum m_bundleResourceMap;
210};
211
212static bool getStringListResource(ResID resourceID, Vector<String>& stringList) {
213    Handle stringListHandle = Get1Resource('STR#', resourceID);
214    if (!stringListHandle || !*stringListHandle)
215        return false;
216
217    // Get the string list size.
218    Size stringListSize = GetHandleSize(stringListHandle);
219    if (stringListSize < static_cast<Size>(sizeof(UInt16)))
220        return false;
221
222    CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle);
223
224    unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle);
225    unsigned char* end = ptr + stringListSize;
226
227    // Get the number of strings in the string list.
228    UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr);
229    ptr += sizeof(UInt16);
230
231    for (UInt16 i = 0; i < numStrings; ++i) {
232        // We're past the end of the string, bail.
233        if (ptr >= end)
234            return false;
235
236        // Get the string length.
237        unsigned char stringLength = *ptr++;
238
239        RetainPtr<CFStringRef> cfString(AdoptCF, CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull));
240        if (!cfString.get())
241            return false;
242
243        stringList.append(cfString.get());
244        ptr += stringLength;
245    }
246
247    if (ptr != end)
248        return false;
249
250    return true;
251}
252
253static const ResID PluginNameOrDescriptionStringNumber = 126;
254static const ResID MIMEDescriptionStringNumber = 127;
255static const ResID MIMEListStringStringNumber = 128;
256
257static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginInfo& pluginInfo)
258{
259    ResourceMap resourceMap(bundle);
260    if (!resourceMap.isValid())
261        return false;
262
263    // Get the description and name string list.
264    Vector<String> descriptionAndName;
265    if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName))
266        return false;
267
268    // Get the MIME types and extensions string list. This list needs to be a multiple of two.
269    Vector<String> mimeTypesAndExtensions;
270    if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions))
271        return false;
272
273    if (mimeTypesAndExtensions.size() % 2)
274        return false;
275
276    // Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types.
277    Vector<String> mimeTypeDescriptions;
278    if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions))
279        return false;
280
281    // Add all MIME types.
282    for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) {
283        MimeClassInfo mimeClassInfo;
284
285        const String& mimeType = mimeTypesAndExtensions[i * 2];
286        String description;
287        if (i < mimeTypeDescriptions.size())
288            description = mimeTypeDescriptions[i];
289
290        mimeClassInfo.type = mimeType.lower();
291        mimeClassInfo.desc = description;
292
293        Vector<String> extensions;
294        mimeTypesAndExtensions[i * 2 + 1].split(',', extensions);
295
296        for (size_t i = 0; i < extensions.size(); ++i)
297            mimeClassInfo.extensions.append(extensions[i].lower());
298
299        pluginInfo.mimes.append(mimeClassInfo);
300    }
301
302    // Set the description and name if they exist.
303    if (descriptionAndName.size() > 0)
304        pluginInfo.desc = descriptionAndName[0];
305    if (descriptionAndName.size() > 1)
306        pluginInfo.name = descriptionAndName[1];
307
308    return true;
309}
310
311bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginInfoStore::Plugin& plugin)
312{
313    RetainPtr<CFStringRef> bundlePath(AdoptCF, pluginPath.createCFString());
314    RetainPtr<CFURLRef> bundleURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath.get(), kCFURLPOSIXPathStyle, false));
315
316    // Try to initialize the bundle.
317    RetainPtr<CFBundleRef> bundle(AdoptCF, CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
318    if (!bundle)
319        return false;
320
321    // Check if this bundle is an NPAPI plug-in.
322    UInt32 packageType = 0;
323    CFBundleGetPackageInfo(bundle.get(), &packageType, 0);
324    if (packageType != FOUR_CHAR_CODE('BRPL'))
325        return false;
326
327    // Check that the architecture is valid.
328    cpu_type_t pluginArchitecture = 0;
329    if (!getPluginArchitecture(bundle.get(), pluginArchitecture))
330        return false;
331
332    // Check that there's valid info for this plug-in.
333    if (!getPluginInfoFromPropertyLists(bundle.get(), plugin.info) &&
334        !getPluginInfoFromCarbonResources(bundle.get(), plugin.info))
335        return false;
336
337    plugin.path = pluginPath;
338    plugin.pluginArchitecture = pluginArchitecture;
339    plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get());
340    plugin.versionNumber = CFBundleGetVersionNumber(bundle.get());
341
342    RetainPtr<CFStringRef> filename(AdoptCF, CFURLCopyLastPathComponent(bundleURL.get()));
343    plugin.info.file = filename.get();
344
345    if (plugin.info.name.isNull())
346        plugin.info.name = plugin.info.file;
347    if (plugin.info.desc.isNull())
348        plugin.info.desc = plugin.info.file;
349
350    return true;
351}
352
353void NetscapePluginModule::determineQuirks()
354{
355    PluginInfoStore::Plugin plugin;
356    if (!getPluginInfo(m_pluginPath, plugin))
357        return;
358
359    if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") {
360        // Flash requires that the return value of getprogname() be "WebKitPluginHost".
361        m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost);
362
363        // Flash supports snapshotting.
364        m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting);
365    }
366
367    if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") {
368        // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever
369        // there's a 'background' attribute.
370        m_pluginQuirks.add(PluginQuirks::MakeTransparentIfBackgroundAttributeExists);
371    }
372
373#ifndef NP_NO_QUICKDRAW
374    if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") {
375        // The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events
376        // so we'll allow it to be instantiated even though we don't support QuickDraw.
377        m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
378    }
379#endif
380}
381
382} // namespace WebKit
383