// 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/host_content_settings_map.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <utility>

#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/content_settings/core/browser/content_settings_default_provider.h"
#include "components/content_settings/core/browser/content_settings_details.h"
#include "components/content_settings/core/browser/content_settings_ephemeral_provider.h"
#include "components/content_settings/core/browser/content_settings_info.h"
#include "components/content_settings/core/browser/content_settings_observable_provider.h"
#include "components/content_settings/core/browser/content_settings_policy_provider.h"
#include "components/content_settings/core/browser/content_settings_pref_provider.h"
#include "components/content_settings/core/browser/content_settings_provider.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/user_modifiable_provider.h"
#include "components/content_settings/core/browser/website_settings_registry.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/features.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "net/base/net_errors.h"
#include "net/base/static_cookie_policy.h"
#include "url/gurl.h"

using content_settings::WebsiteSettingsInfo;
using content_settings::ContentSettingsInfo;

namespace {

typedef std::vector<content_settings::Rule> Rules;

typedef std::pair<std::string, std::string> StringPair;

struct ProviderNamesSourceMapEntry {
  const char* provider_name;
  content_settings::SettingSource provider_source;
};

const HostContentSettingsMap::ProviderType kFirstProvider =
    HostContentSettingsMap::POLICY_PROVIDER;
const HostContentSettingsMap::ProviderType kFirstUserModifiableProvider =
    HostContentSettingsMap::NOTIFICATION_ANDROID_PROVIDER;

constexpr ProviderNamesSourceMapEntry kProviderNamesSourceMap[] = {
    {"policy", content_settings::SETTING_SOURCE_POLICY},
    {"supervised_user", content_settings::SETTING_SOURCE_SUPERVISED},
    {"extension", content_settings::SETTING_SOURCE_EXTENSION},
    {"notification_android", content_settings::SETTING_SOURCE_USER},
    {"ephemeral", content_settings::SETTING_SOURCE_USER},
    {"preference", content_settings::SETTING_SOURCE_USER},
    {"default", content_settings::SETTING_SOURCE_USER},
    {"tests", content_settings::SETTING_SOURCE_USER},
    {"tests_other", content_settings::SETTING_SOURCE_USER},
};

static_assert(
    arraysize(kProviderNamesSourceMap) ==
        HostContentSettingsMap::NUM_PROVIDER_TYPES,
    "kProviderNamesSourceMap should have NUM_PROVIDER_TYPES elements");

// Ensure that kFirstUserModifiableProvider is actually the highest precedence
// user modifiable provider.
constexpr bool FirstUserModifiableProviderIsHighestPrecedence() {
  for (size_t i = 0; i < kFirstUserModifiableProvider; ++i) {
    if (kProviderNamesSourceMap[i].provider_source ==
        content_settings::SETTING_SOURCE_USER) {
      return false;
    }
  }
  return kProviderNamesSourceMap[kFirstUserModifiableProvider]
             .provider_source == content_settings::SETTING_SOURCE_USER;
}
static_assert(FirstUserModifiableProviderIsHighestPrecedence(),
              "kFirstUserModifiableProvider is not the highest precedence user "
              "modifiable provider.");

// Returns true if the |content_type| supports a resource identifier.
// Resource identifiers are supported (but not required) for plugins.
bool SupportsResourceIdentifier(ContentSettingsType content_type) {
  return content_type == CONTENT_SETTINGS_TYPE_PLUGINS;
}

bool SchemeCanBeWhitelisted(const std::string& scheme) {
  return scheme == content_settings::kChromeDevToolsScheme ||
         scheme == content_settings::kExtensionScheme ||
         scheme == content_settings::kChromeUIScheme;
}

// Handles inheritance of settings from the regular profile into the incognito
// profile.
std::unique_ptr<base::Value> ProcessIncognitoInheritanceBehavior(
    ContentSettingsType content_type,
    std::unique_ptr<base::Value> value) {
  // Website setting inheritance can be completely disallowed.
  const WebsiteSettingsInfo* website_settings_info =
      content_settings::WebsiteSettingsRegistry::GetInstance()->Get(
          content_type);
  if (website_settings_info &&
      website_settings_info->incognito_behavior() ==
          WebsiteSettingsInfo::DONT_INHERIT_IN_INCOGNITO) {
    return nullptr;
  }

  // Content setting inheritance can be for settings, that are more permissive
  // than the initial value of a content setting.
  const ContentSettingsInfo* content_settings_info =
      content_settings::ContentSettingsRegistry::GetInstance()->Get(
          content_type);
  if (content_settings_info) {
    ContentSettingsInfo::IncognitoBehavior behaviour =
        content_settings_info->incognito_behavior();
    switch (behaviour) {
      case ContentSettingsInfo::INHERIT_IN_INCOGNITO:
        return value;
      case ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE:
        ContentSetting setting =
            content_settings::ValueToContentSetting(value.get());
        const base::Value* initial_value = content_settings_info
            ->website_settings_info()->initial_default_value();
        ContentSetting initial_setting =
            content_settings::ValueToContentSetting(initial_value);
        if (content_settings::IsMorePermissive(setting, initial_setting))
          return content_settings::ContentSettingToValue(initial_setting);
        return value;
    }
  }

  return value;
}

content_settings::PatternPair GetPatternsFromScopingType(
    WebsiteSettingsInfo::ScopingType scoping_type,
    const GURL& primary_url,
    const GURL& secondary_url) {
  DCHECK(!primary_url.is_empty());
  content_settings::PatternPair patterns;

  switch (scoping_type) {
    case WebsiteSettingsInfo::REQUESTING_DOMAIN_ONLY_SCOPE:
      patterns.first = ContentSettingsPattern::FromURL(primary_url);
      patterns.second = ContentSettingsPattern::Wildcard();
      break;
    case WebsiteSettingsInfo::TOP_LEVEL_ORIGIN_ONLY_SCOPE:
    case WebsiteSettingsInfo::REQUESTING_ORIGIN_ONLY_SCOPE:
      patterns.first = ContentSettingsPattern::FromURLNoWildcard(primary_url);
      patterns.second = ContentSettingsPattern::Wildcard();
      break;
    case WebsiteSettingsInfo::REQUESTING_ORIGIN_AND_TOP_LEVEL_ORIGIN_SCOPE:
      DCHECK(!secondary_url.is_empty());
      patterns.first = ContentSettingsPattern::FromURLNoWildcard(primary_url);
      patterns.second =
          ContentSettingsPattern::FromURLNoWildcard(secondary_url);
      break;
  }
  return patterns;
}

content_settings::PatternPair GetPatternsForContentSettingsType(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType type) {
  const WebsiteSettingsInfo* website_settings_info =
      content_settings::WebsiteSettingsRegistry::GetInstance()->Get(type);
  DCHECK(website_settings_info);
  content_settings::PatternPair patterns = GetPatternsFromScopingType(
      website_settings_info->scoping_type(), primary_url, secondary_url);
  return patterns;
}

// This enum is used to collect Flash permission data.
enum class FlashPermissions {
  kFirstTime = 0,
  kRepeated = 1,
  kMaxValue = kRepeated,
};

}  // namespace

