Extract operations now offer built-in Sort and Unique options. Unique operation offers option to count occurances. Closes #1334.

This commit is contained in:
n1474335 2022-04-14 18:08:16 +01:00
parent 6959e2cf01
commit 9a982f05ac
14 changed files with 397 additions and 206 deletions

View file

@ -44,7 +44,13 @@ class ExtractDates extends Operation {
date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
return search(input, regex, null, displayTotal);
const results = search(input, regex);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract domains operation
@ -25,9 +26,19 @@ class ExtractDomains extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": true
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -38,8 +49,21 @@ class ExtractDomains extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0];
return search(input, DOMAIN_REGEX, null, displayTotal);
const [displayTotal, sort, unique] = args;
const results = search(
input,
DOMAIN_REGEX,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract email addresses operation
@ -25,9 +26,19 @@ class ExtractEmailAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -38,10 +49,23 @@ class ExtractEmailAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0],
const [displayTotal, sort, unique] = args,
// email regex from: https://www.regextester.com/98066
regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}\])/ig;
return search(input, regex, null, displayTotal);
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract file paths operation
@ -25,19 +26,29 @@ class ExtractFilePaths extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Windows",
"type": "boolean",
"value": true
name: "Windows",
type: "boolean",
value: true
},
{
"name": "UNIX",
"type": "boolean",
"value": true
name: "UNIX",
type: "boolean",
value: true
},
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -48,7 +59,7 @@ class ExtractFilePaths extends Operation {
* @returns {string}
*/
run(input, args) {
const [includeWinPath, includeUnixPath, displayTotal] = args,
const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args,
winDrive = "[A-Z]:\\\\",
winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
winExt = "[A-Z\\d]{1,6}",
@ -65,12 +76,25 @@ class ExtractFilePaths extends Operation {
filePaths = unixPath;
}
if (filePaths) {
const regex = new RegExp(filePaths, "ig");
return search(input, regex, null, displayTotal);
} else {
if (!filePaths) {
return "";
}
const regex = new RegExp(filePaths, "ig");
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { ipSort } from "../lib/Sort.mjs";
/**
* Extract IP addresses operation
@ -25,24 +26,34 @@ class ExtractIPAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "IPv4",
"type": "boolean",
"value": true
name: "IPv4",
type: "boolean",
value: true
},
{
"name": "IPv6",
"type": "boolean",
"value": false
name: "IPv6",
type: "boolean",
value: false
},
{
"name": "Remove local IPv4 addresses",
"type": "boolean",
"value": false
name: "Remove local IPv4 addresses",
type: "boolean",
value: false
},
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -53,7 +64,7 @@ class ExtractIPAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args,
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
ipv4 = "(?:(?:\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d|\\d)(?:\\/\\d{1,2})?",
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})";
let ips = "";
@ -66,23 +77,29 @@ class ExtractIPAddresses extends Operation {
ips = ipv6;
}
if (ips) {
const regex = new RegExp(ips, "ig");
if (!ips) return "";
if (removeLocal) {
const ten = "10\\..+",
oneninetwo = "192\\.168\\..+",
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
onetwoseven = "127\\..+",
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
"|" + oneseventwo + "|" + onetwoseven + ")");
const regex = new RegExp(ips, "ig");
return search(input, regex, removeRegex, displayTotal);
} else {
return search(input, regex, null, displayTotal);
}
const ten = "10\\..+",
oneninetwo = "192\\.168\\..+",
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
onetwoseven = "127\\..+",
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
"|" + oneseventwo + "|" + onetwoseven + ")");
const results = search(
input,
regex,
removeLocal ? removeRegex : null,
sort ? ipSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return "";
return results.join("\n");
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search } from "../lib/Extract.mjs";
import { hexadecimalSort } from "../lib/Sort.mjs";
/**
* Extract MAC addresses operation
@ -25,9 +26,19 @@ class ExtractMACAddresses extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -38,10 +49,21 @@ class ExtractMACAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0],
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
const [displayTotal, sort, unique] = args,
regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig,
results = search(
input,
regex,
null,
sort ? hexadecimalSort : null,
unique
);
return search(input, regex, null, displayTotal);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -6,6 +6,7 @@
import Operation from "../Operation.mjs";
import { search, URL_REGEX } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Extract URLs operation
@ -25,9 +26,19 @@ class ExtractURLs extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -38,8 +49,20 @@ class ExtractURLs extends Operation {
* @returns {string}
*/
run(input, args) {
const displayTotal = args[0];
return search(input, URL_REGEX, null, displayTotal);
const [displayTotal, sort, unique] = args;
const results = search(
input,
URL_REGEX,
null,
sort ? caseInsensitiveSort : null,
unique
);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -7,6 +7,7 @@
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs";
/**
* Sort operation
@ -57,120 +58,19 @@ class Sort extends Operation {
if (order === "Alphabetical (case sensitive)") {
sorted = sorted.sort();
} else if (order === "Alphabetical (case insensitive)") {
sorted = sorted.sort(Sort._caseInsensitiveSort);
sorted = sorted.sort(caseInsensitiveSort);
} else if (order === "IP address") {
sorted = sorted.sort(Sort._ipSort);
sorted = sorted.sort(ipSort);
} else if (order === "Numeric") {
sorted = sorted.sort(Sort._numericSort);
sorted = sorted.sort(numericSort);
} else if (order === "Numeric (hexadecimal)") {
sorted = sorted.sort(Sort._hexadecimalSort);
sorted = sorted.sort(hexadecimalSort);
}
if (sortReverse) sorted.reverse();
return sorted.join(delim);
}
/**
* Comparison operation for sorting of strings ignoring case.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _caseInsensitiveSort(a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
}
/**
* Comparison operation for sorting of IPv4 addresses.
*
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _ipSort(a, b) {
let a_ = a.split("."),
b_ = b.split(".");
a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
if (isNaN(a_) && !isNaN(b_)) return 1;
if (!isNaN(a_) && isNaN(b_)) return -1;
if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
return a_ - b_;
}
/**
* Comparison operation for sorting of numeric values.
*
* @author Chris van Marle
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _numericSort(a, b) {
const a_ = a.split(/([^\d]+)/),
b_ = b.split(/([^\d]+)/);
for (let i = 0; i < a_.length && i < b.length; ++i) {
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
if (isNaN(a_[i]) && isNaN(b_[i])) {
const ret = a_[i].localeCompare(b_[i]); // Compare strings
if (ret !== 0) return ret;
}
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
}
}
return a.localeCompare(b);
}
/**
* Comparison operation for sorting of hexadecimal values.
*
* @author Chris van Marle
* @private
* @param {string} a
* @param {string} b
* @returns {number}
*/
static _hexadecimalSort(a, b) {
let a_ = a.split(/([^\da-f]+)/i),
b_ = b.split(/([^\da-f]+)/i);
a_ = a_.map(v => {
const t = parseInt(v, 16);
return isNaN(t) ? v : t;
});
b_ = b_.map(v => {
const t = parseInt(v, 16);
return isNaN(t) ? v : t;
});
for (let i = 0; i < a_.length && i < b.length; ++i) {
if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
if (isNaN(a_[i]) && isNaN(b_[i])) {
const ret = a_[i].localeCompare(b_[i]); // Compare strings
if (ret !== 0) return ret;
}
if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
}
}
return a.localeCompare(b);
}
}
export default Sort;

View file

@ -7,6 +7,7 @@
import Operation from "../Operation.mjs";
import XRegExp from "xregexp";
import { search } from "../lib/Extract.mjs";
import { caseInsensitiveSort } from "../lib/Sort.mjs";
/**
* Strings operation
@ -27,27 +28,37 @@ class Strings extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Encoding",
"type": "option",
"value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
name: "Encoding",
type: "option",
value: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
},
{
"name": "Minimum length",
"type": "number",
"value": 4
name: "Minimum length",
type: "number",
value: 4
},
{
"name": "Match",
"type": "option",
"value": [
name: "Match",
type: "option",
value: [
"[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
"[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
]
},
{
"name": "Display total",
"type": "boolean",
"value": false
name: "Display total",
type: "boolean",
value: false
},
{
name: "Sort",
type: "boolean",
value: false
},
{
name: "Unique",
type: "boolean",
value: false
}
];
}
@ -58,7 +69,7 @@ class Strings extends Operation {
* @returns {string}
*/
run(input, args) {
const [encoding, minLen, matchType, displayTotal] = args,
const [encoding, minLen, matchType, displayTotal, sort, unique] = args,
alphanumeric = "A-Z\\d",
punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
printable = "\x20-\x7e",
@ -108,8 +119,19 @@ class Strings extends Operation {
}
const regex = new XRegExp(strings, "ig");
const results = search(
input,
regex,
null,
sort ? caseInsensitiveSort : null,
unique
);
return search(input, regex, null, displayTotal);
if (displayTotal) {
return `Total found: ${results.length}\n\n${results.join("\n")}`;
} else {
return results.join("\n");
}
}
}

View file

@ -26,9 +26,14 @@ class Unique extends Operation {
this.outputType = "string";
this.args = [
{
"name": "Delimiter",
"type": "option",
"value": INPUT_DELIM_OPTIONS
name: "Delimiter",
type: "option",
value: INPUT_DELIM_OPTIONS
},
{
name: "Display count",
type: "boolean",
value: false
}
];
}
@ -39,8 +44,23 @@ class Unique extends Operation {
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0]);
return input.split(delim).unique().join(delim);
const delim = Utils.charRep(args[0]),
count = args[1];
if (count) {
const valMap = input.split(delim).reduce((acc, curr) => {
if (Object.prototype.hasOwnProperty.call(acc, curr)) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim);
} else {
return input.split(delim).unique().join(delim);
}
}
}