1// Copyright 2013 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.
4
5#include "chrome/browser/prefs/pref_hash_filter.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "base/metrics/histogram.h"
11#include "base/prefs/pref_service.h"
12#include "base/prefs/pref_store.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/time/time.h"
15#include "base/values.h"
16#include "chrome/browser/prefs/pref_hash_store.h"
17#include "chrome/browser/prefs/pref_hash_store_transaction.h"
18#include "chrome/browser/prefs/tracked/dictionary_hash_store_contents.h"
19#include "chrome/browser/prefs/tracked/tracked_atomic_preference.h"
20#include "chrome/browser/prefs/tracked/tracked_split_preference.h"
21#include "chrome/common/pref_names.h"
22#include "components/pref_registry/pref_registry_syncable.h"
23
24namespace {
25
26void CleanupDeprecatedTrackedPreferences(
27    base::DictionaryValue* pref_store_contents,
28    PrefHashStoreTransaction* hash_store_transaction) {
29  // Add deprecated previously tracked preferences below for them to be cleaned
30  // up from both the pref files and the hash store.
31  static const char* kDeprecatedTrackedPreferences[] = {
32    // TODO(gab): Remove in M41+.
33    "extensions.known_disabled",
34  };
35
36  for (size_t i = 0; i < arraysize(kDeprecatedTrackedPreferences); ++i) {
37    const char* key = kDeprecatedTrackedPreferences[i];
38    pref_store_contents->Remove(key, NULL);
39    hash_store_transaction->ClearHash(key);
40  }
41}
42
43}  // namespace
44
45PrefHashFilter::PrefHashFilter(
46    scoped_ptr<PrefHashStore> pref_hash_store,
47    const std::vector<TrackedPreferenceMetadata>& tracked_preferences,
48    const base::Closure& on_reset_on_load,
49    TrackedPreferenceValidationDelegate* delegate,
50    size_t reporting_ids_count,
51    bool report_super_mac_validity)
52    : pref_hash_store_(pref_hash_store.Pass()),
53      on_reset_on_load_(on_reset_on_load),
54      report_super_mac_validity_(report_super_mac_validity) {
55  DCHECK(pref_hash_store_);
56  DCHECK_GE(reporting_ids_count, tracked_preferences.size());
57
58  for (size_t i = 0; i < tracked_preferences.size(); ++i) {
59    const TrackedPreferenceMetadata& metadata = tracked_preferences[i];
60
61    scoped_ptr<TrackedPreference> tracked_preference;
62    switch (metadata.strategy) {
63      case TRACKING_STRATEGY_ATOMIC:
64        tracked_preference.reset(
65            new TrackedAtomicPreference(metadata.name,
66                                        metadata.reporting_id,
67                                        reporting_ids_count,
68                                        metadata.enforcement_level,
69                                        delegate));
70        break;
71      case TRACKING_STRATEGY_SPLIT:
72        tracked_preference.reset(
73            new TrackedSplitPreference(metadata.name,
74                                       metadata.reporting_id,
75                                       reporting_ids_count,
76                                       metadata.enforcement_level,
77                                       delegate));
78        break;
79    }
80    DCHECK(tracked_preference);
81
82    bool is_new = tracked_paths_.add(metadata.name,
83                                     tracked_preference.Pass()).second;
84    DCHECK(is_new);
85  }
86}
87
88PrefHashFilter::~PrefHashFilter() {
89  // Ensure new values for all |changed_paths_| have been flushed to
90  // |pref_hash_store_| already.
91  DCHECK(changed_paths_.empty());
92}
93
94// static
95void PrefHashFilter::RegisterProfilePrefs(
96    user_prefs::PrefRegistrySyncable* registry) {
97  // See GetResetTime for why this is a StringPref and not Int64Pref.
98  registry->RegisterStringPref(
99      prefs::kPreferenceResetTime,
100      base::Int64ToString(base::Time().ToInternalValue()),
101      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
102}
103
104// static
105base::Time PrefHashFilter::GetResetTime(PrefService* user_prefs) {
106  // Provide our own implementation (identical to the PrefService::GetInt64) in
107  // order to ensure it remains consistent with the way we store this value
108  // (which we do via a PrefStore, preventing us from reusing
109  // PrefService::SetInt64).
110  int64 internal_value = base::Time().ToInternalValue();
111  if (!base::StringToInt64(
112          user_prefs->GetString(prefs::kPreferenceResetTime),
113          &internal_value)) {
114    // Somehow the value stored on disk is not a valid int64.
115    NOTREACHED();
116    return base::Time();
117  }
118  return base::Time::FromInternalValue(internal_value);
119}
120
121// static
122void PrefHashFilter::ClearResetTime(PrefService* user_prefs) {
123  user_prefs->ClearPref(prefs::kPreferenceResetTime);
124}
125
126void PrefHashFilter::Initialize(base::DictionaryValue* pref_store_contents) {
127  scoped_ptr<PrefHashStoreTransaction> hash_store_transaction(
128      pref_hash_store_->BeginTransaction(scoped_ptr<HashStoreContents>(
129          new DictionaryHashStoreContents(pref_store_contents))));
130  for (TrackedPreferencesMap::const_iterator it = tracked_paths_.begin();
131       it != tracked_paths_.end(); ++it) {
132    const std::string& initialized_path = it->first;
133    const TrackedPreference* initialized_preference = it->second;
134    const base::Value* value = NULL;
135    pref_store_contents->Get(initialized_path, &value);
136    initialized_preference->OnNewValue(value, hash_store_transaction.get());
137  }
138}
139
140// Marks |path| has having changed if it is part of |tracked_paths_|. A new hash
141// will be stored for it the next time FilterSerializeData() is invoked.
142void PrefHashFilter::FilterUpdate(const std::string& path) {
143  TrackedPreferencesMap::const_iterator it = tracked_paths_.find(path);
144  if (it != tracked_paths_.end())
145    changed_paths_.insert(std::make_pair(path, it->second));
146}
147
148// Updates the stored hashes for |changed_paths_| before serializing data to
149// disk. This is required as storing the hash everytime a pref's value changes
150// is too expensive (see perf regression @ http://crbug.com/331273).
151void PrefHashFilter::FilterSerializeData(
152    base::DictionaryValue* pref_store_contents) {
153  if (!changed_paths_.empty()) {
154    base::TimeTicks checkpoint = base::TimeTicks::Now();
155    {
156      scoped_ptr<PrefHashStoreTransaction> hash_store_transaction(
157          pref_hash_store_->BeginTransaction(scoped_ptr<HashStoreContents>(
158              new DictionaryHashStoreContents(pref_store_contents))));
159      for (ChangedPathsMap::const_iterator it = changed_paths_.begin();
160           it != changed_paths_.end(); ++it) {
161        const std::string& changed_path = it->first;
162        const TrackedPreference* changed_preference = it->second;
163        const base::Value* value = NULL;
164        pref_store_contents->Get(changed_path, &value);
165        changed_preference->OnNewValue(value, hash_store_transaction.get());
166      }
167      changed_paths_.clear();
168    }
169    // TODO(gab): Remove this histogram by Feb 21 2014; after sufficient timing
170    // data has been gathered from the wild to be confident this doesn't
171    // significantly affect performance on the UI thread.
172    UMA_HISTOGRAM_TIMES("Settings.FilterSerializeDataTime",
173                        base::TimeTicks::Now() - checkpoint);
174  }
175}
176
177void PrefHashFilter::FinalizeFilterOnLoad(
178    const PostFilterOnLoadCallback& post_filter_on_load_callback,
179    scoped_ptr<base::DictionaryValue> pref_store_contents,
180    bool prefs_altered) {
181  DCHECK(pref_store_contents);
182  base::TimeTicks checkpoint = base::TimeTicks::Now();
183
184  bool did_reset = false;
185  {
186    scoped_ptr<PrefHashStoreTransaction> hash_store_transaction(
187        pref_hash_store_->BeginTransaction(scoped_ptr<HashStoreContents>(
188            new DictionaryHashStoreContents(pref_store_contents.get()))));
189
190    CleanupDeprecatedTrackedPreferences(
191        pref_store_contents.get(), hash_store_transaction.get());
192
193    if (report_super_mac_validity_) {
194      UMA_HISTOGRAM_BOOLEAN("Settings.HashesDictionaryTrusted",
195                            hash_store_transaction->IsSuperMACValid());
196    }
197
198    for (TrackedPreferencesMap::const_iterator it = tracked_paths_.begin();
199         it != tracked_paths_.end(); ++it) {
200      if (it->second->EnforceAndReport(pref_store_contents.get(),
201                                       hash_store_transaction.get())) {
202        did_reset = true;
203        prefs_altered = true;
204      }
205    }
206    if (hash_store_transaction->StampSuperMac())
207      prefs_altered = true;
208  }
209
210  if (did_reset) {
211    pref_store_contents->Set(prefs::kPreferenceResetTime,
212                             new base::StringValue(base::Int64ToString(
213                                 base::Time::Now().ToInternalValue())));
214    FilterUpdate(prefs::kPreferenceResetTime);
215
216    if (!on_reset_on_load_.is_null())
217      on_reset_on_load_.Run();
218  }
219
220  // TODO(gab): Remove this histogram by Feb 21 2014; after sufficient timing
221  // data has been gathered from the wild to be confident this doesn't
222  // significantly affect startup.
223  UMA_HISTOGRAM_TIMES("Settings.FilterOnLoadTime",
224                      base::TimeTicks::Now() - checkpoint);
225
226  post_filter_on_load_callback.Run(pref_store_contents.Pass(), prefs_altered);
227}
228