Move plug-in enumeration back to the main thread
[WebKit-https.git] / Source / WebKit2 / Shared / Plugins / Netscape / mac / NetscapePluginModuleMac.mm
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 #if ENABLE(NETSCAPE_PLUGIN_API)
30
31 #import "PluginProcessProxy.h"
32 #import <WebCore/WebCoreNSStringExtras.h>
33 #import <wtf/HashSet.h>
34 #import <wtf/MainThread.h>
35
36 using namespace WebCore;
37
38 namespace WebKit {
39
40 static bool getPluginArchitecture(CFBundleRef bundle, PluginModuleInfo& plugin)
41 {
42     RetainPtr<CFArrayRef> pluginArchitecturesArray(AdoptCF, CFBundleCopyExecutableArchitectures(bundle));
43     if (!pluginArchitecturesArray)
44         return false;
45
46     // Turn the array into a set.
47     HashSet<unsigned> architectures;
48     for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) {
49         CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i));
50         
51         SInt32 architecture;
52         if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture))
53             continue;
54         architectures.add(architecture);
55     }
56     
57 #ifdef __x86_64__
58     // We only support 64-bit Intel plug-ins on 64-bit Intel.
59     if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) {
60         plugin.pluginArchitecture = CPU_TYPE_X86_64;
61         return true;
62     }
63
64     // We also support 32-bit Intel plug-ins on 64-bit Intel.
65     if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
66         plugin.pluginArchitecture = CPU_TYPE_X86;
67         return true;
68     }
69 #elif defined(__i386__)
70     // We only support 32-bit Intel plug-ins on 32-bit Intel.
71     if (architectures.contains(kCFBundleExecutableArchitectureI386)) {
72         plugin.pluginArchitecture = CPU_TYPE_X86;
73         return true;
74     }
75 #elif defined(__ppc64__)
76     // We only support 64-bit PPC plug-ins on 64-bit PPC.
77     if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) {
78         plugin.pluginArchitecture = CPU_TYPE_POWERPC64;
79         return true;
80     }
81 #elif defined(__ppc__)
82     // We only support 32-bit PPC plug-ins on 32-bit PPC.
83     if (architectures.contains(kCFBundleExecutableArchitecturePPC)) {
84         plugin.pluginArchitecture = CPU_TYPE_POWERPC;
85         return true;
86     }
87 #else
88 #error "Unhandled architecture"
89 #endif
90
91     return false;
92 }
93
94 static RetainPtr<CFDictionaryRef> contentsOfPropertyListAtURL(CFURLRef propertyListURL)
95 {
96     RetainPtr<NSData> propertyListData = adoptNS([[NSData alloc] initWithContentsOfURL:(NSURL *)propertyListURL]);
97     if (!propertyListData)
98         return 0;
99
100     RetainPtr<CFPropertyListRef> propertyList(AdoptCF, CFPropertyListCreateWithData(kCFAllocatorDefault, (CFDataRef)propertyListData.get(), kCFPropertyListImmutable, 0, 0));
101     if (!propertyList)
102         return 0;
103
104     if (CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID())
105         return 0;
106
107     return RetainPtr<CFDictionaryRef>(AdoptCF, static_cast<CFDictionaryRef>(propertyList.leakRef()));
108 }
109
110 static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle, const PluginModuleInfo& plugin)
111 {
112     CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")));
113     if (propertyListFilename) {
114         RetainPtr<CFStringRef> propertyListPath(AdoptCF, CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename));
115         RetainPtr<CFURLRef> propertyListURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE));
116
117         RetainPtr<CFDictionaryRef> propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
118
119 #if ENABLE(PLUGIN_PROCESS)
120         if (!propertyList && PluginProcessProxy::createPropertyListFile(plugin))
121             propertyList = contentsOfPropertyListAtURL(propertyListURL.get());
122 #endif
123
124         if (!propertyList)
125             return 0;
126         
127         return static_cast<CFDictionaryRef>(CFDictionaryGetValue(propertyList.get(), CFSTR("WebPluginMIMETypes")));
128     }
129     
130     return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")));
131 }
132
133 static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginModuleInfo& plugin)
134 {
135     RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle, plugin);
136     if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID())
137         return false;
138
139     // Get the plug-in name.
140     CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")));
141     if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID())
142         plugin.info.name = pluginName;
143     
144     // Get the plug-in description.
145     CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")));
146     if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID())
147         plugin.info.desc = pluginDescription;
148     
149     // Get the MIME type mapping dictionary.
150     CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get());          
151     Vector<CFStringRef> mimeTypesVector(numMimeTypes);
152     Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes);
153     CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data()));
154     
155     for (CFIndex i = 0; i < numMimeTypes; ++i) {
156         MimeClassInfo mimeClassInfo;
157         
158         // If this MIME type is invalid, ignore it.
159         CFStringRef mimeType = mimeTypesVector[i];
160         if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0)
161             continue;
162
163         // If this MIME type doesn't have a valid info dictionary, ignore it.
164         CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i];
165         if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID())
166             continue;
167
168         // FIXME: Consider storing disabled MIME types.
169         CFTypeRef isEnabled = CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeEnabled"));
170         if (isEnabled) {
171             if (CFGetTypeID(isEnabled) == CFNumberGetTypeID()) {
172                 int value;
173                 if (!CFNumberGetValue(static_cast<CFNumberRef>(isEnabled), kCFNumberIntType, &value) || !value)
174                     continue;
175             } else if (CFGetTypeID(isEnabled) == CFBooleanGetTypeID()) {
176                 if (!CFBooleanGetValue(static_cast<CFBooleanRef>(isEnabled)))
177                     continue;
178             } else
179                 continue;
180         }
181
182         // Get the MIME type description.
183         CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription")));
184         if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID())
185             mimeTypeDescription = 0;
186
187         mimeClassInfo.type = String(mimeType).lower();
188         mimeClassInfo.desc = mimeTypeDescription;
189
190         // Now get the extensions for this MIME type.
191         CFIndex numExtensions = 0;
192         CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions")));
193         if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID())
194             numExtensions = CFArrayGetCount(extensionsArray);
195
196         for (CFIndex i = 0; i < numExtensions; ++i) {
197             CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i));
198             if (!extension || CFGetTypeID(extension) != CFStringGetTypeID())
199                 continue;
200
201             // The DivX plug-in lists multiple extensions in a comma separated string instead of using
202             // multiple array elements in the property list. Work around this here by splitting the
203             // extension string into components.
204             Vector<String> extensionComponents;
205             String(extension).lower().split(',', extensionComponents);
206
207             for (size_t i = 0; i < extensionComponents.size(); ++i)
208                 mimeClassInfo.extensions.append(extensionComponents[i]);
209         }
210
211         // Add this MIME type.
212         plugin.info.mimes.append(mimeClassInfo);
213     }
214
215     return true;    
216 }
217
218 #if COMPILER(CLANG)
219 #pragma clang diagnostic push
220 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
221 #endif
222
223 class ResourceMap {
224 public:
225     explicit ResourceMap(CFBundleRef bundle)
226         : m_bundle(bundle)
227         , m_currentResourceFile(CurResFile())
228         , m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle))
229     {
230         UseResFile(m_bundleResourceMap);
231     }
232
233     ~ResourceMap()
234     {
235         // Close the resource map.
236         CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap);
237         
238         // And restore the old resource.
239         UseResFile(m_currentResourceFile);
240     }
241
242     bool isValid() const { return m_bundleResourceMap != -1; }
243
244 private:
245     CFBundleRef m_bundle;
246     ResFileRefNum m_currentResourceFile;
247     ResFileRefNum m_bundleResourceMap;
248 };
249
250 static bool getStringListResource(ResID resourceID, Vector<String>& stringList) {
251     Handle stringListHandle = Get1Resource('STR#', resourceID);
252     if (!stringListHandle || !*stringListHandle)
253         return false;
254
255     // Get the string list size.
256     Size stringListSize = GetHandleSize(stringListHandle);
257     if (stringListSize < static_cast<Size>(sizeof(UInt16)))
258         return false;
259
260     CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle);
261
262     unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle);
263     unsigned char* end = ptr + stringListSize;
264     
265     // Get the number of strings in the string list.
266     UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr);
267     ptr += sizeof(UInt16);
268
269     for (UInt16 i = 0; i < numStrings; ++i) {
270         // We're past the end of the string, bail.
271         if (ptr >= end)
272             return false;
273
274         // Get the string length.
275         unsigned char stringLength = *ptr++;
276
277         RetainPtr<CFStringRef> cfString(AdoptCF, CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull));
278         if (!cfString.get())
279             return false;
280
281         stringList.append(cfString.get());
282         ptr += stringLength;
283     }
284
285     if (ptr != end)
286         return false;
287
288     return true;
289 }
290
291 #if COMPILER(CLANG)
292 #pragma clang diagnostic pop
293 #endif
294
295 static const ResID PluginNameOrDescriptionStringNumber = 126;
296 static const ResID MIMEDescriptionStringNumber = 127;
297 static const ResID MIMEListStringStringNumber = 128;
298
299 static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginModuleInfo& plugin)
300 {
301     ASSERT(isMainThread());
302
303     ResourceMap resourceMap(bundle);
304     if (!resourceMap.isValid())
305         return false;
306
307     // Get the description and name string list.
308     Vector<String> descriptionAndName;
309     if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName))
310         return false;
311
312     // Get the MIME types and extensions string list. This list needs to be a multiple of two.
313     Vector<String> mimeTypesAndExtensions;
314     if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions))
315         return false;
316
317     if (mimeTypesAndExtensions.size() % 2)
318         return false;
319
320     // Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types.
321     Vector<String> mimeTypeDescriptions;
322     if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions))
323         return false;
324
325     // Add all MIME types.
326     for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) {
327         MimeClassInfo mimeClassInfo;
328         
329         const String& mimeType = mimeTypesAndExtensions[i * 2];
330         String description;
331         if (i < mimeTypeDescriptions.size())
332             description = mimeTypeDescriptions[i];
333         
334         mimeClassInfo.type = mimeType.lower();
335         mimeClassInfo.desc = description;
336         
337         Vector<String> extensions;
338         mimeTypesAndExtensions[i * 2 + 1].split(',', extensions);
339         
340         for (size_t i = 0; i < extensions.size(); ++i)
341             mimeClassInfo.extensions.append(extensions[i].lower());
342
343         plugin.info.mimes.append(mimeClassInfo);
344     }
345
346     // Set the description and name if they exist.
347     if (descriptionAndName.size() > 0)
348         plugin.info.desc = descriptionAndName[0];
349     if (descriptionAndName.size() > 1)
350         plugin.info.name = descriptionAndName[1];
351
352     return true;
353 }
354
355 bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginModuleInfo& plugin)
356 {
357     RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
358     
359     // Try to initialize the bundle.
360     RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
361     if (!bundle)
362         return false;
363     
364     // Check if this bundle is an NPAPI plug-in.
365     UInt32 packageType = 0;
366     CFBundleGetPackageInfo(bundle.get(), &packageType, 0);
367     if (packageType != FOUR_CHAR_CODE('BRPL'))
368         return false;
369     
370     // Check that the architecture is valid.
371     if (!getPluginArchitecture(bundle.get(), plugin))
372         return false;
373
374     plugin.path = pluginPath;
375     plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get());
376     if (CFTypeRef versionTypeRef = CFBundleGetValueForInfoDictionaryKey(bundle.get(), kCFBundleVersionKey)) {
377         if (CFGetTypeID(versionTypeRef) == CFStringGetTypeID())
378             plugin.versionString = static_cast<CFStringRef>(versionTypeRef);
379     }
380     
381     // Check that there's valid info for this plug-in.
382     if (!getPluginInfoFromPropertyLists(bundle.get(), plugin) &&
383         !getPluginInfoFromCarbonResources(bundle.get(), plugin))
384         return false;
385     
386     RetainPtr<CFStringRef> filename(AdoptCF, CFURLCopyLastPathComponent(bundleURL.get()));
387     plugin.info.file = filename.get();
388     
389     if (plugin.info.name.isNull())
390         plugin.info.name = plugin.info.file;
391     if (plugin.info.desc.isNull())
392         plugin.info.desc = plugin.info.file;
393     
394     return true;
395 }
396
397 bool NetscapePluginModule::createPluginMIMETypesPreferences(const String& pluginPath)
398 {
399     RetainPtr<CFURLRef> bundleURL = adoptCF(CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false));
400     
401     RetainPtr<CFBundleRef> bundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, bundleURL.get()));
402     if (!bundle)
403         return false;
404
405     if (!CFBundleLoadExecutable(bundle.get()))
406         return false;
407
408     void (*createPluginMIMETypesPreferences)(void) = reinterpret_cast<void (*)(void)>(CFBundleGetFunctionPointerForName(bundle.get(), CFSTR("BP_CreatePluginMIMETypesPreferences")));
409     if (!createPluginMIMETypesPreferences)
410         return false;
411
412     createPluginMIMETypesPreferences();
413     return true;
414 }
415
416 // FIXME: This doesn't need to be platform-specific.
417 class PluginVersion {
418 public:
419     static PluginVersion parse(const String& versionString);
420
421     bool isLessThan(unsigned componentA) const;
422     bool isValid() const { return !m_versionComponents.isEmpty(); }
423
424 private:
425     PluginVersion()
426     {
427     }
428
429     Vector<unsigned, 4> m_versionComponents;
430 };
431
432 PluginVersion PluginVersion::parse(const String& versionString)
433 {
434     PluginVersion version;
435
436     Vector<String> versionStringComponents;
437     versionString.split(".", versionStringComponents);
438     for (size_t i = 0; i < versionStringComponents.size(); ++i) {
439         bool successfullyParsed = false;
440         unsigned versionComponent = versionStringComponents[i].toUInt(&successfullyParsed);
441         if (!successfullyParsed)
442             return PluginVersion();
443
444         version.m_versionComponents.append(versionComponent);
445     }
446
447     return version;
448 }
449
450 bool PluginVersion::isLessThan(unsigned componentA) const
451 {
452     ASSERT(isValid());
453
454     return m_versionComponents[0] < componentA;
455 }
456
457 void NetscapePluginModule::determineQuirks()
458 {
459     PluginModuleInfo plugin;
460     if (!getPluginInfo(m_pluginPath, plugin))
461         return;
462
463     if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") {
464         // Flash requires that the return value of getprogname() be "WebKitPluginHost".
465         m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost);
466
467         // Flash supports snapshotting.
468         m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting);
469
470         // Flash returns a retained Core Animation layer.
471         m_pluginQuirks.add(PluginQuirks::ReturnsRetainedCoreAnimationLayer);
472
473         // Flash has a bug where NSExceptions can be released too early.
474         m_pluginQuirks.add(PluginQuirks::LeakAllThrownNSExceptions);
475     }
476
477     if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") {
478         // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever
479         // there's a 'background' attribute that's set to a transparent color.
480         m_pluginQuirks.add(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists);
481
482         // Silverlight has a workaround for a leak in Safari 2. This workaround is
483         // applied when the user agent does not contain "Version/3" so we append it
484         // at the end of the user agent.
485         m_pluginQuirks.add(PluginQuirks::AppendVersion3UserAgent);
486
487         PluginVersion pluginVersion = PluginVersion::parse(plugin.versionString);
488         if (pluginVersion.isValid()) {
489             if (pluginVersion.isLessThan(4)) {
490                 // Versions of Silverlight prior to 4 don't retain the scriptable NPObject.
491                 m_pluginQuirks.add(PluginQuirks::ReturnsNonRetainedScriptableNPObject);
492             }
493         }
494     }
495
496     if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") {
497         // <rdar://problem/8440903>: AppleConnect has a bug where it does not
498         // understand the parameter names specified in the <object> element that
499         // embeds its plug-in. 
500         m_pluginQuirks.add(PluginQuirks::WantsLowercaseParameterNames);
501
502 #ifndef NP_NO_QUICKDRAW
503         // The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events
504         // so we'll allow it to be instantiated even though we don't support QuickDraw.
505         m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
506 #endif
507     }
508
509 #ifndef NP_NO_QUICKDRAW
510     if (plugin.bundleIdentifier == "com.microsoft.sharepoint.browserplugin") {
511         // The Microsoft SharePoint plug-in uses QuickDraw but doesn't paint or receive events
512         // so we'll allow it to be instantiated even though we don't support QuickDraw.
513         m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport);
514     }
515
516     if (plugin.bundleIdentifier == "com.jattesaker.macid2.NPPlugin") {
517         // The BankID 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);
520     }
521 #endif
522
523     if (plugin.bundleIdentifier == "com.adobe.acrobat.pdfviewerNPAPI") {
524         // The Adobe Reader plug-in wants wheel events.
525         m_pluginQuirks.add(PluginQuirks::WantsWheelEvents);
526     }
527 }
528
529 } // namespace WebKit
530
531 #endif // ENABLE(NETSCAPE_PLUGIN_API)