plugin_info_message_filter.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
147be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org// Copyright (c) 2012 The Chromium Authors. All rights reserved.
247be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org// Use of this source code is governed by a BSD-style license that can be
347be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org// found in the LICENSE file.
447be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
547be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/plugins/plugin_info_message_filter.h"
647be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
747be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/bind.h"
847be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/memory/scoped_ptr.h"
947be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/metrics/histogram.h"
1047be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/prefs/pref_service.h"
1147be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/strings/utf_string_conversions.h"
1247be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/content_settings/content_settings_utils.h"
1347be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/content_settings/host_content_settings_map.h"
1447be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
1547be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/plugins/plugin_finder.h"
1647be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/plugins/plugin_metadata.h"
1747be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/plugins/plugin_prefs.h"
1847be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/profiles/profile.h"
1947be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/common/chrome_content_client.h"
2047be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/common/content_settings.h"
2147be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/common/pref_names.h"
2247be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/common/render_messages.h"
2347be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "content/public/browser/browser_thread.h"
2447be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "content/public/browser/plugin_service.h"
2547be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "content/public/browser/plugin_service_filter.h"
2647be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "url/gurl.h"
2747be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
2847be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
2947be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
3047be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#if defined(ENABLE_EXTENSIONS)
3147be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "chrome/browser/guest_view/web_view/web_view_renderer_state.h"
3247be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#endif
3347be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
3447be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#if defined(OS_WIN)
3547be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#include "base/win/metro.h"
3647be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org#endif
3747be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
3847be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.orgusing content::PluginService;
3947be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.orgusing content::WebPluginInfo;
4047be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
4147be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.orgnamespace {
4247be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org
4347be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org// For certain sandboxed Pepper plugins, use the JavaScript Content Settings.
4447be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.orgbool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) {
4547be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org  if (plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
4647be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS) {
4747be73b8629244d6bb63a28198f97f040ce53d21henrike@webrtc.org    return false;
48  }
49
50  // Treat Native Client invocations like JavaScript.
51  if (plugin.name == base::ASCIIToUTF16(ChromeContentClient::kNaClPluginName))
52    return true;
53
54#if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
55  // Treat CDM invocations like JavaScript.
56  if (plugin.name == base::ASCIIToUTF16(kWidevineCdmDisplayName)) {
57    DCHECK(plugin.type == WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS);
58    return true;
59  }
60#endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
61
62  return false;
63}
64
65#if defined(ENABLE_PEPPER_CDMS)
66
67enum PluginAvailabilityStatusForUMA {
68  PLUGIN_NOT_REGISTERED,
69  PLUGIN_AVAILABLE,
70  PLUGIN_DISABLED,
71  PLUGIN_AVAILABILITY_STATUS_MAX
72};
73
74static void SendPluginAvailabilityUMA(const std::string& mime_type,
75                                      PluginAvailabilityStatusForUMA status) {
76#if defined(WIDEVINE_CDM_AVAILABLE)
77  // Only report results for Widevine CDM.
78  if (mime_type != kWidevineCdmPluginMimeType)
79    return;
80
81  UMA_HISTOGRAM_ENUMERATION("Plugin.AvailabilityStatus.WidevineCdm",
82                            status, PLUGIN_AVAILABILITY_STATUS_MAX);
83#endif  // defined(WIDEVINE_CDM_AVAILABLE)
84}
85
86#endif  // defined(ENABLE_PEPPER_CDMS)
87
88}  // namespace
89
90PluginInfoMessageFilter::Context::Context(int render_process_id,
91                                          Profile* profile)
92    : render_process_id_(render_process_id),
93      resource_context_(profile->GetResourceContext()),
94      host_content_settings_map_(profile->GetHostContentSettingsMap()),
95      plugin_prefs_(PluginPrefs::GetForProfile(profile)) {
96  allow_outdated_plugins_.Init(prefs::kPluginsAllowOutdated,
97                               profile->GetPrefs());
98  allow_outdated_plugins_.MoveToThread(
99      content::BrowserThread::GetMessageLoopProxyForThread(
100          content::BrowserThread::IO));
101  always_authorize_plugins_.Init(prefs::kPluginsAlwaysAuthorize,
102                                 profile->GetPrefs());
103  always_authorize_plugins_.MoveToThread(
104      content::BrowserThread::GetMessageLoopProxyForThread(
105          content::BrowserThread::IO));
106}
107
108PluginInfoMessageFilter::Context::~Context() {
109}
110
111PluginInfoMessageFilter::PluginInfoMessageFilter(
112    int render_process_id,
113    Profile* profile)
114    : BrowserMessageFilter(ChromeMsgStart),
115      context_(render_process_id, profile),
116      weak_ptr_factory_(this) {
117}
118
119bool PluginInfoMessageFilter::OnMessageReceived(const IPC::Message& message) {
120  IPC_BEGIN_MESSAGE_MAP(PluginInfoMessageFilter, message)
121    IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_GetPluginInfo,
122                                    OnGetPluginInfo)
123#if defined(ENABLE_PEPPER_CDMS)
124    IPC_MESSAGE_HANDLER(
125        ChromeViewHostMsg_IsInternalPluginAvailableForMimeType,
126        OnIsInternalPluginAvailableForMimeType)
127#endif
128    IPC_MESSAGE_UNHANDLED(return false)
129  IPC_END_MESSAGE_MAP()
130  return true;
131}
132
133void PluginInfoMessageFilter::OnDestruct() const {
134  const_cast<PluginInfoMessageFilter*>(this)->
135      weak_ptr_factory_.InvalidateWeakPtrs();
136
137  // Destroy on the UI thread because we contain a |PrefMember|.
138  content::BrowserThread::DeleteOnUIThread::Destruct(this);
139}
140
141PluginInfoMessageFilter::~PluginInfoMessageFilter() {}
142
143struct PluginInfoMessageFilter::GetPluginInfo_Params {
144  int render_frame_id;
145  GURL url;
146  GURL top_origin_url;
147  std::string mime_type;
148};
149
150void PluginInfoMessageFilter::OnGetPluginInfo(
151    int render_frame_id,
152    const GURL& url,
153    const GURL& top_origin_url,
154    const std::string& mime_type,
155    IPC::Message* reply_msg) {
156  GetPluginInfo_Params params = {
157    render_frame_id,
158    url,
159    top_origin_url,
160    mime_type
161  };
162  PluginService::GetInstance()->GetPlugins(
163      base::Bind(&PluginInfoMessageFilter::PluginsLoaded,
164                 weak_ptr_factory_.GetWeakPtr(),
165                 params, reply_msg));
166}
167
168void PluginInfoMessageFilter::PluginsLoaded(
169    const GetPluginInfo_Params& params,
170    IPC::Message* reply_msg,
171    const std::vector<WebPluginInfo>& plugins) {
172  ChromeViewHostMsg_GetPluginInfo_Output output;
173  // This also fills in |actual_mime_type|.
174  scoped_ptr<PluginMetadata> plugin_metadata;
175  if (context_.FindEnabledPlugin(params.render_frame_id, params.url,
176                                 params.top_origin_url, params.mime_type,
177                                 &output.status, &output.plugin,
178                                 &output.actual_mime_type,
179                                 &plugin_metadata)) {
180    context_.DecidePluginStatus(params, output.plugin, plugin_metadata.get(),
181                                &output.status);
182  }
183
184  if (plugin_metadata) {
185    output.group_identifier = plugin_metadata->identifier();
186    output.group_name = plugin_metadata->name();
187  }
188
189  context_.MaybeGrantAccess(output.status, output.plugin.path);
190
191  ChromeViewHostMsg_GetPluginInfo::WriteReplyParams(reply_msg, output);
192  Send(reply_msg);
193}
194
195#if defined(ENABLE_PEPPER_CDMS)
196
197void PluginInfoMessageFilter::OnIsInternalPluginAvailableForMimeType(
198    const std::string& mime_type,
199    bool* is_available,
200    std::vector<base::string16>* additional_param_names,
201    std::vector<base::string16>* additional_param_values) {
202  std::vector<WebPluginInfo> plugins;
203  PluginService::GetInstance()->GetInternalPlugins(&plugins);
204
205  bool is_plugin_disabled = false;
206  for (size_t i = 0; i < plugins.size(); ++i) {
207    const WebPluginInfo& plugin = plugins[i];
208    const std::vector<content::WebPluginMimeType>& mime_types =
209        plugin.mime_types;
210    for (size_t j = 0; j < mime_types.size(); ++j) {
211      if (mime_types[j].mime_type == mime_type) {
212        if (!context_.IsPluginEnabled(plugin)) {
213          is_plugin_disabled = true;
214          break;
215        }
216
217        *is_available = true;
218        *additional_param_names = mime_types[j].additional_param_names;
219        *additional_param_values = mime_types[j].additional_param_values;
220        SendPluginAvailabilityUMA(mime_type, PLUGIN_AVAILABLE);
221        return;
222      }
223    }
224  }
225
226  *is_available = false;
227  SendPluginAvailabilityUMA(
228      mime_type, is_plugin_disabled ? PLUGIN_DISABLED : PLUGIN_NOT_REGISTERED);
229}
230
231#endif // defined(ENABLE_PEPPER_CDMS)
232
233void PluginInfoMessageFilter::Context::DecidePluginStatus(
234    const GetPluginInfo_Params& params,
235    const WebPluginInfo& plugin,
236    const PluginMetadata* plugin_metadata,
237    ChromeViewHostMsg_GetPluginInfo_Status* status) const {
238#if defined(OS_WIN)
239  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI &&
240      base::win::IsMetroProcess()) {
241    status->value =
242        ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
243    return;
244  }
245#endif
246  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI) {
247    CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
248    // NPAPI plugins are not supported inside <webview> guests.
249#if defined(ENABLE_EXTENSIONS)
250    if (extensions::WebViewRendererState::GetInstance()->IsGuest(
251        render_process_id_)) {
252      status->value =
253          ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
254      return;
255    }
256#endif
257  }
258
259  ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT;
260  bool uses_default_content_setting = true;
261  bool is_managed = false;
262  // Check plug-in content settings. The primary URL is the top origin URL and
263  // the secondary URL is the plug-in URL.
264  GetPluginContentSetting(plugin, params.top_origin_url, params.url,
265                          plugin_metadata->identifier(), &plugin_setting,
266                          &uses_default_content_setting, &is_managed);
267  DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT);
268
269  PluginMetadata::SecurityStatus plugin_status =
270      plugin_metadata->GetSecurityStatus(plugin);
271#if defined(ENABLE_PLUGIN_INSTALLATION)
272  // Check if the plug-in is outdated.
273  if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE &&
274      !allow_outdated_plugins_.GetValue()) {
275    if (allow_outdated_plugins_.IsManaged()) {
276      status->value =
277          ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed;
278    } else {
279      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked;
280    }
281    return;
282  }
283#endif
284  // Check if the plug-in or its group is enabled by policy.
285  PluginPrefs::PolicyStatus plugin_policy =
286      plugin_prefs_->PolicyStatusForPlugin(plugin.name);
287  PluginPrefs::PolicyStatus group_policy =
288      plugin_prefs_->PolicyStatusForPlugin(plugin_metadata->name());
289
290  // Check if the plug-in requires authorization.
291  if (plugin_status ==
292          PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION &&
293      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
294      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS &&
295      !always_authorize_plugins_.GetValue() &&
296      plugin_setting != CONTENT_SETTING_BLOCK &&
297      uses_default_content_setting &&
298      plugin_policy != PluginPrefs::POLICY_ENABLED &&
299      group_policy != PluginPrefs::POLICY_ENABLED &&
300      !ChromePluginServiceFilter::GetInstance()->IsPluginRestricted(
301          plugin.path)) {
302    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
303    return;
304  }
305
306  // Check if the plug-in is crashing too much.
307  if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) &&
308      !always_authorize_plugins_.GetValue() &&
309      plugin_setting != CONTENT_SETTING_BLOCK &&
310      uses_default_content_setting) {
311    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
312    return;
313  }
314
315  if (plugin_setting == CONTENT_SETTING_ASK) {
316      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay;
317  } else if (plugin_setting == CONTENT_SETTING_BLOCK) {
318    status->value =
319        is_managed ? ChromeViewHostMsg_GetPluginInfo_Status::kBlockedByPolicy
320                   : ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
321  }
322
323  if (status->value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed) {
324    // Allow an embedder of <webview> to block a plugin from being loaded inside
325    // the guest. In order to do this, set the status to 'Unauthorized' here,
326    // and update the status as appropriate depending on the response from the
327    // embedder.
328#if defined(ENABLE_EXTENSIONS)
329    if (extensions::WebViewRendererState::GetInstance()->IsGuest(
330        render_process_id_))
331      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
332
333#endif
334  }
335}
336
337bool PluginInfoMessageFilter::Context::FindEnabledPlugin(
338    int render_frame_id,
339    const GURL& url,
340    const GURL& top_origin_url,
341    const std::string& mime_type,
342    ChromeViewHostMsg_GetPluginInfo_Status* status,
343    WebPluginInfo* plugin,
344    std::string* actual_mime_type,
345    scoped_ptr<PluginMetadata>* plugin_metadata) const {
346  bool allow_wildcard = true;
347  std::vector<WebPluginInfo> matching_plugins;
348  std::vector<std::string> mime_types;
349  PluginService::GetInstance()->GetPluginInfoArray(
350      url, mime_type, allow_wildcard, &matching_plugins, &mime_types);
351  if (matching_plugins.empty()) {
352    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound;
353    return false;
354  }
355
356  content::PluginServiceFilter* filter =
357      PluginService::GetInstance()->GetFilter();
358  size_t i = 0;
359  for (; i < matching_plugins.size(); ++i) {
360    if (!filter || filter->IsPluginAvailable(render_process_id_,
361                                             render_frame_id,
362                                             resource_context_,
363                                             url,
364                                             top_origin_url,
365                                             &matching_plugins[i])) {
366      break;
367    }
368  }
369
370  // If we broke out of the loop, we have found an enabled plug-in.
371  bool enabled = i < matching_plugins.size();
372  if (!enabled) {
373    // Otherwise, we only found disabled plug-ins, so we take the first one.
374    i = 0;
375    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled;
376  }
377
378  *plugin = matching_plugins[i];
379  *actual_mime_type = mime_types[i];
380  if (plugin_metadata)
381    *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin);
382
383  return enabled;
384}
385
386void PluginInfoMessageFilter::Context::GetPluginContentSetting(
387    const WebPluginInfo& plugin,
388    const GURL& policy_url,
389    const GURL& plugin_url,
390    const std::string& resource,
391    ContentSetting* setting,
392    bool* uses_default_content_setting,
393    bool* is_managed) const {
394  scoped_ptr<base::Value> value;
395  content_settings::SettingInfo info;
396  bool uses_plugin_specific_setting = false;
397  if (ShouldUseJavaScriptSettingForPlugin(plugin)) {
398    value.reset(
399        host_content_settings_map_->GetWebsiteSetting(
400            policy_url, policy_url, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
401            std::string(), &info));
402  } else {
403    content_settings::SettingInfo specific_info;
404    scoped_ptr<base::Value> specific_setting(
405        host_content_settings_map_->GetWebsiteSetting(
406            policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, resource,
407            &specific_info));
408    content_settings::SettingInfo general_info;
409    scoped_ptr<base::Value> general_setting(
410        host_content_settings_map_->GetWebsiteSetting(
411            policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS,
412            std::string(), &general_info));
413
414    // If there is a plugin-specific setting, we use it, unless the general
415    // setting was set by policy, in which case it takes precedence.
416    uses_plugin_specific_setting = specific_setting &&
417        (general_info.source != content_settings::SETTING_SOURCE_POLICY);
418    if (uses_plugin_specific_setting) {
419      value = specific_setting.Pass();
420      info = specific_info;
421    } else {
422      value = general_setting.Pass();
423      info = general_info;
424    }
425  }
426  *setting = content_settings::ValueToContentSetting(value.get());
427  *uses_default_content_setting =
428      !uses_plugin_specific_setting &&
429      info.primary_pattern == ContentSettingsPattern::Wildcard() &&
430      info.secondary_pattern == ContentSettingsPattern::Wildcard();
431  *is_managed = info.source == content_settings::SETTING_SOURCE_POLICY;
432}
433
434void PluginInfoMessageFilter::Context::MaybeGrantAccess(
435    const ChromeViewHostMsg_GetPluginInfo_Status& status,
436    const base::FilePath& path) const {
437  if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed ||
438      status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) {
439    ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
440        render_process_id_, path);
441  }
442}
443
444bool PluginInfoMessageFilter::Context::IsPluginEnabled(
445    const content::WebPluginInfo& plugin) const {
446  return plugin_prefs_->IsPluginEnabled(plugin);
447}
448