148 lines
4.0 KiB
Plaintext
148 lines
4.0 KiB
Plaintext
import { MatcherBaseClass, MatchOptions, RepeatMatch } from '../../types'
|
|
import Scoring from '../../scoring'
|
|
import Matching from '../../Matching'
|
|
import Options from '../../Options'
|
|
import { MaybePromise } from 'rollup'
|
|
|
|
/*
|
|
*-------------------------------------------------------------------------------
|
|
* repeats (aaa, abcabcabc) ------------------------------
|
|
*-------------------------------------------------------------------------------
|
|
*/
|
|
class MatchRepeat extends MatcherBaseClass {
|
|
private scoring: Scoring
|
|
|
|
constructor(options: Options) {
|
|
super(options)
|
|
this.scoring = new Scoring(options)
|
|
}
|
|
|
|
// eslint-disable-next-line max-statements
|
|
match({ password, omniMatch }: MatchOptions) {
|
|
const matches: (RepeatMatch | Promise<RepeatMatch>)[] = []
|
|
let lastIndex = 0
|
|
while (lastIndex < password.length) {
|
|
const greedyMatch = this.getGreedyMatch(password, lastIndex)
|
|
const lazyMatch = this.getLazyMatch(password, lastIndex)
|
|
if (greedyMatch == null) {
|
|
break
|
|
}
|
|
const { match, baseToken } = this.setMatchToken(greedyMatch, lazyMatch)
|
|
|
|
if (match) {
|
|
const j = match.index + match[0].length - 1
|
|
const baseGuesses = this.getBaseGuesses(baseToken, omniMatch)
|
|
matches.push(this.normalizeMatch(baseToken, j, match, baseGuesses))
|
|
|
|
lastIndex = j + 1
|
|
}
|
|
}
|
|
|
|
const hasPromises = matches.some((match) => {
|
|
return match instanceof Promise
|
|
})
|
|
if (hasPromises) {
|
|
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
return Promise.all(matches)
|
|
}
|
|
return matches as RepeatMatch[]
|
|
}
|
|
|
|
// eslint-disable-next-line max-params
|
|
normalizeMatch(
|
|
baseToken: string,
|
|
j: number,
|
|
match: RegExpExecArray,
|
|
baseGuesses: number | Promise<number>,
|
|
): MaybePromise<RepeatMatch> {
|
|
const baseMatch: RepeatMatch = {
|
|
pattern: 'repeat',
|
|
i: match.index,
|
|
j,
|
|
token: match[0],
|
|
baseToken,
|
|
baseGuesses: 0,
|
|
repeatCount: match[0].length / baseToken.length,
|
|
}
|
|
if (baseGuesses instanceof Promise) {
|
|
return baseGuesses.then((resolvedBaseGuesses) => {
|
|
return {
|
|
...baseMatch,
|
|
baseGuesses: resolvedBaseGuesses,
|
|
}
|
|
})
|
|
}
|
|
return {
|
|
...baseMatch,
|
|
baseGuesses,
|
|
}
|
|
}
|
|
|
|
getGreedyMatch(password: string, lastIndex: number) {
|
|
const greedy = /(.+)\1+/g
|
|
greedy.lastIndex = lastIndex
|
|
return greedy.exec(password)
|
|
}
|
|
|
|
getLazyMatch(password: string, lastIndex: number) {
|
|
const lazy = /(.+?)\1+/g
|
|
lazy.lastIndex = lastIndex
|
|
return lazy.exec(password)
|
|
}
|
|
|
|
setMatchToken(
|
|
greedyMatch: RegExpExecArray,
|
|
lazyMatch: RegExpExecArray | null,
|
|
) {
|
|
const lazyAnchored = /^(.+?)\1+$/
|
|
let match
|
|
let baseToken = ''
|
|
if (lazyMatch && greedyMatch[0].length > lazyMatch[0].length) {
|
|
// greedy beats lazy for 'aabaab'
|
|
// greedy: [aabaab, aab]
|
|
// lazy: [aa, a]
|
|
match = greedyMatch
|
|
// greedy's repeated string might itself be repeated, eg.
|
|
// aabaab in aabaabaabaab.
|
|
// run an anchored lazy match on greedy's repeated string
|
|
// to find the shortest repeated string
|
|
const temp = lazyAnchored.exec(match[0])
|
|
if (temp) {
|
|
baseToken = temp[1]
|
|
}
|
|
} else {
|
|
// lazy beats greedy for 'aaaaa'
|
|
// greedy: [aaaa, aa]
|
|
// lazy: [aaaaa, a]
|
|
match = lazyMatch
|
|
if (match) {
|
|
baseToken = match[1]
|
|
}
|
|
}
|
|
return {
|
|
match,
|
|
baseToken,
|
|
}
|
|
}
|
|
|
|
getBaseGuesses(baseToken: string, omniMatch: Matching) {
|
|
const matches = omniMatch.match(baseToken)
|
|
if (matches instanceof Promise) {
|
|
return matches.then((resolvedMatches) => {
|
|
const baseAnalysis = this.scoring.mostGuessableMatchSequence(
|
|
baseToken,
|
|
resolvedMatches,
|
|
)
|
|
return baseAnalysis.guesses
|
|
})
|
|
}
|
|
const baseAnalysis = this.scoring.mostGuessableMatchSequence(
|
|
baseToken,
|
|
matches,
|
|
)
|
|
return baseAnalysis.guesses
|
|
}
|
|
}
|
|
|
|
export default MatchRepeat
|