plugin_info_message_filter.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 "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(kWidevineCdmDisplayName)) {
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_(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_.InvalidateWeakPtrs();
109
110  // Destroy on the UI thread because we contain a |PrefMember|.
111  content::BrowserThread::DeleteOnUIThread::Destruct(this);
112}
113
114PluginInfoMessageFilter::~PluginInfoMessageFilter() {}
115
116struct PluginInfoMessageFilter::GetPluginInfo_Params {
117  int render_view_id;
118  GURL url;
119  GURL top_origin_url;
120  std::string mime_type;
121};
122
123void PluginInfoMessageFilter::OnGetPluginInfo(
124    int render_view_id,
125    const GURL& url,
126    const GURL& top_origin_url,
127    const std::string& mime_type,
128    IPC::Message* reply_msg) {
129  GetPluginInfo_Params params = {
130    render_view_id,
131    url,
132    top_origin_url,
133    mime_type
134  };
135  PluginService::GetInstance()->GetPlugins(
136      base::Bind(&PluginInfoMessageFilter::PluginsLoaded,
137                 weak_ptr_factory_.GetWeakPtr(),
138                 params, reply_msg));
139}
140
141void PluginInfoMessageFilter::PluginsLoaded(
142    const GetPluginInfo_Params& params,
143    IPC::Message* reply_msg,
144    const std::vector<WebPluginInfo>& plugins) {
145  ChromeViewHostMsg_GetPluginInfo_Output output;
146  // This also fills in |actual_mime_type|.
147  scoped_ptr<PluginMetadata> plugin_metadata;
148  if (context_.FindEnabledPlugin(params.render_view_id, params.url,
149                                 params.top_origin_url, params.mime_type,
150                                 &output.status, &output.plugin,
151                                 &output.actual_mime_type,
152                                 &plugin_metadata)) {
153    context_.DecidePluginStatus(params, output.plugin, plugin_metadata.get(),
154                                &output.status);
155  }
156
157  if (plugin_metadata) {
158    output.group_identifier = plugin_metadata->identifier();
159    output.group_name = plugin_metadata->name();
160  }
161
162  context_.MaybeGrantAccess(output.status, output.plugin.path);
163
164  ChromeViewHostMsg_GetPluginInfo::WriteReplyParams(reply_msg, output);
165  Send(reply_msg);
166}
167
168void PluginInfoMessageFilter::Context::DecidePluginStatus(
169    const GetPluginInfo_Params& params,
170    const WebPluginInfo& plugin,
171    const PluginMetadata* plugin_metadata,
172    ChromeViewHostMsg_GetPluginInfo_Status* status) const {
173#if defined(OS_WIN)
174  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI &&
175      base::win::IsMetroProcess()) {
176    status->value =
177        ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
178    return;
179  }
180#endif
181
182  ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT;
183  bool uses_default_content_setting = true;
184  // Check plug-in content settings. The primary URL is the top origin URL and
185  // the secondary URL is the plug-in URL.
186  GetPluginContentSetting(plugin, params.top_origin_url, params.url,
187                          plugin_metadata->identifier(), &plugin_setting,
188                          &uses_default_content_setting);
189  DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT);
190
191  PluginMetadata::SecurityStatus plugin_status =
192      plugin_metadata->GetSecurityStatus(plugin);
193#if defined(ENABLE_PLUGIN_INSTALLATION)
194  // Check if the plug-in is outdated.
195  if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE &&
196      !allow_outdated_plugins_.GetValue()) {
197    if (allow_outdated_plugins_.IsManaged()) {
198      status->value =
199          ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed;
200    } else {
201      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked;
202    }
203    return;
204  }
205#endif
206
207  // Check if the plug-in requires authorization.
208  if (plugin_status ==
209          PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION &&
210      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
211      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS &&
212      !always_authorize_plugins_.GetValue() &&
213      plugin_setting != CONTENT_SETTING_BLOCK &&
214      uses_default_content_setting) {
215    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
216    return;
217  }
218
219  // Check if the plug-in is crashing too much.
220  if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) &&
221      !always_authorize_plugins_.GetValue() &&
222      plugin_setting != CONTENT_SETTING_BLOCK &&
223      uses_default_content_setting) {
224    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
225    return;
226  }
227
228  if (plugin_setting == CONTENT_SETTING_ASK)
229    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay;
230  else if (plugin_setting == CONTENT_SETTING_BLOCK)
231    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
232}
233
234bool PluginInfoMessageFilter::Context::FindEnabledPlugin(
235    int render_view_id,
236    const GURL& url,
237    const GURL& top_origin_url,
238    const std::string& mime_type,
239    ChromeViewHostMsg_GetPluginInfo_Status* status,
240    WebPluginInfo* plugin,
241    std::string* actual_mime_type,
242    scoped_ptr<PluginMetadata>* plugin_metadata) const {
243  bool allow_wildcard = true;
244  std::vector<WebPluginInfo> matching_plugins;
245  std::vector<std::string> mime_types;
246  PluginService::GetInstance()->GetPluginInfoArray(
247      url, mime_type, allow_wildcard, &matching_plugins, &mime_types);
248  if (matching_plugins.empty()) {
249    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound;
250    return false;
251  }
252
253  content::PluginServiceFilter* filter =
254      PluginService::GetInstance()->GetFilter();
255  size_t i = 0;
256  for (; i < matching_plugins.size(); ++i) {
257    if (!filter || filter->IsPluginAvailable(render_process_id_,
258                                             render_view_id,
259                                             resource_context_,
260                                             url,
261                                             top_origin_url,
262                                             &matching_plugins[i])) {
263      break;
264    }
265  }
266
267  // If we broke out of the loop, we have found an enabled plug-in.
268  bool enabled = i < matching_plugins.size();
269  if (!enabled) {
270    // Otherwise, we only found disabled plug-ins, so we take the first one.
271    i = 0;
272    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled;
273  }
274
275  *plugin = matching_plugins[i];
276  *actual_mime_type = mime_types[i];
277  if (plugin_metadata)
278    *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin);
279
280  return enabled;
281}
282
283void PluginInfoMessageFilter::Context::GetPluginContentSetting(
284    const WebPluginInfo& plugin,
285    const GURL& policy_url,
286    const GURL& plugin_url,
287    const std::string& resource,
288    ContentSetting* setting,
289    bool* uses_default_content_setting) const {
290
291  scoped_ptr<base::Value> value;
292  content_settings::SettingInfo info;
293  bool uses_plugin_specific_setting = false;
294  if (ShouldUseJavaScriptSettingForPlugin(plugin)) {
295    value.reset(
296        host_content_settings_map_->GetWebsiteSetting(
297            policy_url, policy_url, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
298            std::string(), &info));
299  } else {
300    value.reset(
301        host_content_settings_map_->GetWebsiteSetting(
302            policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, resource,
303            &info));
304    if (value.get()) {
305      uses_plugin_specific_setting = true;
306    } else {
307      value.reset(host_content_settings_map_->GetWebsiteSetting(
308          policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, std::string(),
309          &info));
310    }
311  }
312  *setting = content_settings::ValueToContentSetting(value.get());
313  *uses_default_content_setting =
314      !uses_plugin_specific_setting &&
315      info.primary_pattern == ContentSettingsPattern::Wildcard() &&
316      info.secondary_pattern == ContentSettingsPattern::Wildcard();
317}
318
319void PluginInfoMessageFilter::Context::MaybeGrantAccess(
320    const ChromeViewHostMsg_GetPluginInfo_Status& status,
321    const base::FilePath& path) const {
322  if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed ||
323      status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) {
324    ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
325        render_process_id_, path);
326  }
327}
328
329