plugin_info_message_filter.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 "chrome/browser/plugins/plugin_info_message_filter.h"
6
7#include "base/bind.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/metrics/histogram.h"
10#include "base/prefs/pref_service.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/content_settings/content_settings_utils.h"
13#include "chrome/browser/content_settings/host_content_settings_map.h"
14#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
15#include "chrome/browser/plugins/plugin_finder.h"
16#include "chrome/browser/plugins/plugin_metadata.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/chrome_content_client.h"
19#include "chrome/common/content_settings.h"
20#include "chrome/common/pref_names.h"
21#include "chrome/common/render_messages.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/browser/plugin_service.h"
24#include "content/public/browser/plugin_service_filter.h"
25#include "url/gurl.h"
26
27#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
28
29#if defined(OS_WIN)
30#include "base/win/metro.h"
31#endif
32
33using content::PluginService;
34using content::WebPluginInfo;
35
36namespace {
37
38// For certain sandboxed Pepper plugins, use the JavaScript Content Settings.
39bool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) {
40  if (plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
41      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS) {
42    return false;
43  }
44
45  // Treat Native Client invocations like JavaScript.
46  if (plugin.name == ASCIIToUTF16(chrome::ChromeContentClient::kNaClPluginName))
47    return true;
48
49#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
50  // Treat CDM invocations like JavaScript.
51  if (plugin.name == ASCIIToUTF16(kWidevineCdmDisplayName)) {
52    DCHECK(plugin.type == WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS);
53    return true;
54  }
55#endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
56
57  return false;
58}
59
60}  // namespace
61
62PluginInfoMessageFilter::Context::Context(int render_process_id,
63                                          Profile* profile)
64    : render_process_id_(render_process_id),
65      resource_context_(profile->GetResourceContext()),
66      host_content_settings_map_(profile->GetHostContentSettingsMap()) {
67  allow_outdated_plugins_.Init(prefs::kPluginsAllowOutdated,
68                               profile->GetPrefs());
69  allow_outdated_plugins_.MoveToThread(
70      content::BrowserThread::GetMessageLoopProxyForThread(
71          content::BrowserThread::IO));
72  always_authorize_plugins_.Init(prefs::kPluginsAlwaysAuthorize,
73                                 profile->GetPrefs());
74  always_authorize_plugins_.MoveToThread(
75      content::BrowserThread::GetMessageLoopProxyForThread(
76          content::BrowserThread::IO));
77}
78
79PluginInfoMessageFilter::Context::Context()
80    : render_process_id_(0),
81      resource_context_(NULL),
82      host_content_settings_map_(NULL) {
83}
84
85PluginInfoMessageFilter::Context::~Context() {
86}
87
88PluginInfoMessageFilter::PluginInfoMessageFilter(
89    int render_process_id,
90    Profile* profile)
91    : context_(render_process_id, profile),
92      weak_ptr_factory_(this) {
93}
94
95bool PluginInfoMessageFilter::OnMessageReceived(const IPC::Message& message,
96                                                bool* message_was_ok) {
97  IPC_BEGIN_MESSAGE_MAP_EX(PluginInfoMessageFilter, message, *message_was_ok)
98    IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_GetPluginInfo,
99                                    OnGetPluginInfo)
100    IPC_MESSAGE_UNHANDLED(return false)
101  IPC_END_MESSAGE_MAP()
102  return true;
103}
104
105void PluginInfoMessageFilter::OnDestruct() const {
106  const_cast<PluginInfoMessageFilter*>(this)->
107      weak_ptr_factory_.InvalidateWeakPtrs();
108
109  // Destroy on the UI thread because we contain a |PrefMember|.
110  content::BrowserThread::DeleteOnUIThread::Destruct(this);
111}
112
113PluginInfoMessageFilter::~PluginInfoMessageFilter() {}
114
115struct PluginInfoMessageFilter::GetPluginInfo_Params {
116  int render_view_id;
117  GURL url;
118  GURL top_origin_url;
119  std::string mime_type;
120};
121
122void PluginInfoMessageFilter::OnGetPluginInfo(
123    int render_view_id,
124    const GURL& url,
125    const GURL& top_origin_url,
126    const std::string& mime_type,
127    IPC::Message* reply_msg) {
128  GetPluginInfo_Params params = {
129    render_view_id,
130    url,
131    top_origin_url,
132    mime_type
133  };
134  PluginService::GetInstance()->GetPlugins(
135      base::Bind(&PluginInfoMessageFilter::PluginsLoaded,
136                 weak_ptr_factory_.GetWeakPtr(),
137                 params, reply_msg));
138}
139
140void PluginInfoMessageFilter::PluginsLoaded(
141    const GetPluginInfo_Params& params,
142    IPC::Message* reply_msg,
143    const std::vector<WebPluginInfo>& plugins) {
144  ChromeViewHostMsg_GetPluginInfo_Output output;
145  // This also fills in |actual_mime_type|.
146  scoped_ptr<PluginMetadata> plugin_metadata;
147  if (context_.FindEnabledPlugin(params.render_view_id, params.url,
148                                 params.top_origin_url, params.mime_type,
149                                 &output.status, &output.plugin,
150                                 &output.actual_mime_type,
151                                 &plugin_metadata)) {
152    context_.DecidePluginStatus(params, output.plugin, plugin_metadata.get(),
153                                &output.status);
154  }
155
156  if (plugin_metadata) {
157    output.group_identifier = plugin_metadata->identifier();
158    output.group_name = plugin_metadata->name();
159  }
160
161  context_.MaybeGrantAccess(output.status, output.plugin.path);
162
163  ChromeViewHostMsg_GetPluginInfo::WriteReplyParams(reply_msg, output);
164  Send(reply_msg);
165}
166
167void PluginInfoMessageFilter::Context::DecidePluginStatus(
168    const GetPluginInfo_Params& params,
169    const WebPluginInfo& plugin,
170    const PluginMetadata* plugin_metadata,
171    ChromeViewHostMsg_GetPluginInfo_Status* status) const {
172#if defined(OS_WIN)
173  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI &&
174      base::win::IsMetroProcess()) {
175    status->value =
176        ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
177    return;
178  }
179#endif
180
181  ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT;
182  bool uses_default_content_setting = true;
183  // Check plug-in content settings. The primary URL is the top origin URL and
184  // the secondary URL is the plug-in URL.
185  GetPluginContentSetting(plugin, params.top_origin_url, params.url,
186                          plugin_metadata->identifier(), &plugin_setting,
187                          &uses_default_content_setting);
188  DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT);
189
190  PluginMetadata::SecurityStatus plugin_status =
191      plugin_metadata->GetSecurityStatus(plugin);
192#if defined(ENABLE_PLUGIN_INSTALLATION)
193  // Check if the plug-in is outdated.
194  if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE &&
195      !allow_outdated_plugins_.GetValue()) {
196    if (allow_outdated_plugins_.IsManaged()) {
197      status->value =
198          ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed;
199    } else {
200      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked;
201    }
202    return;
203  }
204#endif
205
206  // Check if the plug-in requires authorization.
207  if (plugin_status ==
208          PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION &&
209      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
210      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS &&
211      !always_authorize_plugins_.GetValue() &&
212      plugin_setting != CONTENT_SETTING_BLOCK &&
213      uses_default_content_setting) {
214    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
215    return;
216  }
217
218  // Check if the plug-in is crashing too much.
219  if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) &&
220      !always_authorize_plugins_.GetValue() &&
221      plugin_setting != CONTENT_SETTING_BLOCK &&
222      uses_default_content_setting) {
223    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
224    return;
225  }
226
227  if (plugin_setting == CONTENT_SETTING_ASK)
228    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay;
229  else if (plugin_setting == CONTENT_SETTING_BLOCK)
230    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
231}
232
233bool PluginInfoMessageFilter::Context::FindEnabledPlugin(
234    int render_view_id,
235    const GURL& url,
236    const GURL& top_origin_url,
237    const std::string& mime_type,
238    ChromeViewHostMsg_GetPluginInfo_Status* status,
239    WebPluginInfo* plugin,
240    std::string* actual_mime_type,
241    scoped_ptr<PluginMetadata>* plugin_metadata) const {
242  bool allow_wildcard = true;
243  std::vector<WebPluginInfo> matching_plugins;
244  std::vector<std::string> mime_types;
245  PluginService::GetInstance()->GetPluginInfoArray(
246      url, mime_type, allow_wildcard, &matching_plugins, &mime_types);
247  if (matching_plugins.empty()) {
248    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound;
249    return false;
250  }
251
252  content::PluginServiceFilter* filter =
253      PluginService::GetInstance()->GetFilter();
254  size_t i = 0;
255  for (; i < matching_plugins.size(); ++i) {
256    if (!filter || filter->IsPluginAvailable(render_process_id_,
257                                             render_view_id,
258                                             resource_context_,
259                                             url,
260                                             top_origin_url,
261                                             &matching_plugins[i])) {
262      break;
263    }
264  }
265
266  // If we broke out of the loop, we have found an enabled plug-in.
267  bool enabled = i < matching_plugins.size();
268  if (!enabled) {
269    // Otherwise, we only found disabled plug-ins, so we take the first one.
270    i = 0;
271    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled;
272  }
273
274  *plugin = matching_plugins[i];
275  *actual_mime_type = mime_types[i];
276  if (plugin_metadata)
277    *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin);
278
279  return enabled;
280}
281
282void PluginInfoMessageFilter::Context::GetPluginContentSetting(
283    const WebPluginInfo& plugin,
284    const GURL& policy_url,
285    const GURL& plugin_url,
286    const std::string& resource,
287    ContentSetting* setting,
288    bool* uses_default_content_setting) const {
289
290  scoped_ptr<base::Value> value;
291  content_settings::SettingInfo info;
292  bool uses_plugin_specific_setting = false;
293  if (ShouldUseJavaScriptSettingForPlugin(plugin)) {
294    value.reset(
295        host_content_settings_map_->GetWebsiteSetting(
296            policy_url, policy_url, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
297            std::string(), &info));
298  } else {
299    value.reset(
300        host_content_settings_map_->GetWebsiteSetting(
301            policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, resource,
302            &info));
303    if (value.get()) {
304      uses_plugin_specific_setting = true;
305    } else {
306      value.reset(host_content_settings_map_->GetWebsiteSetting(
307          policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, std::string(),
308          &info));
309    }
310  }
311  *setting = content_settings::ValueToContentSetting(value.get());
312  *uses_default_content_setting =
313      !uses_plugin_specific_setting &&
314      info.primary_pattern == ContentSettingsPattern::Wildcard() &&
315      info.secondary_pattern == ContentSettingsPattern::Wildcard();
316}
317
318void PluginInfoMessageFilter::Context::MaybeGrantAccess(
319    const ChromeViewHostMsg_GetPluginInfo_Status& status,
320    const base::FilePath& path) const {
321  if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed ||
322      status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) {
323    ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
324        render_process_id_, path);
325  }
326}
327
328