Move StartsWith[ASCII] to base namespace.
[chromium-blink-merge.git] / components / password_manager / core / browser / affiliation_utils.cc
blob98c9099f6399bc3d9c638c4ce2d56e37f931b664
1 // Copyright 2014 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.
5 #include "components/password_manager/core/browser/affiliation_utils.h"
7 #include <algorithm>
8 #include <ostream>
10 #include "base/base64.h"
11 #include "base/command_line.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/string_util.h"
15 #include "components/autofill/core/common/password_form.h"
16 #include "components/password_manager/core/common/password_manager_switches.h"
17 #include "components/variations/variations_associated_data.h"
18 #include "net/base/escape.h"
19 #include "url/third_party/mozilla/url_parse.h"
20 #include "url/url_canon_stdstring.h"
22 namespace password_manager {
24 namespace {
26 // The scheme used for identifying Android applications.
27 const char kAndroidAppScheme[] = "android";
29 // The name of the field trial controlling affiliation-based matching.
30 const char kFieldTrialName[] = "AffiliationBasedMatching";
32 // Returns a StringPiece corresponding to |component| in |uri|, or the empty
33 // string in case there is no such component.
34 base::StringPiece ComponentString(const std::string& uri,
35 const url::Component& component) {
36 if (!component.is_valid())
37 return base::StringPiece();
38 return base::StringPiece(uri.c_str() + component.begin, component.len);
41 // Returns true if the passed ASCII |input| string contains nothing else than
42 // alphanumeric characters and those in |other_characters|.
43 bool ContainsOnlyAlphanumericAnd(const base::StringPiece& input,
44 const base::StringPiece& other_characters) {
45 for (char c : input) {
46 if (!IsAsciiAlpha(c) && !IsAsciiDigit(c) &&
47 other_characters.find(c) == base::StringPiece::npos)
48 return false;
50 return true;
53 // Canonicalizes a Web facet URI, and returns true if canonicalization was
54 // successful and produced a valid URI.
55 bool CanonicalizeWebFacetURI(const std::string& input_uri,
56 const url::Parsed& input_parsed,
57 std::string* canonical_uri) {
58 url::Parsed canonical_parsed;
59 url::StdStringCanonOutput canonical_output(canonical_uri);
61 bool canonicalization_succeeded = url::CanonicalizeStandardURL(
62 input_uri.c_str(), input_uri.size(), input_parsed, nullptr,
63 &canonical_output, &canonical_parsed);
64 canonical_output.Complete();
66 if (canonicalization_succeeded && canonical_parsed.host.is_nonempty() &&
67 !canonical_parsed.username.is_valid() &&
68 !canonical_parsed.password.is_valid() &&
69 ComponentString(*canonical_uri, canonical_parsed.path) == "/" &&
70 !canonical_parsed.query.is_valid() && !canonical_parsed.ref.is_valid()) {
71 // Get rid of the trailing slash added by url::CanonicalizeStandardURL().
72 DCHECK_EQ((size_t)canonical_parsed.path.begin, canonical_uri->size() - 1);
73 canonical_uri->erase(canonical_parsed.path.begin,
74 canonical_parsed.path.len);
75 return true;
77 return false;
80 // Adds padding until the length of the base64-encoded |data| becomes a multiple
81 // of 4, and returns true if the thusly obtained |data| is now correctly padded,
82 // i.e., there are at most 2 padding characters ('=') at the very end.
83 bool CanonicalizeBase64Padding(std::string* data) {
84 while (data->size() % 4u != 0u)
85 data->push_back('=');
87 size_t first_padding = data->find_first_of('=');
88 return first_padding == std::string::npos ||
89 (data->size() - first_padding <= 2u &&
90 data->find_first_not_of('=', first_padding) == std::string::npos);
93 // Canonicalizes the username component in an Android facet URI (containing the
94 // certificate hash), and returns true if canonicalization was successful and
95 // produced a valid non-empty component.
96 bool CanonicalizeHashComponent(const base::StringPiece& input_hash,
97 url::CanonOutput* canonical_output) {
98 // Characters other than alphanumeric that are used in the "URL and filename
99 // safe" base64 alphabet; plus the padding ('=').
100 const char kBase64NonAlphanumericChars[] = "-_=";
102 // We need net::UnescapeRule::URL_SPECIAL_CHARS to unescape the padding ('=').
103 std::string base64_encoded_hash = net::UnescapeURLComponent(
104 input_hash.as_string(), net::UnescapeRule::URL_SPECIAL_CHARS);
106 if (!base64_encoded_hash.empty() &&
107 CanonicalizeBase64Padding(&base64_encoded_hash) &&
108 ContainsOnlyAlphanumericAnd(base64_encoded_hash,
109 kBase64NonAlphanumericChars)) {
110 canonical_output->Append(base64_encoded_hash.data(),
111 base64_encoded_hash.size());
112 canonical_output->push_back('@');
113 return true;
115 return false;
118 // Canonicalizes the host component in an Android facet URI (containing the
119 // package name), and returns true if canonicalization was successful and
120 // produced a valid non-empty component.
121 bool CanonicalizePackageNameComponent(
122 const base::StringPiece& input_package_name,
123 url::CanonOutput* canonical_output) {
124 // Characters other than alphanumeric that are permitted in the package names.
125 const char kPackageNameNonAlphanumericChars[] = "._";
127 std::string package_name = net::UnescapeURLComponent(
128 input_package_name.as_string(), net::UnescapeRule::NORMAL);
130 // TODO(engedy): We might want to use a regex to check this more throughly.
131 if (!package_name.empty() &&
132 ContainsOnlyAlphanumericAnd(package_name,
133 kPackageNameNonAlphanumericChars)) {
134 canonical_output->Append(package_name.data(), package_name.size());
135 return true;
137 return false;
140 // Canonicalizes an Android facet URI, and returns true if canonicalization was
141 // successful and produced a valid URI.
142 bool CanonicalizeAndroidFacetURI(const std::string& input_uri,
143 const url::Parsed& input_parsed,
144 std::string* canonical_uri) {
145 url::StdStringCanonOutput canonical_output(canonical_uri);
147 url::Component unused;
148 bool success = url::CanonicalizeScheme(
149 input_uri.c_str(), input_parsed.scheme, &canonical_output, &unused);
151 canonical_output.push_back('/');
152 canonical_output.push_back('/');
154 // We cannot use url::CanonicalizeUserInfo as that would percent encode the
155 // base64 padding characters ('=').
156 success &= CanonicalizeHashComponent(
157 ComponentString(input_uri, input_parsed.username), &canonical_output);
159 // We cannot use url::CanonicalizeHost as that would convert the package name
160 // to lower case, but the package name is case sensitive.
161 success &= CanonicalizePackageNameComponent(
162 ComponentString(input_uri.data(), input_parsed.host), &canonical_output);
164 canonical_output.Complete();
166 return success && !input_parsed.password.is_nonempty() &&
167 (!input_parsed.path.is_nonempty() ||
168 ComponentString(input_uri, input_parsed.path) == "/") &&
169 !input_parsed.port.is_nonempty() && !input_parsed.query.is_valid() &&
170 !input_parsed.ref.is_valid();
173 // Computes the canonicalized form of |uri| into |canonical_uri|, and returns
174 // true if canonicalization was successful and produced a valid URI.
175 bool ParseAndCanonicalizeFacetURI(const std::string& input_uri,
176 std::string* canonical_uri) {
177 DCHECK(canonical_uri);
178 canonical_uri->clear();
179 canonical_uri->reserve(input_uri.size() + 32);
181 url::Parsed input_parsed;
182 url::ParseStandardURL(input_uri.c_str(), input_uri.size(), &input_parsed);
184 base::StringPiece scheme = ComponentString(input_uri, input_parsed.scheme);
185 if (base::LowerCaseEqualsASCII(scheme.begin(), scheme.end(),
186 url::kHttpsScheme)) {
187 return CanonicalizeWebFacetURI(input_uri, input_parsed, canonical_uri);
188 } else if (base::LowerCaseEqualsASCII(scheme.begin(), scheme.end(),
189 kAndroidAppScheme)) {
190 return CanonicalizeAndroidFacetURI(input_uri, input_parsed, canonical_uri);
192 return false;
195 } // namespace
198 // FacetURI -------------------------------------------------------------------
200 FacetURI::FacetURI() : is_valid_(false) {
203 // static
204 FacetURI FacetURI::FromPotentiallyInvalidSpec(const std::string& spec) {
205 std::string canonical_spec;
206 bool is_valid = ParseAndCanonicalizeFacetURI(spec, &canonical_spec);
207 return FacetURI(canonical_spec, is_valid);
210 // static
211 FacetURI FacetURI::FromCanonicalSpec(const std::string& canonical_spec) {
212 return FacetURI(canonical_spec, true);
215 bool FacetURI::operator==(const FacetURI& other) const {
216 DCHECK(is_empty() || is_valid());
217 DCHECK(other.is_empty() || other.is_valid());
218 return canonical_spec_ == other.canonical_spec_;
221 bool FacetURI::operator!=(const FacetURI& other) const {
222 DCHECK(is_empty() || is_valid());
223 DCHECK(other.is_empty() || other.is_valid());
224 return canonical_spec_ != other.canonical_spec_;
227 bool FacetURI::operator<(const FacetURI& other) const {
228 DCHECK(is_empty() || is_valid());
229 DCHECK(other.is_empty() || other.is_valid());
230 return canonical_spec_ < other.canonical_spec_;
233 bool FacetURI::operator>(const FacetURI& other) const {
234 DCHECK(is_empty() || is_valid());
235 DCHECK(other.is_empty() || other.is_valid());
236 return canonical_spec_ > other.canonical_spec_;
239 bool FacetURI::IsValidWebFacetURI() const {
240 return scheme() == url::kHttpsScheme;
243 bool FacetURI::IsValidAndroidFacetURI() const {
244 return scheme() == kAndroidAppScheme;
247 std::string FacetURI::scheme() const {
248 return is_valid()
249 ? ComponentString(canonical_spec_, parsed_.scheme).as_string()
250 : "";
253 std::string FacetURI::android_package_name() const {
254 if (!IsValidAndroidFacetURI())
255 return "";
256 return ComponentString(canonical_spec_, parsed_.host).as_string();
259 FacetURI::FacetURI(const std::string& canonical_spec, bool is_valid)
260 : is_valid_(is_valid), canonical_spec_(canonical_spec) {
261 // TODO(engedy): Refactor code in order to avoid to avoid parsing the URL
262 // twice.
263 url::ParseStandardURL(canonical_spec_.c_str(), canonical_spec_.size(),
264 &parsed_);
268 // AffiliatedFacetsWithUpdateTime ---------------------------------------------
270 AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime() {
273 AffiliatedFacetsWithUpdateTime::~AffiliatedFacetsWithUpdateTime() {
277 // Helpers --------------------------------------------------------------------
279 std::ostream& operator<<(std::ostream& os, const FacetURI& facet_uri) {
280 return os << facet_uri.potentially_invalid_spec();
283 bool AreEquivalenceClassesEqual(const AffiliatedFacets& a,
284 const AffiliatedFacets& b) {
285 if (a.size() != b.size())
286 return false;
288 std::vector<FacetURI> a_sorted(a.begin(), a.end());
289 std::vector<FacetURI> b_sorted(b.begin(), b.end());
290 std::sort(a_sorted.begin(), a_sorted.end());
291 std::sort(b_sorted.begin(), b_sorted.end());
292 return std::equal(a_sorted.begin(), a_sorted.end(), b_sorted.begin());
295 bool IsAffiliationBasedMatchingEnabled(const base::CommandLine& command_line) {
296 // Note: It is important to always query the field trial state, to ensure that
297 // UMA reports the correct group.
298 const std::string group_name =
299 base::FieldTrialList::FindFullName(kFieldTrialName);
301 if (command_line.HasSwitch(switches::kDisableAffiliationBasedMatching))
302 return false;
303 if (command_line.HasSwitch(switches::kEnableAffiliationBasedMatching))
304 return true;
305 return base::StartsWithASCII(group_name, "Enabled", /*case_sensitive=*/false);
308 bool IsPropagatingPasswordChangesToWebCredentialsEnabled(
309 const base::CommandLine& command_line) {
310 // Note: It is important to always query the variation param first, which, in
311 // turn, queries the field trial state, which ensures that UMA reports the
312 // correct group if it is forced by a command line flag.
313 const std::string update_enabled = variations::GetVariationParamValue(
314 kFieldTrialName, "propagate_password_changes_to_web");
316 if (command_line.HasSwitch(switches::kDisableAffiliationBasedMatching))
317 return false;
318 if (command_line.HasSwitch(switches::kEnableAffiliationBasedMatching))
319 return true;
320 return base::LowerCaseEqualsASCII(update_enabled, "enabled");
323 bool IsAffiliationRequestsForDummyFacetsEnabled(
324 const base::CommandLine& command_line) {
325 const std::string synthesizing_enabled = variations::GetVariationParamValue(
326 kFieldTrialName, "affiliation_requests_for_dummy_facets");
327 if (command_line.HasSwitch(switches::kDisableAffiliationBasedMatching))
328 return false;
329 if (command_line.HasSwitch(switches::kEnableAffiliationBasedMatching))
330 return true;
331 return base::LowerCaseEqualsASCII(synthesizing_enabled, "enabled");
334 bool IsValidAndroidFacetURI(const std::string& url) {
335 FacetURI facet = FacetURI::FromPotentiallyInvalidSpec(url);
336 return facet.IsValidAndroidFacetURI();
339 std::string GetHumanReadableOrigin(const autofill::PasswordForm& password_form,
340 const std::string& languages) {
341 password_manager::FacetURI facet_uri =
342 password_manager::FacetURI::FromPotentiallyInvalidSpec(
343 password_form.signon_realm);
344 if (facet_uri.IsValidAndroidFacetURI())
345 return facet_uri.scheme() + "://" + facet_uri.android_package_name();
346 return base::UTF16ToUTF8(net::FormatUrl(password_form.origin, languages));
348 } // namespace password_manager