blob: 3f929fb5a46f16ff6e1b126054f4fe2ae8ba315a [file]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_LOOKALIKES_CORE_LOOKALIKE_URL_UTIL_H_
#define COMPONENTS_LOOKALIKES_CORE_LOOKALIKE_URL_UTIL_H_
#include <string>
#include <string_view>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/raw_span.h"
#include "components/lookalikes/core/safety_tips.pb.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/url_formatter/url_formatter.h"
#include "components/version_info/channel.h"
#include "url/gurl.h"
namespace lookalikes {
// Name of the histograms recorded by the interstitial for lookalike match
// types.
extern const char kInterstitialHistogramName[];
extern const char kIncognitoInterstitialHistogramName[];
// Register applicable preferences with the provided registry.
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
// Returns the console message to be shown in devtools when a URL is flagged by
// a lookalike heuristic. If is_new_heuristic is true, the message is for a new
// heuristic that's not fully launched and it has an extra line about future
// behavior of Chrome.
std::string GetConsoleMessage(const GURL& lookalike_url, bool is_new_heuristic);
// Used for |GetTargetEmbeddingType| return value. It shows if the target
// embedding triggers on the input domain, and if it does, what type of warning
// should be shown to the user.
enum class TargetEmbeddingType {
kNone = 0,
kInterstitial = 1,
kSafetyTip = 2,
};
// Used for |GetComboSquattingType| return value.
// It shows if the brand name in the flagged domain
// comes from the hard-coded brand names or from site engagements.
enum class ComboSquattingType {
kNone = 0,
kHardCoded = 1,
kSiteEngagement = 2,
};
// Used for UKM. There is only a single LookalikeUrlMatchType per navigation.
enum class LookalikeUrlMatchType {
kNone = 0,
// DEPRECATED: Use kSkeletonMatchTop500 or kSkeletonMatchTop5k.
// kTopSite = 1,
kSkeletonMatchSiteEngagement = 2,
kEditDistance = 3,
kEditDistanceSiteEngagement = 4,
kTargetEmbedding = 5,
kSkeletonMatchTop500 = 6,
kSkeletonMatchTop5k = 7,
kTargetEmbeddingForSafetyTips = 8,
// The domain name failed IDN spoof checks but didn't match a safe hostname.
// As a result, there is no URL to suggest to the user in the form of "Did
// you mean <url>?".
kFailedSpoofChecks = 9,
kCharacterSwapSiteEngagement = 10,
kCharacterSwapTop500 = 11,
// Combo Squatting uses manually
// curated lists of hard-coded keywords (kPopularKeywordsforCSQ in
// lookalike_url_util.cc) and both manually curated hard-coded brand names
// (kBrandNamesforCSQ in lookalike_url_util.cc) and brand names from
// SiteEngagement to flag domains.
kComboSquatting = 12,
kComboSquattingSiteEngagement = 13,
// Append new items to the end of the list above; do not modify or replace
// existing values. Comment out obsolete items.
kMaxValue = kComboSquattingSiteEngagement,
};
// Used for UKM. There is only a single LookalikeUrlBlockingPageUserAction per
// navigation.
enum class LookalikeUrlBlockingPageUserAction {
kInterstitialNotShown = 0,
kClickThrough = 1,
kAcceptSuggestion = 2,
kCloseOrBack = 3,
// Append new items to the end of the list above; do not modify or replace
// existing values. Comment out obsolete items.
kMaxValue = kCloseOrBack,
};
// Used for metrics. Multiple events can occur per navigation.
enum class NavigationSuggestionEvent {
kNone = 0,
// Interstitial results recorded using security_interstitials::MetricsHelper
// kInfobarShown = 1,
// kLinkClicked = 2,
// DEPRECATED: Use kMatchSkeletonTop500 or kMatchSkeletonTop5k.
// kMatchTopSite = 3,
kMatchSiteEngagement = 4,
kMatchEditDistance = 5,
kMatchEditDistanceSiteEngagement = 6,
kMatchTargetEmbedding = 7,
kMatchSkeletonTop500 = 8,
kMatchSkeletonTop5k = 9,
kMatchTargetEmbeddingForSafetyTips = 10,
kFailedSpoofChecks = 11,
kMatchCharacterSwapSiteEngagement = 12,
kMatchCharacterSwapTop500 = 13,
kComboSquatting = 14,
kComboSquattingSiteEngagement = 15,
// Append new items to the end of the list above; do not modify or
// replace existing values. Comment out obsolete items.
kMaxValue = kComboSquattingSiteEngagement,
};
struct TopBucketDomainsParams {
// Skeletons of top bucket domains. This is the top 500 or 1000 most popular
// domains (though, there can be fewer than 500 or 1000 skeletons in this
// array).
// This field is not a raw_ptr<> because it only ever points to statically-
// allocated memory which is never freed, so it cannot dangle.
// Spanification note: unlike the raw_spans below, the pointed-to arrays
// are defined in generated files, so their size isn't exposed in the header.
// To get compile-time safety guarantees, the header itself would have to be
// generated as well.
RAW_PTR_EXCLUSION const char* const* edit_distance_skeletons;
// Number of skeletons in `edit_distance_skeletons`.
size_t num_edit_distance_skeletons;
};
struct ComboSquattingParams {
// An array of brand names (such as "google", "youtube") and their skeletons
// (in pairs). The first item in each pair is the brand name and the second
// item is its skeleton. Brand names should be usable in domain names (i.e.
// lower case, no punctuation except for - etc.)
base::raw_span<const std::string_view[2]> brand_names;
// List of popular keywords such as "login", "online".
base::raw_span<const std::string_view> popular_keywords;
// Needed to provide a non-trivial destructor so NoDestructor can be used.
~ComboSquattingParams() {}
};
struct DomainInfo {
// The full ASCII hostname, used in detecting target embedding. For
// "https://www.google.com/mail" this will be "www.google.com".
const std::string hostname;
// eTLD+1, used for skeleton and edit distance comparison. Must be ASCII.
// Empty for non-unique domains, localhost or sites whose eTLD+1 is empty.
const std::string domain_and_registry;
// eTLD+1 without the registry part, and with a trailing period. For
// "www.google.com", this will be "google.". Used for edit distance
// comparisons. Empty for non-unique domains, localhost or sites whose eTLD+1
// is empty.
const std::string domain_without_registry;
// Result of IDN conversion of domain_and_registry field.
const url_formatter::IDNConversionResult idn_result;
// Skeletons of domain_and_registry field.
const url_formatter::Skeletons skeletons;
// Skeletons of domain_without_registry field.
const url_formatter::Skeletons domain_without_registry_skeletons;
DomainInfo(
const std::string& arg_hostname,
const std::string& arg_domain_and_registry,
const std::string& arg_domain_without_registry,
const url_formatter::IDNConversionResult& arg_idn_result,
const url_formatter::Skeletons& arg_skeletons,
const url_formatter::Skeletons& arg_domain_without_registry_skeletons);
~DomainInfo();
DomainInfo(const DomainInfo& other);
};
// Returns a DomainInfo instance computed from |hostname|. Will return empty
// fields for non-unique hostnames (e.g. site.test), localhost or sites whose
// eTLD+1 is empty.
DomainInfo GetDomainInfo(const std::string& hostname);
// Convenience function for returning GetDomainInfo(url.host()).
DomainInfo GetDomainInfo(const GURL& url);
// Returns true if the Levenshtein distance between |str1| and |str2| is at most
// one. This has O(max(n,m)) complexity as opposed to O(n*m) of the usual edit
// distance computation.
bool IsEditDistanceAtMostOne(const std::u16string& str1,
const std::u16string& str2);
// Returns whether |navigated_domain| and |matched_domain| are likely to be edit
// distance false positives, and thus the user should *not* be warned.
//
// Assumes |navigated_domain| and |matched_domain| are edit distance of 1 apart.
bool IsLikelyEditDistanceFalsePositive(const DomainInfo& navigated_domain,
const DomainInfo& matched_domain);
// Returns whether |navigated_domain| and |matched_domain| are likely to be
// character swap false positives, and thus the user should *not* be warned.
//
// Assumes |navigated_domain| and |matched_domain| are within 1 character swap.
bool IsLikelyCharacterSwapFalsePositive(const DomainInfo& navigated_domain,
const DomainInfo& matched_domain);
// Returns eTLD+1 of |hostname|. This excludes private registries, and returns
// "blogspot.com" for "test.blogspot.com" (blogspot.com is listed as a private
// registry). We do this to be consistent with url_formatter's top domain list
// which doesn't have a notion of private registries.
std::string GetETLDPlusOne(const std::string& hostname);
// Records an interstitial histogram entry for the given match type.
void RecordUMAFromMatchType(LookalikeUrlMatchType match_type,
bool is_incognito);
using LookalikeTargetAllowlistChecker =
base::RepeatingCallback<bool(const std::string&)>;
// Returns true if a domain is visually similar to the hostname of |url|. The
// matching domain can be a top domain or an engaged site. Similarity
// check is made using both visual skeleton and edit distance comparison. If
// this returns true, match details will be written into |matched_domain|.
// Pointer arguments can't be nullptr.
bool GetMatchingDomain(
const DomainInfo& navigated_domain,
const std::vector<DomainInfo>& engaged_sites,
const LookalikeTargetAllowlistChecker& in_target_allowlist,
const reputation::SafetyTipsConfig* config_proto,
std::string* matched_domain,
LookalikeUrlMatchType* match_type);
// Checks to see if a URL is a target embedding lookalike. This function sets
// |safe_hostname| to the url of the embedded target domain. See the unit tests
// for what qualifies as target embedding.
TargetEmbeddingType GetTargetEmbeddingType(
const std::string& hostname,
const std::vector<DomainInfo>& engaged_sites,
const LookalikeTargetAllowlistChecker& in_target_allowlist,
const reputation::SafetyTipsConfig* config_proto,
std::string* safe_hostname);
// Same as GetTargetEmbeddingType, but explicitly state whether or not a safety
// tip is permitted via |safety_tips_allowed|. Safety tips are presently only
// used for tail embedding (e.g. "evil-google.com"). This function may return
// kSafetyTip preferentially to kInterstitial -- call with !safety_tips_allowed
// if you're interested in determining if there's *also* an interstitial.
TargetEmbeddingType SearchForEmbeddings(
const std::string& hostname,
const std::vector<DomainInfo>& engaged_sites,
const LookalikeTargetAllowlistChecker& in_target_allowlist,
const reputation::SafetyTipsConfig* config_proto,
bool safety_tips_allowed,
std::string* safe_hostname);
// Returns true if a navigation to an IDN should be blocked.
bool ShouldBlockBySpoofCheckResult(const DomainInfo& navigated_domain);
// Checks whether the given url is allowlisted by enterprise policy, and
// thus no warnings should be shown on that host.
bool IsAllowedByEnterprisePolicy(const PrefService* pref_service,
const GURL& url);
// Add the given hosts to the allowlist policy setting.
void SetEnterpriseAllowlistForTesting(PrefService* pref_service,
const std::vector<std::string>& hosts);
// Returns true if |str1| and |str2| are identical except that two adjacent
// characters are swapped. E.g. example.com vs exapmle.com.
bool HasOneCharacterSwap(const std::u16string& str1,
const std::u16string& str2);
// Sets information about top bucket domains for testing.
void SetTopBucketDomainsParamsForTesting(const TopBucketDomainsParams& params);
// Resets information about top bucket domains for testing.
void ResetTopBucketDomainsParamsForTesting();
// Returns true if the launch configuration provided by the component updater
// enables `heuristic` for the given `etld_plus_one`.
bool IsHeuristicEnabledForHostname(
const reputation::SafetyTipsConfig* config_proto,
reputation::HeuristicLaunchConfig::Heuristic heuristic,
const std::string& lookalike_etld_plus_one,
version_info::Channel channel);
// Set brand names and keywords for testing Combo Squatting heuristic.
void SetComboSquattingParamsForTesting(const ComboSquattingParams& params);
// Reset brand names and keywords after testing Combo Squatting heuristic.
void ResetComboSquattingParamsForTesting();
// Check if |navigated_domain| is Combo Squatting lookalike.
// It gets |engaged_sites| to use its brand names in addition to hard coded
// brand names. The function sets |matched_domain| to suggest to the user
// instead of the Combo Squatting domain.
ComboSquattingType GetComboSquattingType(
const DomainInfo& navigated_domain,
const std::vector<DomainInfo>& engaged_sites,
std::string* matched_domain);
// Returns true if `etld_plus_one` has a TLD that's considered safe for
// lookalike checks, such as government sites.
bool IsSafeTLD(const std::string& hostname);
// The action to take for a given lookalike match.
enum class LookalikeActionType {
// No action.
kNone,
// Only record metrics, don't show any UI warnings.
kRecordMetrics,
// Show a safety tip.
kShowSafetyTip,
// Show an interstitial.
kShowInterstitial,
};
// Returns the action to take for the given `etld_plus_one` and lookalike
// `match_type`. Uses `config` to check whether the heuristic UI is enabled
// via gradual rollout.
LookalikeActionType GetActionForMatchType(
const reputation::SafetyTipsConfig* config,
version_info::Channel channel,
const std::string& etld_plus_one,
LookalikeUrlMatchType match_type);
// Returns the suggested URL for the given parameters. Returns an https URL for
// top domain matches because it's more likely for top sites to support https.
GURL GetSuggestedURL(LookalikeUrlMatchType match_type,
const GURL& navigated_url,
const std::string& matched_hostname);
} // namespace lookalikes
#endif // COMPONENTS_LOOKALIKES_CORE_LOOKALIKE_URL_UTIL_H_