114 lines
3.8 KiB
Plaintext
114 lines
3.8 KiB
Plaintext
'use strict';
|
|
|
|
var helper = require('../../utils/helper.cjs');
|
|
var types = require('../../types.cjs');
|
|
|
|
/*
|
|
* ------------------------------------------------------------------------------
|
|
* spatial match (qwerty/dvorak/keypad and so on) -----------------------------------------
|
|
* ------------------------------------------------------------------------------
|
|
*/
|
|
class MatchSpatial extends types.MatcherBaseClass {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.SHIFTED_RX = /[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?]/;
|
|
}
|
|
match({
|
|
password
|
|
}) {
|
|
const matches = [];
|
|
Object.keys(this.options.graphs).forEach(graphName => {
|
|
const graph = this.options.graphs[graphName];
|
|
helper.extend(matches, this.helper(password, graph, graphName));
|
|
});
|
|
return helper.sorted(matches);
|
|
}
|
|
checkIfShifted(graphName, password, index) {
|
|
if (!graphName.includes('keypad') &&
|
|
// initial character is shifted
|
|
this.SHIFTED_RX.test(password.charAt(index))) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
// eslint-disable-next-line complexity, max-statements
|
|
helper(password, graph, graphName) {
|
|
let shiftedCount;
|
|
const matches = [];
|
|
let i = 0;
|
|
const passwordLength = password.length;
|
|
while (i < passwordLength - 1) {
|
|
let j = i + 1;
|
|
let lastDirection = null;
|
|
let turns = 0;
|
|
shiftedCount = this.checkIfShifted(graphName, password, i);
|
|
while (true) {
|
|
const prevChar = password.charAt(j - 1);
|
|
const adjacents = graph[prevChar] || [];
|
|
let found = false;
|
|
let curDirection = -1;
|
|
// consider growing pattern by one character if j hasn't gone over the edge.
|
|
if (j < passwordLength) {
|
|
const curChar = password.charAt(j);
|
|
const adjacentsLength = adjacents.length;
|
|
for (let k = 0; k < adjacentsLength; k += 1) {
|
|
const adjacent = adjacents[k];
|
|
curDirection += 1;
|
|
// eslint-disable-next-line max-depth
|
|
if (adjacent) {
|
|
const adjacentIndex = adjacent.indexOf(curChar);
|
|
// eslint-disable-next-line max-depth
|
|
if (adjacentIndex !== -1) {
|
|
found = true;
|
|
// eslint-disable-next-line max-depth
|
|
if (adjacentIndex === 1) {
|
|
// # index 1 in the adjacency means the key is shifted,
|
|
// # 0 means unshifted: A vs a, % vs 5, etc.
|
|
// # for example, 'q' is adjacent to the entry '2@'.
|
|
// # @ is shifted w/ index 1, 2 is unshifted.
|
|
shiftedCount += 1;
|
|
}
|
|
const foundDirection = curDirection;
|
|
// eslint-disable-next-line max-depth
|
|
if (lastDirection !== foundDirection) {
|
|
// # adding a turn is correct even in the initial
|
|
// case when last_direction is null:
|
|
// # every spatial pattern starts with a turn.
|
|
turns += 1;
|
|
lastDirection = foundDirection;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if the current pattern continued, extend j and try to grow again
|
|
if (found) {
|
|
j += 1;
|
|
// otherwise push the pattern discovered so far, if any...
|
|
} else {
|
|
// don't consider length 1 or 2 chains.
|
|
if (j - i > 2) {
|
|
matches.push({
|
|
pattern: 'spatial',
|
|
i,
|
|
j: j - 1,
|
|
token: password.slice(i, j),
|
|
graph: graphName,
|
|
turns,
|
|
shiftedCount
|
|
});
|
|
}
|
|
// ...and then start a new search for the rest of the password.
|
|
i = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
}
|
|
|
|
module.exports = MatchSpatial;
|
|
//# sourceMappingURL=matching.cjs.map
|