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