// Copyright (c) 2012 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/content_settings/core/browser/content_settings_default_provider.h"

#include <memory>
#include <string>
#include <vector>

#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "components/content_settings/core/browser/content_settings_info.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/browser/website_settings_info.h"
#include "components/content_settings/core/browser/website_settings_registry.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "url/gurl.h"

namespace content_settings {

namespace {

// These settings are no longer used, and should be deleted on profile startup.
#if !defined(OS_IOS)
const char kObsoleteFullscreenDefaultPref[] =
    "profile.default_content_setting_values.fullscreen";
#if !defined(OS_ANDROID)
const char kObsoleteMouseLockDefaultPref[] =
    "profile.default_content_setting_values.mouselock";
#endif  // !defined(OS_ANDROID)
#endif  // !defined(OS_IOS)

ContentSetting GetDefaultValue(const WebsiteSettingsInfo* info) {
  const base::Value* initial_default = info->initial_default_value();
  if (!initial_default)
    return CONTENT_SETTING_DEFAULT;
  int result = 0;
  bool success = initial_default->GetAsInteger(&result);
  DCHECK(success);
  return static_cast<ContentSetting>(result);
}

ContentSetting GetDefaultValue(ContentSettingsType type) {
  return GetDefaultValue(WebsiteSettingsRegistry::GetInstance()->Get(type));
}

const std::string& GetPrefName(ContentSettingsType type) {
  return WebsiteSettingsRegistry::GetInstance()
      ->Get(type)
      ->default_value_pref_name();
}

class DefaultRuleIterator : public RuleIterator {
 public:
  explicit DefaultRuleIterator(const base::Value* value) {
    if (value)
      value_.reset(value->DeepCopy());
  }

  bool HasNext() const override { return !!value_; }

  Rule Next() override {
    DCHECK(HasNext());
    return Rule(ContentSettingsPattern::Wildcard(),
                ContentSettingsPattern::Wildcard(),
                value_.release());
  }

 private:
  std::unique_ptr<base::Value> value_;
};

}  // namespace

// static
void DefaultProvider::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  // Register the default settings' preferences.
  WebsiteSettingsRegistry* website_settings =
      WebsiteSettingsRegistry::GetInstance();
  for (const WebsiteSettingsInfo* info : *website_settings) {
    registry->RegisterIntegerPref(info->default_value_pref_name(),
                                  GetDefaultValue(info),
                                  info->GetPrefRegistrationFlags());
  }

  // Obsolete prefs -------------------------------------------------------

  // These prefs have been removed, but need to be registered so they can
  // be deleted on startup.
#if !defined(OS_IOS)
  registry->RegisterIntegerPref(
      kObsoleteFullscreenDefaultPref, 0,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#if !defined(OS_ANDROID)
  registry->RegisterIntegerPref(
      kObsoleteMouseLockDefaultPref, 0,
      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
#endif  // !defined(OS_ANDROID)
#endif  // !defined(OS_IOS)
}

DefaultProvider::DefaultProvider(PrefService* prefs, bool incognito)
    : prefs_(prefs),
      is_incognito_(incognito),
      updating_preferences_(false) {
  DCHECK(prefs_);

  // Remove the obsolete preferences from the pref file.
  DiscardObsoletePreferences();

  // Read global defaults.
  ReadDefaultSettings();

  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultCookiesSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_COOKIES))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultPopupsSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_POPUPS))),
      CONTENT_SETTING_NUM_SETTINGS);
#if !defined(OS_IOS) && !defined(OS_ANDROID)
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultImagesSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_IMAGES))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultPluginsSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_PLUGINS))),
      CONTENT_SETTING_NUM_SETTINGS);
#endif

