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