HostContentSettingsMap::HostContentSettingsMap(PrefService* prefs,
                                               bool is_incognito_profile,
                                               bool is_guest_profile,
                                               bool store_last_modified)
    : RefcountedKeyedService(base::ThreadTaskRunnerHandle::Get()),
#ifndef NDEBUG
      used_from_thread_id_(base::PlatformThread::CurrentId()),
#endif
      prefs_(prefs),
      is_incognito_(is_incognito_profile || is_guest_profile),
      store_last_modified_(store_last_modified),
      weak_ptr_factory_(this) {
  DCHECK(!(is_incognito_profile && is_guest_profile));

  content_settings::PolicyProvider* policy_provider =
      new content_settings::PolicyProvider(prefs_);
  content_settings_providers_[POLICY_PROVIDER] =
      base::WrapUnique(policy_provider);
  policy_provider->AddObserver(this);

  pref_provider_ = new content_settings::PrefProvider(prefs_, is_incognito_,
                                                      store_last_modified_);
  content_settings_providers_[PREF_PROVIDER] = base::WrapUnique(pref_provider_);
  user_modifiable_providers_.push_back(pref_provider_);
  pref_provider_->AddObserver(this);

  // This ensures that content settings are cleared for the guest profile. This
  // wouldn't be needed except that we used to allow settings to be stored for
  // the guest profile and so we need to ensure those get cleared.
  if (is_guest_profile)
    pref_provider_->ClearPrefs();

  content_settings::EphemeralProvider* ephemeral_provider =
      new content_settings::EphemeralProvider(store_last_modified_);
  content_settings_providers_[EPHEMERAL_PROVIDER] =
      base::WrapUnique(ephemeral_provider);
  user_modifiable_providers_.push_back(ephemeral_provider);
  ephemeral_provider->AddObserver(this);

  auto default_provider = std::make_unique<content_settings::DefaultProvider>(
      prefs_, is_incognito_);
  default_provider->AddObserver(this);
  content_settings_providers_[DEFAULT_PROVIDER] = std::move(default_provider);

  InitializePluginsDataSettings();
  RecordExceptionMetrics();
}

