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