blob: e6cf81a5effd93bbd36a5fe4f575c2e894c655d4 [file] [log] [blame]
/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "NetscapePluginModule.h"
#if ENABLE(NETSCAPE_PLUGIN_API)
#import "PluginProcessProxy.h"
#import "PluginSandboxProfile.h"
#import <WebCore/WebCoreNSStringExtras.h>
#import <wtf/HashSet.h>
#import <wtf/MainThread.h>
using namespace WebCore;
namespace WebKit {
static bool getPluginArchitecture(CFBundleRef bundle, PluginModuleInfo& plugin)
{
RetainPtr<CFArrayRef> pluginArchitecturesArray = adoptCF(CFBundleCopyExecutableArchitectures(bundle));
if (!pluginArchitecturesArray)
return false;
// Turn the array into a set.
HashSet<unsigned> architectures;
for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) {
CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i));
SInt32 architecture;
if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture))
continue;
architectures.add(architecture);
}
#ifdef __x86_64__
// We only support 64-bit Intel plug-ins on 64-bit Intel.
if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) {
plugin.pluginArchitecture = CPU_TYPE_X86_64;
return true;
}
// We also support 32-bit Intel plug-ins on 64-bit Intel.
if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
plugin.pluginArchitecture = CPU_TYPE_X86;
return true;
}
#elif defined(__i386__)
// We only support 32-bit Intel plug-ins on 32-bit Intel.
if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
plugin.pluginArchitecture = CPU_TYPE_X86;
return true;
}
#elif defined(__ppc64__)
// We only support 64-bit PPC plug-ins on 64-bit PPC.
if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) {
plugin.pluginArchitecture = CPU_TYPE_POWERPC64;
return true;
}
#elif defined(__ppc__)
// We only support 32-bit PPC plug-ins on 32-bit PPC.
if (architectures.contains(kCFBundleExecutableArchitecturePPC)) {
plugin.pluginArchitecture = CPU_TYPE_POWERPC;
return true;
}
#else
#error "Unhandled architecture"
#endif
return false;
}
static RetainPtr<CFDictionaryRef> contentsOfPropertyListAtURL(CFURLRef propertyListURL)
{
RetainPtr<NSData> propertyListData = adoptNS([[NSData alloc] initWithContentsOfURL:(NSURL *)propertyListURL]);
if (!propertyListData)
return 0;
RetainPtr<CFPropertyListRef> propertyList = adoptCF(CFPropertyListCreateWithData(kCFAllocatorDefault, (CFDataRef)propertyListData.get(), kCFPropertyListImmutable, 0, 0));
if (!propertyList)
return 0;
if (CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID())
return 0;
return static_cast<CFDictionaryRef>(propertyList.get());
}
static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle, const PluginModuleInfo& plugin)
{
CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")));
if (propertyListFilename) {
RetainPtr<CFStringRef> propertyListPath = adoptCF(CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename));
RetainPtr<CFURLRef> propertyListURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE));
RetainPtr<CFDictionaryRef> propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
if (!propertyList && PluginProcessProxy::createPropertyListFile(plugin))
propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
if (!propertyList)
return 0;
return static_cast<CFDictionaryRef>(CFDictionaryGetValue(propertyList.get(), CFSTR("WebPluginMIMETypes")));
}
return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")));
}
static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginModuleInfo& plugin)
{
RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle, plugin);
if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID())
return false;
// Get the plug-in name.
CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")));
if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID())
plugin.info.name = pluginName;
// Get the plug-in description.
CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")));
if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID())
plugin.info.desc = pluginDescription;
// Get the MIME type mapping dictionary.
CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get());
Vector<CFStringRef> mimeTypesVector(numMimeTypes);
Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes);
CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data()));
for (CFIndex i = 0; i < numMimeTypes; ++i) {
MimeClassInfo mimeClassInfo;
// If this MIME type is invalid, ignore it.
CFStringRef mimeType = mimeTypesVector[i];
if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0)
continue;
// If this MIME type doesn't have a valid info dictionary, ignore it.
CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i];
if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID())
continue;
// FIXME: Consider storing disabled MIME types.
CFTypeRef isEnabled = CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeEnabled"));
if (isEnabled) {
if (CFGetTypeID(isEnabled) == CFNumberGetTypeID()) {
int value;
if (!CFNumberGetValue(static_cast<CFNumberRef>(isEnabled), kCFNumberIntType, &value) || !value)
continue;
} else if (CFGetTypeID(isEnabled) == CFBooleanGetTypeID()) {
if (!CFBooleanGetValue(static_cast<CFBooleanRef>(isEnabled)))
continue;
} else
continue;
}
// Get the MIME type description.
CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription")));
if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID())
mimeTypeDescription = 0;
mimeClassInfo.type = String(mimeType).lower();
mimeClassInfo.desc = mimeTypeDescription;
// Now get the extensions for this MIME type.
CFIndex numExtensions = 0;
CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions")));
if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID())
numExtensions = CFArrayGetCount(extensionsArray);
for (CFIndex i = 0; i < numExtensions; ++i) {
CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i));
if (!extension || CFGetTypeID(extension) != CFStringGetTypeID())
continue;
// The DivX plug-in lists multiple extensions in a comma separated string instead of using
// multiple array elements in the property list. Work around this here by splitting the
// extension string into components.
Vector<String> extensionComponents;
String(extension).lower().split(',', extensionComponents);
for (size_t i = 0; i < extensionComponents.size(); ++i)
mimeClassInfo.extensions.append(extensionComponents[i]);
}
// Add this MIME type.
plugin.info.mimes.append(mimeClassInfo);
}
return true;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
class ResourceMap {
public:
explicit ResourceMap(CFBundleRef bundle)
: m_bundle(bundle)
, m_currentResourceFile(CurResFile())
, m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle))
{
UseResFile(m_bundleResourceMap);
}
~ResourceMap()
{
// Close the resource map.
CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap);
// And restore the old resource.
UseResFile(m_currentResourceFile);
}
bool isValid() const { return m_bundleResourceMap != -1; }
private:
CFBundleRef m_bundle;
ResFileRefNum m_currentResourceFile;
ResFileRefNum m_bundleResourceMap;
};
static bool getStringListResource(ResID resourceID, Vector<String>& stringList) {
Handle stringListHandle = Get1Resource('STR#', resourceID);
if (!stringListHandle || !*stringListHandle)
return false;
// Get the string list size.
Size stringListSize = GetHandleSize(stringListHandle);
if (stringListSize < static_cast<Size>(sizeof(UInt16)))
return false;
CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle);
unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle);
unsigned char* end = ptr + stringListSize;
// Get the number of strings in the string list.
UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr);
ptr += sizeof(UInt16);
for (UInt16 i = 0; i < numStrings; ++i) {
// We're past the end of the string, bail.
if (ptr >= end)
return false;
// Get the string length.
unsigned char stringLength = *ptr++;
RetainPtr<CFStringRef> cfString = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull));
if (!cfString.get())
return false;
stringList.append(cfString.get());
ptr += stringLength;
}
if (ptr != end)
return false;
return true;
}
#pragma clang diagnostic pop
static const ResID PluginNameOrDescriptionStringNumber = 126;
static const ResID MIMEDescriptionStringNumber = 127;
static const ResID MIMEListStringStringNumber = 128;
static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginModuleInfo& plugin)
{
ASSERT(RunLoop::isMain());
ResourceMap resourceMap(bundle);
if (!resourceMap.isValid())
return false;
// Get the description and name string list.
Vector<String> descriptionAndName;
if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName))
return false;
// Get the MIME types and extensions string list. This list needs to be a multiple of two.
Vector<String> mimeTypesAndExtensions;
if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions))
return false;
if (mimeTypesAndExtensions.size() % 2)
return false;
// Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types.
Vector<String> mimeTypeDescriptions;
if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions))
return false;
// Add all MIME types.
for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) {
MimeClassInfo mimeClassInfo;
const String& mimeType = mimeTypesAndExtensions[i * 2];
String description;
if (i < mimeTypeDescriptions.size())
description = mimeTypeDescriptions[i];
mimeClassInfo.type = mimeType.lower();
mimeClassInfo.desc = description;
Vector<String> extensions;
mimeTypesAndExtensions[i * 2 + 1].split(',', extensions);
for (size_t i = 0; i < extensions.size(); ++i)
mimeClassInfo.extensions.append(extensions[i].lower());
plugin.info.mimes.append(mimeClassInfo);
}
// Set the description and name if they exist.
if (descriptionAndName.size() > 0)
plugin.info.desc = descriptionAndName[0];
if (descriptionAndName.size() > 1)
plugin.info.name = descriptionAndName[1];
return true;
}
bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginModuleInfo& plugin)
{
RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
// Try to initialize the bundle.
RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
if (!bundle)
return false;
// Check if this bundle is an NPAPI plug-in.
UInt32 packageType = 0;
CFBundleGetPackageInfo(bundle.get(), &packageType, 0);
if (packageType != FOUR_CHAR_CODE('BRPL'))
return false;
// Check that the architecture is valid.
if (!getPluginArchitecture(bundle.get(), plugin))
return false;
plugin.path = pluginPath;
plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get());
if (CFTypeRef versionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), kCFBundleVersionKey)) {
if (CFGetTypeID(versionTypeRef) == CFStringGetTypeID())
plugin.versionString = static_cast<CFStringRef>(versionTypeRef);
}
if (CFTypeRef shortVersionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("CFBundleShortVersionString"))) {
if (CFGetTypeID(shortVersionTypeRef) == CFStringGetTypeID())
plugin.shortVersionString = static_cast<CFStringRef>(shortVersionTypeRef);
}
if (CFTypeRef preferencePathTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("WebPluginPreferencePanePath"))) {
if (CFGetTypeID(preferencePathTypeRef) == CFStringGetTypeID())
plugin.preferencePanePath = static_cast<CFStringRef>(preferencePathTypeRef);
}
// Check that there's valid info for this plug-in.
if (!getPluginInfoFromPropertyLists(bundle.get(), plugin) &&
!getPluginInfoFromCarbonResources(bundle.get(), plugin))
return false;
plugin.hasSandboxProfile = pluginHasSandboxProfile(plugin.bundleIdentifier);
RetainPtr<CFStringRef> filename = adoptCF(CFURLCopyLastPathComponent(bundleURL.get()));
plugin.info.file = filename.get();
if (plugin.info.name.isNull())
plugin.info.name = plugin.info.file;
if (plugin.info.desc.isNull())
plugin.info.desc = plugin.info.file;
plugin.info.isApplicationPlugin = false;
plugin.info.clientLoadPolicy = PluginLoadClientPolicyUndefined;
#if PLATFORM(MAC)
plugin.info.bundleIdentifier = plugin.bundleIdentifier;
plugin.info.versionString = plugin.versionString;
#endif
return true;
}
bool NetscapePluginModule::createPluginMIMETypesPreferences(const String& pluginPath)
{
RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
if (!bundle)
return false;
if (!CFBundleLoadExecutable(bundle.get()))
return false;
void (*createPluginMIMETypesPreferences)(void) = reinterpret_cast<void (*)(void)>(CFBundleGetFunctionPointerForName(bundle.get(), CFSTR("BP_CreatePluginMIMETypesPreferences")));
if (!createPluginMIMETypesPreferences)
return false;
createPluginMIMETypesPreferences();
return true;
}
// FIXME: This doesn't need to be platform-specific.
class PluginVersion {
public:
static PluginVersion parse(const String& versionString);
bool isLessThan(unsigned componentA) const;
bool isValid() const { return !m_versionComponents.isEmpty(); }
private:
PluginVersion()
{
}
Vector<unsigned, 4> m_versionComponents;
};
PluginVersion PluginVersion::parse(const String& versionString)
{
PluginVersion version;
Vector<String> versionStringComponents;
versionString.split('.', versionStringComponents);
for (size_t i = 0; i < versionStringComponents.size(); ++i) {
bool successfullyParsed = false;
unsigned versionComponent = versionStringComponents[i].toUInt(&successfullyParsed);
if (!successfullyParsed)
return PluginVersion();
version.m_versionComponents.append(versionComponent);
}
return version;
}
bool PluginVersion::isLessThan(unsigned componentA) const
{
ASSERT(isValid());
return m_versionComponents[0] < componentA;
}
void NetscapePluginModule::determineQuirks()
{
PluginModuleInfo plugin;
if (!getPluginInfo(m_pluginPath, plugin))
return;
if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") {
// Flash requires that the return value of getprogname() be "WebKitPluginHost".
m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost);
// Flash supports snapshotting.
m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting);
// Flash returns a retained Core Animation layer.
m_pluginQuirks.add(PluginQuirks::ReturnsRetainedCoreAnimationLayer);
// Flash has a bug where NSExceptions can be released too early.
m_pluginQuirks.add(PluginQuirks::LeakAllThrownNSExceptions);
}
if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") {
// Silverlight doesn't explicitly opt into transparency, so we'll do it whenever
// there's a 'background' attribute that's set to a transparent color.
m_pluginQuirks.add(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists);
// Silverlight has a workaround for a leak in Safari 2. This workaround is
// applied when the user agent does not contain "Version/3" so we append it
// at the end of the user agent.
m_pluginQuirks.add(PluginQuirks::AppendVersion3UserAgent);
PluginVersion pluginVersion = PluginVersion::parse(plugin.versionString);
if (pluginVersion.isValid()) {
if (pluginVersion.isLessThan(4)) {
// Versions of Silverlight prior to 4 don't retain the scriptable NPObject.
m_pluginQuirks.add(PluginQuirks::ReturnsNonRetainedScriptableNPObject);
}
}
}
if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") {
// <rdar://problem/8440903>: AppleConnect has a bug where it does not
// understand the parameter names specified in the <object> element that
// embeds its plug-in.
m_pluginQuirks.add(PluginQuirks::WantsLowercaseParameterNames);
#ifndef NP_NO_QUICKDRAW
// The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events
// so we'll allow it to be instantiated even though we don't support QuickDraw.
m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
#endif
}
#ifndef NP_NO_QUICKDRAW
if (plugin.bundleIdentifier == "com.microsoft.sharepoint.browserplugin") {
// The Microsoft SharePoint plug-in uses QuickDraw but doesn't paint or receive events
// so we'll allow it to be instantiated even though we don't support QuickDraw.
m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
}
if (plugin.bundleIdentifier == "com.jattesaker.macid2.NPPlugin") {
// The BankID plug-in uses QuickDraw but doesn't paint or receive events
// so we'll allow it to be instantiated even though we don't support QuickDraw.
m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
}
#endif
if (plugin.bundleIdentifier == "com.adobe.acrobat.pdfviewerNPAPI") {
// The Adobe Reader plug-in wants wheel events.
m_pluginQuirks.add(PluginQuirks::WantsWheelEvents);
}
}
} // namespace WebKit
#endif // ENABLE(NETSCAPE_PLUGIN_API)