| 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..e03cec91f30cf1266a645b3b4ee14457e353cbf2
|
| --- /dev/null
|
| +++ b/components/password_manager/core/browser/affiliation_utils.cc
|
| @@ -0,0 +1,262 @@
|
| +// 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 "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,
|
| + 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.
|
| +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;
|
| +}
|
| +
|
| +// 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 to unescape the padding ('=').
|
| + std::string base64_encoded_hash = net::UnescapeURLComponent(
|
| + input_hash.as_string(), net::UnescapeRule::URL_SPECIAL_CHARS);
|
| +
|
| + 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 (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_);
|
| + 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
|
|
|