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_prefs.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/command_line.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/path_service.h" 14 #include "base/prefs/scoped_user_pref_update.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/values.h" 18 #include "build/build_config.h" 19 #include "chrome/browser/browser_process.h" 20 #include "chrome/browser/chrome_notification_types.h" 21 #include "chrome/browser/plugins/plugin_installer.h" 22 #include "chrome/browser/plugins/plugin_metadata.h" 23 #include "chrome/browser/plugins/plugin_prefs_factory.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/common/chrome_constants.h" 26 #include "chrome/common/chrome_content_client.h" 27 #include "chrome/common/chrome_paths.h" 28 #include "chrome/common/chrome_switches.h" 29 #include "chrome/common/pref_names.h" 30 #include "components/browser_context_keyed_service/browser_context_keyed_service.h" 31 #include "content/public/browser/browser_thread.h" 32 #include "content/public/browser/notification_service.h" 33 #include "content/public/browser/plugin_service.h" 34 #include "content/public/common/webplugininfo.h" 35 36 using content::BrowserThread; 37 using content::PluginService; 38 39 namespace { 40 41 bool IsComponentUpdatedPepperFlash(const base::FilePath& plugin) { 42 if (plugin.BaseName().value() == chrome::kPepperFlashPluginFilename) { 43 base::FilePath component_updated_pepper_flash_dir; 44 if (PathService::Get(chrome::DIR_COMPONENT_UPDATED_PEPPER_FLASH_PLUGIN, 45 &component_updated_pepper_flash_dir) && 46 component_updated_pepper_flash_dir.IsParent(plugin)) { 47 return true; 48 } 49 } 50 51 return false; 52 } 53 54 } // namespace 55 56 PluginPrefs::PluginState::PluginState() { 57 } 58 59 PluginPrefs::PluginState::~PluginState() { 60 } 61 62 bool PluginPrefs::PluginState::Get(const base::FilePath& plugin, 63 bool* enabled) const { 64 base::FilePath key = ConvertMapKey(plugin); 65 std::map<base::FilePath, bool>::const_iterator iter = state_.find(key); 66 if (iter != state_.end()) { 67 *enabled = iter->second; 68 return true; 69 } 70 return false; 71 } 72 73 void PluginPrefs::PluginState::Set(const base::FilePath& plugin, bool enabled) { 74 state_[ConvertMapKey(plugin)] = enabled; 75 } 76 77 base::FilePath PluginPrefs::PluginState::ConvertMapKey( 78 const base::FilePath& plugin) const { 79 // Keep the state of component-updated and bundled Pepper Flash in sync. 80 if (IsComponentUpdatedPepperFlash(plugin)) { 81 base::FilePath bundled_pepper_flash; 82 if (PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, 83 &bundled_pepper_flash)) { 84 return bundled_pepper_flash; 85 } 86 } 87 88 return plugin; 89 } 90 91 // static 92 scoped_refptr<PluginPrefs> PluginPrefs::GetForProfile(Profile* profile) { 93 return PluginPrefsFactory::GetPrefsForProfile(profile); 94 } 95 96 // static 97 scoped_refptr<PluginPrefs> PluginPrefs::GetForTestingProfile( 98 Profile* profile) { 99 return static_cast<PluginPrefs*>( 100 PluginPrefsFactory::GetInstance()->SetTestingFactoryAndUse( 101 profile, &PluginPrefsFactory::CreateForTestingProfile).get()); 102 } 103 104 void PluginPrefs::EnablePluginGroup(bool enabled, 105 const base::string16& group_name) { 106 PluginService::GetInstance()->GetPlugins( 107 base::Bind(&PluginPrefs::EnablePluginGroupInternal, 108 this, enabled, group_name)); 109 } 110 111 void PluginPrefs::EnablePluginGroupInternal( 112 bool enabled, 113 const base::string16& group_name, 114 const std::vector<content::WebPluginInfo>& plugins) { 115 base::AutoLock auto_lock(lock_); 116 PluginFinder* finder = PluginFinder::GetInstance(); 117 118 // Set the desired state for the group. 119 plugin_group_state_[group_name] = enabled; 120 121 // Update the state for all plug-ins in the group. 122 for (size_t i = 0; i < plugins.size(); ++i) { 123 scoped_ptr<PluginMetadata> plugin(finder->GetPluginMetadata(plugins[i])); 124 if (group_name != plugin->name()) 125 continue; 126 plugin_state_.Set(plugins[i].path, enabled); 127 } 128 129 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 130 base::Bind(&PluginPrefs::OnUpdatePreferences, this, plugins)); 131 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 132 base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this)); 133 } 134 135 void PluginPrefs::EnablePlugin( 136 bool enabled, const base::FilePath& path, 137 const base::Callback<void(bool)>& callback) { 138 PluginFinder* finder = PluginFinder::GetInstance(); 139 content::WebPluginInfo plugin; 140 bool can_enable = true; 141 if (PluginService::GetInstance()->GetPluginInfoByPath(path, &plugin)) { 142 scoped_ptr<PluginMetadata> plugin_metadata( 143 finder->GetPluginMetadata(plugin)); 144 PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name); 145 PolicyStatus group_status = PolicyStatusForPlugin(plugin_metadata->name()); 146 if (enabled) { 147 if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED) 148 can_enable = false; 149 } else { 150 if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED) 151 can_enable = false; 152 } 153 } else { 154 NOTREACHED(); 155 } 156 157 if (!can_enable) { 158 base::MessageLoop::current()->PostTask(FROM_HERE, 159 base::Bind(callback, false)); 160 return; 161 } 162 163 PluginService::GetInstance()->GetPlugins( 164 base::Bind(&PluginPrefs::EnablePluginInternal, this, 165 enabled, path, finder, callback)); 166 } 167 168 void PluginPrefs::EnablePluginInternal( 169 bool enabled, 170 const base::FilePath& path, 171 PluginFinder* plugin_finder, 172 const base::Callback<void(bool)>& callback, 173 const std::vector<content::WebPluginInfo>& plugins) { 174 { 175 // Set the desired state for the plug-in. 176 base::AutoLock auto_lock(lock_); 177 plugin_state_.Set(path, enabled); 178 } 179 180 base::string16 group_name; 181 for (size_t i = 0; i < plugins.size(); ++i) { 182 if (plugins[i].path == path) { 183 scoped_ptr<PluginMetadata> plugin_metadata( 184 plugin_finder->GetPluginMetadata(plugins[i])); 185 // set the group name for this plug-in. 186 group_name = plugin_metadata->name(); 187 DCHECK_EQ(enabled, IsPluginEnabled(plugins[i])); 188 break; 189 } 190 } 191 192 bool all_disabled = true; 193 for (size_t i = 0; i < plugins.size(); ++i) { 194 scoped_ptr<PluginMetadata> plugin_metadata( 195 plugin_finder->GetPluginMetadata(plugins[i])); 196 DCHECK(!plugin_metadata->name().empty()); 197 if (group_name == plugin_metadata->name()) { 198 all_disabled = all_disabled && !IsPluginEnabled(plugins[i]); 199 } 200 } 201 202 if (!group_name.empty()) { 203 // Update the state for the corresponding plug-in group. 204 base::AutoLock auto_lock(lock_); 205 plugin_group_state_[group_name] = !all_disabled; 206 } 207 208 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 209 base::Bind(&PluginPrefs::OnUpdatePreferences, this, plugins)); 210 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 211 base::Bind(&PluginPrefs::NotifyPluginStatusChanged, this)); 212 callback.Run(true); 213 } 214 215 PluginPrefs::PolicyStatus PluginPrefs::PolicyStatusForPlugin( 216 const base::string16& name) const { 217 base::AutoLock auto_lock(lock_); 218 if (IsStringMatchedInSet(name, policy_enabled_plugin_patterns_)) { 219 return POLICY_ENABLED; 220 } else if (IsStringMatchedInSet(name, policy_disabled_plugin_patterns_) && 221 !IsStringMatchedInSet( 222 name, policy_disabled_plugin_exception_patterns_)) { 223 return POLICY_DISABLED; 224 } else { 225 return NO_POLICY; 226 } 227 } 228 229 bool PluginPrefs::IsPluginEnabled(const content::WebPluginInfo& plugin) const { 230 scoped_ptr<PluginMetadata> plugin_metadata( 231 PluginFinder::GetInstance()->GetPluginMetadata(plugin)); 232 base::string16 group_name = plugin_metadata->name(); 233 234 // Check if the plug-in or its group is enabled by policy. 235 PolicyStatus plugin_status = PolicyStatusForPlugin(plugin.name); 236 PolicyStatus group_status = PolicyStatusForPlugin(group_name); 237 if (plugin_status == POLICY_ENABLED || group_status == POLICY_ENABLED) 238 return true; 239 240 // Check if the plug-in or its group is disabled by policy. 241 if (plugin_status == POLICY_DISABLED || group_status == POLICY_DISABLED) 242 return false; 243 244 // If enabling NaCl, make sure the plugin is also enabled. See bug 245 // http://code.google.com/p/chromium/issues/detail?id=81010 for more 246 // information. 247 // TODO(dspringer): When NaCl is on by default, remove this code. 248 if ((plugin.name == 249 ASCIIToUTF16(ChromeContentClient::kNaClPluginName)) && 250 CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableNaCl)) { 251 return true; 252 } 253 254 base::AutoLock auto_lock(lock_); 255 // Check user preferences for the plug-in. 256 bool plugin_enabled = false; 257 if (plugin_state_.Get(plugin.path, &plugin_enabled)) 258 return plugin_enabled; 259 260 // Check user preferences for the plug-in group. 261 std::map<base::string16, bool>::const_iterator group_it( 262 plugin_group_state_.find(group_name)); 263 if (group_it != plugin_group_state_.end()) 264 return group_it->second; 265 266 // Default to enabled. 267 return true; 268 } 269 270 void PluginPrefs::UpdatePatternsAndNotify(std::set<base::string16>* patterns, 271 const std::string& pref_name) { 272 base::AutoLock auto_lock(lock_); 273 ListValueToStringSet(prefs_->GetList(pref_name.c_str()), patterns); 274 275 NotifyPluginStatusChanged(); 276 } 277 278 /*static*/ 279 bool PluginPrefs::IsStringMatchedInSet( 280 const base::string16& name, 281 const std::set<base::string16>& pattern_set) { 282 std::set<base::string16>::const_iterator pattern(pattern_set.begin()); 283 while (pattern != pattern_set.end()) { 284 if (MatchPattern(name, *pattern)) 285 return true; 286 ++pattern; 287 } 288 289 return false; 290 } 291 292 /* static */ 293 void PluginPrefs::ListValueToStringSet(const ListValue* src, 294 std::set<base::string16>* dest) { 295 DCHECK(src); 296 DCHECK(dest); 297 dest->clear(); 298 ListValue::const_iterator end(src->end()); 299 for (ListValue::const_iterator current(src->begin()); 300 current != end; ++current) { 301 base::string16 plugin_name; 302 if ((*current)->GetAsString(&plugin_name)) { 303 dest->insert(plugin_name); 304 } 305 } 306 } 307 308 void PluginPrefs::SetPrefs(PrefService* prefs) { 309 prefs_ = prefs; 310 bool update_internal_dir = false; 311 base::FilePath last_internal_dir = 312 prefs_->GetFilePath(prefs::kPluginsLastInternalDirectory); 313 base::FilePath cur_internal_dir; 314 if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &cur_internal_dir) && 315 cur_internal_dir != last_internal_dir) { 316 update_internal_dir = true; 317 prefs_->SetFilePath( 318 prefs::kPluginsLastInternalDirectory, cur_internal_dir); 319 } 320 321 bool migrate_to_pepper_flash = false; 322 #if defined(OS_WIN) || defined(OS_MACOSX) 323 // If bundled NPAPI Flash is enabled while Pepper Flash is disabled, we 324 // would like to turn Pepper Flash on. And we only want to do it once. 325 // TODO(yzshen): Remove all |migrate_to_pepper_flash|-related code after it 326 // has been run once by most users. (Maybe Chrome 24 or Chrome 25.) 327 // NOTE(shess): Keep in mind that Mac is on a different schedule. 328 if (!prefs_->GetBoolean(prefs::kPluginsMigratedToPepperFlash)) { 329 prefs_->SetBoolean(prefs::kPluginsMigratedToPepperFlash, true); 330 migrate_to_pepper_flash = true; 331 } 332 #endif 333 334 bool remove_component_pepper_flash_settings = false; 335 // If component-updated Pepper Flash is disabled, we would like to remove that 336 // settings item. And we only want to do it once. (Please see the comments of 337 // kPluginsRemovedOldComponentPepperFlashSettings for why.) 338 // TODO(yzshen): Remove all |remove_component_pepper_flash_settings|-related 339 // code after it has been run once by most users. 340 if (!prefs_->GetBoolean( 341 prefs::kPluginsRemovedOldComponentPepperFlashSettings)) { 342 prefs_->SetBoolean(prefs::kPluginsRemovedOldComponentPepperFlashSettings, 343 true); 344 remove_component_pepper_flash_settings = true; 345 } 346 347 { // Scoped update of prefs::kPluginsPluginsList. 348 ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList); 349 ListValue* saved_plugins_list = update.Get(); 350 if (saved_plugins_list && !saved_plugins_list->empty()) { 351 // The following four variables are only valid when 352 // |migrate_to_pepper_flash| is set to true. 353 base::FilePath npapi_flash; 354 base::FilePath pepper_flash; 355 DictionaryValue* pepper_flash_node = NULL; 356 bool npapi_flash_enabled = false; 357 if (migrate_to_pepper_flash) { 358 PathService::Get(chrome::FILE_FLASH_PLUGIN, &npapi_flash); 359 PathService::Get(chrome::FILE_PEPPER_FLASH_PLUGIN, &pepper_flash); 360 } 361 362 // Used when |remove_component_pepper_flash_settings| is set to true. 363 ListValue::iterator component_pepper_flash_node = 364 saved_plugins_list->end(); 365 366 for (ListValue::iterator it = saved_plugins_list->begin(); 367 it != saved_plugins_list->end(); 368 ++it) { 369 if (!(*it)->IsType(Value::TYPE_DICTIONARY)) { 370 LOG(WARNING) << "Invalid entry in " << prefs::kPluginsPluginsList; 371 continue; // Oops, don't know what to do with this item. 372 } 373 374 DictionaryValue* plugin = static_cast<DictionaryValue*>(*it); 375 base::string16 group_name; 376 bool enabled; 377 if (!plugin->GetBoolean("enabled", &enabled)) 378 enabled = true; 379 380 base::FilePath::StringType path; 381 // The plugin list constains all the plugin files in addition to the 382 // plugin groups. 383 if (plugin->GetString("path", &path)) { 384 // Files have a path attribute, groups don't. 385 base::FilePath plugin_path(path); 386 387 // The path to the intenral plugin directory changes everytime Chrome 388 // is auto-updated, since it contains the current version number. For 389 // example, it changes from foobar\Chrome\Application\21.0.1180.83 to 390 // foobar\Chrome\Application\21.0.1180.89. 391 // However, we would like the settings of internal plugins to persist 392 // across Chrome updates. Therefore, we need to recognize those paths 393 // that are within the previous internal plugin directory, and update 394 // them in the prefs accordingly. 395 if (update_internal_dir) { 396 base::FilePath relative_path; 397 398 // Extract the part of |plugin_path| that is relative to 399 // |last_internal_dir|. For example, |relative_path| will be 400 // foo\bar.dll if |plugin_path| is <last_internal_dir>\foo\bar.dll. 401 // 402 // Every iteration the last path component from |plugin_path| is 403 // removed and prepended to |relative_path| until we get up to 404 // |last_internal_dir|. 405 while (last_internal_dir.IsParent(plugin_path)) { 406 relative_path = plugin_path.BaseName().Append(relative_path); 407 408 base::FilePath old_path = plugin_path; 409 plugin_path = plugin_path.DirName(); 410 // To be extra sure that we won't end up in an infinite loop. 411 if (old_path == plugin_path) { 412 NOTREACHED(); 413 break; 414 } 415 } 416 417 // If |relative_path| is empty, |plugin_path| is not within 418 // |last_internal_dir|. We don't need to update it. 419 if (!relative_path.empty()) { 420 plugin_path = cur_internal_dir.Append(relative_path); 421 path = plugin_path.value(); 422 plugin->SetString("path", path); 423 } 424 } 425 426 if (migrate_to_pepper_flash && 427 base::FilePath::CompareEqualIgnoreCase( 428 path, npapi_flash.value())) { 429 npapi_flash_enabled = enabled; 430 } else if (migrate_to_pepper_flash && 431 base::FilePath::CompareEqualIgnoreCase( 432 path, pepper_flash.value())) { 433 if (!enabled) 434 pepper_flash_node = plugin; 435 } else if (remove_component_pepper_flash_settings && 436 IsComponentUpdatedPepperFlash(plugin_path)) { 437 if (!enabled) { 438 component_pepper_flash_node = it; 439 // Skip setting |enabled| into |plugin_state_|. 440 continue; 441 } 442 } 443 444 plugin_state_.Set(plugin_path, enabled); 445 } else if (!enabled && plugin->GetString("name", &group_name)) { 446 // Otherwise this is a list of groups. 447 plugin_group_state_[group_name] = false; 448 } 449 } 450 451 if (npapi_flash_enabled && pepper_flash_node) { 452 DCHECK(migrate_to_pepper_flash); 453 pepper_flash_node->SetBoolean("enabled", true); 454 plugin_state_.Set(pepper_flash, true); 455 } 456 457 if (component_pepper_flash_node != saved_plugins_list->end()) { 458 DCHECK(remove_component_pepper_flash_settings); 459 saved_plugins_list->Erase(component_pepper_flash_node, NULL); 460 } 461 } else { 462 // If the saved plugin list is empty, then the call to UpdatePreferences() 463 // below failed in an earlier run, possibly because the user closed the 464 // browser too quickly. 465 466 // Only want one PDF plugin enabled at a time. See http://crbug.com/50105 467 // for background. 468 plugin_group_state_[ASCIIToUTF16( 469 PluginMetadata::kAdobeReaderGroupName)] = false; 470 } 471 } // Scoped update of prefs::kPluginsPluginsList. 472 473 // Build the set of policy enabled/disabled plugin patterns once and cache it. 474 // Don't do this in the constructor, there's no profile available there. 475 ListValueToStringSet(prefs_->GetList(prefs::kPluginsDisabledPlugins), 476 &policy_disabled_plugin_patterns_); 477 ListValueToStringSet( 478 prefs_->GetList(prefs::kPluginsDisabledPluginsExceptions), 479 &policy_disabled_plugin_exception_patterns_); 480 ListValueToStringSet(prefs_->GetList(prefs::kPluginsEnabledPlugins), 481 &policy_enabled_plugin_patterns_); 482 483 registrar_.Init(prefs_); 484 485 // Because pointers to our own members will remain unchanged for the 486 // lifetime of |registrar_| (which we also own), we can bind their 487 // pointer values directly in the callbacks to avoid string-based 488 // lookups at notification time. 489 registrar_.Add(prefs::kPluginsDisabledPlugins, 490 base::Bind(&PluginPrefs::UpdatePatternsAndNotify, 491 base::Unretained(this), 492 &policy_disabled_plugin_patterns_)); 493 registrar_.Add(prefs::kPluginsDisabledPluginsExceptions, 494 base::Bind(&PluginPrefs::UpdatePatternsAndNotify, 495 base::Unretained(this), 496 &policy_disabled_plugin_exception_patterns_)); 497 registrar_.Add(prefs::kPluginsEnabledPlugins, 498 base::Bind(&PluginPrefs::UpdatePatternsAndNotify, 499 base::Unretained(this), 500 &policy_enabled_plugin_patterns_)); 501 502 NotifyPluginStatusChanged(); 503 } 504 505 void PluginPrefs::ShutdownOnUIThread() { 506 prefs_ = NULL; 507 registrar_.RemoveAll(); 508 } 509 510 PluginPrefs::PluginPrefs() : profile_(NULL), 511 prefs_(NULL) { 512 } 513 514 PluginPrefs::~PluginPrefs() { 515 } 516 517 void PluginPrefs::SetPolicyEnforcedPluginPatterns( 518 const std::set<base::string16>& disabled_patterns, 519 const std::set<base::string16>& disabled_exception_patterns, 520 const std::set<base::string16>& enabled_patterns) { 521 policy_disabled_plugin_patterns_ = disabled_patterns; 522 policy_disabled_plugin_exception_patterns_ = disabled_exception_patterns; 523 policy_enabled_plugin_patterns_ = enabled_patterns; 524 } 525 526 void PluginPrefs::OnUpdatePreferences( 527 const std::vector<content::WebPluginInfo>& plugins) { 528 if (!prefs_) 529 return; 530 531 PluginFinder* finder = PluginFinder::GetInstance(); 532 ListPrefUpdate update(prefs_, prefs::kPluginsPluginsList); 533 ListValue* plugins_list = update.Get(); 534 plugins_list->Clear(); 535 536 base::FilePath internal_dir; 537 if (PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &internal_dir)) 538 prefs_->SetFilePath(prefs::kPluginsLastInternalDirectory, internal_dir); 539 540 base::AutoLock auto_lock(lock_); 541 542 // Add the plugin files. 543 std::set<base::string16> group_names; 544 for (size_t i = 0; i < plugins.size(); ++i) { 545 DictionaryValue* summary = new DictionaryValue(); 546 summary->SetString("path", plugins[i].path.value()); 547 summary->SetString("name", plugins[i].name); 548 summary->SetString("version", plugins[i].version); 549 bool enabled = true; 550 plugin_state_.Get(plugins[i].path, &enabled); 551 summary->SetBoolean("enabled", enabled); 552 plugins_list->Append(summary); 553 554 scoped_ptr<PluginMetadata> plugin_metadata( 555 finder->GetPluginMetadata(plugins[i])); 556 // Insert into a set of all group names. 557 group_names.insert(plugin_metadata->name()); 558 } 559 560 // Add the plug-in groups. 561 for (std::set<base::string16>::const_iterator it = group_names.begin(); 562 it != group_names.end(); ++it) { 563 DictionaryValue* summary = new DictionaryValue(); 564 summary->SetString("name", *it); 565 bool enabled = true; 566 std::map<base::string16, bool>::iterator gstate_it = 567 plugin_group_state_.find(*it); 568 if (gstate_it != plugin_group_state_.end()) 569 enabled = gstate_it->second; 570 summary->SetBoolean("enabled", enabled); 571 plugins_list->Append(summary); 572 } 573 } 574 575 void PluginPrefs::NotifyPluginStatusChanged() { 576 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 577 content::NotificationService::current()->Notify( 578 chrome::NOTIFICATION_PLUGIN_ENABLE_STATUS_CHANGED, 579 content::Source<Profile>(profile_), 580 content::NotificationService::NoDetails()); 581 } 582