// static
void HostContentSettingsMap::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  // Ensure the content settings are all registered.
  content_settings::ContentSettingsRegistry::GetInstance();

  registry->RegisterIntegerPref(prefs::kContentSettingsWindowLastTabIndex, 0);

  // Register the prefs for the content settings providers.
  content_settings::DefaultProvider::RegisterProfilePrefs(registry);
  content_settings::PrefProvider::RegisterProfilePrefs(registry);
  content_settings::PolicyProvider::RegisterProfilePrefs(registry);
}

void HostContentSettingsMap::RegisterUserModifiableProvider(
    ProviderType type,
    std::unique_ptr<content_settings::UserModifiableProvider> provider) {
  user_modifiable_providers_.push_back(provider.get());
  RegisterProvider(type, std::move(provider));
}

void HostContentSettingsMap::RegisterProvider(
    ProviderType type,
    std::unique_ptr<content_settings::ObservableProvider> provider) {
  DCHECK(!content_settings_providers_[type]);
  provider->AddObserver(this);
  content_settings_providers_[type] = std::move(provider);

#ifndef NDEBUG
  DCHECK_NE(used_from_thread_id_, base::kInvalidThreadId)
      << "Used from multiple threads before initialization complete.";
#endif

  OnContentSettingChanged(ContentSettingsPattern(),
                          ContentSettingsPattern(),
                          CONTENT_SETTINGS_TYPE_DEFAULT,
                          std::string());
}

ContentSetting HostContentSettingsMap::GetDefaultContentSettingFromProvider(
    ContentSettingsType content_type,
    content_settings::ProviderInterface* provider) const {
  std::unique_ptr<content_settings::RuleIterator> rule_iterator(
      provider->GetRuleIterator(content_type, std::string(), false));

  if (rule_iterator) {
    ContentSettingsPattern wildcard = ContentSettingsPattern::Wildcard();
    while (rule_iterator->HasNext()) {
      content_settings::Rule rule = rule_iterator->Next();
      if (rule.primary_pattern == wildcard &&
          rule.secondary_pattern == wildcard) {
        return content_settings::ValueToContentSetting(rule.value.get());
      }
    }
  }
  return CONTENT_SETTING_DEFAULT;
}

ContentSetting HostContentSettingsMap::GetDefaultContentSettingInternal(
    ContentSettingsType content_type,
    ProviderType* provider_type) const {
  DCHECK(provider_type);
  UsedContentSettingsProviders();

  // Iterate through the list of providers and return the first non-NULL value
  // that matches |primary_url| and |secondary_url|.
  for (const auto& provider_pair : content_settings_providers_) {
    if (provider_pair.first == PREF_PROVIDER)
      continue;
    ContentSetting default_setting = GetDefaultContentSettingFromProvider(
        content_type, provider_pair.second.get());
    if (is_incognito_) {
      default_setting = content_settings::ValueToContentSetting(
          ProcessIncognitoInheritanceBehavior(
              content_type,
              content_settings::ContentSettingToValue(default_setting))
              .get());
    }
    if (default_setting != CONTENT_SETTING_DEFAULT) {
      *provider_type = provider_pair.first;
      return default_setting;
    }
  }

  return CONTENT_SETTING_DEFAULT;
}

ContentSetting HostContentSettingsMap::GetDefaultContentSetting(
    ContentSettingsType content_type,
    std::string* provider_id) const {
  ProviderType provider_type = NUM_PROVIDER_TYPES;
  ContentSetting content_setting =
      GetDefaultContentSettingInternal(content_type, &provider_type);
  if (content_setting != CONTENT_SETTING_DEFAULT && provider_id)
    *provider_id = kProviderNamesSourceMap[provider_type].provider_name;
  return content_setting;
}

