diff options
Diffstat (limited to 'dom/plugins/base/nsPluginsDirDarwin.cpp')
-rw-r--r-- | dom/plugins/base/nsPluginsDirDarwin.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/dom/plugins/base/nsPluginsDirDarwin.cpp b/dom/plugins/base/nsPluginsDirDarwin.cpp new file mode 100644 index 000000000..6edc4fd6a --- /dev/null +++ b/dom/plugins/base/nsPluginsDirDarwin.cpp @@ -0,0 +1,591 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + nsPluginsDirDarwin.cpp + + Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes. + + by Patrick C. Beard. + */ + +#include "GeckoChildProcessHost.h" +#include "base/process_util.h" + +#include "prlink.h" +#include "prnetdb.h" +#include "nsXPCOM.h" + +#include "nsPluginsDir.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginsDirUtils.h" + +#include "nsILocalFileMac.h" +#include "mozilla/UniquePtr.h" + +#include "nsCocoaFeatures.h" +#if defined(MOZ_CRASHREPORTER) +#include "nsExceptionHandler.h" +#endif + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> + +typedef NS_NPAPIPLUGIN_CALLBACK(const char *, NP_GETMIMEDESCRIPTION) (); +typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES) (BPSupportedMIMETypes *mimeInfo, UInt32 flags); + +/* +** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory. +** The caller is responsible for calling CFRelease() to deallocate. +*/ +static CFBundleRef getPluginBundle(const char* path) +{ + CFBundleRef bundle = nullptr; + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, path, + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + bundle = ::CFBundleCreate(nullptr, bundleURL); + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } + return bundle; +} + +static nsresult toCFURLRef(nsIFile* file, CFURLRef& outURL) +{ + nsCOMPtr<nsILocalFileMac> lfm = do_QueryInterface(file); + if (!lfm) + return NS_ERROR_FAILURE; + CFURLRef url; + nsresult rv = lfm->GetCFURL(&url); + if (NS_SUCCEEDED(rv)) + outURL = url; + + return rv; +} + +bool nsPluginsDir::IsPluginFile(nsIFile* file) +{ + nsCString fileName; + file->GetNativeLeafName(fileName); + /* + * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X + * 10.5.3 (see bug 436575). + */ + if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) { + NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)"); + return false; + } + return true; +} + +// Caller is responsible for freeing returned buffer. +static char* CFStringRefToUTF8Buffer(CFStringRef cfString) +{ + const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); + if (buffer) { + return PL_strdup(buffer); + } + + int bufferLength = + ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString), + kCFStringEncodingUTF8) + 1; + char* newBuffer = static_cast<char*>(moz_xmalloc(bufferLength)); + if (!newBuffer) { + return nullptr; + } + + if (!::CFStringGetCString(cfString, newBuffer, bufferLength, + kCFStringEncodingUTF8)) { + free(newBuffer); + return nullptr; + } + + newBuffer = static_cast<char*>(moz_xrealloc(newBuffer, + strlen(newBuffer) + 1)); + return newBuffer; +} + +class AutoCFTypeObject { +public: + explicit AutoCFTypeObject(CFTypeRef aObject) + { + mObject = aObject; + } + ~AutoCFTypeObject() + { + ::CFRelease(mObject); + } +private: + CFTypeRef mObject; +}; + +static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) { + if (!mimeDict) { + return true; + } + + CFTypeRef value; + if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), &value)) { + if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) { + return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + } + } + return true; +} + +static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle) +{ + CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")); + if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) { + return nullptr; + } + + FSRef homeDir; + if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &homeDir) != noErr) { + return nullptr; + } + + CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir); + if (!userDirURL) { + return nullptr; + } + + AutoCFTypeObject userDirURLAutorelease(userDirURL); + CFStringRef mimeFilePath = ::CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), static_cast<CFStringRef>(mimeFileName)); + if (!mimeFilePath) { + return nullptr; + } + + AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath); + CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, userDirURL); + if (!mimeFileURL) { + return nullptr; + } + + AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL); + SInt32 errorCode = 0; + CFDataRef mimeFileData = nullptr; + Boolean result = ::CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, &errorCode); + if (!result) { + return nullptr; + } + + AutoCFTypeObject mimeFileDataAutorelease(mimeFileData); + if (errorCode != 0) { + return nullptr; + } + + CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData(kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr); + if (!propertyList) { + return nullptr; + } + + AutoCFTypeObject propertyListAutorelease(propertyList); + if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) { + return nullptr; + } + + CFTypeRef mimeTypes = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) { + return nullptr; + } + + return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); +} + +static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle) +{ + CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle); + + if (!mimeDict) { + CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) + return; + mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); + } + + AutoCFTypeObject mimeDictAutorelease(mimeDict); + int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict); + + // Allocate memory for mime data + int mimeDataArraySize = mimeDictKeyCount * sizeof(char*); + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeTypeArray) + return; + memset(info.fMimeTypeArray, 0, mimeDataArraySize); + info.fExtensionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fExtensionArray) + return; + memset(info.fExtensionArray, 0, mimeDataArraySize); + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeDescriptionArray) + return; + memset(info.fMimeDescriptionArray, 0, mimeDataArraySize); + + // Allocate memory for mime dictionary keys and values + mozilla::UniquePtr<CFTypeRef[]> keys(new CFTypeRef[mimeDictKeyCount]); + if (!keys) + return; + mozilla::UniquePtr<CFTypeRef[]> values(new CFTypeRef[mimeDictKeyCount]); + if (!values) + return; + + info.fVariantCount = 0; + + ::CFDictionaryGetKeysAndValues(mimeDict, keys.get(), values.get()); + for (int i = 0; i < mimeDictKeyCount; i++) { + CFTypeRef mimeString = keys[i]; + if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) { + continue; + } + CFTypeRef mimeDict = values[i]; + if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) { + if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) { + continue; + } + info.fMimeTypeArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString)); + if (!info.fMimeTypeArray[info.fVariantCount]) { + continue; + } + CFTypeRef extensions = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions")); + if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) { + int extensionCount = ::CFArrayGetCount(static_cast<CFArrayRef>(extensions)); + CFMutableStringRef extensionList = ::CFStringCreateMutable(kCFAllocatorDefault, 0); + for (int j = 0; j < extensionCount; j++) { + CFTypeRef extension = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j); + if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) { + if (j > 0) + ::CFStringAppend(extensionList, CFSTR(",")); + ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), static_cast<CFStringRef>(extension)); + } + } + info.fExtensionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList)); + ::CFRelease(extensionList); + } + CFTypeRef description = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginTypeDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fMimeDescriptionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + info.fVariantCount++; + } +} + +nsPluginFile::nsPluginFile(nsIFile *spec) + : mPlugin(spec) +{ +} + +nsPluginFile::~nsPluginFile() {} + +nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) +{ + if (!mPlugin) + return NS_ERROR_NULL_POINTER; + + // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need + // (for now) to load the bundle's executable. However this can cause + // problems: CFBundleCreate() doesn't run the bundle's executable's + // initialization code, while NSAddImage() and dlopen() do run it. So using + // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause + // a bundle's initialization code to run earlier than expected, and lead to + // crashes. See bug 577967. +#ifdef __LP64__ + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(), + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL); + if (bundle) { + CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle); + if (executableURL) { + if (!::CFURLGetFileSystemRepresentation(executableURL, true, (UInt8*)&executablePath, PATH_MAX)) + executablePath[0] = '\0'; + ::CFRelease(executableURL); + } + ::CFRelease(bundle); + } + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } +#else + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + const char *executablePath = bundlePath.get(); +#endif + + *outLibrary = PR_LoadLibrary(executablePath); + pLibrary = *outLibrary; + if (!pLibrary) { + return NS_ERROR_FAILURE; + } +#ifdef DEBUG + printf("[loaded plugin %s]\n", bundlePath.get()); +#endif + return NS_OK; +} + +static char* p2cstrdup(StringPtr pstr) +{ + int len = pstr[0]; + char* cstr = static_cast<char*>(moz_xmalloc(len + 1)); + if (cstr) { + memmove(cstr, pstr + 1, len); + cstr[len] = '\0'; + } + return cstr; +} + +static char* GetNextPluginStringFromHandle(Handle h, short *index) +{ + char *ret = p2cstrdup((unsigned char*)(*h + *index)); + *index += (ret ? strlen(ret) : 0) + 1; + return ret; +} + +static bool IsCompatibleArch(nsIFile *file) +{ + CFURLRef pluginURL = nullptr; + if (NS_FAILED(toCFURLRef(file, pluginURL))) + return false; + + bool isPluginFile = false; + + CFBundleRef pluginBundle = ::CFBundleCreate(kCFAllocatorDefault, pluginURL); + if (pluginBundle) { + UInt32 packageType, packageCreator; + ::CFBundleGetPackageInfo(pluginBundle, &packageType, &packageCreator); + if (packageType == 'BRPL' || packageType == 'IEPL' || packageType == 'NSPL') { + // Get path to plugin as a C string. + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + if (!::CFURLGetFileSystemRepresentation(pluginURL, true, (UInt8*)&executablePath, PATH_MAX)) { + executablePath[0] = '\0'; + } + + uint32_t pluginLibArchitectures; + nsresult rv = mozilla::ipc::GeckoChildProcessHost::GetArchitecturesForBinary(executablePath, &pluginLibArchitectures); + if (NS_FAILED(rv)) { + return false; + } + + uint32_t supportedArchitectures = +#ifdef __LP64__ + mozilla::ipc::GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin); +#else + base::GetCurrentProcessArchitecture(); +#endif + + // Consider the plugin architecture valid if there is any overlap in the masks. + isPluginFile = !!(supportedArchitectures & pluginLibArchitectures); + } + ::CFRelease(pluginBundle); + } + + ::CFRelease(pluginURL); + return isPluginFile; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary) +{ + *outLibrary = nullptr; + + nsresult rv = NS_OK; + + if (!IsCompatibleArch(mPlugin)) { + return NS_ERROR_FAILURE; + } + + // clear out the info, except for the first field. + memset(&info, 0, sizeof(info)); + + // Try to get a bundle reference. + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) + return rv; + CFBundleRef bundle = getPluginBundle(path.get()); + + // fill in full path + info.fFullPath = PL_strdup(path.get()); + + // fill in file name + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) + return rv; + info.fFileName = PL_strdup(fileName.get()); + + // Get fName + if (bundle) { + CFTypeRef name = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")); + if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID()) + info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name)); + } + + // Get fDescription + if (bundle) { + CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fDescription = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + + // Get fVersion + if (bundle) { + // Look for the release version first + CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); + if (!version) // try the build version + version = ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey); + if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID()) + info.fVersion = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version)); + } + + // The last thing we need to do is get MIME data + // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray + + // First look for data in a bundle plist + if (bundle) { + ParsePlistPluginInfo(info, bundle); + ::CFRelease(bundle); + if (info.fVariantCount > 0) + return NS_OK; + } + + // Don't load "fbplugin" or any plugins whose name starts with "fbplugin_" + // (Facebook plugins) if we're running on OS X 10.10 (Yosemite) or later. + // A "fbplugin" file crashes on load, in the call to LoadPlugin() below. + // See bug 1086977. + if (nsCocoaFeatures::OnYosemiteOrLater()) { + if (fileName.EqualsLiteral("fbplugin") || + StringBeginsWith(fileName, NS_LITERAL_CSTRING("fbplugin_"))) { + nsAutoCString msg; + msg.AppendPrintf("Preventing load of %s (see bug 1086977)", + fileName.get()); + NS_WARNING(msg.get()); + return NS_ERROR_FAILURE; + } +#if defined(MOZ_CRASHREPORTER) + // The block above assumes that "fbplugin" is the filename of the plugin + // to be blocked, or that the filename starts with "fbplugin_". But we + // don't yet know for sure if this is always true. So for the time being + // record extra information in our crash logs. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug_1086977"), + fileName); +#endif + } + + // It's possible that our plugin has 2 entry points that'll give us mime type + // info. Quicktime does this to get around the need of having admin rights to + // change mime info in the resource fork. We need to use this info instead of + // the resource. See bug 113464. + + // Sadly we have to load the library for this to work. + rv = LoadPlugin(outLibrary); +#if defined(MOZ_CRASHREPORTER) + if (nsCocoaFeatures::OnYosemiteOrLater()) { + // If we didn't crash in LoadPlugin(), change the previous annotation so we + // don't sow confusion. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug_1086977"), + NS_LITERAL_CSTRING("Didn't crash, please ignore")); + } +#endif + if (NS_FAILED(rv)) + return rv; + + // Try to get data from NP_GetMIMEDescription + if (pLibrary) { + NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME); + if (pfnGetMimeDesc) + ParsePluginMimeDescription(pfnGetMimeDesc(), info); + if (info.fVariantCount) + return NS_OK; + } + + // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data + BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, nullptr}; + + // Try to get data from BP_GetSupportedMIMETypes + if (pLibrary) { + BP_GETSUPPORTEDMIMETYPES pfnMime = (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol(pLibrary, "BP_GetSupportedMIMETypes"); + if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) { + info.fVariantCount = (**(short**)mi.typeStrings) / 2; + ::HLock(mi.typeStrings); + if (mi.infoStrings) // it's possible some plugins have infoStrings missing + ::HLock(mi.infoStrings); + } + } + + // Fill in the info struct based on the data in the BPSupportedMIMETypes struct + int variantCount = info.fVariantCount; + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeTypeArray) + return NS_ERROR_OUT_OF_MEMORY; + info.fExtensionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fExtensionArray) + return NS_ERROR_OUT_OF_MEMORY; + if (mi.infoStrings) { + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeDescriptionArray) + return NS_ERROR_OUT_OF_MEMORY; + } + short mimeIndex = 2; + short descriptionIndex = 2; + for (int i = 0; i < variantCount; i++) { + info.fMimeTypeArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + info.fExtensionArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + if (mi.infoStrings) + info.fMimeDescriptionArray[i] = GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex); + } + + ::HUnlock(mi.typeStrings); + ::DisposeHandle(mi.typeStrings); + if (mi.infoStrings) { + ::HUnlock(mi.infoStrings); + ::DisposeHandle(mi.infoStrings); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) +{ + free(info.fName); + free(info.fDescription); + int variantCount = info.fVariantCount; + for (int i = 0; i < variantCount; i++) { + free(info.fMimeTypeArray[i]); + free(info.fExtensionArray[i]); + free(info.fMimeDescriptionArray[i]); + } + free(info.fMimeTypeArray); + free(info.fMimeDescriptionArray); + free(info.fExtensionArray); + free(info.fFileName); + free(info.fFullPath); + free(info.fVersion); + + return NS_OK; +} |