| /* |
| * 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" |
| |
| #import <WebCore/WebCoreNSStringExtras.h> |
| #import <wtf/HashSet.h> |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| static bool getPluginArchitecture(CFBundleRef bundle, cpu_type_t& pluginArchitecture) |
| { |
| 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)) { |
| pluginArchitecture = CPU_TYPE_X86_64; |
| return true; |
| } |
| |
| // We also support 32-bit Intel plug-ins on 64-bit Intel. |
| if (architectures.contains(kCFBundleExecutableArchitectureI386)) { |
| 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)) { |
| 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)) { |
| 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)) { |
| pluginArchitecture = CPU_TYPE_POWERPC; |
| return true; |
| } |
| #else |
| #error "Unhandled architecture" |
| #endif |
| |
| return false; |
| } |
| |
| static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle) |
| { |
| 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)); |
| |
| CFDataRef propertyListData; |
| CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, propertyListURL.get(), &propertyListData, 0, 0, 0); |
| RetainPtr<CFPropertyListRef> propertyList(AdoptCF, CFPropertyListCreateWithData(kCFAllocatorDefault, propertyListData, kCFPropertyListImmutable, 0, 0)); |
| if (propertyListData) |
| CFRelease(propertyListData); |
| |
| // FIXME: Have the plug-in create the MIME types property list if it doesn't exist. |
| // https://bugs.webkit.org/show_bug.cgi?id=57204 |
| if (!propertyList || CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID()) |
| return 0; |
| |
| return static_cast<CFDictionaryRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList.get()), CFSTR("WebPluginMIMETypes"))); |
| } |
| |
| return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes"))); |
| } |
| |
| static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginInfo& pluginInfo) |
| { |
| RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle); |
| 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()) |
| pluginInfo.name = pluginName; |
| |
| // Get the plug-in description. |
| CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription"))); |
| if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID()) |
| pluginInfo.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; |
| |
| // 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. |
| pluginInfo.mimes.append(mimeClassInfo); |
| } |
| |
| return true; |
| } |
| |
| 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; |
| } |
| |
| static const ResID PluginNameOrDescriptionStringNumber = 126; |
| static const ResID MIMEDescriptionStringNumber = 127; |
| static const ResID MIMEListStringStringNumber = 128; |
| |
| static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginInfo& pluginInfo) |
| { |
| 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()); |
| |
| pluginInfo.mimes.append(mimeClassInfo); |
| } |
| |
| // Set the description and name if they exist. |
| if (descriptionAndName.size() > 0) |
| pluginInfo.desc = descriptionAndName[0]; |
| if (descriptionAndName.size() > 1) |
| pluginInfo.name = descriptionAndName[1]; |
| |
| return true; |
| } |
| |
| bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginInfoStore::Plugin& plugin) |
| { |
| RetainPtr<CFStringRef> bundlePath(AdoptCF, pluginPath.createCFString()); |
| RetainPtr<CFURLRef> bundleURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath.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. |
| cpu_type_t pluginArchitecture = 0; |
| if (!getPluginArchitecture(bundle.get(), pluginArchitecture)) |
| return false; |
| |
| // Check that there's valid info for this plug-in. |
| if (!getPluginInfoFromPropertyLists(bundle.get(), plugin.info) && |
| !getPluginInfoFromCarbonResources(bundle.get(), plugin.info)) |
| return false; |
| |
| plugin.path = pluginPath; |
| plugin.pluginArchitecture = pluginArchitecture; |
| plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get()); |
| plugin.versionNumber = CFBundleGetVersionNumber(bundle.get()); |
| |
| 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; |
| |
| return true; |
| } |
| |
| void NetscapePluginModule::determineQuirks() |
| { |
| PluginInfoStore::Plugin 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); |
| } |
| |
| if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") { |
| // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever |
| // there's a 'background' attribute. |
| m_pluginQuirks.add(PluginQuirks::MakeTransparentIfBackgroundAttributeExists); |
| } |
| |
| #ifndef NP_NO_QUICKDRAW |
| if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") { |
| // 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 |
| } |
| |
| } // namespace WebKit |