Index: components/password_manager/core/browser/affiliation_utils.cc |
diff --git a/components/password_manager/core/browser/affiliation_utils.cc b/components/password_manager/core/browser/affiliation_utils.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..19b9ba1c01c0c8bc5e38d3717710f6c5c3e497fb |
--- /dev/null |
+++ b/components/password_manager/core/browser/affiliation_utils.cc |
@@ -0,0 +1,314 @@ |
+// Copyright 2014 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 "components/password_manager/core/browser/affiliation_utils.h" |
+ |
+#include <algorithm> |
+#include <ostream> |
+ |
+#include "base/base64.h" |
+#include "base/strings/string_piece.h" |
+#include "base/strings/string_util.h" |
+#include "components/autofill/core/common/password_form.h" |
+#include "components/url_formatter/elide_url.h" |
+#include "components/variations/variations_associated_data.h" |
+#include "net/base/escape.h" |
+#include "url/third_party/mozilla/url_parse.h" |
+#include "url/url_canon_stdstring.h" |
+ |
+namespace password_manager { |
+ |
+namespace { |
+ |
+// The scheme used for identifying Android applications. |
+const char kAndroidAppScheme[] = "android"; |
+ |
+// Returns a StringPiece corresponding to |component| in |uri|, or the empty |
+// string in case there is no such component. |
+base::StringPiece ComponentString(const std::string& uri, |
+ const url::Component& component) { |
+ if (!component.is_valid()) |
+ return base::StringPiece(); |
+ return base::StringPiece(uri.c_str() + component.begin, component.len); |
+} |
+ |
+// Returns true if the passed ASCII |input| string contains nothing else than |
+// alphanumeric characters and those in |other_characters|. |
+bool ContainsOnlyAlphanumericAnd(const base::StringPiece& input, |
+ const base::StringPiece& other_characters) { |
+ for (char c : input) { |
+ if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && |
+ other_characters.find(c) == base::StringPiece::npos) |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+// Canonicalizes a Web facet URI, and returns true if canonicalization was |
+// successful and produced a valid URI. |
+bool CanonicalizeWebFacetURI(const std::string& input_uri, |
+ const url::Parsed& input_parsed, |
+ std::string* canonical_uri) { |
+ url::Parsed canonical_parsed; |
+ url::StdStringCanonOutput canonical_output(canonical_uri); |
+ |
+ bool canonicalization_succeeded = url::CanonicalizeStandardURL( |
+ input_uri.c_str(), input_uri.size(), input_parsed, nullptr, |
+ &canonical_output, &canonical_parsed); |
+ canonical_output.Complete(); |
+ |
+ if (canonicalization_succeeded && canonical_parsed.host.is_nonempty() && |
+ !canonical_parsed.username.is_valid() && |
+ !canonical_parsed.password.is_valid() && |
+ ComponentString(*canonical_uri, canonical_parsed.path) == "/" && |
+ !canonical_parsed.query.is_valid() && !canonical_parsed.ref.is_valid()) { |
+ // Get rid of the trailing slash added by url::CanonicalizeStandardURL(). |
+ DCHECK_EQ((size_t)canonical_parsed.path.begin, canonical_uri->size() - 1); |
+ canonical_uri->erase(canonical_parsed.path.begin, |
+ canonical_parsed.path.len); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+// Adds padding until the length of the base64-encoded |data| becomes a multiple |
+// of 4, and returns true if the thusly obtained |data| is now correctly padded, |
+// i.e., there are at most 2 padding characters ('=') at the very end. |
+bool CanonicalizeBase64Padding(std::string* data) { |
+ while (data->size() % 4u != 0u) |
+ data->push_back('='); |
+ |
+ size_t first_padding = data->find_first_of('='); |
+ return first_padding == std::string::npos || |
+ (data->size() - first_padding <= 2u && |
+ data->find_first_not_of('=', first_padding) == std::string::npos); |
+} |
+ |
+// Canonicalizes the username component in an Android facet URI (containing the |
+// certificate hash), and returns true if canonicalization was successful and |
+// produced a valid non-empty component. |
+bool CanonicalizeHashComponent(const base::StringPiece& input_hash, |
+ url::CanonOutput* canonical_output) { |
+ // Characters other than alphanumeric that are used in the "URL and filename |
+ // safe" base64 alphabet; plus the padding ('='). |
+ const char kBase64NonAlphanumericChars[] = "-_="; |
+ |
+ // We need net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS to |
+ // unescape the padding ('='). |
+ std::string base64_encoded_hash = net::UnescapeURLComponent( |
+ input_hash.as_string(), |
+ net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS); |
+ |
+ if (!base64_encoded_hash.empty() && |
+ CanonicalizeBase64Padding(&base64_encoded_hash) && |
+ ContainsOnlyAlphanumericAnd(base64_encoded_hash, |
+ kBase64NonAlphanumericChars)) { |
+ canonical_output->Append(base64_encoded_hash.data(), |
+ base64_encoded_hash.size()); |
+ canonical_output->push_back('@'); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+// Canonicalizes the host component in an Android facet URI (containing the |
+// package name), and returns true if canonicalization was successful and |
+// produced a valid non-empty component. |
+bool CanonicalizePackageNameComponent( |
+ const base::StringPiece& input_package_name, |
+ url::CanonOutput* canonical_output) { |
+ // Characters other than alphanumeric that are permitted in the package names. |
+ const char kPackageNameNonAlphanumericChars[] = "._"; |
+ |
+ std::string package_name = net::UnescapeURLComponent( |
+ input_package_name.as_string(), net::UnescapeRule::NORMAL); |
+ |
+ // TODO(engedy): We might want to use a regex to check this more throughly. |
+ if (!package_name.empty() && |
+ ContainsOnlyAlphanumericAnd(package_name, |
+ kPackageNameNonAlphanumericChars)) { |
+ canonical_output->Append(package_name.data(), package_name.size()); |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+// Canonicalizes an Android facet URI, and returns true if canonicalization was |
+// successful and produced a valid URI. |
+bool CanonicalizeAndroidFacetURI(const std::string& input_uri, |
+ const url::Parsed& input_parsed, |
+ std::string* canonical_uri) { |
+ url::StdStringCanonOutput canonical_output(canonical_uri); |
+ |
+ url::Component unused; |
+ bool success = url::CanonicalizeScheme( |
+ input_uri.c_str(), input_parsed.scheme, &canonical_output, &unused); |
+ |
+ canonical_output.push_back('/'); |
+ canonical_output.push_back('/'); |
+ |
+ // We cannot use url::CanonicalizeUserInfo as that would percent encode the |
+ // base64 padding characters ('='). |
+ success &= CanonicalizeHashComponent( |
+ ComponentString(input_uri, input_parsed.username), &canonical_output); |
+ |
+ // We cannot use url::CanonicalizeHost as that would convert the package name |
+ // to lower case, but the package name is case sensitive. |
+ success &= CanonicalizePackageNameComponent( |
+ ComponentString(input_uri.data(), input_parsed.host), &canonical_output); |
+ |
+ canonical_output.Complete(); |
+ |
+ return success && !input_parsed.password.is_nonempty() && |
+ (!input_parsed.path.is_nonempty() || |
+ ComponentString(input_uri, input_parsed.path) == "/") && |
+ !input_parsed.port.is_nonempty() && !input_parsed.query.is_valid() && |
+ !input_parsed.ref.is_valid(); |
+} |
+ |
+// Computes the canonicalized form of |uri| into |canonical_uri|, and returns |
+// true if canonicalization was successful and produced a valid URI. |
+bool ParseAndCanonicalizeFacetURI(const std::string& input_uri, |
+ std::string* canonical_uri) { |
+ DCHECK(canonical_uri); |
+ canonical_uri->clear(); |
+ canonical_uri->reserve(input_uri.size() + 32); |
+ |
+ url::Parsed input_parsed; |
+ url::ParseStandardURL(input_uri.c_str(), input_uri.size(), &input_parsed); |
+ |
+ base::StringPiece scheme = ComponentString(input_uri, input_parsed.scheme); |
+ if (base::LowerCaseEqualsASCII(scheme, url::kHttpsScheme)) { |
+ return CanonicalizeWebFacetURI(input_uri, input_parsed, canonical_uri); |
+ } else if (base::LowerCaseEqualsASCII(scheme, kAndroidAppScheme)) { |
+ return CanonicalizeAndroidFacetURI(input_uri, input_parsed, canonical_uri); |
+ } |
+ return false; |
+} |
+ |
+} // namespace |
+ |
+ |
+// FacetURI ------------------------------------------------------------------- |
+ |
+FacetURI::FacetURI() : is_valid_(false) { |
+} |
+ |
+// static |
+FacetURI FacetURI::FromPotentiallyInvalidSpec(const std::string& spec) { |
+ std::string canonical_spec; |
+ bool is_valid = ParseAndCanonicalizeFacetURI(spec, &canonical_spec); |
+ return FacetURI(canonical_spec, is_valid); |
+} |
+ |
+// static |
+FacetURI FacetURI::FromCanonicalSpec(const std::string& canonical_spec) { |
+ return FacetURI(canonical_spec, true); |
+} |
+ |
+bool FacetURI::operator==(const FacetURI& other) const { |
+ DCHECK(is_empty() || is_valid()); |
+ DCHECK(other.is_empty() || other.is_valid()); |
+ return canonical_spec_ == other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator!=(const FacetURI& other) const { |
+ DCHECK(is_empty() || is_valid()); |
+ DCHECK(other.is_empty() || other.is_valid()); |
+ return canonical_spec_ != other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator<(const FacetURI& other) const { |
+ DCHECK(is_empty() || is_valid()); |
+ DCHECK(other.is_empty() || other.is_valid()); |
+ return canonical_spec_ < other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator>(const FacetURI& other) const { |
+ DCHECK(is_empty() || is_valid()); |
+ DCHECK(other.is_empty() || other.is_valid()); |
+ return canonical_spec_ > other.canonical_spec_; |
+} |
+ |
+bool FacetURI::IsValidWebFacetURI() const { |
+ return scheme() == url::kHttpsScheme; |
+} |
+ |
+bool FacetURI::IsValidAndroidFacetURI() const { |
+ return scheme() == kAndroidAppScheme; |
+} |
+ |
+std::string FacetURI::scheme() const { |
+ return is_valid() |
+ ? ComponentString(canonical_spec_, parsed_.scheme).as_string() |
+ : ""; |
+} |
+ |
+std::string FacetURI::android_package_name() const { |
+ if (!IsValidAndroidFacetURI()) |
+ return ""; |
+ return ComponentString(canonical_spec_, parsed_.host).as_string(); |
+} |
+ |
+FacetURI::FacetURI(const std::string& canonical_spec, bool is_valid) |
+ : is_valid_(is_valid), canonical_spec_(canonical_spec) { |
+ // TODO(engedy): Refactor code in order to avoid to avoid parsing the URL |
+ // twice. |
+ url::ParseStandardURL(canonical_spec_.c_str(), canonical_spec_.size(), |
+ &parsed_); |
+} |
+ |
+ |
+// AffiliatedFacetsWithUpdateTime --------------------------------------------- |
+ |
+AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime() { |
+} |
+ |
+AffiliatedFacetsWithUpdateTime::AffiliatedFacetsWithUpdateTime( |
+ const AffiliatedFacetsWithUpdateTime& other) = default; |
+ |
+AffiliatedFacetsWithUpdateTime::~AffiliatedFacetsWithUpdateTime() { |
+} |
+ |
+ |
+// Helpers -------------------------------------------------------------------- |
+ |
+std::ostream& operator<<(std::ostream& os, const FacetURI& facet_uri) { |
+ return os << facet_uri.potentially_invalid_spec(); |
+} |
+ |
+bool AreEquivalenceClassesEqual(const AffiliatedFacets& a, |
+ const AffiliatedFacets& b) { |
+ if (a.size() != b.size()) |
+ return false; |
+ |
+ std::vector<FacetURI> a_sorted(a.begin(), a.end()); |
+ std::vector<FacetURI> b_sorted(b.begin(), b.end()); |
+ std::sort(a_sorted.begin(), a_sorted.end()); |
+ std::sort(b_sorted.begin(), b_sorted.end()); |
+ return std::equal(a_sorted.begin(), a_sorted.end(), b_sorted.begin()); |
+} |
+ |
+bool IsValidAndroidFacetURI(const std::string& url) { |
+ FacetURI facet = FacetURI::FromPotentiallyInvalidSpec(url); |
+ return facet.IsValidAndroidFacetURI(); |
+} |
+ |
+std::string GetHumanReadableOrigin( |
+ const autofill::PasswordForm& password_form) { |
+ FacetURI facet_uri = |
+ FacetURI::FromPotentiallyInvalidSpec(password_form.signon_realm); |
+ if (facet_uri.IsValidAndroidFacetURI()) |
+ return GetHumanReadableOriginForAndroidUri(facet_uri); |
+ |
+ return base::UTF16ToUTF8( |
+ url_formatter::FormatUrlForSecurityDisplay(password_form.origin)); |
+} |
+ |
+std::string GetHumanReadableOriginForAndroidUri(const FacetURI facet_uri) { |
+ DCHECK(facet_uri.IsValidAndroidFacetURI()); |
+ return facet_uri.scheme() + "://" + facet_uri.android_package_name(); |
+} |
+ |
+} // namespace password_manager |