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