ContentSetting HostContentSettingsMap::GetContentSetting(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier) const {
  DCHECK(content_settings::ContentSettingsRegistry::GetInstance()->Get(
      content_type));
  std::unique_ptr<base::Value> value = GetWebsiteSetting(
      primary_url, secondary_url, content_type, resource_identifier, nullptr);
  return content_settings::ValueToContentSetting(value.get());
}

ContentSetting HostContentSettingsMap::GetUserModifiableContentSetting(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier) const {
  DCHECK(content_settings::ContentSettingsRegistry::GetInstance()->Get(
      content_type));
  std::unique_ptr<base::Value> value = GetWebsiteSettingInternal(
      primary_url, secondary_url, content_type, resource_identifier,
      kFirstUserModifiableProvider, nullptr);
  return content_settings::ValueToContentSetting(value.get());
}

void HostContentSettingsMap::GetSettingsForOneType(
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    ContentSettingsForOneType* settings) const {
  DCHECK(SupportsResourceIdentifier(content_type) ||
         resource_identifier.empty());
  DCHECK(settings);
  UsedContentSettingsProviders();

  settings->clear();
  for (const auto& provider_pair : content_settings_providers_) {
    // For each provider, iterate first the incognito-specific rules, then the
    // normal rules.
    if (is_incognito_) {
      AddSettingsForOneType(provider_pair.second.get(), provider_pair.first,
                            content_type, resource_identifier, settings, true);
    }
    AddSettingsForOneType(provider_pair.second.get(), provider_pair.first,
                          content_type, resource_identifier, settings, false);
  }
}

void HostContentSettingsMap::SetDefaultContentSetting(
    ContentSettingsType content_type,
    ContentSetting setting) {
  std::unique_ptr<base::Value> value;
  // A value of CONTENT_SETTING_DEFAULT implies deleting the content setting.
  if (setting != CONTENT_SETTING_DEFAULT) {
    DCHECK(content_settings::ContentSettingsRegistry::GetInstance()
               ->Get(content_type)
               ->IsDefaultSettingValid(setting));
    value.reset(new base::Value(setting));
  }
  SetWebsiteSettingCustomScope(ContentSettingsPattern::Wildcard(),
                               ContentSettingsPattern::Wildcard(), content_type,
                               std::string(), std::move(value));
}

void HostContentSettingsMap::SetWebsiteSettingDefaultScope(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    std::unique_ptr<base::Value> value) {
  content_settings::PatternPair patterns = GetPatternsForContentSettingsType(
      primary_url, secondary_url, content_type);
  ContentSettingsPattern primary_pattern = patterns.first;
  ContentSettingsPattern secondary_pattern = patterns.second;
  if (!primary_pattern.IsValid() || !secondary_pattern.IsValid())
    return;

  SetWebsiteSettingCustomScope(primary_pattern, secondary_pattern, content_type,
                               resource_identifier, std::move(value));
}

void HostContentSettingsMap::SetWebsiteSettingCustomScope(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    std::unique_ptr<base::Value> value) {
  DCHECK(SupportsResourceIdentifier(content_type) ||
         resource_identifier.empty());
  // TODO(crbug.com/731126): Verify that assumptions for notification content
  // settings are met.
  UsedContentSettingsProviders();

  for (const auto& provider_pair : content_settings_providers_) {
    if (provider_pair.second->SetWebsiteSetting(
            primary_pattern, secondary_pattern, content_type,
            resource_identifier, value.get())) {
      // If successful then ownership is passed to the provider.
      ignore_result(value.release());
      return;
    }
  }
  NOTREACHED();
}

bool HostContentSettingsMap::CanSetNarrowestContentSetting(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType type) const {
  content_settings::PatternPair patterns =
      GetNarrowestPatterns(primary_url, secondary_url, type);
  return patterns.first.IsValid() && patterns.second.IsValid();
}

void HostContentSettingsMap::SetNarrowestContentSetting(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType type,
    ContentSetting setting) {
  content_settings::PatternPair patterns =
      GetNarrowestPatterns(primary_url, secondary_url, type);

  if (!patterns.first.IsValid() || !patterns.second.IsValid())
    return;

  SetContentSettingCustomScope(patterns.first, patterns.second, type,
                               std::string(), setting);
}

