// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #include "webkit/plugins/npapi/plugin_lib.h" #include "base/mac/scoped_cftyperef.h" #include "base/memory/scoped_ptr.h" #include "base/native_library.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "webkit/plugins/npapi/plugin_list.h" using base::mac::ScopedCFTypeRef; namespace webkit { namespace npapi { namespace { const short kSTRTypeDefinitionResourceID = 128; const short kSTRTypeDescriptionResourceID = 127; const short kSTRPluginDescriptionResourceID = 126; NSDictionary* GetMIMETypes(CFBundleRef bundle) { NSString* mime_filename = (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")); if (mime_filename) { // get the file NSString* mime_path = [NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), mime_filename]; NSDictionary* mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; // is it valid? bool valid_file = false; if (mime_file_dict) { NSString* l10n_name = [mime_file_dict objectForKey:@"WebPluginLocalizationName"]; NSString* preferred_l10n = [[NSLocale currentLocale] localeIdentifier]; if ([l10n_name isEqualToString:preferred_l10n]) valid_file = true; } if (valid_file) return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; // dammit, I didn't want to have to do this typedef void (*CreateMIMETypesPrefsPtr)(void); CreateMIMETypesPrefsPtr create_prefs_file = (CreateMIMETypesPrefsPtr)CFBundleGetFunctionPointerForName( bundle, CFSTR("BP_CreatePluginMIMETypesPreferences")); if (!create_prefs_file) return nil; create_prefs_file(); // one more time mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; if (mime_file_dict) return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; else return nil; } else { return (NSDictionary*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")); } } bool ReadPlistPluginInfo(const FilePath& filename, CFBundleRef bundle, WebPluginInfo* info) { NSDictionary* mime_types = GetMIMETypes(bundle); if (!mime_types) return false; // no type info here; try elsewhere for (NSString* mime_type in [mime_types allKeys]) { NSDictionary* mime_dict = [mime_types objectForKey:mime_type]; NSString* mime_desc = [mime_dict objectForKey:@"WebPluginTypeDescription"]; NSArray* mime_exts = [mime_dict objectForKey:@"WebPluginExtensions"]; WebPluginMimeType mime; mime.mime_type = base::SysNSStringToUTF8([mime_type lowercaseString]); // Remove PDF from the list of types handled by QuickTime, since it provides // a worse experience than just downloading the PDF. if (mime.mime_type == "application/pdf" && StartsWithASCII(filename.BaseName().value(), "QuickTime", false)) { continue; } if (mime_desc) mime.description = base::SysNSStringToUTF16(mime_desc); for (NSString* ext in mime_exts) mime.file_extensions.push_back( base::SysNSStringToUTF8([ext lowercaseString])); info->mime_types.push_back(mime); } NSString* plugin_name = (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")); NSString* plugin_vers = (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); NSString* plugin_desc = (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")); if (plugin_name) info->name = base::SysNSStringToUTF16(plugin_name); else info->name = UTF8ToUTF16(filename.BaseName().value()); info->path = filename; if (plugin_vers) info->version = base::SysNSStringToUTF16(plugin_vers); if (plugin_desc) info->desc = base::SysNSStringToUTF16(plugin_desc); else info->desc = UTF8ToUTF16(filename.BaseName().value()); info->enabled = WebPluginInfo::USER_ENABLED; return true; } class ScopedBundleResourceFile { public: ScopedBundleResourceFile(CFBundleRef bundle) : bundle_(bundle) { old_ref_num_ = CurResFile(); bundle_ref_num_ = CFBundleOpenBundleResourceMap(bundle); UseResFile(bundle_ref_num_); } ~ScopedBundleResourceFile() { UseResFile(old_ref_num_); CFBundleCloseBundleResourceMap(bundle_, bundle_ref_num_); } private: CFBundleRef bundle_; CFBundleRefNum bundle_ref_num_; ResFileRefNum old_ref_num_; }; bool GetSTRResource(CFBundleRef bundle, short res_id, std::vector* contents) { Handle res_handle = Get1Resource('STR#', res_id); if (!res_handle || !*res_handle) return false; char* pointer = *res_handle; short num_strings = *(short*)pointer; pointer += sizeof(short); for (short i = 0; i < num_strings; ++i) { // Despite being 8-bits wide, these are legacy encoded. Make a round trip. ScopedCFTypeRef str(CFStringCreateWithPascalStringNoCopy( kCFAllocatorDefault, (unsigned char*)pointer, GetApplicationTextEncoding(), // is this right? kCFAllocatorNull)); // perhaps CFStringGetSystemEncoding? if (!str.get()) return false; contents->push_back(base::SysCFStringRefToUTF8(str.get())); pointer += 1+*reinterpret_cast(pointer); } return true; } bool ReadSTRPluginInfo(const FilePath& filename, CFBundleRef bundle, WebPluginInfo* info) { ScopedBundleResourceFile res_file(bundle); std::vector type_strings; if (!GetSTRResource(bundle, kSTRTypeDefinitionResourceID, &type_strings)) return false; std::vector type_descs; bool have_type_descs = GetSTRResource(bundle, kSTRTypeDescriptionResourceID, &type_descs); std::vector plugin_descs; bool have_plugin_descs = GetSTRResource(bundle, kSTRPluginDescriptionResourceID, &plugin_descs); size_t num_types = type_strings.size()/2; for (size_t i = 0; i < num_types; ++i) { WebPluginMimeType mime; mime.mime_type = StringToLowerASCII(type_strings[2*i]); if (have_type_descs && i < type_descs.size()) mime.description = UTF8ToUTF16(type_descs[i]); base::SplitString( StringToLowerASCII(type_strings[2*i+1]), ',', &mime.file_extensions); info->mime_types.push_back(mime); } NSString* plugin_vers = (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); if (have_plugin_descs && plugin_descs.size() > 1) info->name = UTF8ToUTF16(plugin_descs[1]); else info->name = UTF8ToUTF16(filename.BaseName().value()); info->path = filename; if (plugin_vers) info->version = base::SysNSStringToUTF16(plugin_vers); if (have_plugin_descs && !plugin_descs.empty()) info->desc = UTF8ToUTF16(plugin_descs[0]); else info->desc = UTF8ToUTF16(filename.BaseName().value()); info->enabled = WebPluginInfo::USER_ENABLED; return true; } } // anonymous namespace bool PluginLib::ReadWebPluginInfo(const FilePath &filename, WebPluginInfo* info) { // There are two ways to get information about plugin capabilities. One is an // Info.plist set of keys, documented at // http://developer.apple.com/documentation/InternetWeb/Conceptual/WebKit_PluginProgTopic/Concepts/AboutPlugins.html . // The other is a set of STR# resources, documented at // https://developer.mozilla.org/En/Gecko_Plugin_API_Reference/Plug-in_Development_Overview . // // Historically, the data was maintained in the STR# resources. Apple, with // the introduction of WebKit, noted the weaknesses of resources and moved the // information into the Info.plist. Mozilla had always supported a // NP_GetMIMEDescription() entry point for Unix plugins and also supports it // on the Mac to supplement the STR# format. WebKit does not support // NP_GetMIMEDescription() and neither do we. (That entry point is documented // at https://developer.mozilla.org/en/NP_GetMIMEDescription .) We prefer the // Info.plist format because it's more extensible and has a defined encoding, // but will fall back to the STR# format of the data if it is not present in // the Info.plist. // // The parsing of the data lives in the two functions ReadSTRPluginInfo() and // ReadPlistPluginInfo(), but a summary of the formats follows. // // Each data type handled by a plugin has several properties: // - <> // - <>..<> // - <> // // Each plugin may have any number of types defined. In addition, the plugin // itself has properties: // - <> // - <> // // For the Info.plist version, the data is formatted as follows (in text plist // format): // { // ... the usual plist keys ... // WebPluginDescription = <>; // WebPluginMIMETypes = { // <> = { // WebPluginExtensions = ( // <>, // ... // <>, // ); // WebPluginTypeDescription = <>; // }; // <> = { ... }; // ... // <> = { ... }; // }; // WebPluginName = <>; // } // // Alternatively (and this is undocumented), rather than a WebPluginMIMETypes // key, there may be a WebPluginMIMETypesFilename key. If it is present, then // it is the name of a file in the user's preferences folder in which to find // the WebPluginMIMETypes key. If the key is present but the file doesn't // exist, we must load the plugin and call a specific function to have the // plugin create the file. // // If we do not find those keys in the Info.plist, we fall back to the STR# // resources. In them, the data is formatted as follows: // STR# 128 // (1) <> // (2) <>,...,<> // (3) <> // (4) <>,...,<> // (...) // (2n+1) <> // (2n+2) <>,...,<> // STR# 127 // (1) <> // (2) <> // (...) // (n+1) <> // STR# 126 // (1) <> // (2) <> // // Strictly speaking, only STR# 128 is required. ScopedCFTypeRef bundle_url(CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, (const UInt8*)filename.value().c_str(), filename.value().length(), true)); if (!bundle_url) return false; ScopedCFTypeRef bundle(CFBundleCreate(kCFAllocatorDefault, bundle_url.get())); if (!bundle) return false; // preflight OSType type = 0; CFBundleGetPackageInfo(bundle.get(), &type, NULL); if (type != FOUR_CHAR_CODE('BRPL')) return false; CFErrorRef error; Boolean would_load = CFBundlePreflightExecutable(bundle.get(), &error); if (!would_load) return false; // get the info if (ReadPlistPluginInfo(filename, bundle.get(), info)) return true; if (ReadSTRPluginInfo(filename, bundle.get(), info)) return true; // ... or not return false; } } // namespace npapi } // namespace webkit