{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} {-| Copyright: This file is part of the package zxcvbn-hs. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at: https://code.devalot.com/sthenauth/zxcvbn-hs No part of this package, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. License: MIT -} module Text.Password.Strength.Internal.Match ( -- * Matching Tokens Against Known Patterns Match(..), Matches, matches ) where -------------------------------------------------------------------------------- -- Library Imports: import Control.Lens ((^.), _1, views, minimumByOf) import Data.Function (on) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (mapMaybe, catMaybes) import Data.Text (Text) import qualified Data.Text as Text import Data.Time.Calendar (Day) -------------------------------------------------------------------------------- -- Project Imports: import Text.Password.Strength.Internal.Config import Text.Password.Strength.Internal.Date import Text.Password.Strength.Internal.Dictionary import Text.Password.Strength.Internal.Keyboard import Text.Password.Strength.Internal.L33t import Text.Password.Strength.Internal.Repeat import Text.Password.Strength.Internal.Sequence import Text.Password.Strength.Internal.Token -------------------------------------------------------------------------------- -- | The known patterns we are searching for. data Match = DictionaryMatch Rank -- ^ The associated token was found in a frequency dictionary with -- the specified rank. | ReverseDictionaryMatch Rank -- ^ The associated token was found in a frequency dictionary, but -- only after its characters were reversed. | L33tMatch Rank L33t -- ^ The associated token was found in a frequency dictionary, but -- only after its characters were translated from l33t speak to -- English. | KeyboardMatch KeyboardPattern -- ^ The associated token is wholly made up of an adjacent -- sequence of characters that make a pattern on a keyboard. | SequenceMatch Delta -- ^ The characters of the associated token form a sequence -- because the delta between all the characters is the same. -- -- Examples: -- -- * abc --- * 135 | DateMatch Date -- ^ The associated token wholly contains a date. | RepeatMatch Repeat Token -- ^ The associated token is an adjacent repeat of another token -- (the one given to this constructor). The number of times it -- repeats is given as 'Repeat'. deriving Show -------------------------------------------------------------------------------- -- | Information about how a token matches a specific match pattern. type Matches = Map Token [Match] -------------------------------------------------------------------------------- -- | All possible matches after various transformations. matches :: Config -> Day -> Text -> Matches matches cfg day = repeats . foldr (\t -> Map.insert t (check t)) Map.empty . allTokens where check :: Token -> [Match] check t = catMaybes [dict t, rdict t, l33ts t, seqMatch t, dateMatch t] ++ kbd t -- Tokens that appear in a dictionary. dict :: Token -> Maybe Match dict t = DictionaryMatch <$> rank cfg (^. tokenLower) t -- Tokens that, when reversed, appear in a dictionary. rdict :: Token -> Maybe Match rdict t = ReverseDictionaryMatch <$> rank cfg (views tokenLower Text.reverse) t -- Tokens that, when decoded, appear in a dictionary. -- -- A token may l33t decode into several words that are then looked -- up in the word dictionaries. The word with the lowest rank is -- kept and the others are discarded. l33ts :: Token -> Maybe Match l33ts t = let ts = l33t t -- Decoding may result in multiple outputs. rnk l = (,l) <$> rank cfg (^. l33tText) l in uncurry L33tMatch <$> minimumByOf traverse (compare `on` (^. _1)) (mapMaybe rnk ts) -- A token that is a pattern on one or more keyboards. kbd :: Token -> [Match] kbd t = KeyboardMatch <$> mapMaybe (`keyboardPattern` t) (cfg ^. keyboardGraphs) -- Characters in a token form a sequence. seqMatch :: Token -> Maybe Match seqMatch t = SequenceMatch <$> isSequence (t ^. tokenChars) -- Characters in a token form a date. dateMatch :: Token -> Maybe Match dateMatch t = DateMatch <$> isDate day (t ^. tokenChars) -- Tokens that are repeats of some other token. repeats :: Matches -> Matches repeats ms = let rmap = mkRepeatMap ms f t = (\(n, t') -> (t', [RepeatMatch n t])) <$> repeatMatch rmap t g t m = maybe m (\(k,v) -> Map.insertWith (<>) k v m) (f t) in Map.foldrWithKey (const . g) ms ms