content_settings::PatternPair HostContentSettingsMap::GetNarrowestPatterns (
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType type) const {
  // Permission settings are specified via rules. There exists always at least
  // one rule for the default setting. Get the rule that currently defines
  // the permission for the given permission |type|. Then test whether the
  // existing rule is more specific than the rule we are about to create. If
  // the existing rule is more specific, than change the existing rule instead
  // of creating a new rule that would be hidden behind the existing rule.
  content_settings::SettingInfo info;
  std::unique_ptr<base::Value> v = GetWebsiteSettingInternal(
      primary_url, secondary_url, type, std::string(), kFirstProvider, &info);
  if (info.source != content_settings::SETTING_SOURCE_USER) {
    // Return an invalid pattern if the current setting is not a user setting
    // and thus can't be changed.
    return content_settings::PatternPair();
  }

  content_settings::PatternPair patterns = GetPatternsForContentSettingsType(
      primary_url, secondary_url, type);

  ContentSettingsPattern::Relation r1 =
      info.primary_pattern.Compare(patterns.first);
  if (r1 == ContentSettingsPattern::PREDECESSOR) {
    patterns.first = info.primary_pattern;
  } else if (r1 == ContentSettingsPattern::IDENTITY) {
    ContentSettingsPattern::Relation r2 =
        info.secondary_pattern.Compare(patterns.second);
    DCHECK(r2 != ContentSettingsPattern::DISJOINT_ORDER_POST &&
           r2 != ContentSettingsPattern::DISJOINT_ORDER_PRE);
    if (r2 == ContentSettingsPattern::PREDECESSOR)
      patterns.second = info.secondary_pattern;
  }

  return patterns;
}

void HostContentSettingsMap::SetContentSettingCustomScope(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    ContentSetting setting) {
  DCHECK(content_settings::ContentSettingsRegistry::GetInstance()->Get(
      content_type));

  // Record stats on Flash permission grants with ephemeral storage.
  if (content_type == CONTENT_SETTINGS_TYPE_PLUGINS &&
      setting == CONTENT_SETTING_ALLOW &&
      base::FeatureList::IsEnabled(
          content_settings::features::kEnableEphemeralFlashPermission)) {
    GURL url(primary_pattern.ToString());
    ContentSettingsPattern temp_patterns[2];
    std::unique_ptr<base::Value> value(GetContentSettingValueAndPatterns(
        content_settings_providers_[PREF_PROVIDER].get(), url, url,
        CONTENT_SETTINGS_TYPE_PLUGINS_DATA, resource_identifier, is_incognito_,
        temp_patterns, temp_patterns + 1));

    UMA_HISTOGRAM_ENUMERATION(
        "ContentSettings.EphemeralFlashPermission",
        value ? FlashPermissions::kRepeated : FlashPermissions::kFirstTime);
  }

  std::unique_ptr<base::Value> value;
  // A value of CONTENT_SETTING_DEFAULT implies deleting the content setting.
  if (setting != CONTENT_SETTING_DEFAULT) {
    DCHECK(content_settings::ContentSettingsRegistry::GetInstance()
               ->Get(content_type)
               ->IsSettingValid(setting));
    value.reset(new base::Value(setting));
  }
  SetWebsiteSettingCustomScope(primary_pattern, secondary_pattern, content_type,
                               resource_identifier, std::move(value));
}

void HostContentSettingsMap::SetContentSettingDefaultScope(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    ContentSetting setting) {
  content_settings::PatternPair patterns = GetPatternsForContentSettingsType(
      primary_url, secondary_url, content_type);

  ContentSettingsPattern primary_pattern = patterns.first;
  ContentSettingsPattern secondary_pattern = patterns.second;
  if (!primary_pattern.IsValid() || !secondary_pattern.IsValid())
    return;

  SetContentSettingCustomScope(primary_pattern, secondary_pattern, content_type,
                               resource_identifier, setting);
}

base::WeakPtr<HostContentSettingsMap> HostContentSettingsMap::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void HostContentSettingsMap::SetClockForTesting(base::Clock* clock) {
  pref_provider_->SetClockForTesting(clock);
}

