match method

  1. @override
List<List<L33tMatch>> match(
  1. String password
)
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<L33tMatch>> match(String password) {
  final List<L33tMatch> matches = <L33tMatch>[];
  final List<PasswordWithChanges> cleanedPasswords = cleanPassword(
    password,
    options.l33tMaxSubstitutions,
    options.trieNodeRoot,
  );
  bool hasFullMatch = false;
  bool isFullSubstitution = true;
  for (final PasswordWithChanges cleanedPassword in cleanedPasswords) {
    if (hasFullMatch) break;
    final List<DictionaryMatch> dictionaryMatches = dictionaryMatcher.match(
      cleanedPassword.password,
      useLevenshtein: isFullSubstitution,
    )[0];
    // Only the first entry has a full substitution.
    isFullSubstitution = false;
    for (final DictionaryMatch match in dictionaryMatches) {
      if (!hasFullMatch) {
        hasFullMatch = match.start == 0 && match.end == password.length;
      }
      final Iterable<IndexedPasswordChange> previousChanges =
          cleanedPassword.changes.where((IndexedPasswordChange changes) {
        return changes.start < match.start;
      });
      final int i = previousChanges.fold(match.start,
          (int val, IndexedPasswordChange change) {
        return val - change.clean.length + change.l33t.length;
      });
      final Iterable<IndexedPasswordChange> usedChanges =
          cleanedPassword.changes.where((IndexedPasswordChange changes) {
        return changes.start >= match.start && changes.start < match.end;
      });
      final int j = usedChanges.fold(match.end - match.start + i,
          (int val, IndexedPasswordChange change) {
        return val - change.clean.length + change.l33t.length;
      });
      final String token = password.substring(i, j);
      final Set<String> seen = <String>{};
      // Filter duplicates.
      final List<PasswordChange> changes = <PasswordChange>[
        for (final IndexedPasswordChange change in usedChanges)
          if (seen.add('${change.l33t} -> ${change.clean}'))
            PasswordChange(
              l33t: change.l33t,
              clean: change.clean,
            ),
      ];
      final L33tMatch newMatch = match.toL33tMatch(
        password: password,
        start: i,
        end: j,
        changes: changes,
        changesDisplay: changes.join(', '),
      );
      // Ignore single-character l33t matches to reduce noise.
      // Otherwise '1' matches 'i', '4' matches 'a', both very common
      // English words with low dictionary rank.
      // Only return the matches that contain an actual substitution.
      if (token.length > 1 &&
          token.toLowerCase() != match.matchedWord &&
          !matches.any(newMatch.isDuplicateOf)) {
        matches.add(newMatch);
      }
    }
  }
  return <List<L33tMatch>>[matches];
}