// Copyright 2014 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 "components/search_engines/default_search_policy_handler.h"

#include "base/prefs/pref_value_map.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "components/policy/core/browser/policy_error_map.h"
#include "components/policy/core/common/policy_map.h"
#include "components/search_engines/default_search_manager.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url.h"
#include "grit/components_strings.h"
#include "policy/policy_constants.h"

namespace policy {

namespace {
// Extracts a list from a policy value and adds it to a pref dictionary.
void SetListInPref(const PolicyMap& policies,
                   const char* policy_name,
                   const char* key,
                   base::DictionaryValue* dict) {
  DCHECK(dict);
  const base::Value* policy_value = policies.GetValue(policy_name);
  const base::ListValue* policy_list = NULL;
  if (policy_value) {
    bool is_list = policy_value->GetAsList(&policy_list);
    DCHECK(is_list);
  }
  dict->Set(key, policy_list ? policy_list->DeepCopy() : new base::ListValue());
}

// Extracts a string from a policy value and adds it to a pref dictionary.
void SetStringInPref(const PolicyMap& policies,
                     const char* policy_name,
                     const char* key,
                     base::DictionaryValue* dict) {
  DCHECK(dict);
  const base::Value* policy_value = policies.GetValue(policy_name);
  std::string str;
  if (policy_value) {
    bool is_string = policy_value->GetAsString(&str);
    DCHECK(is_string);
  }
  dict->SetString(key, str);
}

}  // namespace

// List of policy types to preference names, for policies affecting the default
// search provider.
const PolicyToPreferenceMapEntry kDefaultSearchPolicyMap[] = {
  { key::kDefaultSearchProviderEnabled,
    prefs::kDefaultSearchProviderEnabled,
    base::Value::TYPE_BOOLEAN },
  { key::kDefaultSearchProviderName,
    prefs::kDefaultSearchProviderName,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderKeyword,
    prefs::kDefaultSearchProviderKeyword,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderSearchURL,
    prefs::kDefaultSearchProviderSearchURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderSuggestURL,
    prefs::kDefaultSearchProviderSuggestURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderInstantURL,
    prefs::kDefaultSearchProviderInstantURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderIconURL,
    prefs::kDefaultSearchProviderIconURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderEncodings,
    prefs::kDefaultSearchProviderEncodings,
    base::Value::TYPE_LIST },
  { key::kDefaultSearchProviderAlternateURLs,
    prefs::kDefaultSearchProviderAlternateURLs,
    base::Value::TYPE_LIST },
  { key::kDefaultSearchProviderSearchTermsReplacementKey,
    prefs::kDefaultSearchProviderSearchTermsReplacementKey,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderImageURL,
    prefs::kDefaultSearchProviderImageURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderNewTabURL,
    prefs::kDefaultSearchProviderNewTabURL,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderSearchURLPostParams,
    prefs::kDefaultSearchProviderSearchURLPostParams,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderSuggestURLPostParams,
    prefs::kDefaultSearchProviderSuggestURLPostParams,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderInstantURLPostParams,
    prefs::kDefaultSearchProviderInstantURLPostParams,
    base::Value::TYPE_STRING },
  { key::kDefaultSearchProviderImageURLPostParams,
    prefs::kDefaultSearchProviderImageURLPostParams,
    base::Value::TYPE_STRING },
};

// List of policy types to preference names, for policies affecting the default
// search provider.
const PolicyToPreferenceMapEntry kDefaultSearchPolicyDataMap[] = {
    {key::kDefaultSearchProviderName, DefaultSearchManager::kShortName,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderKeyword, DefaultSearchManager::kKeyword,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderSearchURL, DefaultSearchManager::kURL,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderSuggestURL,
     DefaultSearchManager::kSuggestionsURL, base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderInstantURL, DefaultSearchManager::kInstantURL,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderIconURL, DefaultSearchManager::kFaviconURL,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderEncodings,
     DefaultSearchManager::kInputEncodings, base::Value::TYPE_LIST},
    {key::kDefaultSearchProviderAlternateURLs,
     DefaultSearchManager::kAlternateURLs, base::Value::TYPE_LIST},
    {key::kDefaultSearchProviderSearchTermsReplacementKey,
     DefaultSearchManager::kSearchTermsReplacementKey,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderImageURL, DefaultSearchManager::kImageURL,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderNewTabURL, DefaultSearchManager::kNewTabURL,
     base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderSearchURLPostParams,
     DefaultSearchManager::kSearchURLPostParams, base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderSuggestURLPostParams,
     DefaultSearchManager::kSuggestionsURLPostParams, base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderInstantURLPostParams,
     DefaultSearchManager::kInstantURLPostParams, base::Value::TYPE_STRING},
    {key::kDefaultSearchProviderImageURLPostParams,
     DefaultSearchManager::kImageURLPostParams, base::Value::TYPE_STRING},
};

// DefaultSearchEncodingsPolicyHandler implementation --------------------------

DefaultSearchEncodingsPolicyHandler::DefaultSearchEncodingsPolicyHandler()
    : TypeCheckingPolicyHandler(key::kDefaultSearchProviderEncodings,
                                base::Value::TYPE_LIST) {}

DefaultSearchEncodingsPolicyHandler::~DefaultSearchEncodingsPolicyHandler() {
}

void DefaultSearchEncodingsPolicyHandler::ApplyPolicySettings(
    const PolicyMap& policies, PrefValueMap* prefs) {
  // The DefaultSearchProviderEncodings policy has type list, but the related
  // preference has type string. Convert one into the other here, using
  // ';' as a separator.
  const base::Value* value = policies.GetValue(policy_name());
  const base::ListValue* list;
  if (!value || !value->GetAsList(&list))
    return;

  base::ListValue::const_iterator iter(list->begin());
  base::ListValue::const_iterator end(list->end());
  std::vector<std::string> string_parts;
  for (; iter != end; ++iter) {
    std::string s;
    if ((*iter)->GetAsString(&s)) {
      string_parts.push_back(s);
    }
  }
  std::string encodings = JoinString(string_parts, ';');
  prefs->SetString(prefs::kDefaultSearchProviderEncodings, encodings);
}


// DefaultSearchPolicyHandler implementation -----------------------------------

DefaultSearchPolicyHandler::DefaultSearchPolicyHandler() {
  for (size_t i = 0; i < arraysize(kDefaultSearchPolicyMap); ++i) {
    const char* policy_name = kDefaultSearchPolicyMap[i].policy_name;
    if (policy_name == key::kDefaultSearchProviderEncodings) {
      handlers_.push_back(new DefaultSearchEncodingsPolicyHandler());
    } else {
      handlers_.push_back(new SimplePolicyHandler(
          policy_name,
          kDefaultSearchPolicyMap[i].preference_path,
          kDefaultSearchPolicyMap[i].value_type));
    }
  }
}

DefaultSearchPolicyHandler::~DefaultSearchPolicyHandler() {
  STLDeleteElements(&handlers_);
}

bool DefaultSearchPolicyHandler::CheckPolicySettings(const PolicyMap& policies,
                                                     PolicyErrorMap* errors) {
  if (!CheckIndividualPolicies(policies, errors))
    return false;

  if (DefaultSearchProviderIsDisabled(policies)) {
    // Add an error for all specified default search policies except
    // DefaultSearchProviderEnabled.

    for (std::vector<TypeCheckingPolicyHandler*>::const_iterator handler =
             handlers_.begin();
         handler != handlers_.end(); ++handler) {
      const char* policy_name = (*handler)->policy_name();
      if (policy_name != key::kDefaultSearchProviderEnabled &&
          HasDefaultSearchPolicy(policies, policy_name)) {
        errors->AddError(policy_name, IDS_POLICY_DEFAULT_SEARCH_DISABLED);
      }
    }
    return true;
  }

  const base::Value* url;
  std::string dummy;
  if (DefaultSearchURLIsValid(policies, &url, &dummy) ||
      !AnyDefaultSearchPoliciesSpecified(policies))
    return true;
  errors->AddError(key::kDefaultSearchProviderSearchURL, url ?
      IDS_POLICY_INVALID_SEARCH_URL_ERROR : IDS_POLICY_NOT_SPECIFIED_ERROR);
  return false;
}

void DefaultSearchPolicyHandler::HandleDictionaryPref(const PolicyMap& policies,
                                                      PrefValueMap* prefs) {
  if (DefaultSearchProviderIsDisabled(policies)) {
    scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
    dict->SetBoolean(DefaultSearchManager::kDisabledByPolicy, true);
    DefaultSearchManager::AddPrefValueToMap(dict.release(), prefs);
    return;
  }

  // The search URL is required.  The other entries are optional.  Just make
  // sure that they are all specified via policy, so that the regular prefs
  // aren't used.
  const base::Value* dummy;
  std::string url;
  if (!DefaultSearchURLIsValid(policies, &dummy, &url))
    return;

  scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
  for (size_t i = 0; i < arraysize(kDefaultSearchPolicyDataMap); ++i) {
    const char* policy_name = kDefaultSearchPolicyDataMap[i].policy_name;
    switch (kDefaultSearchPolicyDataMap[i].value_type) {
      case base::Value::TYPE_STRING:
        SetStringInPref(policies,
                        policy_name,
                        kDefaultSearchPolicyDataMap[i].preference_path,
                        dict.get());
        break;
      case base::Value::TYPE_LIST:
        SetListInPref(policies,
                      policy_name,
                      kDefaultSearchPolicyDataMap[i].preference_path,
                      dict.get());
        break;
      default:
        NOTREACHED();
        break;
    }
  }

  // Set the fields which are not specified by the policy to default values.
  dict->SetString(DefaultSearchManager::kID,
                  base::Int64ToString(kInvalidTemplateURLID));
  dict->SetInteger(DefaultSearchManager::kPrepopulateID, 0);
  dict->SetString(DefaultSearchManager::kSyncGUID, std::string());
  dict->SetString(DefaultSearchManager::kOriginatingURL, std::string());
  dict->SetBoolean(DefaultSearchManager::kSafeForAutoReplace, true);
  dict->SetDouble(DefaultSearchManager::kDateCreated,
                  base::Time::Now().ToInternalValue());
  dict->SetDouble(DefaultSearchManager::kLastModified,
                  base::Time::Now().ToInternalValue());
  dict->SetInteger(DefaultSearchManager::kUsageCount, 0);
  dict->SetBoolean(DefaultSearchManager::kCreatedByPolicy, true);

  // For the name and keyword, default to the host if not specified.  If
  // there is no host (as is the case with file URLs of the form:
  // "file:///c:/..."), use "_" to guarantee that the keyword is non-empty.
  std::string name, keyword;
  dict->GetString(DefaultSearchManager::kKeyword, &keyword);
  dict->GetString(DefaultSearchManager::kShortName, &name);
  dict->GetString(DefaultSearchManager::kURL, &url);

  std::string host(GURL(url).host());
  if (host.empty())
    host = "_";
  if (name.empty())
    dict->SetString(DefaultSearchManager::kShortName, host);
  if (keyword.empty())
    dict->SetString(DefaultSearchManager::kKeyword, host);

  DefaultSearchManager::AddPrefValueToMap(dict.release(), prefs);
}

void DefaultSearchPolicyHandler::ApplyPolicySettings(const PolicyMap& policies,
                                                     PrefValueMap* prefs) {
  HandleDictionaryPref(policies, prefs);

  if (DefaultSearchProviderIsDisabled(policies)) {
    prefs->SetBoolean(prefs::kDefaultSearchProviderEnabled, false);

    // If default search is disabled, the other fields are ignored.
    prefs->SetString(prefs::kDefaultSearchProviderName, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderSearchURL, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderSuggestURL, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderIconURL, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderEncodings, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderKeyword, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderInstantURL, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderNewTabURL, std::string());
    prefs->SetValue(prefs::kDefaultSearchProviderAlternateURLs,
                    new base::ListValue());
    prefs->SetString(
        prefs::kDefaultSearchProviderSearchTermsReplacementKey, std::string());
    prefs->SetString(prefs::kDefaultSearchProviderImageURL, std::string());
    prefs->SetString(
        prefs::kDefaultSearchProviderSearchURLPostParams, std::string());
    prefs->SetString(
        prefs::kDefaultSearchProviderSuggestURLPostParams, std::string());
    prefs->SetString(
        prefs::kDefaultSearchProviderInstantURLPostParams, std::string());
    prefs->SetString(
        prefs::kDefaultSearchProviderImageURLPostParams, std::string());
  } else {
    // The search URL is required.  The other entries are optional.  Just make
    // sure that they are all specified via policy, so that the regular prefs
    // aren't used.
    const base::Value* dummy;
    std::string url;
    if (DefaultSearchURLIsValid(policies, &dummy, &url)) {

      for (std::vector<TypeCheckingPolicyHandler*>::const_iterator handler =
               handlers_.begin();
           handler != handlers_.end(); ++handler) {
        (*handler)->ApplyPolicySettings(policies, prefs);
      }

      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderSuggestURL);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderIconURL);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderEncodings);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderKeyword);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderInstantURL);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderNewTabURL);
      EnsureListPrefExists(prefs, prefs::kDefaultSearchProviderAlternateURLs);
      EnsureStringPrefExists(
          prefs,
          prefs::kDefaultSearchProviderSearchTermsReplacementKey);
      EnsureStringPrefExists(prefs, prefs::kDefaultSearchProviderImageURL);
      EnsureStringPrefExists(
          prefs,
          prefs::kDefaultSearchProviderSearchURLPostParams);
      EnsureStringPrefExists(
          prefs,
          prefs::kDefaultSearchProviderSuggestURLPostParams);
      EnsureStringPrefExists(
          prefs,
          prefs::kDefaultSearchProviderInstantURLPostParams);
      EnsureStringPrefExists(
          prefs,
          prefs::kDefaultSearchProviderImageURLPostParams);

      // For the name and keyword, default to the host if not specified.  If
      // there is no host (file: URLs?  Not sure), use "_" to guarantee that the
      // keyword is non-empty.
      std::string name, keyword;
      std::string host(GURL(url).host());
      if (host.empty())
        host = "_";
      if (!prefs->GetString(prefs::kDefaultSearchProviderName, &name) ||
          name.empty()) {
        prefs->SetString(prefs::kDefaultSearchProviderName, host);
      }
      if (!prefs->GetString(prefs::kDefaultSearchProviderKeyword, &keyword) ||
          keyword.empty()) {
        prefs->SetString(prefs::kDefaultSearchProviderKeyword, host);
      }

      // And clear the IDs since these are not specified via policy.
      prefs->SetString(prefs::kDefaultSearchProviderID, std::string());
      prefs->SetString(prefs::kDefaultSearchProviderPrepopulateID,
                       std::string());
    }
  }
}

