Commit 2676edf5 authored by Illia Klimov's avatar Illia Klimov Committed by Commit Bot

Remove resource identifier from OriginIdentifierValueMap.

As part of the Flash/Plugins deprecation, the resource identifier
becomes obsolete. This CL removes resource_identifier from the
OriginIdentifierValueMap.

This CL does not include Observer, Providers and HostContentSettingsMap.
They will be cleaned separately.

This CL does not change methods API but makes sure resource identifier
is not used.

Bug: 1134547
Change-Id: I4e222d928b80c73e2a1780619f641333d7a0bc6a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2517722Reviewed-by: default avatarFinnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarChristian Dullweber <dullweber@chromium.org>
Commit-Queue: Illia Klimov <elklm@google.com>
Auto-Submit: Illia Klimov <elklm@google.com>
Cr-Commit-Position: refs/heads/master@{#824856}
parent fa937985
......@@ -16,9 +16,9 @@
TEST(OriginIdentifierValueMapTest, SetGetValue) {
content_settings::OriginIdentifierValueMap map;
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::COOKIES, std::string()));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::COOKIES, std::string()));
map.SetValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsType::COOKIES, std::string(), base::Time(),
......@@ -29,66 +29,64 @@ TEST(OriginIdentifierValueMapTest, SetGetValue) {
map.GetValue(GURL("http://www.google.com"), GURL("http://www.google.com"),
ContentSettingsType::COOKIES, std::string())));
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.youtube.com"),
ContentSettingsType::COOKIES, std::string()));
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.youtube.com"),
GURL("http://www.google.com"),
ContentSettingsType::COOKIES, std::string()));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.youtube.com"),
ContentSettingsType::COOKIES, std::string()));
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::POPUPS, std::string()));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.youtube.com"),
GURL("http://www.google.com"),
ContentSettingsType::COOKIES, std::string()));
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::COOKIES, "resource_id"));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::POPUPS, std::string()));
}
TEST(OriginIdentifierValueMapTest, SetDeleteValue) {
content_settings::OriginIdentifierValueMap map;
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin"));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, std::string()));
// Set sample values.
map.SetValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsType::PLUGINS, "java-plugin", base::Time(),
ContentSettingsType::PLUGINS, std::string(), base::Time(),
base::Value(1), {});
int actual_value;
EXPECT_TRUE(map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin")
ContentSettingsType::PLUGINS, std::string())
->GetAsInteger(&actual_value));
EXPECT_EQ(1, actual_value);
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "flash-plugin"));
EXPECT_EQ(
nullptr,
map.GetValue(GURL("http://www.google.com"), GURL("http://www.google.com"),
ContentSettingsType::NOTIFICATIONS, std::string()));
// Delete non-existing value.
map.DeleteValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsType::PLUGINS, "flash-plugin");
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "flash-plugin"));
ContentSettingsType::NOTIFICATIONS, "flash-plugin");
EXPECT_EQ(
nullptr,
map.GetValue(GURL("http://www.google.com"), GURL("http://www.google.com"),
ContentSettingsType::NOTIFICATIONS, std::string()));
EXPECT_TRUE(map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin")
ContentSettingsType::PLUGINS, std::string())
->GetAsInteger(&actual_value));
EXPECT_EQ(1, actual_value);
// Delete existing value.
map.DeleteValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsType::PLUGINS, "java-plugin");
ContentSettingsType::PLUGINS, std::string());
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin"));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, std::string()));
}
TEST(OriginIdentifierValueMapTest, Clear) {
......@@ -98,7 +96,7 @@ TEST(OriginIdentifierValueMapTest, Clear) {
// Set two values.
map.SetValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsType::PLUGINS, "java-plugin", base::Time(),
ContentSettingsType::PLUGINS, std::string(), base::Time(),
base::Value(1), {});
map.SetValue(ContentSettingsPattern::FromString("[*.]google.com"),
ContentSettingsPattern::FromString("[*.]google.com"),
......@@ -108,16 +106,16 @@ TEST(OriginIdentifierValueMapTest, Clear) {
int actual_value;
EXPECT_TRUE(map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin")
ContentSettingsType::PLUGINS, std::string())
->GetAsInteger(&actual_value));
EXPECT_EQ(1, actual_value);
// Clear the map.
map.clear();
EXPECT_TRUE(map.empty());
EXPECT_EQ(NULL, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, "java-plugin"));
EXPECT_EQ(nullptr, map.GetValue(GURL("http://www.google.com"),
GURL("http://www.google.com"),
ContentSettingsType::PLUGINS, std::string()));
}
TEST(OriginIdentifierValueMapTest, ListEntryPrecedences) {
......@@ -172,7 +170,8 @@ TEST(OriginIdentifierValueMapTest, IterateNonempty) {
{});
std::unique_ptr<content_settings::RuleIterator> rule_iterator(
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(), NULL));
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(),
nullptr));
ASSERT_TRUE(rule_iterator->HasNext());
content_settings::Rule rule = rule_iterator->Next();
EXPECT_EQ(sub_pattern, rule.primary_pattern);
......@@ -210,7 +209,8 @@ TEST(OriginIdentifierValueMapTest, UpdateLastModified) {
{
std::unique_ptr<content_settings::RuleIterator> rule_iterator(
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(), NULL));
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(),
nullptr));
ASSERT_TRUE(rule_iterator->HasNext());
content_settings::Rule rule = rule_iterator->Next();
EXPECT_EQ(sub_pattern, rule.primary_pattern);
......@@ -239,7 +239,8 @@ TEST(OriginIdentifierValueMapTest, UpdateLastModified) {
{
std::unique_ptr<content_settings::RuleIterator> rule_iterator =
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(), NULL);
map.GetRuleIterator(ContentSettingsType::COOKIES, std::string(),
nullptr);
ASSERT_TRUE(rule_iterator->HasNext());
content_settings::Rule rule = rule_iterator->Next();
EXPECT_EQ(sub_pattern, rule.primary_pattern);
......
......@@ -326,27 +326,17 @@ void ContentSettingsStore::ClearContentSettingsForExtensionAndContentType(
const std::string& ext_id,
ExtensionPrefsScope scope,
ContentSettingsType content_type) {
bool notify = false;
{
base::AutoLock lock(lock_);
OriginIdentifierValueMap* map = GetValueMap(ext_id, scope);
DCHECK(map);
// Get all of the resource identifiers for this |content_type|.
std::set<ResourceIdentifier> resource_identifiers;
for (const auto& entry : *map) {
if (entry.first.content_type == content_type)
resource_identifiers.insert(entry.first.resource_identifier);
}
notify = !resource_identifiers.empty();
if (map->find(content_type) == map->end())
return;
for (const ResourceIdentifier& resource_identifier : resource_identifiers)
map->DeleteValues(content_type, resource_identifier);
map->DeleteValues(content_type, std::string());
}
if (notify) {
NotifyOfContentSettingChanged(ext_id, scope != kExtensionPrefsScopeRegular);
}
}
std::unique_ptr<base::ListValue> ContentSettingsStore::GetSettingsForExtension(
......@@ -361,7 +351,7 @@ std::unique_ptr<base::ListValue> ContentSettingsStore::GetSettingsForExtension(
for (const auto& it : *map) {
const auto& key = it.first;
std::unique_ptr<RuleIterator> rule_iterator(
map->GetRuleIterator(key.content_type, key.resource_identifier,
map->GetRuleIterator(key, std::string(),
nullptr)); // We already hold the lock.
if (!rule_iterator)
continue;
......@@ -378,11 +368,7 @@ std::unique_ptr<base::ListValue> ContentSettingsStore::GetSettingsForExtension(
rule.secondary_pattern.ToString());
setting_dict->SetString(
content_settings_api_constants::kContentSettingsTypeKey,
content_settings_helpers::ContentSettingsTypeToString(
key.content_type));
setting_dict->SetString(
content_settings_api_constants::kResourceIdentifierKey,
key.resource_identifier);
content_settings_helpers::ContentSettingsTypeToString(key));
ContentSetting content_setting =
content_settings::ValueToContentSetting(&rule.value);
DCHECK_NE(CONTENT_SETTING_DEFAULT, content_setting);
......@@ -464,9 +450,6 @@ void ContentSettingsStore::SetExtensionContentSettingFromList(
// Plugin content settings are no longer supported for extensions.
continue;
}
std::string resource_identifier;
dict->GetString(content_settings_api_constants::kResourceIdentifierKey,
&resource_identifier);
std::string content_setting_string;
dict->GetString(content_settings_api_constants::kContentSettingKey,
......@@ -479,12 +462,8 @@ void ContentSettingsStore::SetExtensionContentSettingFromList(
// settings to |CONTENT_SETTING_DEFAULT|.
DCHECK_NE(CONTENT_SETTING_DEFAULT, setting);
SetExtensionContentSetting(extension_id,
primary_pattern,
secondary_pattern,
content_settings_type,
resource_identifier,
setting,
SetExtensionContentSetting(extension_id, primary_pattern, secondary_pattern,
content_settings_type, std::string(), setting,
scope);
}
}
......
......@@ -106,22 +106,14 @@ void EphemeralProvider::ClearAllContentSettingsRules(
ContentSettingsType content_type) {
DCHECK(CalledOnValidThread());
// Get all resource identifiers for this |content_type|.
std::set<ResourceIdentifier> resource_identifiers;
for (OriginIdentifierValueMap::EntryMap::const_iterator entry =
content_settings_rules_.begin();
entry != content_settings_rules_.end(); entry++) {
if (entry->first.content_type == content_type)
resource_identifiers.insert(entry->first.resource_identifier);
}
if (content_settings_rules_.find(content_type) ==
content_settings_rules_.end())
return;
for (const ResourceIdentifier& resource_identifier : resource_identifiers)
content_settings_rules_.DeleteValues(content_type, resource_identifier);
content_settings_rules_.DeleteValues(content_type, std::string());
if (!resource_identifiers.empty()) {
NotifyObservers(ContentSettingsPattern(), ContentSettingsPattern(),
content_type, ResourceIdentifier());
}
NotifyObservers(ContentSettingsPattern(), ContentSettingsPattern(),
content_type, ResourceIdentifier());
}
void EphemeralProvider::ShutdownOnUIThread() {
......
......@@ -55,19 +55,6 @@ class RuleIteratorImpl : public RuleIterator {
} // namespace
OriginIdentifierValueMap::EntryMapKey::EntryMapKey(
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier)
: content_type(content_type),
resource_identifier(resource_identifier) {
}
bool OriginIdentifierValueMap::EntryMapKey::operator<(
const OriginIdentifierValueMap::EntryMapKey& other) const {
return std::tie(content_type, resource_identifier) <
std::tie(other.content_type, other.resource_identifier);
}
OriginIdentifierValueMap::PatternPair::PatternPair(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern)
......@@ -92,15 +79,14 @@ std::unique_ptr<RuleIterator> OriginIdentifierValueMap::GetRuleIterator(
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier,
base::Lock* lock) const {
EntryMapKey key(content_type, resource_identifier);
// We access |entries_| here, so we need to lock |lock_| first. The lock must
// be passed to the |RuleIteratorImpl| in a locked state, so that nobody can
// access |entries_| after |find()| but before the |RuleIteratorImpl| is
// We access |entries_| here, so we need to lock |auto_lock| first. The lock
// must be passed to the |RuleIteratorImpl| in a locked state, so that nobody
// can access |entries_| after |find()| but before the |RuleIteratorImpl| is
// created.
std::unique_ptr<base::AutoLock> auto_lock;
if (lock)
auto_lock.reset(new base::AutoLock(*lock));
auto it = entries_.find(key);
auto_lock = std::make_unique<base::AutoLock>(*lock);
auto it = entries_.find(content_type);
if (it == entries_.end())
return nullptr;
return std::unique_ptr<RuleIterator>(new RuleIteratorImpl(
......@@ -123,8 +109,7 @@ const base::Value* OriginIdentifierValueMap::GetValue(
const GURL& secondary_url,
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier) const {
EntryMapKey key(content_type, resource_identifier);
auto it = entries_.find(key);
auto it = entries_.find(content_type);
if (it == entries_.end())
return nullptr;
......@@ -148,9 +133,8 @@ base::Time OriginIdentifierValueMap::GetLastModified(
DCHECK(primary_pattern.IsValid());
DCHECK(secondary_pattern.IsValid());
EntryMapKey key(content_type, resource_identifier);
PatternPair patterns(primary_pattern, secondary_pattern);
auto it = entries_.find(key);
auto it = entries_.find(content_type);
if (it == entries_.end())
return base::Time();
auto r = it->second.find(patterns);
......@@ -172,9 +156,8 @@ void OriginIdentifierValueMap::SetValue(
// TODO(raymes): Remove this after we track down the cause of
// crbug.com/531548.
CHECK_NE(ContentSettingsType::DEFAULT, content_type);
EntryMapKey key(content_type, resource_identifier);
PatternPair patterns(primary_pattern, secondary_pattern);
ValueEntry* entry = &entries_[key][patterns];
ValueEntry* entry = &entries_[content_type][patterns];
entry->value = std::move(value);
entry->last_modified = last_modified;
entry->expiration = constraints.expiration;
......@@ -186,9 +169,8 @@ void OriginIdentifierValueMap::DeleteValue(
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier) {
EntryMapKey key(content_type, resource_identifier);
PatternPair patterns(primary_pattern, secondary_pattern);
auto it = entries_.find(key);
auto it = entries_.find(content_type);
if (it == entries_.end())
return;
it->second.erase(patterns);
......@@ -199,8 +181,7 @@ void OriginIdentifierValueMap::DeleteValue(
void OriginIdentifierValueMap::DeleteValues(
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier) {
EntryMapKey key(content_type, resource_identifier);
entries_.erase(key);
entries_.erase(content_type);
}
void OriginIdentifierValueMap::clear() {
......
......@@ -29,14 +29,6 @@ class RuleIterator;
class OriginIdentifierValueMap {
public:
struct EntryMapKey {
ContentSettingsType content_type;
ResourceIdentifier resource_identifier;
EntryMapKey(ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier);
bool operator<(const OriginIdentifierValueMap::EntryMapKey& other) const;
};
struct PatternPair {
ContentSettingsPattern primary_pattern;
ContentSettingsPattern secondary_pattern;
......@@ -55,7 +47,7 @@ class OriginIdentifierValueMap {
};
typedef std::map<PatternPair, ValueEntry> Rules;
typedef std::map<EntryMapKey, Rules> EntryMap;
typedef std::map<ContentSettingsType, Rules> EntryMap;
EntryMap::iterator begin() {
return entries_.begin();
......@@ -73,6 +65,10 @@ class OriginIdentifierValueMap {
return entries_.end();
}
EntryMap::iterator find(ContentSettingsType content_type) {
return entries_.find(content_type);
}
bool empty() const {
return size() == 0u;
}
......
......@@ -43,49 +43,6 @@ 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:
//
......@@ -206,82 +163,6 @@ TEST(ContentSettingsPref, CanonicalizationWhileReadingFromPrefs) {
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(
ContentSettingsType::PLUGINS, &prefs, &registrar,
kPluginsContentSettingPrefName, false, 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)
// If we are reading from prefs and we have any persistend settings that have
// expired we should remove these to prevent unbounded growth and bloat.
TEST(ContentSettingsPref, ExpirationWhileReadingFromPrefs) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment