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..17601d090ed4c073667cec528fe69667314652a7 |
--- /dev/null |
+++ b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config.cc |
@@ -0,0 +1,189 @@ |
+// 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 "net/base/registry_controlled_domains/registry_controlled_domain.h" |
+#include "url/gurl.h" |
+ |
+namespace safe_browsing { |
+ |
+namespace { |
+ |
+const char kSettingsResetPromptFeatureName[] = "SettingsResetPrompt"; |
+const char kDomainHashesParamName[] = "domain_hashes"; |
+ |
+} // 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; |
+ |
+ // Get the length of the top level domain or registry of the URL. Used |
+ // to guard against trying to match the (effective) TLDs themselves. |
+ size_t registry_length = net::registry_controlled_domains::GetRegistryLength( |
+ fixed_url, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES, |
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
+ // Do not proceed, if |fixed_url| does not have a host or consists entirely of |
+ // a registry or top domain. |
+ if (registry_length == 0 || registry_length == std::string::npos) |
+ return -1; |
+ |
+ // The hashes in the prompt config are generally TLD+1 and identify |
+ // only the topmost levels of URLs that we wish to prompt for. Try to |
+ // match each sensible suffix of the URL host with the hashes in the |
+ // prompt config. For example, if the host is |
+ // "www.sub.domain.com", try hashes for: |
+ // "www.sub.domain.com" |
+ // "sub.domain.com" |
+ // "domain.com" |
+ // We Do not check top level or registry domains to guard against bad |
+ // configuration data. |
+ SHA256Hash hash(crypto::kSHA256Length, '\0'); |
+ base::StringPiece host = fixed_url.host_piece(); |
+ while (host.size() > registry_length) { |
+ 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, |
+ 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("SettingsResetPrompt.ConfigError", error, |
+ CONFIG_ERROR_MAX); |
+ return error == CONFIG_ERROR_OK; |
+} |
+ |
+SettingsResetPromptConfig::ConfigError |
+SettingsResetPromptConfig::ParseDomainHashes( |
+ const std::string& domain_hashes_json) { |
+ 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. |
+ domain_hashes_.clear(); |
+ for (base::DictionaryValue::Iterator iter(*domains_dict); !iter.IsAtEnd(); |
+ iter.Advance()) { |
+ const std::string& hash_string = iter.key(); |
+ 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 = -1; |
+ if (!iter.value().GetAsString(&domain_id_string) || |
+ !base::StringToInt(domain_id_string, &domain_id) || domain_id < 0) { |
+ 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; |
+ } |
+ |
+ return CONFIG_ERROR_OK; |
+} |
+ |
+} // namespace safe_browsing. |