Home | History | Annotate | Download | only in plugins
      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 
     35 using content::PluginService;
     36 using content::WebPluginInfo;
     37 
     38 namespace {
     39 
     40 // For certain sandboxed Pepper plugins, use the JavaScript Content Settings.
     41 bool 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 
     64 PluginInfoMessageFilter::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 
     82 PluginInfoMessageFilter::Context::Context()
     83     : render_process_id_(0),
     84       resource_context_(NULL),
     85       host_content_settings_map_(NULL) {
     86 }
     87 
     88 PluginInfoMessageFilter::Context::~Context() {
     89 }
     90 
     91 PluginInfoMessageFilter::PluginInfoMessageFilter(
     92     int render_process_id,
     93     Profile* profile)
     94     : context_(render_process_id, profile),
     95       weak_ptr_factory_(this) {
     96 }
     97 
     98 bool 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 
    111 void 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 
    119 PluginInfoMessageFilter::~PluginInfoMessageFilter() {}
    120 
    121 struct PluginInfoMessageFilter::GetPluginInfo_Params {
    122   int render_frame_id;
    123   GURL url;
    124   GURL top_origin_url;
    125   std::string mime_type;
    126 };
    127 
    128 void 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 
    146 void 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 
    173 void 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 
    196 void 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 
    292 bool 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 
    341 void 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 
    376 void 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