1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/common/plugin_list.h" 6 7#import <Carbon/Carbon.h> 8#import <Foundation/Foundation.h> 9 10#include "base/file_util.h" 11#include "base/files/file_enumerator.h" 12#include "base/mac/mac_util.h" 13#include "base/mac/scoped_cftyperef.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/native_library.h" 16#include "base/strings/string_number_conversions.h" 17#include "base/strings/string_split.h" 18#include "base/strings/string_util.h" 19#include "base/strings/sys_string_conversions.h" 20#include "base/strings/utf_string_conversions.h" 21 22using base::ScopedCFTypeRef; 23 24namespace content { 25 26namespace { 27 28void GetPluginCommonDirectory(std::vector<base::FilePath>* plugin_dirs, 29 bool user) { 30 // Note that there are no NSSearchPathDirectory constants for these 31 // directories so we can't use Cocoa's NSSearchPathForDirectoriesInDomains(). 32 // Interestingly, Safari hard-codes the location (see 33 // WebKit/WebKit/mac/Plugins/WebPluginDatabase.mm's +_defaultPlugInPaths). 34 FSRef ref; 35 OSErr err = FSFindFolder(user ? kUserDomain : kLocalDomain, 36 kInternetPlugInFolderType, false, &ref); 37 38 if (err) 39 return; 40 41 plugin_dirs->push_back(base::FilePath(base::mac::PathFromFSRef(ref))); 42} 43 44// Returns true if the plugin should be prevented from loading. 45bool IsBlacklistedPlugin(const WebPluginInfo& info) { 46 // We blacklist Gears by included MIME type, since that is more stable than 47 // its name. Be careful about adding any more plugins to this list though, 48 // since it's easy to accidentally blacklist plugins that support lots of 49 // MIME types. 50 for (std::vector<WebPluginMimeType>::const_iterator i = 51 info.mime_types.begin(); i != info.mime_types.end(); ++i) { 52 // The Gears plugin is Safari-specific, so don't load it. 53 if (i->mime_type == "application/x-googlegears") 54 return true; 55 } 56 57 // Versions of Flip4Mac 2.3 before 2.3.6 often hang the renderer, so don't 58 // load them. 59 if (StartsWith(info.name, 60 base::ASCIIToUTF16("Flip4Mac Windows Media"), false) && 61 StartsWith(info.version, base::ASCIIToUTF16("2.3"), false)) { 62 std::vector<base::string16> components; 63 base::SplitString(info.version, '.', &components); 64 int bugfix_version = 0; 65 return (components.size() >= 3 && 66 base::StringToInt(components[2], &bugfix_version) && 67 bugfix_version < 6); 68 } 69 70 return false; 71} 72 73NSDictionary* GetMIMETypes(CFBundleRef bundle) { 74 NSString* mime_filename = 75 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 76 CFSTR("WebPluginMIMETypesFilename")); 77 78 if (mime_filename) { 79 80 // get the file 81 82 NSString* mime_path = 83 [NSString stringWithFormat:@"%@/Library/Preferences/%@", 84 NSHomeDirectory(), mime_filename]; 85 NSDictionary* mime_file_dict = 86 [NSDictionary dictionaryWithContentsOfFile:mime_path]; 87 88 // is it valid? 89 90 bool valid_file = false; 91 if (mime_file_dict) { 92 NSString* l10n_name = 93 [mime_file_dict objectForKey:@"WebPluginLocalizationName"]; 94 NSString* preferred_l10n = [[NSLocale currentLocale] localeIdentifier]; 95 if ([l10n_name isEqualToString:preferred_l10n]) 96 valid_file = true; 97 } 98 99 if (valid_file) 100 return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; 101 102 // dammit, I didn't want to have to do this 103 104 typedef void (*CreateMIMETypesPrefsPtr)(void); 105 CreateMIMETypesPrefsPtr create_prefs_file = 106 (CreateMIMETypesPrefsPtr)CFBundleGetFunctionPointerForName( 107 bundle, CFSTR("BP_CreatePluginMIMETypesPreferences")); 108 if (!create_prefs_file) 109 return nil; 110 create_prefs_file(); 111 112 // one more time 113 114 mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; 115 if (mime_file_dict) 116 return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; 117 else 118 return nil; 119 120 } else { 121 return (NSDictionary*)CFBundleGetValueForInfoDictionaryKey(bundle, 122 CFSTR("WebPluginMIMETypes")); 123 } 124} 125 126bool ReadPlistPluginInfo(const base::FilePath& filename, CFBundleRef bundle, 127 WebPluginInfo* info) { 128 NSDictionary* mime_types = GetMIMETypes(bundle); 129 if (!mime_types) 130 return false; // no type info here; try elsewhere 131 132 for (NSString* mime_type in [mime_types allKeys]) { 133 NSDictionary* mime_dict = [mime_types objectForKey:mime_type]; 134 NSNumber* type_enabled = [mime_dict objectForKey:@"WebPluginTypeEnabled"]; 135 NSString* mime_desc = [mime_dict objectForKey:@"WebPluginTypeDescription"]; 136 NSArray* mime_exts = [mime_dict objectForKey:@"WebPluginExtensions"]; 137 138 // Skip any disabled types. 139 if (type_enabled && ![type_enabled boolValue]) 140 continue; 141 142 WebPluginMimeType mime; 143 mime.mime_type = base::SysNSStringToUTF8([mime_type lowercaseString]); 144 // Remove PDF from the list of types handled by QuickTime, since it provides 145 // a worse experience than just downloading the PDF. 146 if (mime.mime_type == "application/pdf" && 147 StartsWithASCII(filename.BaseName().value(), "QuickTime", false)) { 148 continue; 149 } 150 151 if (mime_desc) 152 mime.description = base::SysNSStringToUTF16(mime_desc); 153 for (NSString* ext in mime_exts) 154 mime.file_extensions.push_back( 155 base::SysNSStringToUTF8([ext lowercaseString])); 156 157 info->mime_types.push_back(mime); 158 } 159 160 NSString* plugin_name = 161 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 162 CFSTR("WebPluginName")); 163 NSString* plugin_vers = 164 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 165 CFSTR("CFBundleShortVersionString")); 166 NSString* plugin_desc = 167 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 168 CFSTR("WebPluginDescription")); 169 170 if (plugin_name) 171 info->name = base::SysNSStringToUTF16(plugin_name); 172 else 173 info->name = base::UTF8ToUTF16(filename.BaseName().value()); 174 info->path = filename; 175 if (plugin_vers) 176 info->version = base::SysNSStringToUTF16(plugin_vers); 177 if (plugin_desc) 178 info->desc = base::SysNSStringToUTF16(plugin_desc); 179 else 180 info->desc = base::UTF8ToUTF16(filename.BaseName().value()); 181 182 return true; 183} 184 185} // namespace 186 187bool PluginList::ReadWebPluginInfo(const base::FilePath &filename, 188 WebPluginInfo* info) { 189 // There are three ways to get information about plugin capabilities: 190 // 1) a set of Info.plist keys, documented at 191 // http://developer.apple.com/documentation/InternetWeb/Conceptual/WebKit_PluginProgTopic/Concepts/AboutPlugins.html . 192 // 2) a set of STR# resources, documented at 193 // https://developer.mozilla.org/En/Gecko_Plugin_API_Reference/Plug-in_Development_Overview . 194 // 3) a NP_GetMIMEDescription() entry point, documented at 195 // https://developer.mozilla.org/en/NP_GetMIMEDescription 196 // 197 // Mozilla supported (3), but WebKit never has, so no plugins rely on it. Most 198 // browsers supported (2) and then added support for (1); Chromium originally 199 // supported (2) and (1), but now supports only (1) as (2) is deprecated. 200 // 201 // For the Info.plist version, the data is formatted as follows (in text plist 202 // format): 203 // { 204 // ... the usual plist keys ... 205 // WebPluginDescription = <<plugindescription>>; 206 // WebPluginMIMETypes = { 207 // <<type0mimetype>> = { 208 // WebPluginExtensions = ( 209 // <<type0fileextension0>>, 210 // ... 211 // <<type0fileextensionk>>, 212 // ); 213 // WebPluginTypeDescription = <<type0description>>; 214 // }; 215 // <<type1mimetype>> = { ... }; 216 // ... 217 // <<typenmimetype>> = { ... }; 218 // }; 219 // WebPluginName = <<pluginname>>; 220 // } 221 // 222 // Alternatively (and this is undocumented), rather than a WebPluginMIMETypes 223 // key, there may be a WebPluginMIMETypesFilename key. If it is present, then 224 // it is the name of a file in the user's preferences folder in which to find 225 // the WebPluginMIMETypes key. If the key is present but the file doesn't 226 // exist, we must load the plugin and call a specific function to have the 227 // plugin create the file. 228 229 ScopedCFTypeRef<CFURLRef> bundle_url(CFURLCreateFromFileSystemRepresentation( 230 kCFAllocatorDefault, (const UInt8*)filename.value().c_str(), 231 filename.value().length(), true)); 232 if (!bundle_url) { 233 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 234 << "PluginLib::ReadWebPluginInfo could not create bundle URL"; 235 return false; 236 } 237 ScopedCFTypeRef<CFBundleRef> bundle(CFBundleCreate(kCFAllocatorDefault, 238 bundle_url.get())); 239 if (!bundle) { 240 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 241 << "PluginLib::ReadWebPluginInfo could not create CFBundleRef"; 242 return false; 243 } 244 245 // preflight 246 247 OSType type = 0; 248 CFBundleGetPackageInfo(bundle.get(), &type, NULL); 249 if (type != FOUR_CHAR_CODE('BRPL')) { 250 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 251 << "PluginLib::ReadWebPluginInfo bundle is not BRPL, is " << type; 252 return false; 253 } 254 255 CFErrorRef error; 256 Boolean would_load = CFBundlePreflightExecutable(bundle.get(), &error); 257 if (!would_load) { 258 ScopedCFTypeRef<CFStringRef> error_string(CFErrorCopyDescription(error)); 259 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 260 << "PluginLib::ReadWebPluginInfo bundle failed preflight: " 261 << base::SysCFStringRefToUTF8(error_string); 262 return false; 263 } 264 265 // get the info 266 267 if (ReadPlistPluginInfo(filename, bundle.get(), info)) 268 return true; 269 270 // ... or not 271 272 return false; 273} 274 275void PluginList::GetPluginDirectories( 276 std::vector<base::FilePath>* plugin_dirs) { 277 if (PluginList::plugins_discovery_disabled_) 278 return; 279 280 // Load from the user's area 281 GetPluginCommonDirectory(plugin_dirs, true); 282 283 // Load from the machine-wide area 284 GetPluginCommonDirectory(plugin_dirs, false); 285} 286 287void PluginList::GetPluginsInDir( 288 const base::FilePath& path, std::vector<base::FilePath>* plugins) { 289 base::FileEnumerator enumerator(path, 290 false, // not recursive 291 base::FileEnumerator::DIRECTORIES); 292 for (base::FilePath path = enumerator.Next(); !path.value().empty(); 293 path = enumerator.Next()) { 294 plugins->push_back(path); 295 } 296} 297 298bool PluginList::ShouldLoadPluginUsingPluginList( 299 const WebPluginInfo& info, 300 std::vector<WebPluginInfo>* plugins) { 301 return !IsBlacklistedPlugin(info); 302} 303 304} // namespace content 305