Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 nsPluginsDirDarwin.cpp
9 Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes.
11 by Patrick C. Beard.
12 */
14 #include "GeckoChildProcessHost.h"
15 #include "base/process_util.h"
17 #include "prlink.h"
18 #include "prnetdb.h"
19 #include "nsXPCOM.h"
21 #include "nsPluginsDir.h"
22 #include "nsNPAPIPlugin.h"
23 #include "nsPluginsDirUtils.h"
25 #include "nsILocalFileMac.h"
27 #include <string.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <fcntl.h>
32 #include <Carbon/Carbon.h>
33 #include <CoreServices/CoreServices.h>
34 #include <mach-o/loader.h>
35 #include <mach-o/fat.h>
37 typedef NS_NPAPIPLUGIN_CALLBACK(const char *, NP_GETMIMEDESCRIPTION) ();
38 typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES) (BPSupportedMIMETypes *mimeInfo, UInt32 flags);
41 /*
42 ** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory.
43 ** The caller is responsible for calling CFRelease() to deallocate.
44 */
45 static CFBundleRef getPluginBundle(const char* path)
46 {
47 CFBundleRef bundle = nullptr;
48 CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, path,
49 kCFStringEncodingUTF8);
50 if (pathRef) {
51 CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef,
52 kCFURLPOSIXPathStyle,
53 true);
54 if (bundleURL) {
55 bundle = ::CFBundleCreate(nullptr, bundleURL);
56 ::CFRelease(bundleURL);
57 }
58 ::CFRelease(pathRef);
59 }
60 return bundle;
61 }
63 static nsresult toCFURLRef(nsIFile* file, CFURLRef& outURL)
64 {
65 nsCOMPtr<nsILocalFileMac> lfm = do_QueryInterface(file);
66 if (!lfm)
67 return NS_ERROR_FAILURE;
68 CFURLRef url;
69 nsresult rv = lfm->GetCFURL(&url);
70 if (NS_SUCCEEDED(rv))
71 outURL = url;
73 return rv;
74 }
76 bool nsPluginsDir::IsPluginFile(nsIFile* file)
77 {
78 nsCString fileName;
79 file->GetNativeLeafName(fileName);
80 /*
81 * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X
82 * 10.5.3 (see bug 436575).
83 */
84 if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) {
85 NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)");
86 return false;
87 }
88 return true;
89 }
91 // Caller is responsible for freeing returned buffer.
92 static char* CFStringRefToUTF8Buffer(CFStringRef cfString)
93 {
94 const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
95 if (buffer) {
96 return PL_strdup(buffer);
97 }
99 int bufferLength =
100 ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString),
101 kCFStringEncodingUTF8) + 1;
102 char* newBuffer = static_cast<char*>(NS_Alloc(bufferLength));
103 if (!newBuffer) {
104 return nullptr;
105 }
107 if (!::CFStringGetCString(cfString, newBuffer, bufferLength,
108 kCFStringEncodingUTF8)) {
109 NS_Free(newBuffer);
110 return nullptr;
111 }
113 newBuffer = static_cast<char*>(NS_Realloc(newBuffer,
114 strlen(newBuffer) + 1));
115 return newBuffer;
116 }
118 class AutoCFTypeObject {
119 public:
120 AutoCFTypeObject(CFTypeRef object)
121 {
122 mObject = object;
123 }
124 ~AutoCFTypeObject()
125 {
126 ::CFRelease(mObject);
127 }
128 private:
129 CFTypeRef mObject;
130 };
132 static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) {
133 if (!mimeDict) {
134 return true;
135 }
137 CFTypeRef value;
138 if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), &value)) {
139 if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) {
140 return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value));
141 }
142 }
143 return true;
144 }
146 static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle)
147 {
148 CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename"));
149 if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) {
150 return nullptr;
151 }
153 FSRef homeDir;
154 if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &homeDir) != noErr) {
155 return nullptr;
156 }
158 CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir);
159 if (!userDirURL) {
160 return nullptr;
161 }
163 AutoCFTypeObject userDirURLAutorelease(userDirURL);
164 CFStringRef mimeFilePath = ::CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), static_cast<CFStringRef>(mimeFileName));
165 if (!mimeFilePath) {
166 return nullptr;
167 }
169 AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath);
170 CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, userDirURL);
171 if (!mimeFileURL) {
172 return nullptr;
173 }
175 AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL);
176 SInt32 errorCode = 0;
177 CFDataRef mimeFileData = nullptr;
178 Boolean result = ::CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, &errorCode);
179 if (!result) {
180 return nullptr;
181 }
183 AutoCFTypeObject mimeFileDataAutorelease(mimeFileData);
184 if (errorCode != 0) {
185 return nullptr;
186 }
188 CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData(kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr);
189 if (!propertyList) {
190 return nullptr;
191 }
193 AutoCFTypeObject propertyListAutorelease(propertyList);
194 if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) {
195 return nullptr;
196 }
198 CFTypeRef mimeTypes = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes"));
199 if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) {
200 return nullptr;
201 }
203 return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes));
204 }
206 static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle)
207 {
208 CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle);
210 if (!mimeDict) {
211 CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes"));
212 if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0)
213 return;
214 mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes));
215 }
217 AutoCFTypeObject mimeDictAutorelease(mimeDict);
218 int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict);
220 // Allocate memory for mime data
221 int mimeDataArraySize = mimeDictKeyCount * sizeof(char*);
222 info.fMimeTypeArray = static_cast<char**>(NS_Alloc(mimeDataArraySize));
223 if (!info.fMimeTypeArray)
224 return;
225 memset(info.fMimeTypeArray, 0, mimeDataArraySize);
226 info.fExtensionArray = static_cast<char**>(NS_Alloc(mimeDataArraySize));
227 if (!info.fExtensionArray)
228 return;
229 memset(info.fExtensionArray, 0, mimeDataArraySize);
230 info.fMimeDescriptionArray = static_cast<char**>(NS_Alloc(mimeDataArraySize));
231 if (!info.fMimeDescriptionArray)
232 return;
233 memset(info.fMimeDescriptionArray, 0, mimeDataArraySize);
235 // Allocate memory for mime dictionary keys and values
236 nsAutoArrayPtr<CFTypeRef> keys(new CFTypeRef[mimeDictKeyCount]);
237 if (!keys)
238 return;
239 nsAutoArrayPtr<CFTypeRef> values(new CFTypeRef[mimeDictKeyCount]);
240 if (!values)
241 return;
243 info.fVariantCount = 0;
245 ::CFDictionaryGetKeysAndValues(mimeDict, keys, values);
246 for (int i = 0; i < mimeDictKeyCount; i++) {
247 CFTypeRef mimeString = keys[i];
248 if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) {
249 continue;
250 }
251 CFTypeRef mimeDict = values[i];
252 if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) {
253 if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) {
254 continue;
255 }
256 info.fMimeTypeArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString));
257 if (!info.fMimeTypeArray[info.fVariantCount]) {
258 continue;
259 }
260 CFTypeRef extensions = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions"));
261 if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) {
262 int extensionCount = ::CFArrayGetCount(static_cast<CFArrayRef>(extensions));
263 CFMutableStringRef extensionList = ::CFStringCreateMutable(kCFAllocatorDefault, 0);
264 for (int j = 0; j < extensionCount; j++) {
265 CFTypeRef extension = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j);
266 if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) {
267 if (j > 0)
268 ::CFStringAppend(extensionList, CFSTR(","));
269 ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), static_cast<CFStringRef>(extension));
270 }
271 }
272 info.fExtensionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList));
273 ::CFRelease(extensionList);
274 }
275 CFTypeRef description = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginTypeDescription"));
276 if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID())
277 info.fMimeDescriptionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description));
278 }
279 info.fVariantCount++;
280 }
281 }
283 nsPluginFile::nsPluginFile(nsIFile *spec)
284 : mPlugin(spec)
285 {
286 }
288 nsPluginFile::~nsPluginFile() {}
290 nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary)
291 {
292 if (!mPlugin)
293 return NS_ERROR_NULL_POINTER;
295 // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need
296 // (for now) to load the bundle's executable. However this can cause
297 // problems: CFBundleCreate() doesn't run the bundle's executable's
298 // initialization code, while NSAddImage() and dlopen() do run it. So using
299 // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause
300 // a bundle's initialization code to run earlier than expected, and lead to
301 // crashes. See bug 577967.
302 #ifdef __LP64__
303 char executablePath[PATH_MAX];
304 executablePath[0] = '\0';
305 nsAutoCString bundlePath;
306 mPlugin->GetNativePath(bundlePath);
307 CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(),
308 kCFStringEncodingUTF8);
309 if (pathRef) {
310 CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef,
311 kCFURLPOSIXPathStyle,
312 true);
313 if (bundleURL) {
314 CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL);
315 if (bundle) {
316 CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle);
317 if (executableURL) {
318 if (!::CFURLGetFileSystemRepresentation(executableURL, true, (UInt8*)&executablePath, PATH_MAX))
319 executablePath[0] = '\0';
320 ::CFRelease(executableURL);
321 }
322 ::CFRelease(bundle);
323 }
324 ::CFRelease(bundleURL);
325 }
326 ::CFRelease(pathRef);
327 }
328 #else
329 nsAutoCString bundlePath;
330 mPlugin->GetNativePath(bundlePath);
331 const char *executablePath = bundlePath.get();
332 #endif
334 *outLibrary = PR_LoadLibrary(executablePath);
335 pLibrary = *outLibrary;
336 if (!pLibrary) {
337 return NS_ERROR_FAILURE;
338 }
339 #ifdef DEBUG
340 printf("[loaded plugin %s]\n", bundlePath.get());
341 #endif
342 return NS_OK;
343 }
345 static char* p2cstrdup(StringPtr pstr)
346 {
347 int len = pstr[0];
348 char* cstr = static_cast<char*>(NS_Alloc(len + 1));
349 if (cstr) {
350 memmove(cstr, pstr + 1, len);
351 cstr[len] = '\0';
352 }
353 return cstr;
354 }
356 static char* GetNextPluginStringFromHandle(Handle h, short *index)
357 {
358 char *ret = p2cstrdup((unsigned char*)(*h + *index));
359 *index += (ret ? strlen(ret) : 0) + 1;
360 return ret;
361 }
363 static bool IsCompatibleArch(nsIFile *file)
364 {
365 CFURLRef pluginURL = nullptr;
366 if (NS_FAILED(toCFURLRef(file, pluginURL)))
367 return false;
369 bool isPluginFile = false;
371 CFBundleRef pluginBundle = ::CFBundleCreate(kCFAllocatorDefault, pluginURL);
372 if (pluginBundle) {
373 UInt32 packageType, packageCreator;
374 ::CFBundleGetPackageInfo(pluginBundle, &packageType, &packageCreator);
375 if (packageType == 'BRPL' || packageType == 'IEPL' || packageType == 'NSPL') {
376 // Get path to plugin as a C string.
377 char executablePath[PATH_MAX];
378 executablePath[0] = '\0';
379 if (!::CFURLGetFileSystemRepresentation(pluginURL, true, (UInt8*)&executablePath, PATH_MAX)) {
380 executablePath[0] = '\0';
381 }
383 uint32_t pluginLibArchitectures;
384 nsresult rv = mozilla::ipc::GeckoChildProcessHost::GetArchitecturesForBinary(executablePath, &pluginLibArchitectures);
385 if (NS_FAILED(rv)) {
386 return false;
387 }
389 uint32_t supportedArchitectures =
390 #ifdef __LP64__
391 mozilla::ipc::GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin);
392 #else
393 base::GetCurrentProcessArchitecture();
394 #endif
396 // Consider the plugin architecture valid if there is any overlap in the masks.
397 isPluginFile = !!(supportedArchitectures & pluginLibArchitectures);
398 }
399 ::CFRelease(pluginBundle);
400 }
402 ::CFRelease(pluginURL);
403 return isPluginFile;
404 }
406 /**
407 * Obtains all of the information currently available for this plugin.
408 */
409 nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary)
410 {
411 *outLibrary = nullptr;
413 nsresult rv = NS_OK;
415 if (!IsCompatibleArch(mPlugin)) {
416 return NS_ERROR_FAILURE;
417 }
419 // clear out the info, except for the first field.
420 memset(&info, 0, sizeof(info));
422 // Try to get a bundle reference.
423 nsAutoCString path;
424 if (NS_FAILED(rv = mPlugin->GetNativePath(path)))
425 return rv;
426 CFBundleRef bundle = getPluginBundle(path.get());
428 // fill in full path
429 info.fFullPath = PL_strdup(path.get());
431 // fill in file name
432 nsAutoCString fileName;
433 if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName)))
434 return rv;
435 info.fFileName = PL_strdup(fileName.get());
437 // Get fName
438 if (bundle) {
439 CFTypeRef name = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName"));
440 if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID())
441 info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name));
442 }
444 // Get fDescription
445 if (bundle) {
446 CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription"));
447 if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID())
448 info.fDescription = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description));
449 }
451 // Get fVersion
452 if (bundle) {
453 // Look for the release version first
454 CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
455 if (!version) // try the build version
456 version = ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
457 if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID())
458 info.fVersion = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version));
459 }
461 // The last thing we need to do is get MIME data
462 // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray
464 // First look for data in a bundle plist
465 if (bundle) {
466 ParsePlistPluginInfo(info, bundle);
467 ::CFRelease(bundle);
468 if (info.fVariantCount > 0)
469 return NS_OK;
470 }
472 // It's possible that our plugin has 2 entry points that'll give us mime type
473 // info. Quicktime does this to get around the need of having admin rights to
474 // change mime info in the resource fork. We need to use this info instead of
475 // the resource. See bug 113464.
477 // Sadly we have to load the library for this to work.
478 rv = LoadPlugin(outLibrary);
479 if (NS_FAILED(rv))
480 return rv;
482 // Try to get data from NP_GetMIMEDescription
483 if (pLibrary) {
484 NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME);
485 if (pfnGetMimeDesc)
486 ParsePluginMimeDescription(pfnGetMimeDesc(), info);
487 if (info.fVariantCount)
488 return NS_OK;
489 }
491 // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data
492 BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, nullptr};
494 // Try to get data from BP_GetSupportedMIMETypes
495 if (pLibrary) {
496 BP_GETSUPPORTEDMIMETYPES pfnMime = (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol(pLibrary, "BP_GetSupportedMIMETypes");
497 if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) {
498 info.fVariantCount = (**(short**)mi.typeStrings) / 2;
499 ::HLock(mi.typeStrings);
500 if (mi.infoStrings) // it's possible some plugins have infoStrings missing
501 ::HLock(mi.infoStrings);
502 }
503 }
505 // Fill in the info struct based on the data in the BPSupportedMIMETypes struct
506 int variantCount = info.fVariantCount;
507 info.fMimeTypeArray = static_cast<char**>(NS_Alloc(variantCount * sizeof(char*)));
508 if (!info.fMimeTypeArray)
509 return NS_ERROR_OUT_OF_MEMORY;
510 info.fExtensionArray = static_cast<char**>(NS_Alloc(variantCount * sizeof(char*)));
511 if (!info.fExtensionArray)
512 return NS_ERROR_OUT_OF_MEMORY;
513 if (mi.infoStrings) {
514 info.fMimeDescriptionArray = static_cast<char**>(NS_Alloc(variantCount * sizeof(char*)));
515 if (!info.fMimeDescriptionArray)
516 return NS_ERROR_OUT_OF_MEMORY;
517 }
518 short mimeIndex = 2;
519 short descriptionIndex = 2;
520 for (int i = 0; i < variantCount; i++) {
521 info.fMimeTypeArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex);
522 info.fExtensionArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex);
523 if (mi.infoStrings)
524 info.fMimeDescriptionArray[i] = GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex);
525 }
527 ::HUnlock(mi.typeStrings);
528 ::DisposeHandle(mi.typeStrings);
529 if (mi.infoStrings) {
530 ::HUnlock(mi.infoStrings);
531 ::DisposeHandle(mi.infoStrings);
532 }
534 return NS_OK;
535 }
537 nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info)
538 {
539 NS_Free(info.fName);
540 NS_Free(info.fDescription);
541 int variantCount = info.fVariantCount;
542 for (int i = 0; i < variantCount; i++) {
543 NS_Free(info.fMimeTypeArray[i]);
544 NS_Free(info.fExtensionArray[i]);
545 NS_Free(info.fMimeDescriptionArray[i]);
546 }
547 NS_Free(info.fMimeTypeArray);
548 NS_Free(info.fMimeDescriptionArray);
549 NS_Free(info.fExtensionArray);
550 NS_Free(info.fFileName);
551 NS_Free(info.fFullPath);
552 NS_Free(info.fVersion);
554 return NS_OK;
555 }