bool DefaultSearchPolicyHandler::CheckIndividualPolicies(
    const PolicyMap& policies,
    PolicyErrorMap* errors) {
  for (std::vector<TypeCheckingPolicyHandler*>::const_iterator handler =
           handlers_.begin();
       handler != handlers_.end(); ++handler) {
    if (!(*handler)->CheckPolicySettings(policies, errors))
      return false;
  }
  return true;
}

bool DefaultSearchPolicyHandler::HasDefaultSearchPolicy(
    const PolicyMap& policies,
    const char* policy_name) {
  return policies.Get(policy_name) != NULL;
}

bool DefaultSearchPolicyHandler::AnyDefaultSearchPoliciesSpecified(
    const PolicyMap& policies) {
  for (std::vector<TypeCheckingPolicyHandler*>::const_iterator handler =
           handlers_.begin();
       handler != handlers_.end(); ++handler) {
    if (policies.Get((*handler)->policy_name()))
      return true;
  }
  return false;
}

bool DefaultSearchPolicyHandler::DefaultSearchProviderIsDisabled(
    const PolicyMap& policies) {
  const base::Value* provider_enabled =
      policies.GetValue(key::kDefaultSearchProviderEnabled);
  bool enabled = true;
  return provider_enabled && provider_enabled->GetAsBoolean(&enabled) &&
      !enabled;
}

bool DefaultSearchPolicyHandler::DefaultSearchURLIsValid(
    const PolicyMap& policies,
    const base::Value** url_value,
    std::string* url_string) {
  *url_value = policies.GetValue(key::kDefaultSearchProviderSearchURL);
  if (!*url_value || !(*url_value)->GetAsString(url_string) ||
      url_string->empty())
    return false;
  TemplateURLData data;
  data.SetURL(*url_string);
  SearchTermsData search_terms_data;
  return TemplateURL(data).SupportsReplacement(search_terms_data);
}

void DefaultSearchPolicyHandler::EnsureStringPrefExists(
    PrefValueMap* prefs,
    const std::string& path) {
  std::string value;
  if (!prefs->GetString(path, &value))
    prefs->SetString(path, value);
}

void DefaultSearchPolicyHandler::EnsureListPrefExists(
    PrefValueMap* prefs,
    const std::string& path) {
  base::Value* value;
  base::ListValue* list_value;
  if (!prefs->GetValue(path, &value) || !value->GetAsList(&list_value))
    prefs->SetValue(path, new base::ListValue());
}

}  // namespace policy