match method

  1. @override
List<List<DictionaryMatch>> match(
  1. String password, {
  2. bool useLevenshtein = true,
})
override

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];
}