plugin_info_message_filter.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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<WebPluginInfo> plugins;
178   PluginService::GetInstance()->GetInternalPlugins(&plugins);
179   for (size_t i = 0; i < plugins.size(); ++i) {
180     const std::vector<content::WebPluginMimeType>& mime_types =
181         plugins[i].mime_types;
182     for (size_t j = 0; j < mime_types.size(); ++j) {
183       if (mime_types[j].mime_type == mime_type) {
184         *is_registered = true;
185         return;
186       }
187     }
188   }
189
190   *is_registered = false;
191}
192
193void PluginInfoMessageFilter::Context::DecidePluginStatus(
194    const GetPluginInfo_Params& params,
195    const WebPluginInfo& plugin,
196    const PluginMetadata* plugin_metadata,
197    ChromeViewHostMsg_GetPluginInfo_Status* status) const {
198#if defined(OS_WIN)
199  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI &&
200      base::win::IsMetroProcess()) {
201    status->value =
202        ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
203    return;
204  }
205#endif
206  if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI) {
207    CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
208    // NPAPI plugins are not supported inside <webview> guests.
209    ExtensionRendererState::WebViewInfo info;
210    if (ExtensionRendererState::GetInstance()->GetWebViewInfo(
211            render_process_id_, params.render_view_id, &info)) {
212      status->value =
213          ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
214      return;
215    }
216  }
217
218  ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT;
219  bool uses_default_content_setting = true;
220  // Check plug-in content settings. The primary URL is the top origin URL and
221  // the secondary URL is the plug-in URL.
222  GetPluginContentSetting(plugin, params.top_origin_url, params.url,
223                          plugin_metadata->identifier(), &plugin_setting,
224                          &uses_default_content_setting);
225  DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT);
226
227  PluginMetadata::SecurityStatus plugin_status =
228      plugin_metadata->GetSecurityStatus(plugin);
229#if defined(ENABLE_PLUGIN_INSTALLATION)
230  // Check if the plug-in is outdated.
231  if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE &&
232      !allow_outdated_plugins_.GetValue()) {
233    if (allow_outdated_plugins_.IsManaged()) {
234      status->value =
235          ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed;
236    } else {
237      status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked;
238    }
239    return;
240  }
241#endif
242  // Check if the plug-in or its group is enabled by policy.
243  PluginPrefs::PolicyStatus plugin_policy =
244      plugin_prefs_->PolicyStatusForPlugin(plugin.name);
245  PluginPrefs::PolicyStatus group_policy =
246      plugin_prefs_->PolicyStatusForPlugin(plugin_metadata->name());
247
248  // Check if the plug-in requires authorization.
249  if (plugin_status ==
250          PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION &&
251      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
252      plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS &&
253      !always_authorize_plugins_.GetValue() &&
254      plugin_setting != CONTENT_SETTING_BLOCK &&
255      uses_default_content_setting &&
256      plugin_policy != PluginPrefs::POLICY_ENABLED &&
257      group_policy != PluginPrefs::POLICY_ENABLED) {
258    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
259    return;
260  }
261
262  // Check if the plug-in is crashing too much.
263  if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) &&
264      !always_authorize_plugins_.GetValue() &&
265      plugin_setting != CONTENT_SETTING_BLOCK &&
266      uses_default_content_setting) {
267    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
268    return;
269  }
270
271  if (plugin_setting == CONTENT_SETTING_ASK)
272    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay;
273  else if (plugin_setting == CONTENT_SETTING_BLOCK)
274    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
275
276  if (status->value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed) {
277    // Allow an embedder of <webview> to block a plugin from being loaded inside
278    // the guest. In order to do this, set the status to 'Unauthorized' here,
279    // and update the status as appropriate depending on the response from the
280    // embedder.
281    ExtensionRendererState::WebViewInfo info;
282    if (ExtensionRendererState::GetInstance()->GetWebViewInfo(
283            render_process_id_, params.render_view_id, &info)) {
284      status->value =
285          ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
286    }
287  }
288}
289
290bool PluginInfoMessageFilter::Context::FindEnabledPlugin(
291    int render_view_id,
292    const GURL& url,
293    const GURL& top_origin_url,
294    const std::string& mime_type,
295    ChromeViewHostMsg_GetPluginInfo_Status* status,
296    WebPluginInfo* plugin,
297    std::string* actual_mime_type,
298    scoped_ptr<PluginMetadata>* plugin_metadata) const {
299  bool allow_wildcard = true;
300  std::vector<WebPluginInfo> matching_plugins;
301  std::vector<std::string> mime_types;
302  PluginService::GetInstance()->GetPluginInfoArray(
303      url, mime_type, allow_wildcard, &matching_plugins, &mime_types);
304  if (matching_plugins.empty()) {
305    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound;
306    return false;
307  }
308
309  content::PluginServiceFilter* filter =
310      PluginService::GetInstance()->GetFilter();
311  size_t i = 0;
312  for (; i < matching_plugins.size(); ++i) {
313    if (!filter || filter->IsPluginAvailable(render_process_id_,
314                                             render_view_id,
315                                             resource_context_,
316                                             url,
317                                             top_origin_url,
318                                             &matching_plugins[i])) {
319      break;
320    }
321  }
322
323  // If we broke out of the loop, we have found an enabled plug-in.
324  bool enabled = i < matching_plugins.size();
325  if (!enabled) {
326    // Otherwise, we only found disabled plug-ins, so we take the first one.
327    i = 0;
328    status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled;
329  }
330
331  *plugin = matching_plugins[i];
332  *actual_mime_type = mime_types[i];
333  if (plugin_metadata)
334    *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin);
335
336  return enabled;
337}
338
339void PluginInfoMessageFilter::Context::GetPluginContentSetting(
340    const WebPluginInfo& plugin,
341    const GURL& policy_url,
342    const GURL& plugin_url,
343    const std::string& resource,
344    ContentSetting* setting,
345    bool* uses_default_content_setting) const {
346  scoped_ptr<base::Value> value;
347  content_settings::SettingInfo info;
348  bool uses_plugin_specific_setting = false;
349  if (ShouldUseJavaScriptSettingForPlugin(plugin)) {
350    value.reset(
351        host_content_settings_map_->GetWebsiteSetting(
352            policy_url, policy_url, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
353            std::string(), &info));
354  } else {
355    value.reset(
356        host_content_settings_map_->GetWebsiteSetting(
357            policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, resource,
358            &info));
359    if (value.get()) {
360      uses_plugin_specific_setting = true;
361    } else {
362      value.reset(host_content_settings_map_->GetWebsiteSetting(
363          policy_url, plugin_url, CONTENT_SETTINGS_TYPE_PLUGINS, std::string(),
364          &info));
365    }
366  }
367  *setting = content_settings::ValueToContentSetting(value.get());
368  *uses_default_content_setting =
369      !uses_plugin_specific_setting &&
370      info.primary_pattern == ContentSettingsPattern::Wildcard() &&
371      info.secondary_pattern == ContentSettingsPattern::Wildcard();
372}
373
374void PluginInfoMessageFilter::Context::MaybeGrantAccess(
375    const ChromeViewHostMsg_GetPluginInfo_Status& status,
376    const base::FilePath& path) const {
377  if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed ||
378      status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) {
379    ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
380        render_process_id_, path);
381  }
382}
383
384