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