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