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/files/file_enumerator.h"
11#include "base/files/file_util.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