// Copyright 2019 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_pref.h"

#include <memory>
#include <utility>

#include "base/bind_helpers.h"
#include "base/stl_util.h"
#include "base/test/gtest_util.h"
#include "base/values.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/test/content_settings_test_utils.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "ppapi/buildflags/buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace content_settings {
namespace {

// Test content setting pattern pairs in string format. The percent-encoded
// sequence "%61" should get canonicalized to the letter 'a'.
constexpr char kTestPatternCanonicalAlpha[] = "https://alpha.com,*";
constexpr char kTestPatternNonCanonicalAlpha1[] = "https://%61lpha.com,*";
constexpr char kTestPatternNonCanonicalAlpha2[] = "https://alph%61.com,*";
constexpr char kTestPatternCanonicalBeta[] = "https://beta.com,*";
constexpr char kTestPatternNonCanonicalBeta[] = "https://bet%61.com,*";

constexpr char kTestContentSettingPrefName[] = "content_settings.test";

constexpr char kLastModifiedKey[] = "last_modified";
constexpr char kSettingKey[] = "setting";
constexpr char kTagKey[] = "tag";

#if BUILDFLAG(ENABLE_PLUGINS)
constexpr char kPluginsContentSettingPrefName[] = "content_settings.plugins";
constexpr char kPerResourceTag[] = "per_resource";

// Tests that a particular pref has the expected values with and without a
// resource id
void LegacyPersistedPluginTests(
    ContentSettingsPref* content_settings_pref,
    const std::string& pattern,
    const GURL& host,
    const std::string& resource,
    ContentSetting no_resource_id_perf_expected_value,
    ContentSetting with_resource_id_perf_expected_value) {
  auto pattern_pair = ParsePatternString(pattern);
  // Retrieving the pref without a resource id for pattern works and
  // its value is the expected one.
  EXPECT_EQ(no_resource_id_perf_expected_value,
            content_settings::ValueToContentSetting(
                content_settings::TestUtils::GetContentSettingValueAndPatterns(
                    content_settings_pref->GetRuleIterator("", false).get(),
                    host, GURL(), &(pattern_pair.first), &(pattern_pair.second))
                    .get()));

  // Retrieving the pref with a resource id will throw.
  EXPECT_DCHECK_DEATH(
      content_settings_pref->GetRuleIterator(resource, false)->HasNext());

  // Allow resource ids for testing in order to test that the perf was correctly
  // loaded from the json. This basically verifies that we did build a correct
  // json and it was parsed and loaded without any issues.
  content_settings_pref->set_allow_resource_identifiers_for_testing();
  EXPECT_EQ(
      with_resource_id_perf_expected_value,
      content_settings::ValueToContentSetting(
          content_settings::TestUtils::GetContentSettingValueAndPatterns(
              content_settings_pref->GetRuleIterator(resource, false).get(),
              host, GURL(), &(pattern_pair.first), &(pattern_pair.second))
              .get()));
  content_settings_pref->reset_allow_resource_identifiers_for_testing();
}

#endif  // BUILDFLAG(ENABLE_PLUGINS)

// Creates a JSON dictionary representing a dummy content setting exception
// value in preferences. The setting will be marked with the |tag| like so:
//
//   value = {
//     "last_modified": "...",
//     "setting": {
//       "tag": "...",
//     }
//   }
base::Value CreateDummyContentSettingValue(base::StringPiece tag) {
  base::Value setting(base::Value::Type::DICTIONARY);
  setting.SetKey(kTagKey, base::Value(tag));

  base::Value pref_value(base::Value::Type::DICTIONARY);
  pref_value.SetKey(kLastModifiedKey, base::Value("13189876543210000"));
  pref_value.SetKey(kSettingKey, std::move(setting));
  return pref_value;
}

// Given the JSON dictionary representing the "setting" stored under a content
// setting exception value, returns the tag.
std::string GetTagFromDummyContentSetting(const base::Value& setting) {
  const auto* tag = setting.FindKey(kTagKey);
  return tag ? tag->GetString() : std::string();
}

// Given the JSON dictionary representing a content setting exception value,
// returns the tag.
std::string GetTagFromDummyContentSettingValue(const base::Value& pref_value) {
  const auto* setting = pref_value.FindKey(kSettingKey);
  return setting ? GetTagFromDummyContentSetting(*setting) : std::string();
}

}  // namespace

