import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const' import { SequenceMatch } from '../../types' type UpdateParams = { i: number j: number delta: number password: string result: any[] } interface SequenceMatchOptions { password: string } /* *------------------------------------------------------------------------------- * sequences (abcdef) ------------------------------ *------------------------------------------------------------------------------- */ class MatchSequence { MAX_DELTA = 5 // eslint-disable-next-line max-statements match({ password }: SequenceMatchOptions) { /* * Identifies sequences by looking for repeated differences in unicode codepoint. * this allows skipping, such as 9753, and also matches some extended unicode sequences * such as Greek and Cyrillic alphabets. * * for example, consider the input 'abcdb975zy' * * password: a b c d b 9 7 5 z y * index: 0 1 2 3 4 5 6 7 8 9 * delta: 1 1 1 -2 -41 -2 -2 69 1 * * expected result: * [(i, j, delta), ...] = [(0, 3, 1), (5, 7, -2), (8, 9, 1)] */ const result: SequenceMatch[] = [] if (password.length === 1) { return [] } let i = 0 let lastDelta: number | null = null const passwordLength = password.length for (let k = 1; k < passwordLength; k += 1) { const delta = password.charCodeAt(k) - password.charCodeAt(k - 1) if (lastDelta == null) { lastDelta = delta } if (delta !== lastDelta) { const j = k - 1 this.update({ i, j, delta: lastDelta, password, result, }) i = j lastDelta = delta } } this.update({ i, j: passwordLength - 1, delta: lastDelta as number, password, result, }) return result } update({ i, j, delta, password, result }: UpdateParams) { if (j - i > 1 || Math.abs(delta) === 1) { const absoluteDelta = Math.abs(delta) if (absoluteDelta > 0 && absoluteDelta <= this.MAX_DELTA) { const token = password.slice(i, +j + 1 || 9e9) const { sequenceName, sequenceSpace } = this.getSequence(token) return result.push({ pattern: 'sequence', i, j, token: password.slice(i, +j + 1 || 9e9), sequenceName, sequenceSpace, ascending: delta > 0, }) } } return null } getSequence(token: string) { // TODO conservatively stick with roman alphabet size. // (this could be improved) let sequenceName = 'unicode' let sequenceSpace = 26 if (ALL_LOWER.test(token)) { sequenceName = 'lower' sequenceSpace = 26 } else if (ALL_UPPER.test(token)) { sequenceName = 'upper' sequenceSpace = 26 } else if (ALL_DIGIT.test(token)) { sequenceName = 'digits' sequenceSpace = 10 } return { sequenceName, sequenceSpace, } } } export default MatchSequence