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