TEST(ContentSettingsPref, CanonicalizationWhileReadingFromPrefs) {
  // Canonical/non-canonical patterns originally in preferences.
  constexpr const char* kTestOriginalPatterns[] = {
      kTestPatternNonCanonicalAlpha1,
      kTestPatternNonCanonicalAlpha2,
      kTestPatternCanonicalBeta,
      kTestPatternNonCanonicalBeta,
  };

  // Upon construction, ContentSettingPref reads all content setting exception
  // data stored in Preferences for a given content setting. This process also
  // has the side effect that it migrates all data keyed under non-canonical
  // content setting pattern pairs to be keyed under the corresponding canoncial
  // pattern pair, both in Preferences, as well as in ContentSettingPref's
  // in-memory |value_map| representation. There are two edge cases here:
  //
  //   1) If multiple non-canonical patterns map to the same canonical pattern,
  //      the data for the last read pattern is retained, the rest thrown away.
  //   2) We ignore and delete non-canonical pattern pairs if a canonical one
  //      already exists.
  //
  // With regard to the test data, NonCanonicalAlpha1 and NonCanonicalAlpha2
  // would both map to CanonicalAlpha, so the value for the latter should be
  // retained.
  //
  // NonCanonicalBeta would be canonicalized to CanonicalBeta, but because there
  // is already a value under that key, the latter should be retained and the
  // non-canonical value thrown away.
  using CanonicalPatternToTag = std::pair<std::string, std::string>;
  const std::vector<CanonicalPatternToTag> kExpectedPatternsToTags = {
      {kTestPatternCanonicalAlpha, kTestPatternNonCanonicalAlpha2},
      {kTestPatternCanonicalBeta, kTestPatternCanonicalBeta},
  };

  auto original_pref_value = std::make_unique<base::DictionaryValue>();
  for (const auto* pattern : kTestOriginalPatterns) {
    original_pref_value->SetKey(pattern,
                                CreateDummyContentSettingValue(pattern));
  }

  TestingPrefServiceSimple prefs;
  prefs.registry()->RegisterDictionaryPref(kTestContentSettingPrefName);
  prefs.SetUserPref(kTestContentSettingPrefName,
                    std::move(original_pref_value));

  PrefChangeRegistrar registrar;
  registrar.Init(&prefs);
  ContentSettingsPref content_settings_pref(
      CONTENT_SETTINGS_TYPE_MEDIA_ENGAGEMENT, &prefs, &registrar,
      kTestContentSettingPrefName, false, base::DoNothing());

  // Verify that the |value_map| contains the expected content setting patterns
  // and setting.

  std::vector<CanonicalPatternToTag> patterns_to_tags_in_memory;
  auto rule_iterator = content_settings_pref.GetRuleIterator(
      std::string() /* resource_identifier */, false /* is_incognito */);
  while (rule_iterator->HasNext()) {
    auto rule = rule_iterator->Next();
    patterns_to_tags_in_memory.emplace_back(
        CreatePatternString(rule.primary_pattern, rule.secondary_pattern),
        GetTagFromDummyContentSetting(rule.value));
  }

  EXPECT_THAT(patterns_to_tags_in_memory,
              testing::UnorderedElementsAreArray(kExpectedPatternsToTags));

  // Verify that Preferences do, as well.

  std::vector<CanonicalPatternToTag> patterns_to_tags_in_prefs;
  const auto* canonical_pref_value =
      prefs.GetUserPref(kTestContentSettingPrefName);
  ASSERT_TRUE(canonical_pref_value->is_dict());
  for (const auto& key_value : canonical_pref_value->DictItems()) {
    patterns_to_tags_in_prefs.emplace_back(
        key_value.first, GetTagFromDummyContentSettingValue(key_value.second));
  }

  EXPECT_THAT(patterns_to_tags_in_prefs,
              testing::UnorderedElementsAreArray(kExpectedPatternsToTags));
}

