blob: 7defb388d12276265cef1918ad6097e35f67f84d [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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/password_list_sorter.h"
#include <algorithm>
#include "base/strings/utf_string_conversions.h"
#include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/password_manager/core/browser/password_ui_utils.h"
#include "components/password_manager/core/browser/ui/credential_ui_entry.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/gurl.h"
namespace password_manager {
namespace {
constexpr char kSortKeyPartsSeparator = ' ';
// The character that is added to a sort key if there is no federation.
// Note: to separate the entries w/ federation and the entries w/o federation,
// this character should be alphabetically smaller than real federations.
constexpr char kSortKeyNoFederationSymbol = '-';
// Symbols to differentiate between passwords and passkeys.
constexpr char kSortKeyPasskeySymbol = 'k';
constexpr char kSortKeyPasswordSymbol = 'w';
} // namespace
std::string CreateSortKey(const PasswordForm& form, IgnoreStore ignore_store) {
std::string key = CreateSortKey(CredentialUIEntry(form));
if (ignore_store)
return key;
if (form.in_store == PasswordForm::Store::kAccountStore) {
return key + kSortKeyPartsSeparator + std::string("account");
}
return key;
}
std::string CreateSortKey(const CredentialUIEntry& credential) {
std::string shown_origin = GetShownOrigin(credential);
const auto facet_uri =
FacetURI::FromPotentiallyInvalidSpec(credential.GetFirstSignonRealm());
const bool is_android_uri = facet_uri.IsValidAndroidFacetURI();
if (is_android_uri) {
// In case of Android credentials |GetShownOriginAndLinkURl| might return
// the app display name, e.g. the Play Store name of the given application.
// This might or might not correspond to the eTLD+1, which is why
// |shown_origin| is set to the reversed android package name in this case,
// e.g. com.example.android => android.example.com.
shown_origin = SplitByDotAndReverse(facet_uri.android_package_name());
}
std::string site_name =
net::registry_controlled_domains::GetDomainAndRegistry(
shown_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (site_name.empty()) // e.g. localhost.
site_name = shown_origin;
std::string key = site_name + kSortKeyPartsSeparator;
// Since multiple distinct credentials might have the same site name, more
// information is added. For Android credentials this includes the full
// canonical spec which is guaranteed to be unique for a given App.
key += is_android_uri ? facet_uri.canonical_spec()
: SplitByDotAndReverse(shown_origin);
if (!credential.blocked_by_user) {
key += kSortKeyPartsSeparator + base::UTF16ToUTF8(credential.username) +
kSortKeyPartsSeparator + base::UTF16ToUTF8(credential.password);
key += kSortKeyPartsSeparator;
if (!credential.federation_origin.opaque())
key += credential.federation_origin.host();
else
key += kSortKeyNoFederationSymbol;
}
// To separate HTTP/HTTPS credentials, add the scheme to the key.
key += kSortKeyPartsSeparator + GetShownUrl(credential).scheme();
// Separate passwords from passkeys.
key += kSortKeyPartsSeparator;
key += credential.is_passkey ? kSortKeyPasskeySymbol : kSortKeyPasswordSymbol;
return key;
}
std::string CreateUsernamePasswordSortKey(const PasswordForm& form) {
return CreateUsernamePasswordSortKey(CredentialUIEntry(form));
}
std::string CreateUsernamePasswordSortKey(const CredentialUIEntry& credential) {
std::string key;
// The origin isn't taken into account for normal credentials since we want to
// group them together.
if (!credential.blocked_by_user) {
key += base::UTF16ToUTF8(credential.username) + kSortKeyPartsSeparator +
base::UTF16ToUTF8(credential.password);
key += kSortKeyPartsSeparator;
if (!credential.federation_origin.opaque()) {
key += credential.federation_origin.host();
} else {
key += kSortKeyNoFederationSymbol;
}
} else {
// Key for blocked by user credential since it does not store username and
// password. These credentials are not grouped together.
key = GetShownOrigin(credential);
}
return key;
}
void SortEntriesAndHideDuplicates(
std::vector<std::unique_ptr<PasswordForm>>* list,
DuplicatesMap* duplicates) {
std::vector<std::pair<std::string, std::unique_ptr<PasswordForm>>>
keys_to_forms;
keys_to_forms.reserve(list->size());
for (auto& form : *list) {
std::string key = CreateSortKey(*form, IgnoreStore(true));
keys_to_forms.emplace_back(std::move(key), std::move(form));
}
std::sort(keys_to_forms.begin(), keys_to_forms.end());
list->clear();
duplicates->clear();
std::string previous_key;
for (auto& key_to_form : keys_to_forms) {
if (key_to_form.first != previous_key) {
list->push_back(std::move(key_to_form.second));
previous_key = key_to_form.first;
} else {
duplicates->emplace(previous_key, std::move(key_to_form.second));
}
}
}
} // namespace password_manager