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