Files
SnowLuma/.pnpm-store/v11/files/e6/832cb6e66c8236348709db062db93db65dcc2529775447cda858ce75d6062b3657669d466e3c7d8e84318b002296bd2af0fd5558c692128ad1cc670d7fbb56
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

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