match method
Should return the matches for the password
.
A synchronous matcher should return a list (usually of length 1) of lists of matches. An asynchronous matcher can return a list of futures that completes with a list of matches.
Implementation
@override
List<List<DictionaryMatch>> match(
String password, {
bool useLevenshtein = true,
}) {
final List<DictionaryMatch> matches = <DictionaryMatch>[];
final String passwordLower = password.toLowerCase();
for (int start = 0; start < password.length; start++) {
// Whether any dictionary has such a long word.
bool fitsDictionaryWordSize = true;
for (int end = start + 1; end <= password.length; end++) {
if (!fitsDictionaryWordSize) break;
fitsDictionaryWordSize = false;
final String usedPassword = passwordLower.substring(start, end);
DictionaryMatch? tokenMatch;
final RankedDictionaries rankedDictionaries =
options.rankedDictionaries;
final Map<Dictionary, int> rankedDictionariesMaxWordSize =
options.rankedDictionariesMaxWordSize;
for (final Dictionary dictionary in rankedDictionaries.keys) {
final int longestDictionaryWordSize =
rankedDictionariesMaxWordSize[dictionary]!;
if (end > start + longestDictionaryWordSize) continue;
fitsDictionaryWordSize = true;
final RankedDictionary rankedDictionary =
rankedDictionaries[dictionary]!;
final bool isInDictionary =
rankedDictionary.containsKey(usedPassword);
LevenshteinDistance? distance;
// Only use levenshtein distance on full password to minimize the
// performance drop and because otherwise there would be to many
// false positives.
final bool isFullPassword = start == 0 && end == password.length;
if (options.useLevenshteinDistance &&
isFullPassword &&
!isInDictionary &&
useLevenshtein) {
distance = levenshteinDistance(
usedPassword,
rankedDictionary.keys,
options.levenshteinThreshold,
);
}
final bool isLevenshteinMatch = distance != null;
if (isInDictionary || isLevenshteinMatch) {
final String usedRankPassword =
isLevenshteinMatch ? distance.entry : usedPassword;
final int rank = rankedDictionary[usedRankPassword]!;
final DictionaryMatch match = DictionaryMatch(
password: password,
start: start,
end: end,
matchedWord: usedPassword,
rank: rank,
dictionary: dictionary,
levenshteinDistance: distance?.distance,
levenshteinDistanceEntry: distance?.entry,
);
if (dictionary == Dictionary.diceware) {
// Always include matches from diceware because they're scored
// differently.
matches.add(match);
} else if (tokenMatch == null || tokenMatch.rank > match.rank) {
tokenMatch = match;
}
}
}
if (tokenMatch != null) matches.add(tokenMatch);
}
}
return <List<DictionaryMatch>>[matches];
}