118 lines
3.0 KiB
Plaintext
118 lines
3.0 KiB
Plaintext
import { ALL_LOWER, ALL_UPPER, ALL_DIGIT } from '../../data/const.mjs';
|
|
import { MatcherBaseClass } from '../../types.mjs';
|
|
|
|
/*
|
|
*-------------------------------------------------------------------------------
|
|
* sequences (abcdef) ------------------------------
|
|
*-------------------------------------------------------------------------------
|
|
*/
|
|
class MatchSequence extends MatcherBaseClass {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.MAX_DELTA = 5;
|
|
}
|
|
// eslint-disable-next-line max-statements
|
|
match({
|
|
password
|
|
}) {
|
|
/*
|
|
* 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 = [];
|
|
if (password.length === 1) {
|
|
return [];
|
|
}
|
|
let i = 0;
|
|
let lastDelta = 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
|
|
}) {
|
|
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) {
|
|
// 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 { MatchSequence as default };
|
|
//# sourceMappingURL=matching.mjs.map
|