1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/content_settings/core/browser/content_settings_pref.h"
7 #include "base/auto_reset.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/metrics/histogram.h"
11 #include "base/prefs/scoped_user_pref_update.h"
12 #include "base/strings/string_split.h"
13 #include "base/time/clock.h"
14 #include "components/content_settings/core/browser/content_settings_rule.h"
15 #include "components/content_settings/core/browser/content_settings_utils.h"
16 #include "components/content_settings/core/browser/host_content_settings_map.h"
17 #include "components/content_settings/core/common/content_settings.h"
18 #include "components/content_settings/core/common/content_settings_pattern.h"
19 #include "components/content_settings/core/common/pref_names.h"
24 const char kSettingPath
[] = "setting";
25 const char kPerResourceIdentifierPrefName
[] = "per_resource";
26 const char kPerPluginPrefName
[] = "per_plugin";
27 const char kLastUsed
[] = "last_used";
29 ContentSetting
FixObsoleteCookiePromptMode(ContentSettingsType content_type
,
30 ContentSetting setting
) {
31 if (content_type
== CONTENT_SETTINGS_TYPE_COOKIES
&&
32 setting
== CONTENT_SETTING_ASK
) {
33 return CONTENT_SETTING_BLOCK
;
38 // If the given content type supports resource identifiers in user preferences,
39 // returns true and sets |pref_key| to the key in the content settings
40 // dictionary under which per-resource content settings are stored.
41 // Otherwise, returns false.
42 bool SupportsResourceIdentifiers(ContentSettingsType content_type
) {
43 return content_type
== CONTENT_SETTINGS_TYPE_PLUGINS
;
48 namespace content_settings
{
50 ContentSettingsPref::ContentSettingsPref(
51 ContentSettingsType content_type
,
53 PrefChangeRegistrar
* registrar
,
54 const char* pref_name
,
56 bool* updating_old_preferences_flag
,
57 NotifyObserversCallback notify_callback
)
58 : content_type_(content_type
),
60 registrar_(registrar
),
61 pref_name_(pref_name
),
62 is_incognito_(incognito
),
63 updating_preferences_(false),
64 updating_old_preferences_(updating_old_preferences_flag
),
65 notify_callback_(notify_callback
) {
68 // If the migration hasn't happened yet, or if this content setting
69 // is syncable, the parent |PrefProvider| is going to copy the contents
70 // of the old preference to this new preference. There is no need
71 // to initialize this preference separately (in fact, in the case
72 // of migration, we would be writing the empty new preference back to the
73 // old one, erasing it).
74 if (prefs_
->GetBoolean(prefs::kMigratedContentSettingsPatternPairs
) &&
75 !IsContentSettingsTypeSyncable(content_type_
)) {
76 ReadContentSettingsFromPrefAndWriteToOldPref();
81 base::Bind(&ContentSettingsPref::OnPrefChanged
, base::Unretained(this)));
84 ContentSettingsPref::~ContentSettingsPref() {
87 RuleIterator
* ContentSettingsPref::GetRuleIterator(
88 const ResourceIdentifier
& resource_identifier
,
89 bool incognito
) const {
91 return incognito_value_map_
.GetRuleIterator(content_type_
,
94 return value_map_
.GetRuleIterator(content_type_
, resource_identifier
, &lock_
);
97 bool ContentSettingsPref::SetWebsiteSetting(
98 const ContentSettingsPattern
& primary_pattern
,
99 const ContentSettingsPattern
& secondary_pattern
,
100 const ResourceIdentifier
& resource_identifier
,
101 base::Value
* in_value
) {
102 DCHECK(thread_checker_
.CalledOnValidThread());
104 DCHECK(primary_pattern
!= ContentSettingsPattern::Wildcard() ||
105 secondary_pattern
!= ContentSettingsPattern::Wildcard() ||
106 !resource_identifier
.empty());
108 // At this point take the ownership of the |in_value|.
109 scoped_ptr
<base::Value
> value(in_value
);
111 // Update in memory value map.
112 OriginIdentifierValueMap
* map_to_modify
= &incognito_value_map_
;
114 map_to_modify
= &value_map_
;
117 base::AutoLock
auto_lock(lock_
);
119 map_to_modify
->SetValue(
126 map_to_modify
->DeleteValue(
130 resource_identifier
);
133 // Update the content settings preference.
134 if (!is_incognito_
) {
135 UpdatePref(primary_pattern
,
139 if (IsContentSettingsTypeSyncable(content_type_
)) {
140 UpdateOldPref(primary_pattern
,
147 notify_callback_
.Run(
148 primary_pattern
, secondary_pattern
, content_type_
, resource_identifier
);
153 void ContentSettingsPref::ClearAllContentSettingsRules() {
154 DCHECK(thread_checker_
.CalledOnValidThread());
157 OriginIdentifierValueMap
* map_to_modify
= &incognito_value_map_
;
159 map_to_modify
= &value_map_
;
161 std::vector
<Rule
> rules_to_delete
;
163 base::AutoLock
auto_lock(lock_
);
164 scoped_ptr
<RuleIterator
> rule_iterator(
165 map_to_modify
->GetRuleIterator(content_type_
, std::string(), NULL
));
166 // Copy the rules; we cannot call |UpdatePref| while holding |lock_|.
167 while (rule_iterator
->HasNext())
168 rules_to_delete
.push_back(rule_iterator
->Next());
170 map_to_modify
->DeleteValues(content_type_
, std::string());
173 for (std::vector
<Rule
>::const_iterator it
= rules_to_delete
.begin();
174 it
!= rules_to_delete
.end(); ++it
) {
175 UpdatePref(it
->primary_pattern
,
176 it
->secondary_pattern
,
179 if (IsContentSettingsTypeSyncable(content_type_
)) {
180 UpdateOldPref(it
->primary_pattern
,
181 it
->secondary_pattern
,
186 notify_callback_
.Run(ContentSettingsPattern(),
187 ContentSettingsPattern(),
192 void ContentSettingsPref::UpdateLastUsage(
193 const ContentSettingsPattern
& primary_pattern
,
194 const ContentSettingsPattern
& secondary_pattern
,
195 base::Clock
* clock
) {
196 // Don't write if in incognito.
201 // Ensure that |lock_| is not held by this thread, since this function will
202 // send out notifications (by |~DictionaryPrefUpdate|).
205 base::AutoReset
<bool> auto_reset(&updating_preferences_
, true);
207 DictionaryPrefUpdate
update(prefs_
, pref_name_
);
208 base::DictionaryValue
* pattern_pairs_settings
= update
.Get();
210 std::string
pattern_str(
211 CreatePatternString(primary_pattern
, secondary_pattern
));
212 base::DictionaryValue
* settings_dictionary
= NULL
;
213 bool found
= pattern_pairs_settings
->GetDictionaryWithoutPathExpansion(
214 pattern_str
, &settings_dictionary
);
217 settings_dictionary
= new base::DictionaryValue
;
218 pattern_pairs_settings
->SetWithoutPathExpansion(pattern_str
,
219 settings_dictionary
);
222 settings_dictionary
->SetWithoutPathExpansion(
223 kLastUsed
, new base::FundamentalValue(clock
->Now().ToDoubleT()));
227 base::Time
ContentSettingsPref::GetLastUsage(
228 const ContentSettingsPattern
& primary_pattern
,
229 const ContentSettingsPattern
& secondary_pattern
) {
230 const base::DictionaryValue
* pattern_pairs_settings
=
231 prefs_
->GetDictionary(pref_name_
);
232 std::string
pattern_str(
233 CreatePatternString(primary_pattern
, secondary_pattern
));
235 const base::DictionaryValue
* settings_dictionary
= NULL
;
236 bool found
= pattern_pairs_settings
->GetDictionaryWithoutPathExpansion(
237 pattern_str
, &settings_dictionary
);
242 double last_used_time
;
243 found
= settings_dictionary
->GetDoubleWithoutPathExpansion(
244 kLastUsed
, &last_used_time
);
249 return base::Time::FromDoubleT(last_used_time
);
252 size_t ContentSettingsPref::GetNumExceptions() {
253 return value_map_
.size();
256 void ContentSettingsPref::ReadContentSettingsFromPrefAndWriteToOldPref() {
257 // Clear the old preference, so we can copy the exceptions from the new
258 // preference into it. Note that copying in this direction is disallowed
259 // in incognito, to avoid the echo effect: New preference -> PrefProvider ->
260 // Old preference -> Incognito PrefProvider -> New preference -> etc.
261 if (!is_incognito_
&& IsContentSettingsTypeSyncable(content_type_
))
262 ClearOldPreference();
264 // |DictionaryPrefUpdate| sends out notifications when destructed. This
265 // construction order ensures |AutoLock| gets destroyed first and |lock_| is
266 // not held when the notifications are sent. Also, |auto_reset| must be still
267 // valid when the notifications are sent, so that |Observe| skips the
269 base::AutoReset
<bool> auto_reset(&updating_preferences_
, true);
270 DictionaryPrefUpdate
update(prefs_
, pref_name_
);
271 base::AutoLock
auto_lock(lock_
);
273 const base::DictionaryValue
* all_settings_dictionary
=
274 prefs_
->GetDictionary(pref_name_
);
278 // Careful: The returned value could be NULL if the pref has never been set.
279 if (!all_settings_dictionary
)
282 base::DictionaryValue
* mutable_settings
;
283 scoped_ptr
<base::DictionaryValue
> mutable_settings_scope
;
285 if (!is_incognito_
) {
286 mutable_settings
= update
.Get();
288 // Create copy as we do not want to persist anything in OTR prefs.
289 mutable_settings
= all_settings_dictionary
->DeepCopy();
290 mutable_settings_scope
.reset(mutable_settings
);
292 // Convert all Unicode patterns into punycode form, then read.
293 CanonicalizeContentSettingsExceptions(mutable_settings
);
295 size_t cookies_block_exception_count
= 0;
296 size_t cookies_allow_exception_count
= 0;
297 size_t cookies_session_only_exception_count
= 0;
298 for (base::DictionaryValue::Iterator
i(*mutable_settings
); !i
.IsAtEnd();
300 const std::string
& pattern_str(i
.key());
301 std::pair
<ContentSettingsPattern
, ContentSettingsPattern
> pattern_pair
=
302 ParsePatternString(pattern_str
);
303 if (!pattern_pair
.first
.IsValid() ||
304 !pattern_pair
.second
.IsValid()) {
305 // TODO: Change this to DFATAL when crbug.com/132659 is fixed.
306 LOG(ERROR
) << "Invalid pattern strings: " << pattern_str
;
310 // Get settings dictionary for the current pattern string, and read
311 // settings from the dictionary.
312 const base::DictionaryValue
* settings_dictionary
= NULL
;
313 bool is_dictionary
= i
.value().GetAsDictionary(&settings_dictionary
);
314 DCHECK(is_dictionary
);
316 if (SupportsResourceIdentifiers(content_type_
)) {
317 const base::DictionaryValue
* resource_dictionary
= NULL
;
318 if (settings_dictionary
->GetDictionary(
319 kPerResourceIdentifierPrefName
, &resource_dictionary
)) {
320 for (base::DictionaryValue::Iterator
j(*resource_dictionary
);
323 const std::string
& resource_identifier(j
.key());
324 int setting
= CONTENT_SETTING_DEFAULT
;
325 bool is_integer
= j
.value().GetAsInteger(&setting
);
327 DCHECK_NE(CONTENT_SETTING_DEFAULT
, setting
);
328 scoped_ptr
<base::Value
> setting_ptr(
329 new base::FundamentalValue(setting
));
330 value_map_
.SetValue(pattern_pair
.first
,
334 setting_ptr
->DeepCopy());
335 if (!is_incognito_
&& IsContentSettingsTypeSyncable(content_type_
)) {
336 UpdateOldPref(pattern_pair
.first
,
344 base::Value
* value
= NULL
;
345 if (HostContentSettingsMap::ContentTypeHasCompoundValue(content_type_
)) {
346 const base::DictionaryValue
* setting
= NULL
;
347 // TODO(xians): Handle the non-dictionary types.
348 if (settings_dictionary
->GetDictionaryWithoutPathExpansion(
349 kSettingPath
, &setting
)) {
350 DCHECK(!setting
->empty());
351 value
= setting
->DeepCopy();
354 int setting
= CONTENT_SETTING_DEFAULT
;
355 if (settings_dictionary
->GetIntegerWithoutPathExpansion(
356 kSettingPath
, &setting
)) {
357 DCHECK_NE(CONTENT_SETTING_DEFAULT
, setting
);
358 setting
= FixObsoleteCookiePromptMode(content_type_
,
359 ContentSetting(setting
));
360 value
= new base::FundamentalValue(setting
);
365 scoped_ptr
<base::Value
> value_ptr(value
);
366 value_map_
.SetValue(pattern_pair
.first
,
369 ResourceIdentifier(),
371 if (!is_incognito_
&& IsContentSettingsTypeSyncable(content_type_
)) {
372 UpdateOldPref(pattern_pair
.first
,
374 ResourceIdentifier(),
377 if (content_type_
== CONTENT_SETTINGS_TYPE_COOKIES
) {
378 ContentSetting s
= ValueToContentSetting(value
);
380 case CONTENT_SETTING_ALLOW
:
381 ++cookies_allow_exception_count
;
383 case CONTENT_SETTING_BLOCK
:
384 ++cookies_block_exception_count
;
386 case CONTENT_SETTING_SESSION_ONLY
:
387 ++cookies_session_only_exception_count
;
398 if (content_type_
== CONTENT_SETTINGS_TYPE_COOKIES
) {
399 UMA_HISTOGRAM_COUNTS("ContentSettings.NumberOfBlockCookiesExceptions",
400 cookies_block_exception_count
);
401 UMA_HISTOGRAM_COUNTS("ContentSettings.NumberOfAllowCookiesExceptions",
402 cookies_allow_exception_count
);
403 UMA_HISTOGRAM_COUNTS("ContentSettings.NumberOfSessionOnlyCookiesExceptions",
404 cookies_session_only_exception_count
);
408 void ContentSettingsPref::OnPrefChanged() {
409 DCHECK(thread_checker_
.CalledOnValidThread());
411 if (updating_preferences_
)
414 ReadContentSettingsFromPrefAndWriteToOldPref();
416 notify_callback_
.Run(ContentSettingsPattern(),
417 ContentSettingsPattern(),
422 void ContentSettingsPref::UpdatePref(
423 const ContentSettingsPattern
& primary_pattern
,
424 const ContentSettingsPattern
& secondary_pattern
,
425 const ResourceIdentifier
& resource_identifier
,
426 const base::Value
* value
) {
427 // Ensure that |lock_| is not held by this thread, since this function will
428 // send out notifications (by |~DictionaryPrefUpdate|).
431 base::AutoReset
<bool> auto_reset(&updating_preferences_
, true);
433 DictionaryPrefUpdate
update(prefs_
, pref_name_
);
434 base::DictionaryValue
* pattern_pairs_settings
= update
.Get();
436 // Get settings dictionary for the given patterns.
437 std::string
pattern_str(CreatePatternString(primary_pattern
,
439 base::DictionaryValue
* settings_dictionary
= NULL
;
440 bool found
= pattern_pairs_settings
->GetDictionaryWithoutPathExpansion(
441 pattern_str
, &settings_dictionary
);
443 if (!found
&& value
) {
444 settings_dictionary
= new base::DictionaryValue
;
445 pattern_pairs_settings
->SetWithoutPathExpansion(
446 pattern_str
, settings_dictionary
);
449 if (settings_dictionary
) {
450 if (SupportsResourceIdentifiers(content_type_
) &&
451 !resource_identifier
.empty()) {
452 base::DictionaryValue
* resource_dictionary
= NULL
;
453 found
= settings_dictionary
->GetDictionary(
454 kPerResourceIdentifierPrefName
, &resource_dictionary
);
457 return; // Nothing to remove. Exit early.
458 resource_dictionary
= new base::DictionaryValue
;
459 settings_dictionary
->Set(
460 kPerResourceIdentifierPrefName
, resource_dictionary
);
462 // Update resource dictionary.
464 resource_dictionary
->RemoveWithoutPathExpansion(resource_identifier
,
466 if (resource_dictionary
->empty()) {
467 settings_dictionary
->RemoveWithoutPathExpansion(
468 kPerResourceIdentifierPrefName
, NULL
);
471 resource_dictionary
->SetWithoutPathExpansion(
472 resource_identifier
, value
->DeepCopy());
475 // Update settings dictionary.
477 settings_dictionary
->RemoveWithoutPathExpansion(kSettingPath
, NULL
);
478 settings_dictionary
->RemoveWithoutPathExpansion(kLastUsed
, NULL
);
480 settings_dictionary
->SetWithoutPathExpansion(
481 kSettingPath
, value
->DeepCopy());
484 // Remove the settings dictionary if it is empty.
485 if (settings_dictionary
->empty()) {
486 pattern_pairs_settings
->RemoveWithoutPathExpansion(
493 void ContentSettingsPref::UpdateOldPref(
494 const ContentSettingsPattern
& primary_pattern
,
495 const ContentSettingsPattern
& secondary_pattern
,
496 const ResourceIdentifier
& resource_identifier
,
497 const base::Value
* value
) {
498 DCHECK(IsContentSettingsTypeSyncable(content_type_
));
500 // The incognito provider cannot write the settings to avoid echo effect:
501 // New preference -> PrefProvider -> Old preference ->
502 // -> Incognito PrefProvider -> New preference -> etc.
503 DCHECK(!is_incognito_
);
505 if (*updating_old_preferences_
)
508 base::AutoReset
<bool> auto_reset(updating_old_preferences_
, true);
510 DictionaryPrefUpdate
update(prefs_
,
511 prefs::kContentSettingsPatternPairs
);
512 base::DictionaryValue
* pattern_pairs_settings
= update
.Get();
514 // Get settings dictionary for the given patterns.
515 std::string
pattern_str(CreatePatternString(primary_pattern
,
517 base::DictionaryValue
* settings_dictionary
= NULL
;
518 bool found
= pattern_pairs_settings
->GetDictionaryWithoutPathExpansion(
519 pattern_str
, &settings_dictionary
);
521 if (!found
&& value
) {
522 settings_dictionary
= new base::DictionaryValue
;
523 pattern_pairs_settings
->SetWithoutPathExpansion(
524 pattern_str
, settings_dictionary
);
527 if (settings_dictionary
) {
528 if (content_type_
== CONTENT_SETTINGS_TYPE_PLUGINS
&&
529 !resource_identifier
.empty()) {
530 base::DictionaryValue
* resource_dictionary
= NULL
;
531 found
= settings_dictionary
->GetDictionary(
532 kPerPluginPrefName
, &resource_dictionary
);
535 return; // Nothing to remove. Exit early.
536 resource_dictionary
= new base::DictionaryValue
;
537 settings_dictionary
->Set(kPerPluginPrefName
, resource_dictionary
);
539 // Update resource dictionary.
541 resource_dictionary
->RemoveWithoutPathExpansion(resource_identifier
,
543 if (resource_dictionary
->empty()) {
544 settings_dictionary
->RemoveWithoutPathExpansion(
545 kPerPluginPrefName
, NULL
);
548 resource_dictionary
->SetWithoutPathExpansion(
549 resource_identifier
, value
->DeepCopy());
552 // Update settings dictionary.
553 std::string setting_path
= GetTypeName(content_type_
);
555 settings_dictionary
->RemoveWithoutPathExpansion(setting_path
,
557 settings_dictionary
->RemoveWithoutPathExpansion(kLastUsed
, NULL
);
559 settings_dictionary
->SetWithoutPathExpansion(
560 setting_path
, value
->DeepCopy());
563 // Remove the settings dictionary if it is empty.
564 if (settings_dictionary
->empty()) {
565 pattern_pairs_settings
->RemoveWithoutPathExpansion(
572 void ContentSettingsPref::ClearOldPreference() {
573 DCHECK(IsContentSettingsTypeSyncable(content_type_
));
575 if (*updating_old_preferences_
)
578 std::vector
<std::string
> keys
;
580 base::AutoReset
<bool> auto_reset(updating_old_preferences_
, true);
581 DictionaryPrefUpdate
update(prefs_
, prefs::kContentSettingsPatternPairs
);
582 base::DictionaryValue
* old_dictionary
= update
.Get();
584 for (base::DictionaryValue::Iterator
it(*old_dictionary
);
585 !it
.IsAtEnd(); it
.Advance()) {
586 keys
.push_back(it
.key());
589 for (const std::string
& key
: keys
) {
590 base::DictionaryValue
* exception
;
592 old_dictionary
->GetDictionaryWithoutPathExpansion(key
, &exception
);
593 DCHECK(is_dictionary
);
595 exception
->RemoveWithoutPathExpansion(GetTypeName(content_type_
), NULL
);
597 base::DictionaryValue
* last_used
;
598 if (exception
->GetDictionaryWithoutPathExpansion(kLastUsed
, &last_used
)) {
599 last_used
->RemoveWithoutPathExpansion(GetTypeName(content_type_
), NULL
);
601 if (last_used
->empty())
602 exception
->RemoveWithoutPathExpansion(kLastUsed
, NULL
);
605 if (content_type_
== CONTENT_SETTINGS_TYPE_PLUGINS
)
606 exception
->RemoveWithoutPathExpansion(kPerPluginPrefName
, NULL
);
608 if (exception
->empty())
609 old_dictionary
->RemoveWithoutPathExpansion(key
, NULL
);
615 void ContentSettingsPref::CanonicalizeContentSettingsExceptions(
616 base::DictionaryValue
* all_settings_dictionary
) {
617 DCHECK(all_settings_dictionary
);
619 std::vector
<std::string
> remove_items
;
620 base::StringPairs move_items
;
621 for (base::DictionaryValue::Iterator
i(*all_settings_dictionary
);
624 const std::string
& pattern_str(i
.key());
625 std::pair
<ContentSettingsPattern
, ContentSettingsPattern
> pattern_pair
=
626 ParsePatternString(pattern_str
);
627 if (!pattern_pair
.first
.IsValid() ||
628 !pattern_pair
.second
.IsValid()) {
629 LOG(ERROR
) << "Invalid pattern strings: " << pattern_str
;
633 const std::string canonicalized_pattern_str
= CreatePatternString(
634 pattern_pair
.first
, pattern_pair
.second
);
636 if (canonicalized_pattern_str
.empty() ||
637 canonicalized_pattern_str
== pattern_str
) {
641 // Clear old pattern if prefs already have canonicalized pattern.
642 const base::DictionaryValue
* new_pattern_settings_dictionary
= NULL
;
643 if (all_settings_dictionary
->GetDictionaryWithoutPathExpansion(
644 canonicalized_pattern_str
, &new_pattern_settings_dictionary
)) {
645 remove_items
.push_back(pattern_str
);
649 // Move old pattern to canonicalized pattern.
650 const base::DictionaryValue
* old_pattern_settings_dictionary
= NULL
;
651 if (i
.value().GetAsDictionary(&old_pattern_settings_dictionary
)) {
652 move_items
.push_back(
653 std::make_pair(pattern_str
, canonicalized_pattern_str
));
657 for (size_t i
= 0; i
< remove_items
.size(); ++i
) {
658 all_settings_dictionary
->RemoveWithoutPathExpansion(remove_items
[i
], NULL
);
661 for (size_t i
= 0; i
< move_items
.size(); ++i
) {
662 scoped_ptr
<base::Value
> pattern_settings_dictionary
;
663 all_settings_dictionary
->RemoveWithoutPathExpansion(
664 move_items
[i
].first
, &pattern_settings_dictionary
);
665 all_settings_dictionary
->SetWithoutPathExpansion(
666 move_items
[i
].second
, pattern_settings_dictionary
.release());
670 void ContentSettingsPref::AssertLockNotHeld() const {
672 // |Lock::Acquire()| will assert if the lock is held by this thread.
678 } // namespace content_settings