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..8c87015478e39f0317f3a0e6d26c647d390c7f26 |
--- /dev/null |
+++ b/components/password_manager/core/browser/affiliation_utils.cc |
@@ -0,0 +1,243 @@ |
+// 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 "base/base64.h" |
+#include "base/strings/string_piece.h" |
+#include "base/strings/string_util.h" |
+#include "net/base/escape.h" |
+#include "url/third_party/mozilla/url_parse.h" |
+#include "url/url_canon_stdstring.h" |
+#include "url/url_util.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); |
+} |
+ |
+// Extracts the scheme of an unparsed |uri| as a StringPiece, or returns the |
+// empty string on failure. |
+base::StringPiece ExtractScheme(const std::string& uri) { |
+ url::Component scheme_component; |
+ if (url::ExtractScheme(uri.c_str(), uri.size(), &scheme_component)) |
+ return ComponentString(uri, scheme_component); |
+ return base::StringPiece(); |
+} |
+ |
+// 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, |
Mike West
2014/12/09 12:13:34
I'd suggest that this method accept an enum rather
engedy
2014/12/09 14:54:46
I have added name constants at the call sites, PTA
|
+ const base::StringPiece& other_characters) { |
+ for (char c : input) { |
+ if (!IsAsciiAlpha(c) && !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. Even in case of a failure, something |
+// meaningful will be written to |canonical_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, NULL, |
+ &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; |
+} |
+ |
+// 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. Even in case of a failure, something |
+// meaningful will be written to |canonical_output|. |
+bool CanonicalizeHashComponent(const base::StringPiece& input_hash, |
Mike West
2014/12/09 12:13:34
It doesn't look like you're canonicalizing the str
engedy
2014/12/09 14:54:46
Done.
|
+ url::CanonOutput* canonical_output) { |
+ // We need net::UnescapeRule::URL_SPECIAL_CHARS for the '='. |
Mike West
2014/12/09 12:13:34
For which '='? The padding at the end of the hash?
engedy
2014/12/09 14:54:46
Yes, clarified.
|
+ std::string base64_encoded_hash = net::UnescapeURLComponent( |
+ input_hash.as_string(), net::UnescapeRule::URL_SPECIAL_CHARS); |
+ if (!base64_encoded_hash.empty()) { |
+ canonical_output->Append(base64_encoded_hash.data(), |
+ base64_encoded_hash.size()); |
+ canonical_output->push_back('@'); |
+ } |
+ |
+ size_t first_padding = base64_encoded_hash.find_first_of('='); |
+ return !base64_encoded_hash.empty() && base64_encoded_hash.size() % 4 == 0 && |
+ ContainsOnlyAlphanumericAnd(base64_encoded_hash, "-_=") && |
+ (first_padding == std::string::npos || |
Mike West
2014/12/09 12:13:34
I think it makes sense to extract the padding chec
engedy
2014/12/09 14:54:46
Done.
|
+ first_padding == base64_encoded_hash.size() - 1 || |
+ (first_padding == base64_encoded_hash.size() - 2 && |
+ base64_encoded_hash[base64_encoded_hash.size() - 1] == '=')); |
+} |
+ |
+// 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. Even in case of a failure, something |
+// meaningful will be written to |canonical_output|. |
Mike West
2014/12/09 12:13:34
Why would you write something even in a failure ca
engedy
2014/12/09 14:54:46
As discussed, this was for consistency with url_ca
|
+bool CanonicalizePackageNameComponent( |
+ const base::StringPiece& input_package_name, |
+ url::CanonOutput* canonical_output) { |
+ std::string package_name = net::UnescapeURLComponent( |
+ input_package_name.as_string(), net::UnescapeRule::NORMAL); |
+ canonical_output->Append(package_name.data(), package_name.size()); |
+ |
+ // TODO(engedy): We might want to use a regex to check this more throughly. |
+ return !package_name.empty() && |
+ ContainsOnlyAlphanumericAnd(package_name, "_."); |
+} |
+ |
+// Canonicalizes an Android facet URI, and returns true if canonicalization was |
+// successful and produced a valid URI. Even in case of a failure, something |
+// meaningful will be written to |canonical_uri|. |
Mike West
2014/12/09 12:13:34
Ditto: if you've failed, why write anything to the
engedy
2014/12/09 14:54:46
Done.
|
+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 |
+ // 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 |
+// whether or not the result is a valid facet URI. Even if the result is not |
+// valid, |canonical_uri| will contain something reasonable. |
+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 (url::LowerCaseEqualsASCII(scheme.begin(), scheme.end(), |
+ url::kHttpsScheme)) { |
+ return CanonicalizeWebFacetURI(input_uri, input_parsed, canonical_uri); |
+ } else if (url::LowerCaseEqualsASCII(scheme.begin(), scheme.end(), |
+ kAndroidAppScheme)) { |
+ return CanonicalizeAndroidFacetURI(input_uri, input_parsed, canonical_uri); |
+ } |
+ return false; |
+} |
+ |
+} // namespace |
+ |
+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_valid_); |
+ DCHECK(other.is_valid_); |
Mike West
2014/12/09 12:13:34
Hrm. DCHECK seems like a bit much here; perhaps it
engedy
2014/12/09 14:54:46
I would prefer to keep it. The factory method clea
|
+ return canonical_spec_ == other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator!=(const FacetURI& other) const { |
+ DCHECK(is_valid_); |
+ DCHECK(other.is_valid_); |
+ return canonical_spec_ != other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator<(const FacetURI& other) const { |
+ DCHECK(is_valid_); |
+ DCHECK(other.is_valid_); |
+ return canonical_spec_ < other.canonical_spec_; |
+} |
+ |
+bool FacetURI::operator>(const FacetURI& other) const { |
+ DCHECK(is_valid_); |
+ DCHECK(other.is_valid_); |
+ return canonical_spec_ > other.canonical_spec_; |
+} |
+ |
+bool FacetURI::IsValidWebFacetURI() const { |
+ return is_valid_ && ExtractScheme(canonical_spec_) == url::kHttpsScheme; |
+} |
+ |
+bool FacetURI::IsValidAndroidFacetURI() const { |
+ return is_valid_ && ExtractScheme(canonical_spec_) == kAndroidAppScheme; |
+} |
+ |
+FacetURI::FacetURI(const std::string& canonical_spec, bool is_valid) |
+ : is_valid_(is_valid), canonical_spec_(canonical_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()); |
+} |
+ |
+} // namespace password_manager |