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 <WebCore/WebCoreNSStringExtras.h> 30 #import <wtf/HashSet.h> 31 32 using namespace WebCore; 33 34 namespace WebKit { 35 36 static bool getPluginArchitecture(CFBundleRef bundle, cpu_type_t& pluginArchitecture) 37 { 38 RetainPtr<CFArrayRef> pluginArchitecturesArray(AdoptCF, CFBundleCopyExecutableArchitectures(bundle)); 39 if (!pluginArchitecturesArray) 40 return false; 41 42 // Turn the array into a set. 43 HashSet<unsigned> architectures; 44 for (CFIndex i = 0, numPluginArchitectures = CFArrayGetCount(pluginArchitecturesArray.get()); i < numPluginArchitectures; ++i) { 45 CFNumberRef number = static_cast<CFNumberRef>(CFArrayGetValueAtIndex(pluginArchitecturesArray.get(), i)); 46 47 SInt32 architecture; 48 if (!CFNumberGetValue(number, kCFNumberSInt32Type, &architecture)) 49 continue; 50 architectures.add(architecture); 51 } 52 53 #ifdef __x86_64__ 54 // We only support 64-bit Intel plug-ins on 64-bit Intel. 55 if (architectures.contains(kCFBundleExecutableArchitectureX86_64)) { 56 pluginArchitecture = CPU_TYPE_X86_64; 57 return true; 58 } 59 60 // We also support 32-bit Intel plug-ins on 64-bit Intel. 61 if (architectures.contains(kCFBundleExecutableArchitectureI386)) { 62 pluginArchitecture = CPU_TYPE_X86; 63 return true; 64 } 65 #elif defined(__i386__) 66 // We only support 32-bit Intel plug-ins on 32-bit Intel. 67 if (architectures.contains(kCFBundleExecutableArchitectureI386)) { 68 pluginArchitecture = CPU_TYPE_X86; 69 return true; 70 } 71 #elif defined(__ppc64__) 72 // We only support 64-bit PPC plug-ins on 64-bit PPC. 73 if (architectures.contains(kCFBundleExecutableArchitecturePPC64)) { 74 pluginArchitecture = CPU_TYPE_POWERPC64; 75 return true; 76 } 77 #elif defined(__ppc__) 78 // We only support 32-bit PPC plug-ins on 32-bit PPC. 79 if (architectures.contains(kCFBundleExecutableArchitecturePPC)) { 80 pluginArchitecture = CPU_TYPE_POWERPC; 81 return true; 82 } 83 #else 84 #error "Unhandled architecture" 85 #endif 86 87 return false; 88 } 89 90 static RetainPtr<CFDictionaryRef> getMIMETypesFromPluginBundle(CFBundleRef bundle) 91 { 92 CFStringRef propertyListFilename = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename"))); 93 if (propertyListFilename) { 94 RetainPtr<CFStringRef> propertyListPath(AdoptCF, CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@/Library/Preferences/%@"), NSHomeDirectory(), propertyListFilename)); 95 RetainPtr<CFURLRef> propertyListURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, propertyListPath.get(), kCFURLPOSIXPathStyle, FALSE)); 96 97 CFDataRef propertyListData; 98 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, propertyListURL.get(), &propertyListData, 0, 0, 0); 99 RetainPtr<CFPropertyListRef> propertyList(AdoptCF, CFPropertyListCreateWithData(kCFAllocatorDefault, propertyListData, kCFPropertyListImmutable, 0, 0)); 100 if (propertyListData) 101 CFRelease(propertyListData); 102 103 // FIXME: Have the plug-in create the MIME types property list if it doesn't exist. 104 // https://bugs.webkit.org/show_bug.cgi?id=57204 105 if (!propertyList || CFGetTypeID(propertyList.get()) != CFDictionaryGetTypeID()) 106 return 0; 107 108 return static_cast<CFDictionaryRef>(CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList.get()), CFSTR("WebPluginMIMETypes"))); 109 } 110 111 return static_cast<CFDictionaryRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes"))); 112 } 113 114 static bool getPluginInfoFromPropertyLists(CFBundleRef bundle, PluginInfo& pluginInfo) 115 { 116 RetainPtr<CFDictionaryRef> mimeTypes = getMIMETypesFromPluginBundle(bundle); 117 if (!mimeTypes || CFGetTypeID(mimeTypes.get()) != CFDictionaryGetTypeID()) 118 return false; 119 120 // Get the plug-in name. 121 CFStringRef pluginName = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName"))); 122 if (pluginName && CFGetTypeID(pluginName) == CFStringGetTypeID()) 123 pluginInfo.name = pluginName; 124 125 // Get the plug-in description. 126 CFStringRef pluginDescription = static_cast<CFStringRef>(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription"))); 127 if (pluginDescription && CFGetTypeID(pluginDescription) == CFStringGetTypeID()) 128 pluginInfo.desc = pluginDescription; 129 130 // Get the MIME type mapping dictionary. 131 CFIndex numMimeTypes = CFDictionaryGetCount(mimeTypes.get()); 132 Vector<CFStringRef> mimeTypesVector(numMimeTypes); 133 Vector<CFDictionaryRef> mimeTypeInfoVector(numMimeTypes); 134 CFDictionaryGetKeysAndValues(mimeTypes.get(), reinterpret_cast<const void**>(mimeTypesVector.data()), reinterpret_cast<const void**>(mimeTypeInfoVector.data())); 135 136 for (CFIndex i = 0; i < numMimeTypes; ++i) { 137 MimeClassInfo mimeClassInfo; 138 139 // If this MIME type is invalid, ignore it. 140 CFStringRef mimeType = mimeTypesVector[i]; 141 if (!mimeType || CFGetTypeID(mimeType) != CFStringGetTypeID() || CFStringGetLength(mimeType) == 0) 142 continue; 143 144 // If this MIME type doesn't have a valid info dictionary, ignore it. 145 CFDictionaryRef mimeTypeInfo = mimeTypeInfoVector[i]; 146 if (!mimeTypeInfo || CFGetTypeID(mimeTypeInfo) != CFDictionaryGetTypeID()) 147 continue; 148 149 // Get the MIME type description. 150 CFStringRef mimeTypeDescription = static_cast<CFStringRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginTypeDescription"))); 151 if (mimeTypeDescription && CFGetTypeID(mimeTypeDescription) != CFStringGetTypeID()) 152 mimeTypeDescription = 0; 153 154 mimeClassInfo.type = String(mimeType).lower(); 155 mimeClassInfo.desc = mimeTypeDescription; 156 157 // Now get the extensions for this MIME type. 158 CFIndex numExtensions = 0; 159 CFArrayRef extensionsArray = static_cast<CFArrayRef>(CFDictionaryGetValue(mimeTypeInfo, CFSTR("WebPluginExtensions"))); 160 if (extensionsArray && CFGetTypeID(extensionsArray) == CFArrayGetTypeID()) 161 numExtensions = CFArrayGetCount(extensionsArray); 162 163 for (CFIndex i = 0; i < numExtensions; ++i) { 164 CFStringRef extension = static_cast<CFStringRef>(CFArrayGetValueAtIndex(extensionsArray, i)); 165 if (!extension || CFGetTypeID(extension) != CFStringGetTypeID()) 166 continue; 167 168 // The DivX plug-in lists multiple extensions in a comma separated string instead of using 169 // multiple array elements in the property list. Work around this here by splitting the 170 // extension string into components. 171 Vector<String> extensionComponents; 172 String(extension).lower().split(',', extensionComponents); 173 174 for (size_t i = 0; i < extensionComponents.size(); ++i) 175 mimeClassInfo.extensions.append(extensionComponents[i]); 176 } 177 178 // Add this MIME type. 179 pluginInfo.mimes.append(mimeClassInfo); 180 } 181 182 return true; 183 } 184 185 class ResourceMap { 186 public: 187 explicit ResourceMap(CFBundleRef bundle) 188 : m_bundle(bundle) 189 , m_currentResourceFile(CurResFile()) 190 , m_bundleResourceMap(CFBundleOpenBundleResourceMap(m_bundle)) 191 { 192 UseResFile(m_bundleResourceMap); 193 } 194 195 ~ResourceMap() 196 { 197 // Close the resource map. 198 CFBundleCloseBundleResourceMap(m_bundle, m_bundleResourceMap); 199 200 // And restore the old resource. 201 UseResFile(m_currentResourceFile); 202 } 203 204 bool isValid() const { return m_bundleResourceMap != -1; } 205 206 private: 207 CFBundleRef m_bundle; 208 ResFileRefNum m_currentResourceFile; 209 ResFileRefNum m_bundleResourceMap; 210 }; 211 212 static bool getStringListResource(ResID resourceID, Vector<String>& stringList) { 213 Handle stringListHandle = Get1Resource('STR#', resourceID); 214 if (!stringListHandle || !*stringListHandle) 215 return false; 216 217 // Get the string list size. 218 Size stringListSize = GetHandleSize(stringListHandle); 219 if (stringListSize < static_cast<Size>(sizeof(UInt16))) 220 return false; 221 222 CFStringEncoding stringEncoding = stringEncodingForResource(stringListHandle); 223 224 unsigned char* ptr = reinterpret_cast<unsigned char*>(*stringListHandle); 225 unsigned char* end = ptr + stringListSize; 226 227 // Get the number of strings in the string list. 228 UInt16 numStrings = *reinterpret_cast<UInt16*>(ptr); 229 ptr += sizeof(UInt16); 230 231 for (UInt16 i = 0; i < numStrings; ++i) { 232 // We're past the end of the string, bail. 233 if (ptr >= end) 234 return false; 235 236 // Get the string length. 237 unsigned char stringLength = *ptr++; 238 239 RetainPtr<CFStringRef> cfString(AdoptCF, CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, ptr, stringLength, stringEncoding, false, kCFAllocatorNull)); 240 if (!cfString.get()) 241 return false; 242 243 stringList.append(cfString.get()); 244 ptr += stringLength; 245 } 246 247 if (ptr != end) 248 return false; 249 250 return true; 251 } 252 253 static const ResID PluginNameOrDescriptionStringNumber = 126; 254 static const ResID MIMEDescriptionStringNumber = 127; 255 static const ResID MIMEListStringStringNumber = 128; 256 257 static bool getPluginInfoFromCarbonResources(CFBundleRef bundle, PluginInfo& pluginInfo) 258 { 259 ResourceMap resourceMap(bundle); 260 if (!resourceMap.isValid()) 261 return false; 262 263 // Get the description and name string list. 264 Vector<String> descriptionAndName; 265 if (!getStringListResource(PluginNameOrDescriptionStringNumber, descriptionAndName)) 266 return false; 267 268 // Get the MIME types and extensions string list. This list needs to be a multiple of two. 269 Vector<String> mimeTypesAndExtensions; 270 if (!getStringListResource(MIMEListStringStringNumber, mimeTypesAndExtensions)) 271 return false; 272 273 if (mimeTypesAndExtensions.size() % 2) 274 return false; 275 276 // Now get the MIME type descriptions string list. This string list needs to be the same length as the number of MIME types. 277 Vector<String> mimeTypeDescriptions; 278 if (!getStringListResource(MIMEDescriptionStringNumber, mimeTypeDescriptions)) 279 return false; 280 281 // Add all MIME types. 282 for (size_t i = 0; i < mimeTypesAndExtensions.size() / 2; ++i) { 283 MimeClassInfo mimeClassInfo; 284 285 const String& mimeType = mimeTypesAndExtensions[i * 2]; 286 String description; 287 if (i < mimeTypeDescriptions.size()) 288 description = mimeTypeDescriptions[i]; 289 290 mimeClassInfo.type = mimeType.lower(); 291 mimeClassInfo.desc = description; 292 293 Vector<String> extensions; 294 mimeTypesAndExtensions[i * 2 + 1].split(',', extensions); 295 296 for (size_t i = 0; i < extensions.size(); ++i) 297 mimeClassInfo.extensions.append(extensions[i].lower()); 298 299 pluginInfo.mimes.append(mimeClassInfo); 300 } 301 302 // Set the description and name if they exist. 303 if (descriptionAndName.size() > 0) 304 pluginInfo.desc = descriptionAndName[0]; 305 if (descriptionAndName.size() > 1) 306 pluginInfo.name = descriptionAndName[1]; 307 308 return true; 309 } 310 311 bool NetscapePluginModule::getPluginInfo(const String& pluginPath, PluginInfoStore::Plugin& plugin) 312 { 313 RetainPtr<CFStringRef> bundlePath(AdoptCF, pluginPath.createCFString()); 314 RetainPtr<CFURLRef> bundleURL(AdoptCF, CFURLCreateWithFileSystemPath(kCFAllocatorDefault, bundlePath.get(), kCFURLPOSIXPathStyle, false)); 315 316 // Try to initialize the bundle. 317 RetainPtr<CFBundleRef> bundle(AdoptCF, CFBundleCreate(kCFAllocatorDefault, bundleURL.get())); 318 if (!bundle) 319 return false; 320 321 // Check if this bundle is an NPAPI plug-in. 322 UInt32 packageType = 0; 323 CFBundleGetPackageInfo(bundle.get(), &packageType, 0); 324 if (packageType != FOUR_CHAR_CODE('BRPL')) 325 return false; 326 327 // Check that the architecture is valid. 328 cpu_type_t pluginArchitecture = 0; 329 if (!getPluginArchitecture(bundle.get(), pluginArchitecture)) 330 return false; 331 332 // Check that there's valid info for this plug-in. 333 if (!getPluginInfoFromPropertyLists(bundle.get(), plugin.info) && 334 !getPluginInfoFromCarbonResources(bundle.get(), plugin.info)) 335 return false; 336 337 plugin.path = pluginPath; 338 plugin.pluginArchitecture = pluginArchitecture; 339 plugin.bundleIdentifier = CFBundleGetIdentifier(bundle.get()); 340 plugin.versionNumber = CFBundleGetVersionNumber(bundle.get()); 341 342 RetainPtr<CFStringRef> filename(AdoptCF, CFURLCopyLastPathComponent(bundleURL.get())); 343 plugin.info.file = filename.get(); 344 345 if (plugin.info.name.isNull()) 346 plugin.info.name = plugin.info.file; 347 if (plugin.info.desc.isNull()) 348 plugin.info.desc = plugin.info.file; 349 350 return true; 351 } 352 353 void NetscapePluginModule::determineQuirks() 354 { 355 PluginInfoStore::Plugin plugin; 356 if (!getPluginInfo(m_pluginPath, plugin)) 357 return; 358 359 if (plugin.bundleIdentifier == "com.macromedia.Flash Player.plugin") { 360 // Flash requires that the return value of getprogname() be "WebKitPluginHost". 361 m_pluginQuirks.add(PluginQuirks::PrognameShouldBeWebKitPluginHost); 362 363 // Flash supports snapshotting. 364 m_pluginQuirks.add(PluginQuirks::SupportsSnapshotting); 365 } 366 367 if (plugin.bundleIdentifier == "com.microsoft.SilverlightPlugin") { 368 // Silverlight doesn't explicitly opt into transparency, so we'll do it whenever 369 // there's a 'background' attribute. 370 m_pluginQuirks.add(PluginQuirks::MakeTransparentIfBackgroundAttributeExists); 371 } 372 373 #ifndef NP_NO_QUICKDRAW 374 if (plugin.bundleIdentifier == "com.apple.ist.ds.appleconnect.webplugin") { 375 // The AppleConnect plug-in uses QuickDraw but doesn't paint or receive events 376 // so we'll allow it to be instantiated even though we don't support QuickDraw. 377 m_pluginQuirks.add(PluginQuirks::AllowHalfBakedQuickDrawSupport); 378 } 379 #endif 380 } 381 382 } // namespace WebKit 383