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