2 * Copyright (C) 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "NetscapePluginModule.h"
29 #if ENABLE(NETSCAPE_PLUGIN_API)
31 #import "PluginProcessProxy.h"
32 #import "PluginSandboxProfile.h"
33 #import <WebCore/WebCoreNSStringExtras.h>
34 #import <wtf/HashSet.h>
35 #import <wtf/MainThread.h>
37 using namespace WebCore;
41 static bool getPluginArchitecture(CFBundleRef bundle, PluginModuleInfo& plugin)
43 RetainPtr<CFArrayRef> pluginArchitecturesArray = adoptCF(CFBundleCopyExecutableArchitectures(bundle));
44 if (!pluginArchitecturesArray)
47 // Turn the array into a set.
48 HashSet<unsigned> architectures;
49 for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) {
50 CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i));
53 if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture))
55 architectures.add(architecture);
59 // We only support 64-bit Intel plug-ins on 64-bit Intel.
60 if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) {
61 plugin.pluginArchitecture = CPU_TYPE_X86_64;
65 // We also support 32-bit Intel plug-ins on 64-bit Intel.
66 if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
67 plugin.pluginArchitecture = CPU_TYPE_X86;
70 #elif defined(__i386__)
71 // We only support 32-bit Intel plug-ins on 32-bit Intel.
72 if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
73 plugin.pluginArchitecture = CPU_TYPE_X86;
76 #elif defined(__ppc64__)
77 // We only support 64-bit PPC plug-ins on 64-bit PPC.
78 if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) {
79 plugin.pluginArchitecture = CPU_TYPE_POWERPC64;
82 #elif defined(__ppc__)
83 // We only support 32-bit PPC plug-ins on 32-bit PPC.
84 if (architectures.contains(kCFBundleExecutableArchitecturePPC)) {
85 plugin.pluginArchitecture = CPU_TYPE_POWERPC;
89 #error "Unhandled architecture"
95 static RetainPtr<CFDictionaryRef> contentsOfPropertyListAtURL(CFURLRef propertyListURL)
97 RetainPtr<NSData> propertyListData = adoptNS([[NSData alloc] initWithContentsOfURL:(NSURL *)propertyListURL]);
98 if (!propertyListData)
101 RetainPtr<CFPropertyListRef> propertyList = adoptCF(CFPropertyListCreateWithData(kCFAllocatorDefault, (CFDataRef)propertyListData.get(), kCFPropertyListImmutable, 0, 0));
105 if (CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID())
108 return static_cast<CFDictionaryRef>(propertyList.get());
111 static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle, const PluginModuleInfo& plugin)
113 CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")));
114 if (propertyListFilename) {
115 RetainPtr<CFStringRef> propertyListPath = adoptCF(CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename));
116 RetainPtr<CFURLRef> propertyListURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE));
118 RetainPtr<CFDictionaryRef> propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
120 if (!propertyList && PluginProcessProxy::createPropertyListFile(plugin))
121 propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
126 return static_cast<CFDictionaryRef>(CFDictionaryGetValue(propertyList.get(), CFSTR("WebPluginMIMETypes")));
129 return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")));
132 static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginModuleInfo& plugin)
134 RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle, plugin);
135 if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID())
138 // Get the plug-in name.
139 CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")));
140 if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID())
141 plugin.info.name = pluginName;
143 // Get the plug-in description.
144 CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")));
145 if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID())
146 plugin.info.desc = pluginDescription;
148 // Get the MIME type mapping dictionary.
149 CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get());
150 Vector<CFStringRef> mimeTypesVector(numMimeTypes);
151 Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes);
152 CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data()));
154 for (CFIndex i = 0; i < numMimeTypes; ++i) {
155 MimeClassInfo mimeClassInfo;
157 // If this MIME type is invalid, ignore it.
158 CFStringRef mimeType = mimeTypesVector[i];
159 if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0)
162 // If this MIME type doesn't have a valid info dictionary, ignore it.
163 CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i];
164 if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID())
167 // FIXME: Consider storing disabled MIME types.
168 CFTypeRef isEnabled = CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeEnabled"));
170 if (CFGetTypeID(isEnabled) == CFNumberGetTypeID()) {
172 if (!CFNumberGetValue(static_cast<CFNumberRef>(isEnabled), kCFNumberIntType, &value) || !value)
174 } else if (CFGetTypeID(isEnabled) == CFBooleanGetTypeID()) {
175 if (!CFBooleanGetValue(static_cast<CFBooleanRef>(isEnabled)))
181 // Get the MIME type description.
182 CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription")));
183 if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID())
184 mimeTypeDescription = 0;
186 mimeClassInfo.type = String(mimeType).lower();
187 mimeClassInfo.desc = mimeTypeDescription;
189 // Now get the extensions for this MIME type.
190 CFIndex numExtensions = 0;
191 CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions")));
192 if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID())
193 numExtensions = CFArrayGetCount(extensionsArray);
195 for (CFIndex i = 0; i < numExtensions; ++i) {
196 CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i));
197 if (!extension || CFGetTypeID(extension) != CFStringGetTypeID())
200 // The DivX plug-in lists multiple extensions in a comma separated string instead of using
201 // multiple array elements in the property list. Work around this here by splitting the
202 // extension string into components.
203 Vector<String> extensionComponents;
204 String(extension).lower().split(',', extensionComponents);
206 for (size_t i = 0; i < extensionComponents.size(); ++i)
207 mimeClassInfo.extensions.append(extensionComponents[i]);
210 // Add this MIME type.
211 plugin.info.mimes.append(mimeClassInfo);
217 #pragma clang diagnostic push
218 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
222 explicit ResourceMap(CFBundleRef bundle)
224 , m_currentResourceFile(CurResFile())
225 , m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle))
227 UseResFile(m_bundleResourceMap);
232 // Close the resource map.
233 CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap);
235 // And restore the old resource.
236 UseResFile(m_currentResourceFile);
239 bool isValid() const { return m_bundleResourceMap != -1; }
242 CFBundleRef m_bundle;
243 ResFileRefNum m_currentResourceFile;
244 ResFileRefNum m_bundleResourceMap;
247 static bool getStringListResource(ResID resourceID, Vector<String>& stringList) {
248 Handle stringListHandle = Get1Resource('STR#', resourceID);
249 if (!stringListHandle || !*stringListHandle)
252 // Get the string list size.
253 Size stringListSize = GetHandleSize(stringListHandle);
254 if (stringListSize < static_cast<Size>(sizeof(UInt16)))
257 CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle);
259 unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle);
260 unsigned char* end = ptr + stringListSize;
262 // Get the number of strings in the string list.
263 UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr);
264 ptr += sizeof(UInt16);
266 for (UInt16 i = 0; i < numStrings; ++i) {
267 // We're past the end of the string, bail.
271 // Get the string length.
272 unsigned char stringLength = *ptr++;
274 RetainPtr<CFStringRef> cfString = adoptCF(CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull));
278 stringList.append(cfString.get());
288 #pragma clang diagnostic pop
290 static const ResID PluginNameOrDescriptionStringNumber = 126;
291 static const ResID MIMEDescriptionStringNumber = 127;
292 static const ResID MIMEListStringStringNumber = 128;
294 static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginModuleInfo& plugin)
296 ASSERT(RunLoop::isMain());
298 ResourceMap resourceMap(bundle);
299 if (!resourceMap.isValid())
302 // Get the description and name string list.
303 Vector<String> descriptionAndName;
304 if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName))
307 // Get the MIME types and extensions string list. This list needs to be a multiple of two.
308 Vector<String> mimeTypesAndExtensions;
309 if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions))
312 if (mimeTypesAndExtensions.size() % 2)
315 // Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types.
316 Vector<String> mimeTypeDescriptions;
317 if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions))
320 // Add all MIME types.
321 for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) {
322 MimeClassInfo mimeClassInfo;
324 const String& mimeType = mimeTypesAndExtensions[i * 2];
326 if (i < mimeTypeDescriptions.size())
327 description = mimeTypeDescriptions[i];
329 mimeClassInfo.type = mimeType.lower();
330 mimeClassInfo.desc = description;
332 Vector<String> extensions;
333 mimeTypesAndExtensions[i * 2 + 1].split(',', extensions);
335 for (size_t i = 0; i < extensions.size(); ++i)
336 mimeClassInfo.extensions.append(extensions[i].lower());
338 plugin.info.mimes.append(mimeClassInfo);
341 // Set the description and name if they exist.
342 if (descriptionAndName.size() > 0)
343 plugin.info.desc = descriptionAndName[0];
344 if (descriptionAndName.size() > 1)
345 plugin.info.name = descriptionAndName[1];
350 bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginModuleInfo& plugin)
352 RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
354 // Try to initialize the bundle.
355 RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
359 // Check if this bundle is an NPAPI plug-in.
360 UInt32 packageType = 0;
361 CFBundleGetPackageInfo(bundle.get(), &packageType, 0);
362 if (packageType != FOUR_CHAR_CODE('BRPL'))
365 // Check that the architecture is valid.
366 if (!getPluginArchitecture(bundle.get(), plugin))
369 plugin.path = pluginPath;
370 plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get());
371 if (CFTypeRef versionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), kCFBundleVersionKey)) {
372 if (CFGetTypeID(versionTypeRef) == CFStringGetTypeID())
373 plugin.versionString = static_cast<CFStringRef>(versionTypeRef);
376 if (CFTypeRef shortVersionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("CFBundleShortVersionString"))) {
377 if (CFGetTypeID(shortVersionTypeRef) == CFStringGetTypeID())
378 plugin.shortVersionString = static_cast<CFStringRef>(shortVersionTypeRef);
381 if (CFTypeRef preferencePathTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), CFSTR("WebPluginPreferencePanePath"))) {
382 if (CFGetTypeID(preferencePathTypeRef) == CFStringGetTypeID())
383 plugin.preferencePanePath = static_cast<CFStringRef>(preferencePathTypeRef);
386 // Check that there's valid info for this plug-in.
387 if (!getPluginInfoFromPropertyLists(bundle.get(), plugin) &&
388 !getPluginInfoFromCarbonResources(bundle.get(), plugin))
391 plugin.hasSandboxProfile = pluginHasSandboxProfile(plugin.bundleIdentifier);
393 RetainPtr<CFStringRef> filename = adoptCF(CFURLCopyLastPathComponent(bundleURL.get()));
394 plugin.info.file = filename.get();
396 if (plugin.info.name.isNull())
397 plugin.info.name = plugin.info.file;
398 if (plugin.info.desc.isNull())
399 plugin.info.desc = plugin.info.file;
401 plugin.info.isApplicationPlugin = false;
402 plugin.info.clientLoadPolicy = PluginLoadClientPolicyUndefined;
404 plugin.info.bundleIdentifier = plugin.bundleIdentifier;
405 plugin.info.versionString = plugin.versionString;
411 bool NetscapePluginModule::createPluginMIMETypesPreferences(const String& pluginPath)
413 RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
415 RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
419 if (!CFBundleLoadExecutable(bundle.get()))
422 void (*createPluginMIMETypesPreferences)(void) = reinterpret_cast<void (*)(void)>(CFBundleGetFunctionPointerForName(bundle.get(), CFSTR("BP_CreatePluginMIMETypesPreferences")));
423 if (!createPluginMIMETypesPreferences)
426 createPluginMIMETypesPreferences();
430 // FIXME: This doesn't need to be platform-specific.
431 class PluginVersion {
433 static PluginVersion parse(const String& versionString);
435 bool isLessThan(unsigned componentA) const;
436 bool isValid() const { return !m_versionComponents.isEmpty(); }
443 Vector<unsigned, 4> m_versionComponents;
446 PluginVersion PluginVersion::parse(const String& versionString)
448 PluginVersion version;
450 Vector<String> versionStringComponents;
451 versionString.split('.', versionStringComponents);
452 for (size_t i = 0; i < versionStringComponents.size(); ++i) {
453 bool successfullyParsed = false;
454 unsigned versionComponent = versionStringComponents[i].toUInt(&successfullyParsed);
455 if (!successfullyParsed)
456 return PluginVersion();
458 version.m_versionComponents.append(versionComponent);
464 bool PluginVersion::isLessThan(unsigned componentA) const
468 return m_versionComponents[0] < componentA;
471 void NetscapePluginModule::determineQuirks()
473 PluginModuleInfo plugin;
474 if (!getPluginInfo(m_pluginPath, plugin))
477 if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") {
478 // Flash requires that the return value of getprogname() be "WebKitPluginHost".
479 m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost);
481 // Flash supports snapshotting.
482 m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting);
484 // Flash returns a retained Core Animation layer.
485 m_pluginQuirks.add(PluginQuirks::ReturnsRetainedCoreAnimationLayer);
487 // Flash has a bug where NSExceptions can be released too early.
488 m_pluginQuirks.add(PluginQuirks::LeakAllThrownNSExceptions);
491 if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") {
492 // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever
493 // there's a 'background' attribute that's set to a transparent color.
494 m_pluginQuirks.add(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists);
496 // Silverlight has a workaround for a leak in Safari 2. This workaround is
497 // applied when the user agent does not contain "Version/3" so we append it
498 // at the end of the user agent.
499 m_pluginQuirks.add(PluginQuirks::AppendVersion3UserAgent);
501 PluginVersion pluginVersion = PluginVersion::parse(plugin.versionString);
502 if (pluginVersion.isValid()) {
503 if (pluginVersion.isLessThan(4)) {
504 // Versions of Silverlight prior to 4 don't retain the scriptable NPObject.
505 m_pluginQuirks.add(PluginQuirks::ReturnsNonRetainedScriptableNPObject);
510 if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") {
511 // <rdar://problem/8440903>: AppleConnect has a bug where it does not
512 // understand the parameter names specified in the <object> element that
513 // embeds its plug-in.
514 m_pluginQuirks.add(PluginQuirks::WantsLowercaseParameterNames);
516 #ifndef NP_NO_QUICKDRAW
517 // The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events
518 // so we'll allow it to be instantiated even though we don't support QuickDraw.
519 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
523 #ifndef NP_NO_QUICKDRAW
524 if (plugin.bundleIdentifier == "com.microsoft.sharepoint.browserplugin") {
525 // The Microsoft SharePoint plug-in uses QuickDraw but doesn't paint or receive events
526 // so we'll allow it to be instantiated even though we don't support QuickDraw.
527 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
530 if (plugin.bundleIdentifier == "com.jattesaker.macid2.NPPlugin") {
531 // The BankID plug-in uses QuickDraw but doesn't paint or receive events
532 // so we'll allow it to be instantiated even though we don't support QuickDraw.
533 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
537 if (plugin.bundleIdentifier == "com.adobe.acrobat.pdfviewerNPAPI" || plugin.bundleIdentifier == "com.apple.testnetscapeplugin") {
538 // The Adobe Reader plug-in wants wheel events.
539 m_pluginQuirks.add(PluginQuirks::WantsWheelEvents);
543 } // namespace WebKit
545 #endif // ENABLE(NETSCAPE_PLUGIN_API)