void HostContentSettingsMap::RecordExceptionMetrics() {
  for (const content_settings::WebsiteSettingsInfo* info :
       *content_settings::WebsiteSettingsRegistry::GetInstance()) {
    ContentSettingsType content_type = info->type();
    const std::string type_name = info->name();

    ContentSettingsForOneType settings;
    GetSettingsForOneType(content_type, std::string(), &settings);
    size_t num_exceptions = 0;
    for (const ContentSettingPatternSource& setting_entry : settings) {
      // Skip default settings.
      if (setting_entry.primary_pattern == ContentSettingsPattern::Wildcard() &&
          setting_entry.secondary_pattern ==
              ContentSettingsPattern::Wildcard()) {
        continue;
      }

      ContentSettingsPattern::SchemeType scheme =
          setting_entry.primary_pattern.GetScheme();
      UMA_HISTOGRAM_ENUMERATION("ContentSettings.ExceptionScheme", scheme,
                                ContentSettingsPattern::SCHEME_MAX);

      if (scheme == ContentSettingsPattern::SCHEME_FILE) {
        UMA_HISTOGRAM_BOOLEAN("ContentSettings.ExceptionSchemeFile.HasPath",
                              setting_entry.primary_pattern.HasPath());
        size_t num_values;
        int histogram_value =
            ContentSettingTypeToHistogramValue(content_type, &num_values);
        if (setting_entry.primary_pattern.HasPath()) {
          UMA_HISTOGRAM_EXACT_LINEAR(
              "ContentSettings.ExceptionSchemeFile.Type.WithPath",
              histogram_value, num_values);
        } else {
          UMA_HISTOGRAM_EXACT_LINEAR(
              "ContentSettings.ExceptionSchemeFile.Type.WithoutPath",
              histogram_value, num_values);
        }
      }

      if (setting_entry.source == "preference")
        ++num_exceptions;
    }

    std::string histogram_name =
        "ContentSettings.Exceptions." + type_name;

    base::HistogramBase* histogram_pointer = base::Histogram::FactoryGet(
        histogram_name, 1, 1000, 30,
        base::HistogramBase::kUmaTargetedHistogramFlag);
    histogram_pointer->Add(num_exceptions);
  }
}

void HostContentSettingsMap::AddObserver(content_settings::Observer* observer) {
  observers_.AddObserver(observer);
}

void HostContentSettingsMap::RemoveObserver(
    content_settings::Observer* observer) {
  observers_.RemoveObserver(observer);
}

void HostContentSettingsMap::FlushLossyWebsiteSettings() {
  prefs_->SchedulePendingLossyWrites();
}

void HostContentSettingsMap::ClearSettingsForOneType(
    ContentSettingsType content_type) {
  UsedContentSettingsProviders();
  for (const auto& provider_pair : content_settings_providers_)
    provider_pair.second->ClearAllContentSettingsRules(content_type);
  FlushLossyWebsiteSettings();
}

base::Time HostContentSettingsMap::GetSettingLastModifiedDate(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsType content_type) const {
  base::Time most_recent_time;
  for (auto* provider : user_modifiable_providers_) {
    base::Time time = provider->GetWebsiteSettingLastModified(
        primary_pattern, secondary_pattern, content_type, std::string());
    most_recent_time = std::max(time, most_recent_time);
  }
  return most_recent_time;
}

void HostContentSettingsMap::ClearSettingsForOneTypeWithPredicate(
    ContentSettingsType content_type,
    base::Time begin_time,
    base::Time end_time,
    const PatternSourcePredicate& pattern_predicate) {
  if (pattern_predicate.is_null() && begin_time.is_null() &&
      (end_time.is_null() || end_time.is_max())) {
    ClearSettingsForOneType(content_type);
    return;
  }
  UsedContentSettingsProviders();
  ContentSettingsForOneType settings;
  GetSettingsForOneType(content_type, std::string(), &settings);
  for (const ContentSettingPatternSource& setting : settings) {
    if (pattern_predicate.is_null() ||
        pattern_predicate.Run(setting.primary_pattern,
                              setting.secondary_pattern)) {
      for (auto* provider : user_modifiable_providers_) {
        base::Time last_modified = provider->GetWebsiteSettingLastModified(
            setting.primary_pattern, setting.secondary_pattern, content_type,
            std::string());
        if (last_modified >= begin_time &&
            (last_modified < end_time || end_time.is_null())) {
          provider->SetWebsiteSetting(setting.primary_pattern,
                                      setting.secondary_pattern, content_type,
                                      std::string(), nullptr);
        }
      }
    }
  }
}

