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