#if !defined(OS_IOS)
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultJavaScriptSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_JAVASCRIPT))),
      CONTENT_SETTING_NUM_SETTINGS);

  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultLocationSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_GEOLOCATION))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultNotificationsSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS))),
      CONTENT_SETTING_NUM_SETTINGS);

  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultMediaStreamMicSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultMediaStreamCameraSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultMIDISysExSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_MIDI_SYSEX))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultWebBluetoothGuardSetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_BLUETOOTH_GUARD))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION(
      "ContentSettings.DefaultAutoplaySetting",
      IntToContentSetting(prefs_->GetInteger(
          GetPrefName(CONTENT_SETTINGS_TYPE_AUTOPLAY))),
      CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION("ContentSettings.DefaultSubresourceFilterSetting",
                            IntToContentSetting(prefs_->GetInteger(
                                GetPrefName(CONTENT_SETTINGS_TYPE_ADS))),
                            CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION("ContentSettings.DefaultSoundSetting",
                            IntToContentSetting(prefs_->GetInteger(
                                GetPrefName(CONTENT_SETTINGS_TYPE_SOUND))),
                            CONTENT_SETTING_NUM_SETTINGS);
  UMA_HISTOGRAM_ENUMERATION("ContentSettings.DefaultUsbGuardSetting",
                            IntToContentSetting(prefs_->GetInteger(
                                GetPrefName(CONTENT_SETTINGS_TYPE_USB_GUARD))),
                            CONTENT_SETTING_NUM_SETTINGS);
#endif
  pref_change_registrar_.Init(prefs_);
  PrefChangeRegistrar::NamedChangeCallback callback = base::Bind(
      &DefaultProvider::OnPreferenceChanged, base::Unretained(this));
  WebsiteSettingsRegistry* website_settings =
      WebsiteSettingsRegistry::GetInstance();
  for (const WebsiteSettingsInfo* info : *website_settings)
    pref_change_registrar_.Add(info->default_value_pref_name(), callback);
}

DefaultProvider::~DefaultProvider() {
}

bool DefaultProvider::SetWebsiteSetting(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsType content_type,
    const ResourceIdentifier& resource_identifier,
    base::Value* in_value) {
  DCHECK(CalledOnValidThread());
  DCHECK(prefs_);

  // Ignore non default settings
  if (primary_pattern != ContentSettingsPattern::Wildcard() ||
      secondary_pattern != ContentSettingsPattern::Wildcard()) {
    return false;
  }

  // Put |in_value| in a scoped pointer to ensure that it gets cleaned up
  // properly if we don't pass on the ownership.
  std::unique_ptr<base::Value> value(in_value);

  // The default settings may not be directly modified for OTR sessions.
  // Instead, they are synced to the main profile's setting.
  if (is_incognito_)
    return true;

  {
    base::AutoReset<bool> auto_reset(&updating_preferences_, true);
    // Lock the memory map access, so that values are not read by
    // |GetRuleIterator| at the same time as they are written here. Do not lock
    // the preference access though; preference updates send out notifications
    // whose callbacks may try to reacquire the lock on the same thread.
    {
      base::AutoLock lock(lock_);
      ChangeSetting(content_type, value.get());
    }
    WriteToPref(content_type, value.get());
  }

  NotifyObservers(ContentSettingsPattern(),
                  ContentSettingsPattern(),
                  content_type,
                  ResourceIdentifier());

  return true;
}

std::unique_ptr<RuleIterator> DefaultProvider::GetRuleIterator(
    ContentSettingsType content_type,
    const ResourceIdentifier& resource_identifier,
    bool incognito) const {
  // The default provider never has incognito-specific settings.
  if (incognito)
    return nullptr;

  base::AutoLock lock(lock_);
  if (!resource_identifier.empty())
    return nullptr;

  auto it = default_settings_.find(content_type);
  if (it == default_settings_.end()) {
    NOTREACHED();
    return nullptr;
  }
  return std::make_unique<DefaultRuleIterator>(it->second.get());
}

void DefaultProvider::ClearAllContentSettingsRules(
    ContentSettingsType content_type) {
  // TODO(markusheintz): This method is only called when the
  // |DesktopNotificationService| calls |ClearAllSettingsForType| method on the
  // |HostContentSettingsMap|. Don't implement this method yet, otherwise the
  // default notification settings will be cleared as well.
}

void DefaultProvider::ShutdownOnUIThread() {
  DCHECK(CalledOnValidThread());
  DCHECK(prefs_);
  RemoveAllObservers();
  pref_change_registrar_.RemoveAll();
  prefs_ = nullptr;
}

void DefaultProvider::ReadDefaultSettings() {
  base::AutoLock lock(lock_);
  WebsiteSettingsRegistry* website_settings =
      WebsiteSettingsRegistry::GetInstance();
  for (const WebsiteSettingsInfo* info : *website_settings)
    ChangeSetting(info->type(), ReadFromPref(info->type()).get());
}

bool DefaultProvider::IsValueEmptyOrDefault(ContentSettingsType content_type,
                                            base::Value* value) {
  return !value ||
         ValueToContentSetting(value) == GetDefaultValue(content_type);
}

void DefaultProvider::ChangeSetting(ContentSettingsType content_type,
                                    base::Value* value) {
  const ContentSettingsInfo* info =
      ContentSettingsRegistry::GetInstance()->Get(content_type);
  DCHECK(!info || !value ||
         info->IsDefaultSettingValid(ValueToContentSetting(value)));
  default_settings_[content_type] =
      value ? base::WrapUnique(value->DeepCopy())
            : ContentSettingToValue(GetDefaultValue(content_type));
}

void DefaultProvider::WriteToPref(ContentSettingsType content_type,
                                  base::Value* value) {
  if (IsValueEmptyOrDefault(content_type, value)) {
    prefs_->ClearPref(GetPrefName(content_type));
    return;
  }

  int int_value = GetDefaultValue(content_type);
  bool is_integer = value->GetAsInteger(&int_value);
  DCHECK(is_integer);
  prefs_->SetInteger(GetPrefName(content_type), int_value);
}

void DefaultProvider::OnPreferenceChanged(const std::string& name) {
  DCHECK(CalledOnValidThread());
  if (updating_preferences_)
    return;

  // Find out which content setting the preference corresponds to.
  ContentSettingsType content_type = CONTENT_SETTINGS_TYPE_DEFAULT;

  WebsiteSettingsRegistry* website_settings =
      WebsiteSettingsRegistry::GetInstance();
  for (const WebsiteSettingsInfo* info : *website_settings) {
    if (info->default_value_pref_name() == name) {
      content_type = info->type();
      break;
    }
  }

  if (content_type == CONTENT_SETTINGS_TYPE_DEFAULT) {
    NOTREACHED() << "A change of the preference " << name << " was observed, "
                    "but the preference could not be mapped to a content "
                    "settings type.";
    return;
  }

  {
    base::AutoReset<bool> auto_reset(&updating_preferences_, true);
    // Lock the memory map access, so that values are not read by
    // |GetRuleIterator| at the same time as they are written here. Do not lock
    // the preference access though; preference updates send out notifications
    // whose callbacks may try to reacquire the lock on the same thread.
    {
      base::AutoLock lock(lock_);
      ChangeSetting(content_type, ReadFromPref(content_type).get());
    }
  }

  NotifyObservers(ContentSettingsPattern(),
                  ContentSettingsPattern(),
                  content_type,
                  ResourceIdentifier());
}

std::unique_ptr<base::Value> DefaultProvider::ReadFromPref(
    ContentSettingsType content_type) {
  int int_value = prefs_->GetInteger(GetPrefName(content_type));
  return ContentSettingToValue(IntToContentSetting(int_value));
}

void DefaultProvider::DiscardObsoletePreferences() {
  if (is_incognito_)
    return;
  // These prefs were never stored on iOS/Android so they don't need to be
  // deleted.
#if !defined(OS_IOS)
  prefs_->ClearPref(kObsoleteFullscreenDefaultPref);
#if !defined(OS_ANDROID)
  prefs_->ClearPref(kObsoleteMouseLockDefaultPref);

  // ALLOW-by-default is an obsolete pref value for plugins (Flash). Erase that
  // pref and fall back to the default behavior - but preserve other values.
  const std::string& plugins_pref = GetPrefName(CONTENT_SETTINGS_TYPE_PLUGINS);
  if (IntToContentSetting(prefs_->GetInteger(plugins_pref)) ==
      ContentSetting::CONTENT_SETTING_ALLOW) {
    prefs_->ClearPref(plugins_pref);
  }
#endif  // !defined(OS_ANDROID)
#endif  // !defined(OS_IOS)
}

}  // namespace content_settings