#if BUILDFLAG(ENABLE_PLUGINS)
// Test that a legagcy persisted plugin setting does not cause errors and has
// a sane behaviour.
TEST(ContentSettingsPref, LegacyPersistedPluginSetting) {
  const GURL kHost1("http://example.com/");
  const GURL kHost2("http://other-example.com/");
  constexpr char kPattern1[] = "http://example.com,*";
  constexpr char kPattern2[] = "http://other-example.com,*";
  constexpr char kResource[] = "someplugin";

  TestingPrefServiceSimple prefs;
  prefs.registry()->RegisterDictionaryPref(kPluginsContentSettingPrefName);

  // Build a json simulating some pre-existing plugin settings situation where
  // a mix of per_resource and regular settings are present:
  // "content_settings.plugins": {
  //   kPattern1: {
  //    "setting": 1, <-- CONTENT_SETTING_ALLOW
  //    "per_resource": {
  //      "someplugin": 2 <-- CONTENT_SETTING_BLOCK
  //    }
  //   }
  //   kPattern2: {
  //    "per_resource": {
  //      "someplugin": 1 <-- CONTENT_SETTING_ALLOW
  //    }
  //   }
  // }

  auto original_pref_value = std::make_unique<base::DictionaryValue>();

  base::Value per_resource_value1(base::Value::Type::DICTIONARY);
  per_resource_value1.SetKey(kResource, base::Value(CONTENT_SETTING_BLOCK));

  base::Value pref_value1(base::Value::Type::DICTIONARY);
  pref_value1.SetKey(kLastModifiedKey, base::Value("13189876543210000"));
  pref_value1.SetKey(kSettingKey, base::Value(CONTENT_SETTING_ALLOW));
  pref_value1.SetKey(kPerResourceTag, std::move(per_resource_value1));

  original_pref_value->SetKey(kPattern1, std::move(pref_value1));

  base::Value per_resource_value2(base::Value::Type::DICTIONARY);
  per_resource_value2.SetKey(kResource, base::Value(CONTENT_SETTING_ALLOW));

  base::Value pref_value2(base::Value::Type::DICTIONARY);
  pref_value2.SetKey(kLastModifiedKey, base::Value("13189876543210000"));
  pref_value2.SetKey(kPerResourceTag, std::move(per_resource_value2));

  original_pref_value->SetKey(kPattern2, std::move(pref_value2));

  prefs.SetUserPref(kPluginsContentSettingPrefName,
                    std::move(original_pref_value));

  PrefChangeRegistrar registrar;
  registrar.Init(&prefs);
  ContentSettingsPref content_settings_pref(
      CONTENT_SETTINGS_TYPE_PLUGINS, &prefs, &registrar,
      kPluginsContentSettingPrefName, false, base::DoNothing());

  // For kPattern1 retrieving the setting without a resource id returns the
  // CONTENT_SETTING_ALLOW value and retrieving it with the resource id (after
  // allowing resource ids for testing) returns CONTENT_SETTING_BLOCK.
  LegacyPersistedPluginTests(&content_settings_pref, kPattern1, kHost1,
                             kResource, CONTENT_SETTING_ALLOW,
                             CONTENT_SETTING_BLOCK);

  // For kPattern2 retrieving the setting without a resource id returns the
  // CONTENT_SETTING_DEFAULT value since it was not set in the first place and
  // retrieving it with the resource id (after allowing resource ids for
  // testing) returns CONTENT_SETTING_ALLOW.
  LegacyPersistedPluginTests(&content_settings_pref, kPattern2, kHost2,
                             kResource, CONTENT_SETTING_DEFAULT,
                             CONTENT_SETTING_ALLOW);
}
#endif  // BUILDFLAG(ENABLE_PLUGINS)

}  // namespace content_settings
