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 22 using base::ScopedCFTypeRef; 23 24 namespace content { 25 26 namespace { 27 28 void 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. 45 bool 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, ASCIIToUTF16("Flip4Mac Windows Media"), false) && 60 StartsWith(info.version, ASCIIToUTF16("2.3"), false)) { 61 std::vector<base::string16> components; 62 base::SplitString(info.version, '.', &components); 63 int bugfix_version = 0; 64 return (components.size() >= 3 && 65 base::StringToInt(components[2], &bugfix_version) && 66 bugfix_version < 6); 67 } 68 69 return false; 70 } 71 72 NSDictionary* GetMIMETypes(CFBundleRef bundle) { 73 NSString* mime_filename = 74 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 75 CFSTR("WebPluginMIMETypesFilename")); 76 77 if (mime_filename) { 78 79 // get the file 80 81 NSString* mime_path = 82 [NSString stringWithFormat:@"%@/Library/Preferences/%@", 83 NSHomeDirectory(), mime_filename]; 84 NSDictionary* mime_file_dict = 85 [NSDictionary dictionaryWithContentsOfFile:mime_path]; 86 87 // is it valid? 88 89 bool valid_file = false; 90 if (mime_file_dict) { 91 NSString* l10n_name = 92 [mime_file_dict objectForKey:@"WebPluginLocalizationName"]; 93 NSString* preferred_l10n = [[NSLocale currentLocale] localeIdentifier]; 94 if ([l10n_name isEqualToString:preferred_l10n]) 95 valid_file = true; 96 } 97 98 if (valid_file) 99 return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; 100 101 // dammit, I didn't want to have to do this 102 103 typedef void (*CreateMIMETypesPrefsPtr)(void); 104 CreateMIMETypesPrefsPtr create_prefs_file = 105 (CreateMIMETypesPrefsPtr)CFBundleGetFunctionPointerForName( 106 bundle, CFSTR("BP_CreatePluginMIMETypesPreferences")); 107 if (!create_prefs_file) 108 return nil; 109 create_prefs_file(); 110 111 // one more time 112 113 mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; 114 if (mime_file_dict) 115 return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; 116 else 117 return nil; 118 119 } else { 120 return (NSDictionary*)CFBundleGetValueForInfoDictionaryKey(bundle, 121 CFSTR("WebPluginMIMETypes")); 122 } 123 } 124 125 bool ReadPlistPluginInfo(const base::FilePath& filename, CFBundleRef bundle, 126 WebPluginInfo* info) { 127 NSDictionary* mime_types = GetMIMETypes(bundle); 128 if (!mime_types) 129 return false; // no type info here; try elsewhere 130 131 for (NSString* mime_type in [mime_types allKeys]) { 132 NSDictionary* mime_dict = [mime_types objectForKey:mime_type]; 133 NSNumber* type_enabled = [mime_dict objectForKey:@"WebPluginTypeEnabled"]; 134 NSString* mime_desc = [mime_dict objectForKey:@"WebPluginTypeDescription"]; 135 NSArray* mime_exts = [mime_dict objectForKey:@"WebPluginExtensions"]; 136 137 // Skip any disabled types. 138 if (type_enabled && ![type_enabled boolValue]) 139 continue; 140 141 WebPluginMimeType mime; 142 mime.mime_type = base::SysNSStringToUTF8([mime_type lowercaseString]); 143 // Remove PDF from the list of types handled by QuickTime, since it provides 144 // a worse experience than just downloading the PDF. 145 if (mime.mime_type == "application/pdf" && 146 StartsWithASCII(filename.BaseName().value(), "QuickTime", false)) { 147 continue; 148 } 149 150 if (mime_desc) 151 mime.description = base::SysNSStringToUTF16(mime_desc); 152 for (NSString* ext in mime_exts) 153 mime.file_extensions.push_back( 154 base::SysNSStringToUTF8([ext lowercaseString])); 155 156 info->mime_types.push_back(mime); 157 } 158 159 NSString* plugin_name = 160 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 161 CFSTR("WebPluginName")); 162 NSString* plugin_vers = 163 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 164 CFSTR("CFBundleShortVersionString")); 165 NSString* plugin_desc = 166 (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, 167 CFSTR("WebPluginDescription")); 168 169 if (plugin_name) 170 info->name = base::SysNSStringToUTF16(plugin_name); 171 else 172 info->name = UTF8ToUTF16(filename.BaseName().value()); 173 info->path = filename; 174 if (plugin_vers) 175 info->version = base::SysNSStringToUTF16(plugin_vers); 176 if (plugin_desc) 177 info->desc = base::SysNSStringToUTF16(plugin_desc); 178 else 179 info->desc = UTF8ToUTF16(filename.BaseName().value()); 180 181 return true; 182 } 183 184 } // namespace 185 186 bool PluginList::ReadWebPluginInfo(const base::FilePath &filename, 187 WebPluginInfo* info) { 188 // There are three ways to get information about plugin capabilities: 189 // 1) a set of Info.plist keys, documented at 190 // http://developer.apple.com/documentation/InternetWeb/Conceptual/WebKit_PluginProgTopic/Concepts/AboutPlugins.html . 191 // 2) a set of STR# resources, documented at 192 // https://developer.mozilla.org/En/Gecko_Plugin_API_Reference/Plug-in_Development_Overview . 193 // 3) a NP_GetMIMEDescription() entry point, documented at 194 // https://developer.mozilla.org/en/NP_GetMIMEDescription 195 // 196 // Mozilla supported (3), but WebKit never has, so no plugins rely on it. Most 197 // browsers supported (2) and then added support for (1); Chromium originally 198 // supported (2) and (1), but now supports only (1) as (2) is deprecated. 199 // 200 // For the Info.plist version, the data is formatted as follows (in text plist 201 // format): 202 // { 203 // ... the usual plist keys ... 204 // WebPluginDescription = <<plugindescription>>; 205 // WebPluginMIMETypes = { 206 // <<type0mimetype>> = { 207 // WebPluginExtensions = ( 208 // <<type0fileextension0>>, 209 // ... 210 // <<type0fileextensionk>>, 211 // ); 212 // WebPluginTypeDescription = <<type0description>>; 213 // }; 214 // <<type1mimetype>> = { ... }; 215 // ... 216 // <<typenmimetype>> = { ... }; 217 // }; 218 // WebPluginName = <<pluginname>>; 219 // } 220 // 221 // Alternatively (and this is undocumented), rather than a WebPluginMIMETypes 222 // key, there may be a WebPluginMIMETypesFilename key. If it is present, then 223 // it is the name of a file in the user's preferences folder in which to find 224 // the WebPluginMIMETypes key. If the key is present but the file doesn't 225 // exist, we must load the plugin and call a specific function to have the 226 // plugin create the file. 227 228 ScopedCFTypeRef<CFURLRef> bundle_url(CFURLCreateFromFileSystemRepresentation( 229 kCFAllocatorDefault, (const UInt8*)filename.value().c_str(), 230 filename.value().length(), true)); 231 if (!bundle_url) { 232 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 233 << "PluginLib::ReadWebPluginInfo could not create bundle URL"; 234 return false; 235 } 236 ScopedCFTypeRef<CFBundleRef> bundle(CFBundleCreate(kCFAllocatorDefault, 237 bundle_url.get())); 238 if (!bundle) { 239 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 240 << "PluginLib::ReadWebPluginInfo could not create CFBundleRef"; 241 return false; 242 } 243 244 // preflight 245 246 OSType type = 0; 247 CFBundleGetPackageInfo(bundle.get(), &type, NULL); 248 if (type != FOUR_CHAR_CODE('BRPL')) { 249 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 250 << "PluginLib::ReadWebPluginInfo bundle is not BRPL, is " << type; 251 return false; 252 } 253 254 CFErrorRef error; 255 Boolean would_load = CFBundlePreflightExecutable(bundle.get(), &error); 256 if (!would_load) { 257 ScopedCFTypeRef<CFStringRef> error_string(CFErrorCopyDescription(error)); 258 LOG_IF(ERROR, PluginList::DebugPluginLoading()) 259 << "PluginLib::ReadWebPluginInfo bundle failed preflight: " 260 << base::SysCFStringRefToUTF8(error_string); 261 return false; 262 } 263 264 // get the info 265 266 if (ReadPlistPluginInfo(filename, bundle.get(), info)) 267 return true; 268 269 // ... or not 270 271 return false; 272 } 273 274 void PluginList::GetPluginDirectories( 275 std::vector<base::FilePath>* plugin_dirs) { 276 if (PluginList::plugins_discovery_disabled_) 277 return; 278 279 // Load from the user's area 280 GetPluginCommonDirectory(plugin_dirs, true); 281 282 // Load from the machine-wide area 283 GetPluginCommonDirectory(plugin_dirs, false); 284 } 285 286 void PluginList::GetPluginsInDir( 287 const base::FilePath& path, std::vector<base::FilePath>* plugins) { 288 base::FileEnumerator enumerator(path, 289 false, // not recursive 290 base::FileEnumerator::DIRECTORIES); 291 for (base::FilePath path = enumerator.Next(); !path.value().empty(); 292 path = enumerator.Next()) { 293 plugins->push_back(path); 294 } 295 } 296 297 bool PluginList::ShouldLoadPluginUsingPluginList( 298 const WebPluginInfo& info, 299 std::vector<WebPluginInfo>* plugins) { 300 return !IsBlacklistedPlugin(info); 301 } 302 303 } // namespace content 304