Merge 45746dfdf86d73233d5f511ae219401d8793d560 into 4290ea753912378913b1f3f54e0fc5720afeda5d

This commit is contained in:
Harel Katz 2026-08-10 00:25:13 -07:00 committed by GitHub
commit 0d89aa3383
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 245 additions and 10 deletions

View File

@ -21,7 +21,7 @@ class ExtractIPAddresses extends Operation {
this.name = "Extract IP addresses";
this.module = "Regex";
this.description = "Extracts all IPv4 and IPv6 addresses.<br><br>Warning: Given a string <code>1.2.3.4.5.6.7.8</code>, this will match <code>1.2.3.4 and 5.6.7.8</code> so always check the original input!";
this.description = "Extracts all IPv4 and IPv6 addresses.<br><br>When 'Include defanged' is enabled, addresses defanged with <code>[.]</code> (IPv4) or <code>[:]</code> (IPv6) are also extracted, including partial defangs (e.g. <code>192.168[.]1.1</code>). Matches are returned as-is; chain <code>Fang IP Addresses</code> after if you need clean addresses. With 'Sort' enabled, defanged matches sort lexicographically after the plain-IP group.<br><br>Warning: Given a string <code>1.2.3.4.5.6.7.8</code>, this will match <code>1.2.3.4 and 5.6.7.8</code> so always check the original input!";
this.inputType = "string";
this.outputType = "string";
this.args = [
@ -54,6 +54,11 @@ class ExtractIPAddresses extends Operation {
name: "Unique",
type: "boolean",
value: false
},
{
name: "Include defanged",
type: "boolean",
value: false
}
];
}
@ -64,7 +69,13 @@ class ExtractIPAddresses extends Operation {
* @returns {string}
*/
run(input, args) {
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique, includeDefanged] = args,
// Defang-aware separator patterns. When includeDefanged is false these
// collapse to the original literal separators, so the regex behaviour
// is byte-for-byte identical to the pre-change op.
dotSep = includeDefanged ? "(?:\\.|\\[\\.\\])" : "\\.",
colonSep = includeDefanged ? "(?::|\\[:\\])" : ":",
// IPv4 decimal groups can have values 0 to 255. To construct a regex the following sub-regex is reused:
ipv4DecimalByte = "(?:25[0-5]|2[0-4]\\d|1?[0-9]\\d|\\d)",
@ -74,13 +85,16 @@ class ExtractIPAddresses extends Operation {
lookBehind = "(?<!\\d)",
lookAhead = "(?!\\d)",
// Each variant requires exactly 4 groups with literal . between.
ipv4Decimal = "(?:" + lookBehind + ipv4DecimalByte + "\\.){3}" + "(?:" + ipv4DecimalByte + lookAhead + ")",
ipv4Octal = "(?:" + lookBehind + ipv4OctalByte + "\\.){3}" + "(?:" + ipv4OctalByte + lookAhead + ")",
// Each variant requires exactly 4 groups with literal . between (or [.] when defanged).
ipv4Decimal = "(?:" + lookBehind + ipv4DecimalByte + dotSep + "){3}" + "(?:" + ipv4DecimalByte + lookAhead + ")",
ipv4Octal = "(?:" + lookBehind + ipv4OctalByte + dotSep + "){3}" + "(?:" + ipv4OctalByte + lookAhead + ")",
// Then we allow IPv4 addresses to be expressed either entirely in decimal or entirely in Octal
ipv4 = "(?:" + ipv4Decimal + "|" + ipv4Octal + ")",
ipv6 = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}";
// The original IPv6 pattern uses only literal ':' for separators (no regex constructs like (?:...) that would mis-match a colon), so replacing every ':' with colonSep is safe.
ipv6Base = "((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})(([\\dA-F]{1,4}((?!\\3)::|:\\b|(?![\\dA-F])))|(?!\\2\\3)){2}",
ipv6 = includeDefanged ? ipv6Base.replace(/:/g, colonSep) : ipv6Base;
let ips = "";
if (includeIpv4 && includeIpv6) {
@ -95,10 +109,11 @@ class ExtractIPAddresses extends Operation {
const regex = new RegExp(ips, "ig");
const ten = "10\\..+",
oneninetwo = "192\\.168\\..+",
oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
onetwoseven = "127\\..+",
// The removeLocal filter regex gets the same dotSep treatment so defanged local IPs (e.g. 10[.]0[.]0[.]1) are filtered when both flags are on.
const ten = "10" + dotSep + ".+",
oneninetwo = "192" + dotSep + "168" + dotSep + ".+",
oneseventwo = "172" + dotSep + "(?:1[6-9]|2\\d|3[01])" + dotSep + ".+",
onetwoseven = "127" + dotSep + ".+",
removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
"|" + oneseventwo + "|" + onetwoseven + ")");

View File

@ -129,5 +129,225 @@ TestRegister.addTests([
},
],
},
{
name: "ExtractIPAddress backwards-compat: defang off ignores [.] defanged input",
input: "192[.]168[.]1[.]1 plain 10.0.0.1",
expectedOutput: "10.0.0.1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, false]
},
],
},
{
name: "ExtractIPAddress defang on: fully defanged IPv4",
input: "192[.]168[.]1[.]1",
expectedOutput: "192[.]168[.]1[.]1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: fully defanged IPv6 long form",
input: "2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343",
expectedOutput: "2001[:]0db8[:]85a3[:]0000[:]0000[:]8a2e[:]0370[:]7343",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [false, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: fully defanged IPv6 shorthand",
input: "2001[:]db8[:][:]1",
expectedOutput: "2001[:]db8[:][:]1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [false, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: partial defang IPv4",
input: "192.168[.]1.1 and 1[.]2.3[.]4",
expectedOutput: "192.168[.]1.1\n1[.]2.3[.]4",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: mixed plain and defanged in one input",
input: "plain 10.0.0.1 defanged 8[.]8[.]8[.]8",
expectedOutput: "10.0.0.1\n8[.]8[.]8[.]8",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: defanged octal IPv4",
input: "0123[.]0177[.]0234[.]0377",
expectedOutput: "0123[.]0177[.]0234[.]0377",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: partial-defang octal IPv4",
input: "0123.0177[.]0234[.]0377",
expectedOutput: "0123.0177[.]0234[.]0377",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on + removeLocal: full-defang local 10/8 filtered",
input: "10[.]0[.]0[.]1 public 8[.]8[.]8[.]8",
expectedOutput: "8[.]8[.]8[.]8",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, true, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on + removeLocal: partial-defang local 192.168 filtered",
input: "192.168[.]1[.]100 keeps 1.1.1.1",
expectedOutput: "1.1.1.1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, true, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on + removeLocal: full-defang 172.16/12 filtered",
input: "172[.]16[.]0[.]1 public 9[.]9[.]9[.]9",
expectedOutput: "9[.]9[.]9[.]9",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, true, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on + unique: plain and defanged kept distinct",
input: "1.1.1.1 1[.]1[.]1[.]1 1.1.1.1",
expectedOutput: "1.1.1.1\n1[.]1[.]1[.]1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, true, true]
},
],
},
{
name: "ExtractIPAddress defang on + sort: plain group sorts numerically before defanged group sorts lexically",
input: "200.1.1.1 5.5.5.5 100[.]100[.]100[.]100 10[.]0[.]0[.]1",
expectedOutput: "5.5.5.5\n200.1.1.1\n10[.]0[.]0[.]1\n100[.]100[.]100[.]100",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, true, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: IPv6 URI literal brackets stripped, inner extracted",
input: "Connect to [2001:db8::1]:8080 please",
expectedOutput: "2001:db8::1",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [false, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: empty input",
input: "",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: trailing-digit trap does not over-extend defanged IP",
input: "abc 192[.]168[.]1[.]1234",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on + displayTotal: count line includes defanged matches",
input: "1.1.1.1 2[.]2[.]2[.]2 3[.]3[.]3[.]3",
expectedOutput: "Total found: 3\n\n1.1.1.1\n2[.]2[.]2[.]2\n3[.]3[.]3[.]3",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, true, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on, IPv4 toggle off: defanged IPv4 input matches nothing",
input: "192[.]168[.]1[.]1",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [false, true, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on, IPv6 toggle off: defanged IPv6 input matches nothing",
input: "2001[:]db8[:][:]1",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, false, false, false, false, false, true]
},
],
},
{
name: "ExtractIPAddress defang on: pure ASCII text with no IPs returns empty",
input: "the quick brown fox jumps over the lazy dog (no addresses here)",
expectedOutput: "",
recipeConfig: [
{
"op": "Extract IP addresses",
"args": [true, true, false, false, false, false, true]
},
],
},
]);