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