#include "base/metrics/histogram.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
-#include "base/prefs/scoped_user_pref_update.h"
#include "base/values.h"
+#include "chrome/browser/prefs/pref_hash_store_transaction.h"
#include "chrome/common/pref_names.h"
+namespace internals {
+
+const char kHashOfHashesDict[] = "hash_of_hashes";
+const char kStoreVersionsDict[] = "store_versions";
+
+} // namespace internals
+
+class PrefHashStoreImpl::PrefHashStoreTransactionImpl
+ : public PrefHashStoreTransaction {
+ public:
+ // Constructs a PrefHashStoreTransactionImpl which can use the private
+ // members of its |outer| PrefHashStoreImpl.
+ explicit PrefHashStoreTransactionImpl(PrefHashStoreImpl* outer);
+ virtual ~PrefHashStoreTransactionImpl();
+
+ // PrefHashStoreTransaction implementation.
+ virtual ValueState CheckValue(const std::string& path,
+ const base::Value* value) const OVERRIDE;
+ virtual void StoreHash(const std::string& path,
+ const base::Value* value) OVERRIDE;
+ virtual ValueState CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const OVERRIDE;
+ virtual void StoreSplitHash(
+ const std::string& path,
+ const base::DictionaryValue* split_value) OVERRIDE;
+
+ private:
+ // Clears any hashes stored for |path| through |update|.
+ void ClearPath(const std::string& path,
+ DictionaryPrefUpdate* update);
+
+ // Returns true if there are split hashes stored for |path|.
+ bool HasSplitHashesAtPath(const std::string& path) const;
+
+ // Used by StoreHash and StoreSplitHash to store the hash of |new_value| at
+ // |path| under |update|. Allows multiple hashes to be stored under the same
+ // |update|.
+ void StoreHashInternal(const std::string& path,
+ const base::Value* new_value,
+ DictionaryPrefUpdate* update);
+
+ PrefHashStoreImpl* outer_;
+ bool has_changed_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashStoreTransactionImpl);
+};
+
PrefHashStoreImpl::PrefHashStoreImpl(const std::string& hash_store_id,
const std::string& seed,
const std::string& device_id,
// Remove the dictionary corresponding to the profile name, which may have a
// '.'
update->RemoveWithoutPathExpansion(hash_store_id_, NULL);
+
+ // Remove this store's entry in the kStoreVersionsDict.
+ base::DictionaryValue* version_dict;
+ if (update->GetDictionary(internals::kStoreVersionsDict, &version_dict))
+ version_dict->Remove(hash_store_id_, NULL);
+
+ // Remove this store's entry in the kHashOfHashesDict.
+ base::DictionaryValue* hash_of_hashes_dict;
+ if (update->GetDictionaryWithoutPathExpansion(internals::kHashOfHashesDict,
+ &hash_of_hashes_dict)) {
+ hash_of_hashes_dict->Remove(hash_store_id_, NULL);
+ }
}
-bool PrefHashStoreImpl::IsInitialized() const {
- const base::DictionaryValue* pref_hash_dicts =
+scoped_ptr<PrefHashStoreTransaction> PrefHashStoreImpl::BeginTransaction() {
+ return scoped_ptr<PrefHashStoreTransaction>(
+ new PrefHashStoreTransactionImpl(this));
+}
+
+PrefHashStoreImpl::StoreVersion PrefHashStoreImpl::GetCurrentVersion() const {
+ const base::DictionaryValue* pref_hash_data =
local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
- return pref_hash_dicts &&
- pref_hash_dicts->GetDictionaryWithoutPathExpansion(hash_store_id_, NULL);
+
+ if (!pref_hash_data->GetDictionaryWithoutPathExpansion(hash_store_id_, NULL))
+ return VERSION_UNINITIALIZED;
+
+ const base::DictionaryValue* version_dict;
+ int current_version;
+ if (!pref_hash_data->GetDictionary(internals::kStoreVersionsDict,
+ &version_dict) ||
+ !version_dict->GetInteger(hash_store_id_, ¤t_version)) {
+ return VERSION_PRE_MIGRATION;
+ }
+
+ DCHECK_GT(current_version, VERSION_PRE_MIGRATION);
+ return static_cast<StoreVersion>(current_version);
}
-PrefHashStore::ValueState PrefHashStoreImpl::CheckValue(
- const std::string& path, const base::Value* initial_value) const {
- const base::DictionaryValue* pref_hash_dicts =
+bool PrefHashStoreImpl::IsHashDictionaryTrusted() const {
+ const base::DictionaryValue* pref_hash_data =
local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
+ const base::DictionaryValue* hashes_dict = NULL;
+ const base::DictionaryValue* hash_of_hashes_dict = NULL;
+ std::string hash_of_hashes;
+ // The absence of the hashes dictionary isn't trusted. Nor is the absence of
+ // the hash of hashes for this |hash_store_id_|.
+ if (!pref_hash_data->GetDictionaryWithoutPathExpansion(
+ hash_store_id_, &hashes_dict) ||
+ !pref_hash_data->GetDictionaryWithoutPathExpansion(
+ internals::kHashOfHashesDict, &hash_of_hashes_dict) ||
+ !hash_of_hashes_dict->GetStringWithoutPathExpansion(
+ hash_store_id_, &hash_of_hashes)) {
+ return false;
+ }
+
+ return pref_hash_calculator_.Validate(
+ hash_store_id_, hashes_dict, hash_of_hashes) == PrefHashCalculator::VALID;
+}
+
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl(
+ PrefHashStoreImpl* outer) : outer_(outer), has_changed_(false) {
+}
+
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::
+ ~PrefHashStoreTransactionImpl() {
+ DictionaryPrefUpdate update(outer_->local_state_,
+ prefs::kProfilePreferenceHashes);
+ // Update the hash_of_hashes if and only if the hashes dictionary has been
+ // modified in this transaction.
+ if (has_changed_) {
+ // Get the dictionary where the hash of hashes are stored.
+ base::DictionaryValue* hash_of_hashes_dict = NULL;
+ if (!update->GetDictionaryWithoutPathExpansion(internals::kHashOfHashesDict,
+ &hash_of_hashes_dict)) {
+ hash_of_hashes_dict = new base::DictionaryValue;
+ update->SetWithoutPathExpansion(internals::kHashOfHashesDict,
+ hash_of_hashes_dict);
+ }
+
+ // Get the dictionary of hashes (or NULL if it doesn't exist).
+ base::DictionaryValue* hashes_dict = NULL;
+ update->GetDictionaryWithoutPathExpansion(outer_->hash_store_id_,
+ &hashes_dict);
+
+ // Use the |hash_store_id_| as the hashed path to avoid having the hash
+ // depend on kProfilePreferenceHashes.
+ std::string hash_of_hashes(outer_->pref_hash_calculator_.Calculate(
+ outer_->hash_store_id_, hashes_dict));
+ hash_of_hashes_dict->SetStringWithoutPathExpansion(outer_->hash_store_id_,
+ hash_of_hashes);
+ }
+
+ // Mark this hash store has having been updated to the latest version (in
+ // practice only initialization transactions will actually do this, but
+ // since they always occur before minor update transaction it's okay
+ // to unconditionally do this here). Do this even if |!has_changed_| to also
+ // seed version number on unchanged profiles.
+ base::DictionaryValue* store_versions_dict = NULL;
+ if (!update->GetDictionary(internals::kStoreVersionsDict,
+ &store_versions_dict)) {
+ store_versions_dict = new base::DictionaryValue;
+ update->Set(internals::kStoreVersionsDict, store_versions_dict);
+ }
+ store_versions_dict->SetInteger(outer_->hash_store_id_, VERSION_LATEST);
+}
+
+PrefHashStoreTransaction::ValueState
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckValue(
+ const std::string& path, const base::Value* initial_value) const {
+ const base::DictionaryValue* pref_hash_data =
+ outer_->local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
const base::DictionaryValue* hashed_prefs = NULL;
- pref_hash_dicts->GetDictionaryWithoutPathExpansion(hash_store_id_,
+ pref_hash_data->GetDictionaryWithoutPathExpansion(outer_->hash_store_id_,
&hashed_prefs);
std::string last_hash;
if (!hashed_prefs || !hashed_prefs->GetString(path, &last_hash)) {
// In the absence of a hash for this pref, always trust a NULL value, but
// only trust an existing value if the initial hashes dictionary is trusted.
- return (!initial_value || initial_hashes_dictionary_trusted_) ?
+ return (!initial_value || outer_->initial_hashes_dictionary_trusted_) ?
TRUSTED_UNKNOWN_VALUE : UNTRUSTED_UNKNOWN_VALUE;
}
PrefHashCalculator::ValidationResult validation_result =
- pref_hash_calculator_.Validate(path, initial_value, last_hash);
+ outer_->pref_hash_calculator_.Validate(path, initial_value, last_hash);
switch (validation_result) {
case PrefHashCalculator::VALID:
return UNCHANGED;
return UNTRUSTED_UNKNOWN_VALUE;
}
-void PrefHashStoreImpl::StoreHash(
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHash(
const std::string& path, const base::Value* new_value) {
- DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes);
+ DictionaryPrefUpdate update(outer_->local_state_,
+ prefs::kProfilePreferenceHashes);
+ StoreHashInternal(path, new_value, &update);
+}
- // Get the dictionary corresponding to the profile name, which may have a
- // '.'
- base::DictionaryValue* hashes_dict = NULL;
- if (!update->GetDictionaryWithoutPathExpansion(hash_store_id_,
- &hashes_dict)) {
- hashes_dict = new base::DictionaryValue;
- update->SetWithoutPathExpansion(hash_store_id_, hashes_dict);
+PrefHashStoreTransaction::ValueState
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const {
+ DCHECK(invalid_keys && invalid_keys->empty());
+
+ const bool has_hashes = HasSplitHashesAtPath(path);
+
+ // Treat NULL and empty the same; otherwise we would need to store a hash
+ // for the entire dictionary (or some other special beacon) to
+ // differentiate these two cases which are really the same for
+ // dictionaries.
+ if (!initial_split_value || initial_split_value->empty())
+ return has_hashes ? CLEARED : UNCHANGED;
+
+ if (!has_hashes) {
+ return outer_->initial_hashes_dictionary_trusted_ ?
+ TRUSTED_UNKNOWN_VALUE : UNTRUSTED_UNKNOWN_VALUE;
}
- hashes_dict->SetString(
- path, pref_hash_calculator_.Calculate(path, new_value));
-
- // Get the dictionary where the hash of hashes are stored.
- base::DictionaryValue* hash_of_hashes_dict = NULL;
- if (!update->GetDictionaryWithoutPathExpansion(internals::kHashOfHashesPref,
- &hash_of_hashes_dict)) {
- hash_of_hashes_dict = new base::DictionaryValue;
- update->SetWithoutPathExpansion(internals::kHashOfHashesPref,
- hash_of_hashes_dict);
+ std::string keyed_path(path);
+ keyed_path.push_back('.');
+ const size_t common_part_length = keyed_path.length();
+ for (base::DictionaryValue::Iterator it(*initial_split_value); !it.IsAtEnd();
+ it.Advance()) {
+ // Keep the common part from the old |keyed_path| and replace the key to
+ // get the new |keyed_path|.
+ keyed_path.replace(common_part_length, std::string::npos, it.key());
+ ValueState value_state = CheckValue(keyed_path, &it.value());
+ switch (value_state) {
+ case CLEARED:
+ // CLEARED doesn't make sense as a NULL value would never be sampled
+ // by the DictionaryValue::Iterator; in fact it is a known weakness of
+ // this current algorithm to not detect the case where a single key is
+ // cleared entirely from the dictionary pref.
+ NOTREACHED();
+ break;
+ case MIGRATED:
+ // Split tracked preferences were introduced after the migration started
+ // so no migration is expected, but declare it invalid in Release builds
+ // anyways.
+ NOTREACHED();
+ invalid_keys->push_back(it.key());
+ break;
+ case UNCHANGED:
+ break;
+ case CHANGED: // Falls through.
+ case UNTRUSTED_UNKNOWN_VALUE: // Falls through.
+ case TRUSTED_UNKNOWN_VALUE:
+ // Declare this value invalid, whether it was changed or never seen
+ // before (note that the initial hashes being trusted is irrelevant for
+ // individual entries in this scenario).
+ invalid_keys->push_back(it.key());
+ break;
+ }
}
- // Use the |hash_store_id_| as the hashed path to avoid having the hash
- // depend on kProfilePreferenceHashes.
- std::string hash_of_hashes(pref_hash_calculator_.Calculate(hash_store_id_,
- hashes_dict));
- hash_of_hashes_dict->SetStringWithoutPathExpansion(hash_store_id_,
- hash_of_hashes);
+ return invalid_keys->empty() ? UNCHANGED : CHANGED;
}
-bool PrefHashStoreImpl::IsHashDictionaryTrusted() const {
- const base::DictionaryValue* pref_hash_dicts =
- local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
- const base::DictionaryValue* hashes_dict = NULL;
- const base::DictionaryValue* hash_of_hashes_dict = NULL;
- std::string hash_of_hashes;
- // The absence of the hashes dictionary isn't trusted. Nor is the absence of
- // the hash of hashes for this |hash_store_id_|.
- if (!pref_hash_dicts->GetDictionaryWithoutPathExpansion(
- hash_store_id_, &hashes_dict) ||
- !pref_hash_dicts->GetDictionaryWithoutPathExpansion(
- internals::kHashOfHashesPref, &hash_of_hashes_dict) ||
- !hash_of_hashes_dict->GetStringWithoutPathExpansion(
- hash_store_id_, &hash_of_hashes)) {
- return false;
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreSplitHash(
+ const std::string& path,
+ const base::DictionaryValue* split_value) {
+ DictionaryPrefUpdate update(outer_->local_state_,
+ prefs::kProfilePreferenceHashes);
+ ClearPath(path, &update);
+
+ if (split_value) {
+ std::string keyed_path(path);
+ keyed_path.push_back('.');
+ const size_t common_part_length = keyed_path.length();
+ for (base::DictionaryValue::Iterator it(*split_value); !it.IsAtEnd();
+ it.Advance()) {
+ // Keep the common part from the old |keyed_path| and replace the key to
+ // get the new |keyed_path|.
+ keyed_path.replace(common_part_length, std::string::npos, it.key());
+ StoreHashInternal(keyed_path, &it.value(), &update);
+ }
}
+}
- return pref_hash_calculator_.Validate(
- hash_store_id_, hashes_dict, hash_of_hashes) == PrefHashCalculator::VALID;
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ClearPath(
+ const std::string& path,
+ DictionaryPrefUpdate* update) {
+ base::DictionaryValue* hashes_dict = NULL;
+ if (update->Get()->GetDictionaryWithoutPathExpansion(outer_->hash_store_id_,
+ &hashes_dict)) {
+ hashes_dict->Remove(path, NULL);
+ has_changed_ = true;
+ }
+}
+
+bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::HasSplitHashesAtPath(
+ const std::string& path) const {
+ const base::DictionaryValue* pref_hash_data =
+ outer_->local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
+ const base::DictionaryValue* hashed_prefs = NULL;
+ pref_hash_data->GetDictionaryWithoutPathExpansion(outer_->hash_store_id_,
+ &hashed_prefs);
+ return hashed_prefs && hashed_prefs->GetDictionary(path, NULL);
+}
+
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHashInternal(
+ const std::string& path,
+ const base::Value* new_value,
+ DictionaryPrefUpdate* update) {
+ base::DictionaryValue* hashes_dict = NULL;
+
+ // Get the dictionary corresponding to the profile name, which may have a '.'
+ if (!update->Get()->GetDictionaryWithoutPathExpansion(outer_->hash_store_id_,
+ &hashes_dict)) {
+ hashes_dict = new base::DictionaryValue;
+ update->Get()->SetWithoutPathExpansion(outer_->hash_store_id_, hashes_dict);
+ }
+ hashes_dict->SetString(
+ path, outer_->pref_hash_calculator_.Calculate(path, new_value));
+
+ has_changed_ = true;
}