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