Chromium Code Reviews| Index: chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.cc |
| diff --git a/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.cc b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..54ac53fe74cc09d41452b7f906a6856000be5664 |
| --- /dev/null |
| +++ b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.cc |
| @@ -0,0 +1,178 @@ |
| +// Copyright 2017 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 "chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.h" |
| + |
| +#include <utility> |
| + |
| +#include "base/json/json_reader.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/metrics/histogram_macros.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/strings/string_piece.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/values.h" |
| +#include "components/url_formatter/url_fixer.h" |
| +#include "components/variations/variations_associated_data.h" |
| +#include "crypto/sha2.h" |
| +#include "url/gurl.h" |
| + |
| +namespace safe_browsing { |
| + |
| +namespace { |
| + |
| +const char kSettingsResetPromptFeatureName[] = "SettingsResetPrompt"; |
| +const char kDomainHashesParamName[] = "domain_hashes"; |
| +const char kConfigErrorMetric[] = "SettingsResetPrompt.ConfigError"; |
| + |
| +} // namespace. |
| + |
| +const base::Feature kSettingsResetPrompt{kSettingsResetPromptFeatureName, |
| + base::FEATURE_DISABLED_BY_DEFAULT}; |
| + |
| +// static |
| +bool SettingsResetPromptConfig::IsPromptEnabled() { |
| + // TODO(alito): Add prefs to local state to track when the user was |
| + // last prompted and ensure that we only prompt once per reset prompt |
| + // wave. |
| + return base::FeatureList::IsEnabled(kSettingsResetPrompt); |
| +} |
| + |
| +// static |
| +std::unique_ptr<SettingsResetPromptConfig> SettingsResetPromptConfig::Create() { |
| + if (!IsPromptEnabled()) |
| + return nullptr; |
| + |
| + auto prompt_config = base::WrapUnique(new SettingsResetPromptConfig()); |
| + if (!prompt_config->Init()) |
| + return nullptr; |
| + |
| + return prompt_config; |
| +} |
| + |
| +SettingsResetPromptConfig::SettingsResetPromptConfig() {} |
| + |
| +SettingsResetPromptConfig::~SettingsResetPromptConfig() {} |
| + |
| +int SettingsResetPromptConfig::UrlToResetDomainId(const GURL& url) const { |
| + DCHECK(IsPromptEnabled()); |
| + |
| + // Do a best-effort to fix the URL before testing if it is valid. |
| + GURL fixed_url = |
| + url_formatter::FixupURL(url.possibly_invalid_spec(), std::string()); |
| + if (!fixed_url.is_valid()) |
| + return -1; |
| + |
| + // Try to match each sensible suffix of the URL host with the hashes |
|
csharp
2017/01/09 21:54:59
Maybe worth mentioning why we want to try each suf
alito
2017/01/10 00:37:48
Done.
|
| + // in the prompt config. For example, if the host is |
| + // "www.sub.domain.com", try hashes for for: |
| + // "www.sub.domain.com" |
| + // "sub.domain.com" |
| + // "domain.com" |
| + // "com" |
| + SHA256Hash hash(crypto::kSHA256Length, '\0'); |
| + base::StringPiece host = fixed_url.host_piece(); |
| + while (!host.empty()) { |
| + crypto::SHA256HashString(host, hash.data(), crypto::kSHA256Length); |
| + auto iter = domain_hashes_.find(hash); |
| + if (iter != domain_hashes_.end()) |
| + return iter->second; |
| + |
| + size_t next_start_pos = host.find('.'); |
| + next_start_pos = next_start_pos == base::StringPiece::npos |
| + ? base::StringPiece::npos |
| + : next_start_pos + 1; |
| + host = host.substr(next_start_pos); |
| + } |
| + |
| + return -1; |
| +} |
| + |
| +// Implements the hash function for SHA256Hash objects. Simply uses the |
| +// first bytes of the SHA256 hash as its own hash. |
| +size_t SettingsResetPromptConfig::SHA256HashHasher::operator()( |
| + const SHA256Hash& key) const { |
| + DCHECK_EQ(crypto::kSHA256Length, key.size()); |
| + // This is safe because |key| contains 32 bytes while a size_t is |
| + // either 4 or 8 bytes. |
| + return *reinterpret_cast<const size_t*>(key.data()); |
| +} |
| + |
| +// These values are written to logs. New enum values can be added, but |
| +// existing enums must never be renumbered or deleted and reused. If you |
| +// do add values, also update the corresponding enum definition in the |
| +// histograms.xml file. |
| +enum SettingsResetPromptConfig::ConfigError : int { |
| + CONFIG_ERROR_OK = 1, |
|
csharp
2017/01/09 21:54:59
What about setting this to 0? (0 seem a bit more l
alito
2017/01/10 00:37:48
In general, I find reporting zero values to be a h
|
| + CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM = 2, |
| + CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM = 3, |
| + CONFIG_ERROR_BAD_DOMAIN_HASH = 4, |
| + CONFIG_ERROR_BAD_DOMAIN_ID = 5, |
| + CONFIG_ERROR_DUPLICATE_DOMAIN_HASH = 6, |
| + CONFIG_ERROR_MAX |
| +}; |
| + |
| +bool SettingsResetPromptConfig::Init() { |
| + if (!IsPromptEnabled()) |
| + return false; |
| + |
| + std::string domain_hashes_json = variations::GetVariationParamValueByFeature( |
| + kSettingsResetPrompt, kDomainHashesParamName); |
| + ConfigError error = ParseDomainHashes(domain_hashes_json); |
| + UMA_HISTOGRAM_ENUMERATION(kConfigErrorMetric, error, CONFIG_ERROR_MAX); |
| + return error == CONFIG_ERROR_OK; |
| +} |
| + |
| +SettingsResetPromptConfig::ConfigError |
| +SettingsResetPromptConfig::ParseDomainHashes( |
| + const std::string& domain_hashes_json) { |
| + DCHECK(domain_hashes_.empty()); |
| + |
| + if (domain_hashes_json.empty()) |
| + return CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM; |
| + |
| + // Is the input parseable JSON? |
| + std::unique_ptr<base::DictionaryValue> domains_dict = |
| + base::DictionaryValue::From(base::JSONReader::Read(domain_hashes_json)); |
| + if (!domains_dict || domains_dict->empty()) |
| + return CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM; |
| + |
| + // The input JSON should be a hash object with hex-encoded 32-byte |
| + // hashes as keys and integer IDs as values. For example, |
| + // |
| + // {"2714..D7": "1", "2821..CB": "2", ...} |
| + // |
| + // Each key in the hash should be a 64-byte long string and each |
| + // integer ID should fit in an int. |
| + std::unordered_map<SHA256Hash, int, SHA256HashHasher> domain_hashes; |
|
csharp
2017/01/09 21:54:59
What about just clearing domains_hashes_ here and
alito
2017/01/10 00:37:48
Done.
|
| + for (base::DictionaryValue::Iterator iter(*domains_dict); !iter.IsAtEnd(); |
| + iter.Advance()) { |
| + std::string hash_string = iter.key(); |
|
csharp
2017/01/09 21:54:59
const std::string&?
alito
2017/01/10 00:37:48
Done.
|
| + if (hash_string.size() != crypto::kSHA256Length * 2) |
| + return CONFIG_ERROR_BAD_DOMAIN_HASH; |
| + |
| + // Convert hex-encoded hash string to its numeric value as bytes. |
| + SHA256Hash hash; |
| + hash.reserve(crypto::kSHA256Length); |
| + if (!base::HexStringToBytes(hash_string, &hash)) |
| + return CONFIG_ERROR_BAD_DOMAIN_HASH; |
| + |
| + // Convert the ID string to an integer. |
| + std::string domain_id_string; |
| + int domain_id; |
| + if (!iter.value().GetAsString(&domain_id_string) || |
| + !base::StringToInt(domain_id_string, &domain_id)) { |
| + return CONFIG_ERROR_BAD_DOMAIN_ID; |
| + } |
| + |
| + if (!domain_hashes.insert(std::make_pair(std::move(hash), domain_id)) |
| + .second) |
| + return CONFIG_ERROR_DUPLICATE_DOMAIN_HASH; |
| + } |
| + |
| + domain_hashes_ = std::move(domain_hashes); |
| + return CONFIG_ERROR_OK; |
| +} |
| + |
| +} // namespace safe_browsing. |