Chromium Code Reviews| 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 |