text: // Copyright 2018 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/browser_switcher/browser_switcher_prefs.h"
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/files/file_path.h"
  #include "base/metrics/histogram_macros.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/threading/thread_task_runner_handle.h"
  #include "build/build_config.h"
  #include "chrome/browser/browser_switcher/browser_switcher_sitelist.h"
  #include "chrome/browser/policy/profile_policy_connector.h"
  #include "chrome/browser/profiles/profile.h"
  #include "components/pref_registry/pref_registry_syncable.h"
  #include "components/prefs/pref_service.h"
  #include "content/public/browser/browser_thread.h"
  
  #if defined(OS_WIN)
  #include <windows.h>
  #endif
  
  namespace browser_switcher {
  
  namespace {
  
  std::vector<std::string> GetCachedRules(PrefService* prefs,
                                          const std::string& pref_name) {
    std::vector<std::string> rules;
    for (const auto& url : *prefs->GetList(pref_name))
      rules.push_back(url.GetString());
    return rules;
  }
  
  void SetCachedRules(PrefService* prefs,
                      const std::string& pref_name,
                      const std::vector<std::string>& rules) {
    base::ListValue rules_val;
    for (const auto& url : rules)
      rules_val.Append(base::Value(url));
    prefs->Set(pref_name, rules_val);
  }
  
  }  // namespace
  
  RuleSet::RuleSet() = default;
  RuleSet::RuleSet(const RuleSet&) = default;
  RuleSet::~RuleSet() = default;
  
  BrowserSwitcherPrefs::BrowserSwitcherPrefs(Profile* profile)
      : BrowserSwitcherPrefs(
            profile->GetPrefs(),
            profile->GetProfilePolicyConnector()->policy_service()) {}
  
  BrowserSwitcherPrefs::BrowserSwitcherPrefs(
      PrefService* prefs,
      policy::PolicyService* policy_service)
      : policy_service_(policy_service), prefs_(prefs) {
    filtering_change_registrar_.Init(prefs_);
  
    const struct {
      const char* pref_name;
      base::RepeatingCallback<void(BrowserSwitcherPrefs*)> callback;
    } hooks[] = {
      {prefs::kAlternativeBrowserPath,
       base::BindRepeating(&BrowserSwitcherPrefs::AlternativeBrowserPathChanged)},
      {prefs::kAlternativeBrowserParameters,
       base::BindRepeating(
           &BrowserSwitcherPrefs::AlternativeBrowserParametersChanged)},
      {prefs::kUrlList,
       base::BindRepeating(&BrowserSwitcherPrefs::UrlListChanged)},
      {prefs::kUrlGreylist,
       base::BindRepeating(&BrowserSwitcherPrefs::GreylistChanged)},
  #if defined(OS_WIN)
      {prefs::kChromePath,
       base::BindRepeating(&BrowserSwitcherPrefs::ChromePathChanged)},
      {prefs::kChromeParameters,
       base::BindRepeating(&BrowserSwitcherPrefs::ChromeParametersChanged)},
  #endif
    };
  
    // Listen for pref changes, and run all the hooks once to initialize state.
    for (const auto& hook : hooks) {
      auto callback = base::BindRepeating(hook.callback, base::Unretained(this));
      filtering_change_registrar_.Add(hook.pref_name, callback);
      callback.Run();
    }
  
    // When any pref changes, mark this object as 'dirty' for the purpose of
    // triggering observers.
    notifying_change_registrar_.Init(prefs_);
    const char* all_prefs[] = {
      prefs::kEnabled,
      prefs::kAlternativeBrowserPath,
      prefs::kAlternativeBrowserParameters,
      prefs::kKeepLastTab,
      prefs::kUrlList,
      prefs::kUrlGreylist,
      prefs::kExternalSitelistUrl,
      prefs::kExternalGreylistUrl,
  #if defined(OS_WIN)
      prefs::kUseIeSitelist,
      prefs::kChromePath,
      prefs::kChromeParameters,
  #endif
    };
    for (const char* pref_name : all_prefs) {
      notifying_change_registrar_.Add(
          pref_name, base::BindRepeating(&BrowserSwitcherPrefs::MarkDirty,
                                         base::Unretained(this)));
    }
  
    if (policy_service_)
      policy_service_->AddObserver(policy::POLICY_DOMAIN_CHROME, this);
  }
  
  BrowserSwitcherPrefs::~BrowserSwitcherPrefs() = default;
  
  void BrowserSwitcherPrefs::Shutdown() {
    if (policy_service_)
      policy_service_->RemoveObserver(policy::POLICY_DOMAIN_CHROME, this);
  }
  
  // static
  void BrowserSwitcherPrefs::RegisterProfilePrefs(
      user_prefs::PrefRegistrySyncable* registry) {
    registry->RegisterBooleanPref(prefs::kEnabled, false);
    registry->RegisterIntegerPref(prefs::kDelay, 0);
    registry->RegisterStringPref(prefs::kAlternativeBrowserPath, "");
    registry->RegisterListPref(prefs::kAlternativeBrowserParameters);
    registry->RegisterBooleanPref(prefs::kKeepLastTab, true);
    registry->RegisterListPref(prefs::kUrlList);
    registry->RegisterListPref(prefs::kUrlGreylist);
    registry->RegisterStringPref(prefs::kExternalSitelistUrl, "");
    registry->RegisterListPref(prefs::kCachedExternalSitelist);
    registry->RegisterStringPref(prefs::kExternalGreylistUrl, "");
    registry->RegisterListPref(prefs::kCachedExternalGreylist);
  #if defined(OS_WIN)
    registry->RegisterBooleanPref(prefs::kUseIeSitelist, false);
    registry->RegisterListPref(prefs::kCachedIeSitelist);
    registry->RegisterStringPref(prefs::kChromePath, "");
    registry->RegisterListPref(prefs::kChromeParameters);
  #endif
  }
  
  bool BrowserSwitcherPrefs::IsEnabled() const {
    return prefs_->GetBoolean(prefs::kEnabled) &&
           prefs_->IsManagedPreference(prefs::kEnabled);
  }
  
  const std::string& BrowserSwitcherPrefs::GetAlternativeBrowserPath() const {
    return alt_browser_path_;
  }
  
  const std::vector<std::string>&
  BrowserSwitcherPrefs::GetAlternativeBrowserParameters() const {
    return alt_browser_params_;
  }
  
  bool BrowserSwitcherPrefs::KeepLastTab() const {
    return prefs_->GetBoolean(prefs::kKeepLastTab);
  }
  
  int BrowserSwitcherPrefs::GetDelay() const {
    return prefs_->GetInteger(prefs::kDelay);
  }
  
  const RuleSet& BrowserSwitcherPrefs::GetRules() const {
    return rules_;
  }
  
  std::vector<std::string> BrowserSwitcherPrefs::GetCachedExternalSitelist()
      const {
    return GetCachedRules(prefs_, prefs::kCachedExternalSitelist);
  }
  
  void BrowserSwitcherPrefs::SetCachedExternalSitelist(
      const std::vector<std::string>& sitelist) {
    SetCachedRules(prefs_, prefs::kCachedExternalSitelist, sitelist);
  }
  
  std::vector<std::string> BrowserSwitcherPrefs::GetCachedExternalGreylist()
      const {
    return GetCachedRules(prefs_, prefs::kCachedExternalGreylist);
  }
  
  void BrowserSwitcherPrefs::SetCachedExternalGreylist(
      const std::vector<std::string>& greylist) {
    SetCachedRules(prefs_, prefs::kCachedExternalGreylist, greylist);
  }
  
  #if defined(OS_WIN)
  std::vector<std::string> BrowserSwitcherPrefs::GetCachedIeemSitelist() const {
    return GetCachedRules(prefs_, prefs::kCachedIeSitelist);
  }
  
  void BrowserSwitcherPrefs::SetCachedIeemSitelist(
      const std::vector<std::string>& sitelist) {
    SetCachedRules(prefs_, prefs::kCachedIeSitelist, sitelist);
  }
  #endif
  
  GURL BrowserSwitcherPrefs::GetExternalSitelistUrl() const {
    if (!IsEnabled() || !prefs_->IsManagedPreference(prefs::kExternalSitelistUrl))
      return GURL();
    return GURL(prefs_->GetString(prefs::kExternalSitelistUrl));
  }
  
  GURL BrowserSwitcherPrefs::GetExternalGreylistUrl() const {
    if (!IsEnabled() || !prefs_->IsManagedPreference(prefs::kExternalGreylistUrl))
      return GURL();
    return GURL(prefs_->GetString(prefs::kExternalGreylistUrl));
  }
  
  #if defined(OS_WIN)
  bool BrowserSwitcherPrefs::UseIeSitelist() const {
    if (!IsEnabled() || !prefs_->IsManagedPreference(prefs::kUseIeSitelist))
      return false;
    return prefs_->GetBoolean(prefs::kUseIeSitelist);
  }
  
  const base::FilePath& BrowserSwitcherPrefs::GetChromePath() const {
    return chrome_path_;
  }
  
  const std::vector<std::string>& BrowserSwitcherPrefs::GetChromeParameters()
      const {
    return chrome_params_;
  }
  #endif
  
  void BrowserSwitcherPrefs::OnPolicyUpdated(const policy::PolicyNamespace& ns,
                                             const policy::PolicyMap& previous,
                                             const policy::PolicyMap& current) {
    // Let all the other policy observers run first, so that prefs are up-to-date
    // when we run our own callbacks.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::BindOnce(&BrowserSwitcherPrefs::RunCallbacksIfDirty,
                                  weak_ptr_factory_.GetWeakPtr()));
  }
  
  base::CallbackListSubscription
  BrowserSwitcherPrefs::RegisterPrefsChangedCallback(
      BrowserSwitcherPrefs::PrefsChangedCallback cb) {
    return callback_list_.Add(cb);
  }
  
  void BrowserSwitcherPrefs::RunCallbacksIfDirty() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (!dirty_prefs_.empty())
      callback_list_.Notify(this, dirty_prefs_);
    dirty_prefs_.clear();
  }
  
  void BrowserSwitcherPrefs::MarkDirty(const std::string& pref_name) {
    dirty_prefs_.push_back(pref_name);
  }
  
  void BrowserSwitcherPrefs::AlternativeBrowserPathChanged() {
    alt_browser_path_.clear();
    if (prefs_->IsManagedPreference(prefs::kAlternativeBrowserPath))
      alt_browser_path_ = prefs_->GetString(prefs::kAlternativeBrowserPath);
  }
  
  void BrowserSwitcherPrefs::AlternativeBrowserParametersChanged() {
    alt_browser_params_.clear();
    if (!prefs_->IsManagedPreference(prefs::kAlternativeBrowserParameters))
      return;
    const base::ListValue* params =
        prefs_->GetList(prefs::kAlternativeBrowserParameters);
    for (const auto& param : *params) {
      std::string param_string = param.GetString();
      alt_browser_params_.push_back(param_string);
    }
  }
  
  void BrowserSwitcherPrefs::UrlListChanged() {
    rules_.sitelist.clear();
  
    if (!prefs_->IsManagedPreference(prefs::kUrlList))
      return;
  
    UMA_HISTOGRAM_COUNTS_100000(
        "BrowserSwitcher.UrlListSize",
        prefs_->GetList(prefs::kUrlList)->GetList().size());
  
    bool has_wildcard = false;
    for (const auto& url : *prefs_->GetList(prefs::kUrlList)) {
      std::string canonical = url.GetString();
      CanonicalizeRule(&canonical);
      rules_.sitelist.push_back(std::move(canonical));
      if (url.GetString() == "*")
        has_wildcard = true;
    }
  
    UMA_HISTOGRAM_BOOLEAN("BrowserSwitcher.UrlListWildcard", has_wildcard);
  }
  
  void BrowserSwitcherPrefs::GreylistChanged() {
    rules_.greylist.clear();
  
    // This pref is sensitive. Only set through policies.
    if (!prefs_->IsManagedPreference(prefs::kUrlGreylist))
      return;
  
    UMA_HISTOGRAM_COUNTS_100000(
        "BrowserSwitcher.GreylistSize",
        prefs_->GetList(prefs::kUrlGreylist)->GetList().size());
  
    bool has_wildcard = false;
    for (const auto& url : *prefs_->GetList(prefs::kUrlGreylist)) {
      std::string canonical = url.GetString();
      CanonicalizeRule(&canonical);
      rules_.greylist.push_back(std::move(canonical));
      if (url.GetString() == "*")
        has_wildcard = true;
    }
  
    UMA_HISTOGRAM_BOOLEAN("BrowserSwitcher.UrlListWildcard", has_wildcard);
  }
  
  #if defined(OS_WIN)
  void BrowserSwitcherPrefs::ChromePathChanged() {
    chrome_path_.clear();
    if (prefs_->IsManagedPreference(prefs::kChromePath))
      chrome_path_ = prefs_->GetFilePath(prefs::kChromePath);
  #if defined(OS_WIN)
    if (chrome_path_.empty()) {
      base::FilePath::CharType chrome_path[MAX_PATH];
      ::GetModuleFileName(NULL, chrome_path, ARRAYSIZE(chrome_path));
      chrome_path_ = base::FilePath(chrome_path);
    }
  #endif
  }
  
  void BrowserSwitcherPrefs::ChromeParametersChanged() {
    chrome_params_.clear();
    if (!prefs_->IsManagedPreference(prefs::kChromeParameters))
      return;
    const base::ListValue* params = prefs_->GetList(prefs::kChromeParameters);
    for (const auto& param : *params) {
      std::string param_string = param.GetString();
      chrome_params_.push_back(param_string);
    }
  }
  #endif
  
  namespace prefs {
  
  // Path to the executable of the alternative browser, or one of "${chrome}",
  // "${ie}", "${firefox}", "${opera}", "${safari}".
  const char kAlternativeBrowserPath[] =
      "browser_switcher.alternative_browser_path";
  
  // Arguments to pass to the alternative browser when invoking it via
  // |ShellExecute()|.
  const char kAlternativeBrowserParameters[] =
      "browser_switcher.alternative_browser_parameters";
  
  // If true, always keep at least one tab open after switching.
  const char kKeepLastTab[] = "browser_switcher.keep_last_tab";
  
  // List of host domain names to be opened in an alternative browser.
  const char kUrlList[] = "browser_switcher.url_list";
  
  // List of hosts that should not trigger a transition in either browser.
  const char kUrlGreylist[] = "browser_switcher.url_greylist";
  
  // URL with an external XML sitelist file to load.
  const char kExternalSitelistUrl[] = "browser_switcher.external_sitelist_url";
  const char kCachedExternalSitelist[] =
      "browser_switcher.cached_external_sitelist";
  
  // URL with an external XML greylist file to load.
  const char kExternalGreylistUrl[] = "browser_switcher.external_greylist_url";
  const char kCachedExternalGreylist[] =
      "browser_switcher.cached_external_greylist";
  
  #if defined(OS_WIN)
  // If set to true, use the IE Enterprise Mode Sitelist policy.
  const char kUseIeSitelist[] = "browser_switcher.use_ie_sitelist";
  const char kCachedIeSitelist[] = "browser_switcher.cached_ie_sitelist";
  
  // Path to the Chrome executable for the alternative browser.
  const char kChromePath[] = "browser_switcher.chrome_path";
  
  // Arguments the alternative browser should pass to Chrome when launching it.
  const char kChromeParameters[] = "browser_switcher.chrome_parameters";
  #endif
  
  // Disable browser_switcher unless this is set to true.
  const char kEnabled[] = "browser_switcher.enabled";
  
  // How long to wait on chrome://browser-switch (milliseconds).
  const char kDelay[] = "browser_switcher.delay";
  
  }  // namespace prefs
  }  // namespace browser_switcher
  