Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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/safe_browsing/settings_reset_prompt/settings_reset_prom pt_config.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/json/json_reader.h" | |
| 10 #include "base/memory/ptr_util.h" | |
| 11 #include "base/metrics/histogram_macros.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/strings/string_piece.h" | |
| 14 #include "base/strings/string_util.h" | |
| 15 #include "base/values.h" | |
| 16 #include "components/url_formatter/url_fixer.h" | |
| 17 #include "components/variations/variations_associated_data.h" | |
| 18 #include "crypto/sha2.h" | |
| 19 #include "url/gurl.h" | |
| 20 | |
| 21 namespace safe_browsing { | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 const char kSettingsResetPromptFeatureName[] = "SettingsResetPrompt"; | |
| 26 const char kDomainHashesParamName[] = "domain_hashes"; | |
| 27 const char kConfigErrorMetric[] = "SettingsResetPrompt.ConfigError"; | |
| 28 | |
| 29 } // namespace. | |
| 30 | |
| 31 const base::Feature kSettingsResetPrompt{kSettingsResetPromptFeatureName, | |
| 32 base::FEATURE_DISABLED_BY_DEFAULT}; | |
| 33 | |
| 34 // static | |
| 35 bool SettingsResetPromptConfig::IsPromptEnabled() { | |
| 36 // TODO(alito): Add prefs to local state to track when the user was | |
| 37 // last prompted and ensure that we only prompt once per reset prompt | |
| 38 // wave. | |
| 39 return base::FeatureList::IsEnabled(kSettingsResetPrompt); | |
| 40 } | |
| 41 | |
| 42 // static | |
| 43 std::unique_ptr<SettingsResetPromptConfig> SettingsResetPromptConfig::Create() { | |
| 44 if (!IsPromptEnabled()) | |
| 45 return nullptr; | |
| 46 | |
| 47 auto prompt_config = base::WrapUnique(new SettingsResetPromptConfig()); | |
| 48 if (!prompt_config->Init()) | |
| 49 return nullptr; | |
| 50 | |
| 51 return prompt_config; | |
| 52 } | |
| 53 | |
| 54 SettingsResetPromptConfig::SettingsResetPromptConfig() {} | |
| 55 | |
| 56 SettingsResetPromptConfig::~SettingsResetPromptConfig() {} | |
| 57 | |
| 58 int SettingsResetPromptConfig::UrlToResetDomainId(const GURL& url) const { | |
| 59 DCHECK(IsPromptEnabled()); | |
| 60 | |
| 61 // Do a best-effort to fix the URL before testing if it is valid. | |
| 62 GURL fixed_url = | |
| 63 url_formatter::FixupURL(url.possibly_invalid_spec(), std::string()); | |
| 64 if (!fixed_url.is_valid()) | |
| 65 return -1; | |
| 66 | |
| 67 // 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.
| |
| 68 // in the prompt config. For example, if the host is | |
| 69 // "www.sub.domain.com", try hashes for for: | |
| 70 // "www.sub.domain.com" | |
| 71 // "sub.domain.com" | |
| 72 // "domain.com" | |
| 73 // "com" | |
| 74 SHA256Hash hash(crypto::kSHA256Length, '\0'); | |
| 75 base::StringPiece host = fixed_url.host_piece(); | |
| 76 while (!host.empty()) { | |
| 77 crypto::SHA256HashString(host, hash.data(), crypto::kSHA256Length); | |
| 78 auto iter = domain_hashes_.find(hash); | |
| 79 if (iter != domain_hashes_.end()) | |
| 80 return iter->second; | |
| 81 | |
| 82 size_t next_start_pos = host.find('.'); | |
| 83 next_start_pos = next_start_pos == base::StringPiece::npos | |
| 84 ? base::StringPiece::npos | |
| 85 : next_start_pos + 1; | |
| 86 host = host.substr(next_start_pos); | |
| 87 } | |
| 88 | |
| 89 return -1; | |
| 90 } | |
| 91 | |
| 92 // Implements the hash function for SHA256Hash objects. Simply uses the | |
| 93 // first bytes of the SHA256 hash as its own hash. | |
| 94 size_t SettingsResetPromptConfig::SHA256HashHasher::operator()( | |
| 95 const SHA256Hash& key) const { | |
| 96 DCHECK_EQ(crypto::kSHA256Length, key.size()); | |
| 97 // This is safe because |key| contains 32 bytes while a size_t is | |
| 98 // either 4 or 8 bytes. | |
| 99 return *reinterpret_cast<const size_t*>(key.data()); | |
| 100 } | |
| 101 | |
| 102 // These values are written to logs. New enum values can be added, but | |
| 103 // existing enums must never be renumbered or deleted and reused. If you | |
| 104 // do add values, also update the corresponding enum definition in the | |
| 105 // histograms.xml file. | |
| 106 enum SettingsResetPromptConfig::ConfigError : int { | |
| 107 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
| |
| 108 CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM = 2, | |
| 109 CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM = 3, | |
| 110 CONFIG_ERROR_BAD_DOMAIN_HASH = 4, | |
| 111 CONFIG_ERROR_BAD_DOMAIN_ID = 5, | |
| 112 CONFIG_ERROR_DUPLICATE_DOMAIN_HASH = 6, | |
| 113 CONFIG_ERROR_MAX | |
| 114 }; | |
| 115 | |
| 116 bool SettingsResetPromptConfig::Init() { | |
| 117 if (!IsPromptEnabled()) | |
| 118 return false; | |
| 119 | |
| 120 std::string domain_hashes_json = variations::GetVariationParamValueByFeature( | |
| 121 kSettingsResetPrompt, kDomainHashesParamName); | |
| 122 ConfigError error = ParseDomainHashes(domain_hashes_json); | |
| 123 UMA_HISTOGRAM_ENUMERATION(kConfigErrorMetric, error, CONFIG_ERROR_MAX); | |
| 124 return error == CONFIG_ERROR_OK; | |
| 125 } | |
| 126 | |
| 127 SettingsResetPromptConfig::ConfigError | |
| 128 SettingsResetPromptConfig::ParseDomainHashes( | |
| 129 const std::string& domain_hashes_json) { | |
| 130 DCHECK(domain_hashes_.empty()); | |
| 131 | |
| 132 if (domain_hashes_json.empty()) | |
| 133 return CONFIG_ERROR_MISSING_DOMAIN_HASHES_PARAM; | |
| 134 | |
| 135 // Is the input parseable JSON? | |
| 136 std::unique_ptr<base::DictionaryValue> domains_dict = | |
| 137 base::DictionaryValue::From(base::JSONReader::Read(domain_hashes_json)); | |
| 138 if (!domains_dict || domains_dict->empty()) | |
| 139 return CONFIG_ERROR_BAD_DOMAIN_HASHES_PARAM; | |
| 140 | |
| 141 // The input JSON should be a hash object with hex-encoded 32-byte | |
| 142 // hashes as keys and integer IDs as values. For example, | |
| 143 // | |
| 144 // {"2714..D7": "1", "2821..CB": "2", ...} | |
| 145 // | |
| 146 // Each key in the hash should be a 64-byte long string and each | |
| 147 // integer ID should fit in an int. | |
| 148 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.
| |
| 149 for (base::DictionaryValue::Iterator iter(*domains_dict); !iter.IsAtEnd(); | |
| 150 iter.Advance()) { | |
| 151 std::string hash_string = iter.key(); | |
|
csharp
2017/01/09 21:54:59
const std::string&?
alito
2017/01/10 00:37:48
Done.
| |
| 152 if (hash_string.size() != crypto::kSHA256Length * 2) | |
| 153 return CONFIG_ERROR_BAD_DOMAIN_HASH; | |
| 154 | |
| 155 // Convert hex-encoded hash string to its numeric value as bytes. | |
| 156 SHA256Hash hash; | |
| 157 hash.reserve(crypto::kSHA256Length); | |
| 158 if (!base::HexStringToBytes(hash_string, &hash)) | |
| 159 return CONFIG_ERROR_BAD_DOMAIN_HASH; | |
| 160 | |
| 161 // Convert the ID string to an integer. | |
| 162 std::string domain_id_string; | |
| 163 int domain_id; | |
| 164 if (!iter.value().GetAsString(&domain_id_string) || | |
| 165 !base::StringToInt(domain_id_string, &domain_id)) { | |
| 166 return CONFIG_ERROR_BAD_DOMAIN_ID; | |
| 167 } | |
| 168 | |
| 169 if (!domain_hashes.insert(std::make_pair(std::move(hash), domain_id)) | |
| 170 .second) | |
| 171 return CONFIG_ERROR_DUPLICATE_DOMAIN_HASH; | |
| 172 } | |
| 173 | |
| 174 domain_hashes_ = std::move(domain_hashes); | |
| 175 return CONFIG_ERROR_OK; | |
| 176 } | |
| 177 | |
| 178 } // namespace safe_browsing. | |
| OLD | NEW |