void HostContentSettingsMap::OnContentSettingChanged(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsType content_type,
    const std::string& resource_identifier) {
  for (content_settings::Observer& observer : observers_) {
    observer.OnContentSettingChanged(primary_pattern, secondary_pattern,
                                     content_type, resource_identifier);
  }
}

HostContentSettingsMap::~HostContentSettingsMap() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!prefs_);
}

void HostContentSettingsMap::ShutdownOnUIThread() {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(prefs_);
  prefs_ = nullptr;
  for (const auto& provider_pair : content_settings_providers_)
    provider_pair.second->ShutdownOnUIThread();
}

void HostContentSettingsMap::AddSettingsForOneType(
    const content_settings::ProviderInterface* provider,
    ProviderType provider_type,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    ContentSettingsForOneType* settings,
    bool incognito) const {
  std::unique_ptr<content_settings::RuleIterator> rule_iterator(
      provider->GetRuleIterator(content_type, resource_identifier, incognito));
  if (!rule_iterator)
    return;

  while (rule_iterator->HasNext()) {
    const content_settings::Rule& rule = rule_iterator->Next();
    settings->emplace_back(
        rule.primary_pattern, rule.secondary_pattern, rule.value->Clone(),
        kProviderNamesSourceMap[provider_type].provider_name, incognito);
  }
}

void HostContentSettingsMap::UsedContentSettingsProviders() const {
#ifndef NDEBUG
  if (used_from_thread_id_ == base::kInvalidThreadId)
    return;

  if (base::PlatformThread::CurrentId() != used_from_thread_id_)
    used_from_thread_id_ = base::kInvalidThreadId;
#endif
}

std::unique_ptr<base::Value> HostContentSettingsMap::GetWebsiteSetting(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    content_settings::SettingInfo* info) const {
  DCHECK(SupportsResourceIdentifier(content_type) ||
         resource_identifier.empty());

  // Check if the requested setting is whitelisted.
  // TODO(raymes): Move this into GetContentSetting. This has nothing to do with
  // website settings
  const content_settings::ContentSettingsInfo* content_settings_info =
      content_settings::ContentSettingsRegistry::GetInstance()->Get(
          content_type);
  if (content_settings_info) {
    for (const std::string& scheme :
         content_settings_info->whitelisted_schemes()) {
      DCHECK(SchemeCanBeWhitelisted(scheme));

      if (primary_url.SchemeIs(scheme)) {
        if (info) {
          info->source = content_settings::SETTING_SOURCE_WHITELIST;
          info->primary_pattern = ContentSettingsPattern::Wildcard();
          info->secondary_pattern = ContentSettingsPattern::Wildcard();
        }
        return std::unique_ptr<base::Value>(
            new base::Value(CONTENT_SETTING_ALLOW));
      }
    }
  }

  return GetWebsiteSettingInternal(primary_url, secondary_url, content_type,
                                   resource_identifier, kFirstProvider, info);
}

// static
HostContentSettingsMap::ProviderType
HostContentSettingsMap::GetProviderTypeFromSource(const std::string& source) {
  for (size_t i = 0; i < arraysize(kProviderNamesSourceMap); ++i) {
    if (source == kProviderNamesSourceMap[i].provider_name)
      return static_cast<ProviderType>(i);
  }

  NOTREACHED();
  return DEFAULT_PROVIDER;
}

