plugin_list.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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#include <algorithm>
8
9#include "base/command_line.h"
10#include "base/lazy_instance.h"
11#include "base/logging.h"
12#include "base/strings/string_split.h"
13#include "base/strings/string_util.h"
14#include "base/strings/sys_string_conversions.h"
15#include "base/strings/utf_string_conversions.h"
16#include "net/base/mime_util.h"
17#include "url/gurl.h"
18#include "webkit/plugins/plugin_switches.h"
19
20#if defined(OS_WIN)
21#include "content/common/plugin_constants_win.h"
22#endif
23
24namespace content {
25
26namespace {
27
28const char kApplicationOctetStream[] = "application/octet-stream";
29
30base::LazyInstance<PluginList> g_singleton = LAZY_INSTANCE_INITIALIZER;
31
32bool AllowMimeTypeMismatch(const std::string& orig_mime_type,
33                           const std::string& actual_mime_type) {
34  if (orig_mime_type == actual_mime_type) {
35    NOTREACHED();
36    return true;
37  }
38
39  // We do not permit URL-sniff based plug-in MIME type overrides aside from
40  // the case where the "type" was initially missing or generic
41  // (application/octet-stream).
42  // We collected stats to determine this approach isn't a major compat issue,
43  // and we defend against content confusion attacks in various cases, such
44  // as when the user doesn't have the Flash plug-in enabled.
45  bool allow = orig_mime_type.empty() ||
46               orig_mime_type == kApplicationOctetStream;
47  LOG_IF(INFO, !allow) << "Ignoring plugin with unexpected MIME type "
48                       << actual_mime_type << " (expected " << orig_mime_type
49                       << ")";
50  return allow;
51}
52
53}  // namespace
54
55// static
56PluginList* PluginList::Singleton() {
57  return g_singleton.Pointer();
58}
59
60// static
61bool PluginList::DebugPluginLoading() {
62  return CommandLine::ForCurrentProcess()->HasSwitch(
63      switches::kDebugPluginLoading);
64}
65
66void PluginList::DisablePluginsDiscovery() {
67  plugins_discovery_disabled_ = true;
68}
69
70void PluginList::RefreshPlugins() {
71  base::AutoLock lock(lock_);
72  loading_state_ = LOADING_STATE_NEEDS_REFRESH;
73}
74
75void PluginList::AddExtraPluginPath(const base::FilePath& plugin_path) {
76  // Chrome OS only loads plugins from /opt/google/chrome/plugins.
77#if !defined(OS_CHROMEOS)
78  base::AutoLock lock(lock_);
79  extra_plugin_paths_.push_back(plugin_path);
80#endif
81}
82
83void PluginList::RemoveExtraPluginPath(const base::FilePath& plugin_path) {
84  base::AutoLock lock(lock_);
85  std::vector<base::FilePath>::iterator it =
86      std::find(extra_plugin_paths_.begin(), extra_plugin_paths_.end(),
87                plugin_path);
88  if (it != extra_plugin_paths_.end())
89    extra_plugin_paths_.erase(it);
90}
91
92void PluginList::AddExtraPluginDir(const base::FilePath& plugin_dir) {
93  // Chrome OS only loads plugins from /opt/google/chrome/plugins.
94#if !defined(OS_CHROMEOS)
95  base::AutoLock lock(lock_);
96  extra_plugin_dirs_.push_back(plugin_dir);
97#endif
98}
99
100void PluginList::RegisterInternalPlugin(const WebPluginInfo& info,
101                                        bool add_at_beginning) {
102  base::AutoLock lock(lock_);
103
104  internal_plugins_.push_back(info);
105  if (add_at_beginning) {
106    // Newer registrations go earlier in the list so they can override the MIME
107    // types of older registrations.
108    extra_plugin_paths_.insert(extra_plugin_paths_.begin(), info.path);
109  } else {
110    extra_plugin_paths_.push_back(info.path);
111  }
112}
113
114void PluginList::UnregisterInternalPlugin(const base::FilePath& path) {
115  base::AutoLock lock(lock_);
116  for (size_t i = 0; i < internal_plugins_.size(); i++) {
117    if (internal_plugins_[i].path == path) {
118      internal_plugins_.erase(internal_plugins_.begin() + i);
119      return;
120    }
121  }
122  NOTREACHED();
123}
124
125void PluginList::GetInternalPlugins(
126    std::vector<WebPluginInfo>* internal_plugins) {
127  base::AutoLock lock(lock_);
128
129  for (std::vector<WebPluginInfo>::iterator it = internal_plugins_.begin();
130       it != internal_plugins_.end();
131       ++it) {
132    internal_plugins->push_back(*it);
133  }
134}
135
136bool PluginList::ReadPluginInfo(const base::FilePath& filename,
137                                WebPluginInfo* info) {
138  {
139    base::AutoLock lock(lock_);
140    for (size_t i = 0; i < internal_plugins_.size(); ++i) {
141      if (filename == internal_plugins_[i].path) {
142        *info = internal_plugins_[i];
143        return true;
144      }
145    }
146  }
147
148  return PluginList::ReadWebPluginInfo(filename, info);
149}
150
151// static
152bool PluginList::ParseMimeTypes(
153    const std::string& mime_types_str,
154    const std::string& file_extensions_str,
155    const base::string16& mime_type_descriptions_str,
156    std::vector<WebPluginMimeType>* parsed_mime_types) {
157  std::vector<std::string> mime_types, file_extensions;
158  std::vector<base::string16> descriptions;
159  base::SplitString(mime_types_str, '|', &mime_types);
160  base::SplitString(file_extensions_str, '|', &file_extensions);
161  base::SplitString(mime_type_descriptions_str, '|', &descriptions);
162
163  parsed_mime_types->clear();
164
165  if (mime_types.empty())
166    return false;
167
168  for (size_t i = 0; i < mime_types.size(); ++i) {
169    WebPluginMimeType mime_type;
170    mime_type.mime_type = StringToLowerASCII(mime_types[i]);
171    if (file_extensions.size() > i)
172      base::SplitString(file_extensions[i], ',', &mime_type.file_extensions);
173
174    if (descriptions.size() > i) {
175      mime_type.description = descriptions[i];
176
177      // On Windows, the description likely has a list of file extensions
178      // embedded in it (e.g. "SurfWriter file (*.swr)"). Remove an extension
179      // list from the description if it is present.
180      size_t ext = mime_type.description.find(ASCIIToUTF16("(*"));
181      if (ext != base::string16::npos) {
182        if (ext > 1 && mime_type.description[ext - 1] == ' ')
183          ext--;
184
185        mime_type.description.erase(ext);
186      }
187    }
188
189    parsed_mime_types->push_back(mime_type);
190  }
191
192  return true;
193}
194
195PluginList::PluginList()
196    : loading_state_(LOADING_STATE_NEEDS_REFRESH),
197      plugins_discovery_disabled_(false) {
198}
199
200void PluginList::LoadPlugins(bool include_npapi) {
201  {
202    base::AutoLock lock(lock_);
203    if (loading_state_ == LOADING_STATE_UP_TO_DATE)
204      return;
205
206    loading_state_ = LOADING_STATE_REFRESHING;
207  }
208
209  std::vector<WebPluginInfo> new_plugins;
210  base::Closure will_load_callback;
211  {
212    base::AutoLock lock(lock_);
213    will_load_callback = will_load_plugins_callback_;
214  }
215  if (!will_load_callback.is_null())
216    will_load_callback.Run();
217
218  std::vector<base::FilePath> plugin_paths;
219  GetPluginPathsToLoad(&plugin_paths, include_npapi);
220
221  for (std::vector<base::FilePath>::const_iterator it = plugin_paths.begin();
222       it != plugin_paths.end();
223       ++it) {
224    WebPluginInfo plugin_info;
225    LoadPluginIntoPluginList(*it, &new_plugins, &plugin_info);
226  }
227
228  base::AutoLock lock(lock_);
229  plugins_list_.swap(new_plugins);
230
231  // If we haven't been invalidated in the mean time, mark the plug-in list as
232  // up-to-date.
233  if (loading_state_ != LOADING_STATE_NEEDS_REFRESH)
234    loading_state_ = LOADING_STATE_UP_TO_DATE;
235}
236
237bool PluginList::LoadPluginIntoPluginList(
238    const base::FilePath& path,
239    std::vector<WebPluginInfo>* plugins,
240    WebPluginInfo* plugin_info) {
241  LOG_IF(ERROR, PluginList::DebugPluginLoading())
242      << "Loading plugin " << path.value();
243  if (!ReadPluginInfo(path, plugin_info))
244    return false;
245
246  if (!ShouldLoadPluginUsingPluginList(*plugin_info, plugins))
247    return false;
248
249#if defined(OS_WIN) && !defined(NDEBUG)
250  if (path.BaseName().value() != L"npspy.dll")  // Make an exception for NPSPY
251#endif
252  {
253    for (size_t i = 0; i < plugin_info->mime_types.size(); ++i) {
254      // TODO: don't load global handlers for now.
255      // WebKit hands to the Plugin before it tries
256      // to handle mimeTypes on its own.
257      const std::string &mime_type = plugin_info->mime_types[i].mime_type;
258      if (mime_type == "*")
259        return false;
260    }
261  }
262  plugins->push_back(*plugin_info);
263  return true;
264}
265
266void PluginList::GetPluginPathsToLoad(std::vector<base::FilePath>* plugin_paths,
267                                      bool include_npapi) {
268  // Don't want to hold the lock while loading new plugins, so we don't block
269  // other methods if they're called on other threads.
270  std::vector<base::FilePath> extra_plugin_paths;
271  std::vector<base::FilePath> extra_plugin_dirs;
272  {
273    base::AutoLock lock(lock_);
274    extra_plugin_paths = extra_plugin_paths_;
275    extra_plugin_dirs = extra_plugin_dirs_;
276  }
277
278  for (size_t i = 0; i < extra_plugin_paths.size(); ++i) {
279    const base::FilePath& path = extra_plugin_paths[i];
280    if (std::find(plugin_paths->begin(), plugin_paths->end(), path) !=
281        plugin_paths->end()) {
282      continue;
283    }
284    plugin_paths->push_back(path);
285  }
286
287  if (include_npapi) {
288    // A bit confusingly, this function is used to load Pepper plugins as well.
289    // Those are all internal plugins so we have to use extra_plugin_paths.
290    for (size_t i = 0; i < extra_plugin_dirs.size(); ++i)
291      GetPluginsInDir(extra_plugin_dirs[i], plugin_paths);
292
293    std::vector<base::FilePath> directories_to_scan;
294    GetPluginDirectories(&directories_to_scan);
295    for (size_t i = 0; i < directories_to_scan.size(); ++i)
296      GetPluginsInDir(directories_to_scan[i], plugin_paths);
297
298#if defined(OS_WIN)
299    GetPluginPathsFromRegistry(plugin_paths);
300#endif
301  }
302}
303
304void PluginList::SetPlugins(const std::vector<WebPluginInfo>& plugins) {
305  base::AutoLock lock(lock_);
306
307  DCHECK_NE(LOADING_STATE_REFRESHING, loading_state_);
308  loading_state_ = LOADING_STATE_UP_TO_DATE;
309
310  plugins_list_.clear();
311  plugins_list_.insert(plugins_list_.end(), plugins.begin(), plugins.end());
312}
313
314void PluginList::set_will_load_plugins_callback(const base::Closure& callback) {
315  base::AutoLock lock(lock_);
316  will_load_plugins_callback_ = callback;
317}
318
319void PluginList::GetPlugins(std::vector<WebPluginInfo>* plugins,
320                            bool include_npapi) {
321  LoadPlugins(include_npapi);
322  base::AutoLock lock(lock_);
323  plugins->insert(plugins->end(), plugins_list_.begin(), plugins_list_.end());
324}
325
326bool PluginList::GetPluginsNoRefresh(std::vector<WebPluginInfo>* plugins) {
327  base::AutoLock lock(lock_);
328  plugins->insert(plugins->end(), plugins_list_.begin(), plugins_list_.end());
329
330  return loading_state_ == LOADING_STATE_UP_TO_DATE;
331}
332
333void PluginList::GetPluginInfoArray(
334    const GURL& url,
335    const std::string& mime_type,
336    bool allow_wildcard,
337    bool* use_stale,
338    bool include_npapi,
339    std::vector<WebPluginInfo>* info,
340    std::vector<std::string>* actual_mime_types) {
341  DCHECK(mime_type == StringToLowerASCII(mime_type));
342  DCHECK(info);
343
344  if (!use_stale)
345    LoadPlugins(include_npapi);
346  base::AutoLock lock(lock_);
347  if (use_stale)
348    *use_stale = (loading_state_ != LOADING_STATE_UP_TO_DATE);
349  info->clear();
350  if (actual_mime_types)
351    actual_mime_types->clear();
352
353  std::set<base::FilePath> visited_plugins;
354
355  // Add in plugins by mime type.
356  for (size_t i = 0; i < plugins_list_.size(); ++i) {
357    if (SupportsType(plugins_list_[i], mime_type, allow_wildcard)) {
358      base::FilePath path = plugins_list_[i].path;
359      if (visited_plugins.insert(path).second) {
360        info->push_back(plugins_list_[i]);
361        if (actual_mime_types)
362          actual_mime_types->push_back(mime_type);
363      }
364    }
365  }
366
367  // Add in plugins by url.
368  std::string path = url.path();
369  std::string::size_type last_dot = path.rfind('.');
370  if (last_dot != std::string::npos) {
371    std::string extension = StringToLowerASCII(std::string(path, last_dot+1));
372    std::string actual_mime_type;
373    for (size_t i = 0; i < plugins_list_.size(); ++i) {
374      if (SupportsExtension(plugins_list_[i], extension, &actual_mime_type)) {
375        base::FilePath path = plugins_list_[i].path;
376        if (visited_plugins.insert(path).second &&
377            AllowMimeTypeMismatch(mime_type, actual_mime_type)) {
378          info->push_back(plugins_list_[i]);
379          if (actual_mime_types)
380            actual_mime_types->push_back(actual_mime_type);
381        }
382      }
383    }
384  }
385}
386
387bool PluginList::SupportsType(const WebPluginInfo& plugin,
388                              const std::string& mime_type,
389                              bool allow_wildcard) {
390  // Webkit will ask for a plugin to handle empty mime types.
391  if (mime_type.empty())
392    return false;
393
394  for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
395    const WebPluginMimeType& mime_info = plugin.mime_types[i];
396    if (net::MatchesMimeType(mime_info.mime_type, mime_type)) {
397      if (!allow_wildcard && mime_info.mime_type == "*")
398        continue;
399      return true;
400    }
401  }
402  return false;
403}
404
405bool PluginList::SupportsExtension(const WebPluginInfo& plugin,
406                                   const std::string& extension,
407                                   std::string* actual_mime_type) {
408  for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
409    const WebPluginMimeType& mime_type = plugin.mime_types[i];
410    for (size_t j = 0; j < mime_type.file_extensions.size(); ++j) {
411      if (mime_type.file_extensions[j] == extension) {
412        if (actual_mime_type)
413          *actual_mime_type = mime_type.mime_type;
414        return true;
415      }
416    }
417  }
418  return false;
419}
420
421PluginList::~PluginList() {
422}
423
424
425}  // namespace content
426