Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Canceled after 0s
116 lines
3.1 KiB
Plaintext
116 lines
3.1 KiB
Plaintext
import { ALL_UPPER, ALL_LOWER, ALL_DIGIT } from '../../data/const'
|
|
import { MatcherBaseClass, MatchOptions, SequenceMatch } from '../../types'
|
|
|
|
interface UpdateParams {
|
|
i: number
|
|
j: number
|
|
delta: number
|
|
password: string
|
|
result: any[]
|
|
}
|
|
|
|
type SequenceMatchOptions = Pick<MatchOptions, 'password'>
|
|
/*
|
|
*-------------------------------------------------------------------------------
|
|
* sequences (abcdef) ------------------------------
|
|
*-------------------------------------------------------------------------------
|
|
*/
|
|
class MatchSequence extends MatcherBaseClass {
|
|
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!,
|
|
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
|