std::unique_ptr<base::Value> HostContentSettingsMap::GetWebsiteSettingInternal(
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    ProviderType first_provider_to_search,
    content_settings::SettingInfo* info) const {
  UsedContentSettingsProviders();
  ContentSettingsPattern* primary_pattern = nullptr;
  ContentSettingsPattern* secondary_pattern = nullptr;
  if (info) {
    primary_pattern = &info->primary_pattern;
    secondary_pattern = &info->secondary_pattern;
  }

  // The list of |content_settings_providers_| is ordered according to their
  // precedence.
  auto it = content_settings_providers_.lower_bound(first_provider_to_search);
  for (; it != content_settings_providers_.end(); ++it) {
    std::unique_ptr<base::Value> value = GetContentSettingValueAndPatterns(
        it->second.get(), primary_url, secondary_url, content_type,
        resource_identifier, is_incognito_, primary_pattern, secondary_pattern);
    if (value) {
      if (info)
        info->source = kProviderNamesSourceMap[it->first].provider_source;
      return value;
    }
  }

  if (info) {
    info->source = content_settings::SETTING_SOURCE_NONE;
    info->primary_pattern = ContentSettingsPattern();
    info->secondary_pattern = ContentSettingsPattern();
  }
  return std::unique_ptr<base::Value>();
}

// static
std::unique_ptr<base::Value>
HostContentSettingsMap::GetContentSettingValueAndPatterns(
    const content_settings::ProviderInterface* provider,
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsType content_type,
    const std::string& resource_identifier,
    bool include_incognito,
    ContentSettingsPattern* primary_pattern,
    ContentSettingsPattern* secondary_pattern) {
  if (include_incognito) {
    // Check incognito-only specific settings. It's essential that the
    // |RuleIterator| gets out of scope before we get a rule iterator for the
    // normal mode.
    std::unique_ptr<content_settings::RuleIterator> incognito_rule_iterator(
        provider->GetRuleIterator(content_type, resource_identifier,
                                  true /* incognito */));
    std::unique_ptr<base::Value> value = GetContentSettingValueAndPatterns(
        incognito_rule_iterator.get(), primary_url, secondary_url,
        primary_pattern, secondary_pattern);
    if (value)
      return value;
  }
  // No settings from the incognito; use the normal mode.
  std::unique_ptr<content_settings::RuleIterator> rule_iterator(
      provider->GetRuleIterator(content_type, resource_identifier,
                                false /* incognito */));
  std::unique_ptr<base::Value> value = GetContentSettingValueAndPatterns(
      rule_iterator.get(), primary_url, secondary_url, primary_pattern,
      secondary_pattern);
  if (value && include_incognito)
    value = ProcessIncognitoInheritanceBehavior(content_type, std::move(value));
  return value;
}

// static
std::unique_ptr<base::Value>
HostContentSettingsMap::GetContentSettingValueAndPatterns(
    content_settings::RuleIterator* rule_iterator,
    const GURL& primary_url,
    const GURL& secondary_url,
    ContentSettingsPattern* primary_pattern,
    ContentSettingsPattern* secondary_pattern) {
  if (rule_iterator) {
    while (rule_iterator->HasNext()) {
      const content_settings::Rule& rule = rule_iterator->Next();
      if (rule.primary_pattern.Matches(primary_url) &&
          rule.secondary_pattern.Matches(secondary_url)) {
        if (primary_pattern)
          *primary_pattern = rule.primary_pattern;
        if (secondary_pattern)
          *secondary_pattern = rule.secondary_pattern;
        return base::WrapUnique(rule.value->DeepCopy());
      }
    }
  }
  return std::unique_ptr<base::Value>();
}

void HostContentSettingsMap::InitializePluginsDataSettings() {
  if (!content_settings::WebsiteSettingsRegistry::GetInstance()->Get(
          CONTENT_SETTINGS_TYPE_PLUGINS_DATA)) {
    return;
  }
  ContentSettingsForOneType host_settings;
  GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS_DATA, std::string(),
                        &host_settings);
  if (host_settings.empty()) {
    GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS, std::string(),
                          &host_settings);
    for (ContentSettingPatternSource pattern : host_settings) {
      if (pattern.source != "preference")
        continue;
      const GURL primary(pattern.primary_pattern.ToString());
      if (!primary.is_valid())
        continue;
      DCHECK_EQ(ContentSettingsPattern::Relation::IDENTITY,
                ContentSettingsPattern::Wildcard().Compare(
                    pattern.secondary_pattern));
      auto dict = std::make_unique<base::DictionaryValue>();
      constexpr char kFlagKey[] = "flashPreviouslyChanged";
      dict->SetKey(kFlagKey, base::Value(true));
      SetWebsiteSettingDefaultScope(primary, primary,
                                    CONTENT_SETTINGS_TYPE_PLUGINS_DATA,
                                    std::string(), std::move(dict));
    }
  }
}
