cyberchef 9.37.2 → 9.37.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberchef",
3
- "version": "9.37.2",
3
+ "version": "9.37.3",
4
4
  "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
5
5
  "author": "n1474335 <n1474335@gmail.com>",
6
6
  "homepage": "https://gchq.github.io/CyberChef",
@@ -4946,7 +4946,17 @@
4946
4946
  {
4947
4947
  "name": "Display total",
4948
4948
  "type": "boolean",
4949
- "value": true
4949
+ "value": false
4950
+ },
4951
+ {
4952
+ "name": "Sort",
4953
+ "type": "boolean",
4954
+ "value": false
4955
+ },
4956
+ {
4957
+ "name": "Unique",
4958
+ "type": "boolean",
4959
+ "value": false
4950
4960
  }
4951
4961
  ]
4952
4962
  },
@@ -4973,6 +4983,16 @@
4973
4983
  "name": "Display total",
4974
4984
  "type": "boolean",
4975
4985
  "value": false
4986
+ },
4987
+ {
4988
+ "name": "Sort",
4989
+ "type": "boolean",
4990
+ "value": false
4991
+ },
4992
+ {
4993
+ "name": "Unique",
4994
+ "type": "boolean",
4995
+ "value": false
4976
4996
  }
4977
4997
  ]
4978
4998
  },
@@ -4999,6 +5019,16 @@
4999
5019
  "name": "Display total",
5000
5020
  "type": "boolean",
5001
5021
  "value": false
5022
+ },
5023
+ {
5024
+ "name": "Sort",
5025
+ "type": "boolean",
5026
+ "value": false
5027
+ },
5028
+ {
5029
+ "name": "Unique",
5030
+ "type": "boolean",
5031
+ "value": false
5002
5032
  }
5003
5033
  ]
5004
5034
  },
@@ -5091,6 +5121,16 @@
5091
5121
  "name": "Display total",
5092
5122
  "type": "boolean",
5093
5123
  "value": false
5124
+ },
5125
+ {
5126
+ "name": "Sort",
5127
+ "type": "boolean",
5128
+ "value": false
5129
+ },
5130
+ {
5131
+ "name": "Unique",
5132
+ "type": "boolean",
5133
+ "value": false
5094
5134
  }
5095
5135
  ]
5096
5136
  },
@@ -5174,6 +5214,16 @@
5174
5214
  "name": "Display total",
5175
5215
  "type": "boolean",
5176
5216
  "value": false
5217
+ },
5218
+ {
5219
+ "name": "Sort",
5220
+ "type": "boolean",
5221
+ "value": false
5222
+ },
5223
+ {
5224
+ "name": "Unique",
5225
+ "type": "boolean",
5226
+ "value": false
5177
5227
  }
5178
5228
  ]
5179
5229
  },
@@ -5228,6 +5278,16 @@
5228
5278
  "name": "Display total",
5229
5279
  "type": "boolean",
5230
5280
  "value": false
5281
+ },
5282
+ {
5283
+ "name": "Sort",
5284
+ "type": "boolean",
5285
+ "value": false
5286
+ },
5287
+ {
5288
+ "name": "Unique",
5289
+ "type": "boolean",
5290
+ "value": false
5231
5291
  }
5232
5292
  ]
5233
5293
  },
@@ -12712,6 +12772,16 @@
12712
12772
  "name": "Display total",
12713
12773
  "type": "boolean",
12714
12774
  "value": false
12775
+ },
12776
+ {
12777
+ "name": "Sort",
12778
+ "type": "boolean",
12779
+ "value": false
12780
+ },
12781
+ {
12782
+ "name": "Unique",
12783
+ "type": "boolean",
12784
+ "value": false
12715
12785
  }
12716
12786
  ]
12717
12787
  },
@@ -16039,6 +16109,11 @@
16039
16109
  "Colon",
16040
16110
  "Nothing (separate chars)"
16041
16111
  ]
16112
+ },
16113
+ {
16114
+ "name": "Display count",
16115
+ "type": "boolean",
16116
+ "value": false
16042
16117
  }
16043
16118
  ]
16044
16119
  },
@@ -12,15 +12,15 @@
12
12
  *
13
13
  * @param {string} input
14
14
  * @param {RegExp} searchRegex
15
- * @param {RegExp} removeRegex - A regular expression defining results to remove from the
15
+ * @param {RegExp} [removeRegex=null] - A regular expression defining results to remove from the
16
16
  * final list
17
- * @param {boolean} includeTotal - Whether or not to include the total number of results
17
+ * @param {Function} [sortBy=null] - The sorting comparison function to apply
18
+ * @param {boolean} [unique=false] - Whether to unique the results
18
19
  * @returns {string}
19
20
  */
20
- export function search (input, searchRegex, removeRegex, includeTotal) {
21
- let output = "",
22
- total = 0,
23
- match;
21
+ export function search(input, searchRegex, removeRegex=null, sortBy=null, unique=false) {
22
+ let results = [];
23
+ let match;
24
24
 
25
25
  while ((match = searchRegex.exec(input))) {
26
26
  // Moves pointer when an empty string is matched (prevents infinite loop)
@@ -30,14 +30,19 @@ export function search (input, searchRegex, removeRegex, includeTotal) {
30
30
 
31
31
  if (removeRegex && removeRegex.test(match[0]))
32
32
  continue;
33
- total++;
34
- output += match[0] + "\n";
33
+
34
+ results.push(match[0]);
35
+ }
36
+
37
+ if (sortBy) {
38
+ results = results.sort(sortBy);
35
39
  }
36
40
 
37
- if (includeTotal)
38
- output = "Total found: " + total + "\n\n" + output;
41
+ if (unique) {
42
+ results = results.unique();
43
+ }
39
44
 
40
- return output;
45
+ return results;
41
46
  }
42
47
 
43
48
 
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Sorting functions
3
+ *
4
+ * @author n1474335 [n1474335@gmail.com]
5
+ * @copyright Crown Copyright 2022
6
+ * @license Apache-2.0
7
+ *
8
+ */
9
+
10
+ /**
11
+ * Comparison operation for sorting of strings ignoring case.
12
+ *
13
+ * @param {string} a
14
+ * @param {string} b
15
+ * @returns {number}
16
+ */
17
+ export function caseInsensitiveSort(a, b) {
18
+ return a.toLowerCase().localeCompare(b.toLowerCase());
19
+ }
20
+
21
+
22
+ /**
23
+ * Comparison operation for sorting of IPv4 addresses.
24
+ *
25
+ * @param {string} a
26
+ * @param {string} b
27
+ * @returns {number}
28
+ */
29
+ export function ipSort(a, b) {
30
+ let a_ = a.split("."),
31
+ b_ = b.split(".");
32
+
33
+ a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
34
+ b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
35
+
36
+ if (isNaN(a_) && !isNaN(b_)) return 1;
37
+ if (!isNaN(a_) && isNaN(b_)) return -1;
38
+ if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
39
+
40
+ return a_ - b_;
41
+ }
42
+
43
+ /**
44
+ * Comparison operation for sorting of numeric values.
45
+ *
46
+ * @author Chris van Marle
47
+ * @param {string} a
48
+ * @param {string} b
49
+ * @returns {number}
50
+ */
51
+ export function numericSort(a, b) {
52
+ const a_ = a.split(/([^\d]+)/),
53
+ b_ = b.split(/([^\d]+)/);
54
+
55
+ for (let i = 0; i < a_.length && i < b.length; ++i) {
56
+ if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
57
+ if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
58
+ if (isNaN(a_[i]) && isNaN(b_[i])) {
59
+ const ret = a_[i].localeCompare(b_[i]); // Compare strings
60
+ if (ret !== 0) return ret;
61
+ }
62
+ if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
63
+ if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
64
+ }
65
+ }
66
+
67
+ return a.localeCompare(b);
68
+ }
69
+
70
+ /**
71
+ * Comparison operation for sorting of hexadecimal values.
72
+ *
73
+ * @author Chris van Marle
74
+ * @param {string} a
75
+ * @param {string} b
76
+ * @returns {number}
77
+ */
78
+ export function hexadecimalSort(a, b) {
79
+ let a_ = a.split(/([^\da-f]+)/i),
80
+ b_ = b.split(/([^\da-f]+)/i);
81
+
82
+ a_ = a_.map(v => {
83
+ const t = parseInt(v, 16);
84
+ return isNaN(t) ? v : t;
85
+ });
86
+
87
+ b_ = b_.map(v => {
88
+ const t = parseInt(v, 16);
89
+ return isNaN(t) ? v : t;
90
+ });
91
+
92
+ for (let i = 0; i < a_.length && i < b.length; ++i) {
93
+ if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
94
+ if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
95
+ if (isNaN(a_[i]) && isNaN(b_[i])) {
96
+ const ret = a_[i].localeCompare(b_[i]); // Compare strings
97
+ if (ret !== 0) return ret;
98
+ }
99
+ if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
100
+ if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
101
+ }
102
+ }
103
+
104
+ return a.localeCompare(b);
105
+ }
@@ -44,7 +44,13 @@ class ExtractDates extends Operation {
44
44
  date3 = "(?:0[1-9]|1[012])[- /.](?:0[1-9]|[12][0-9]|3[01])[- /.](?:19|20)\\d\\d", // mm/dd/yyyy
45
45
  regex = new RegExp(date1 + "|" + date2 + "|" + date3, "ig");
46
46
 
47
- return search(input, regex, null, displayTotal);
47
+ const results = search(input, regex);
48
+
49
+ if (displayTotal) {
50
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
51
+ } else {
52
+ return results.join("\n");
53
+ }
48
54
  }
49
55
 
50
56
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search, DOMAIN_REGEX } from "../lib/Extract.mjs";
9
+ import { caseInsensitiveSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract domains operation
@@ -25,9 +26,19 @@ class ExtractDomains extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "Display total",
29
- "type": "boolean",
30
- "value": true
29
+ name: "Display total",
30
+ type: "boolean",
31
+ value: false
32
+ },
33
+ {
34
+ name: "Sort",
35
+ type: "boolean",
36
+ value: false
37
+ },
38
+ {
39
+ name: "Unique",
40
+ type: "boolean",
41
+ value: false
31
42
  }
32
43
  ];
33
44
  }
@@ -38,8 +49,21 @@ class ExtractDomains extends Operation {
38
49
  * @returns {string}
39
50
  */
40
51
  run(input, args) {
41
- const displayTotal = args[0];
42
- return search(input, DOMAIN_REGEX, null, displayTotal);
52
+ const [displayTotal, sort, unique] = args;
53
+
54
+ const results = search(
55
+ input,
56
+ DOMAIN_REGEX,
57
+ null,
58
+ sort ? caseInsensitiveSort : null,
59
+ unique
60
+ );
61
+
62
+ if (displayTotal) {
63
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
64
+ } else {
65
+ return results.join("\n");
66
+ }
43
67
  }
44
68
 
45
69
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search } from "../lib/Extract.mjs";
9
+ import { caseInsensitiveSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract email addresses operation
@@ -25,9 +26,19 @@ class ExtractEmailAddresses extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "Display total",
29
- "type": "boolean",
30
- "value": false
29
+ name: "Display total",
30
+ type: "boolean",
31
+ value: false
32
+ },
33
+ {
34
+ name: "Sort",
35
+ type: "boolean",
36
+ value: false
37
+ },
38
+ {
39
+ name: "Unique",
40
+ type: "boolean",
41
+ value: false
31
42
  }
32
43
  ];
33
44
  }
@@ -38,10 +49,23 @@ class ExtractEmailAddresses extends Operation {
38
49
  * @returns {string}
39
50
  */
40
51
  run(input, args) {
41
- const displayTotal = args[0],
52
+ const [displayTotal, sort, unique] = args,
42
53
  // email regex from: https://www.regextester.com/98066
43
54
  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;
44
- return search(input, regex, null, displayTotal);
55
+
56
+ const results = search(
57
+ input,
58
+ regex,
59
+ null,
60
+ sort ? caseInsensitiveSort : null,
61
+ unique
62
+ );
63
+
64
+ if (displayTotal) {
65
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
66
+ } else {
67
+ return results.join("\n");
68
+ }
45
69
  }
46
70
 
47
71
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search } from "../lib/Extract.mjs";
9
+ import { caseInsensitiveSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract file paths operation
@@ -25,19 +26,29 @@ class ExtractFilePaths extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "Windows",
29
- "type": "boolean",
30
- "value": true
29
+ name: "Windows",
30
+ type: "boolean",
31
+ value: true
31
32
  },
32
33
  {
33
- "name": "UNIX",
34
- "type": "boolean",
35
- "value": true
34
+ name: "UNIX",
35
+ type: "boolean",
36
+ value: true
36
37
  },
37
38
  {
38
- "name": "Display total",
39
- "type": "boolean",
40
- "value": false
39
+ name: "Display total",
40
+ type: "boolean",
41
+ value: false
42
+ },
43
+ {
44
+ name: "Sort",
45
+ type: "boolean",
46
+ value: false
47
+ },
48
+ {
49
+ name: "Unique",
50
+ type: "boolean",
51
+ value: false
41
52
  }
42
53
  ];
43
54
  }
@@ -48,7 +59,7 @@ class ExtractFilePaths extends Operation {
48
59
  * @returns {string}
49
60
  */
50
61
  run(input, args) {
51
- const [includeWinPath, includeUnixPath, displayTotal] = args,
62
+ const [includeWinPath, includeUnixPath, displayTotal, sort, unique] = args,
52
63
  winDrive = "[A-Z]:\\\\",
53
64
  winName = "[A-Z\\d][A-Z\\d\\- '_\\(\\)~]{0,61}",
54
65
  winExt = "[A-Z\\d]{1,6}",
@@ -65,12 +76,25 @@ class ExtractFilePaths extends Operation {
65
76
  filePaths = unixPath;
66
77
  }
67
78
 
68
- if (filePaths) {
69
- const regex = new RegExp(filePaths, "ig");
70
- return search(input, regex, null, displayTotal);
71
- } else {
79
+ if (!filePaths) {
72
80
  return "";
73
81
  }
82
+
83
+ const regex = new RegExp(filePaths, "ig");
84
+ const results = search(
85
+ input,
86
+ regex,
87
+ null,
88
+ sort ? caseInsensitiveSort : null,
89
+ unique
90
+ );
91
+
92
+ if (displayTotal) {
93
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
94
+ } else {
95
+ return results.join("\n");
96
+ }
97
+
74
98
  }
75
99
 
76
100
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search } from "../lib/Extract.mjs";
9
+ import { ipSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract IP addresses operation
@@ -25,24 +26,34 @@ class ExtractIPAddresses extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "IPv4",
29
- "type": "boolean",
30
- "value": true
29
+ name: "IPv4",
30
+ type: "boolean",
31
+ value: true
31
32
  },
32
33
  {
33
- "name": "IPv6",
34
- "type": "boolean",
35
- "value": false
34
+ name: "IPv6",
35
+ type: "boolean",
36
+ value: false
36
37
  },
37
38
  {
38
- "name": "Remove local IPv4 addresses",
39
- "type": "boolean",
40
- "value": false
39
+ name: "Remove local IPv4 addresses",
40
+ type: "boolean",
41
+ value: false
41
42
  },
42
43
  {
43
- "name": "Display total",
44
- "type": "boolean",
45
- "value": false
44
+ name: "Display total",
45
+ type: "boolean",
46
+ value: false
47
+ },
48
+ {
49
+ name: "Sort",
50
+ type: "boolean",
51
+ value: false
52
+ },
53
+ {
54
+ name: "Unique",
55
+ type: "boolean",
56
+ value: false
46
57
  }
47
58
  ];
48
59
  }
@@ -53,7 +64,7 @@ class ExtractIPAddresses extends Operation {
53
64
  * @returns {string}
54
65
  */
55
66
  run(input, args) {
56
- const [includeIpv4, includeIpv6, removeLocal, displayTotal] = args,
67
+ const [includeIpv4, includeIpv6, removeLocal, displayTotal, sort, unique] = args,
57
68
  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})?",
58
69
  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})";
59
70
  let ips = "";
@@ -66,23 +77,29 @@ class ExtractIPAddresses extends Operation {
66
77
  ips = ipv6;
67
78
  }
68
79
 
69
- if (ips) {
70
- const regex = new RegExp(ips, "ig");
80
+ if (!ips) return "";
71
81
 
72
- if (removeLocal) {
73
- const ten = "10\\..+",
74
- oneninetwo = "192\\.168\\..+",
75
- oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
76
- onetwoseven = "127\\..+",
77
- removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
78
- "|" + oneseventwo + "|" + onetwoseven + ")");
82
+ const regex = new RegExp(ips, "ig");
79
83
 
80
- return search(input, regex, removeRegex, displayTotal);
81
- } else {
82
- return search(input, regex, null, displayTotal);
83
- }
84
+ const ten = "10\\..+",
85
+ oneninetwo = "192\\.168\\..+",
86
+ oneseventwo = "172\\.(?:1[6-9]|2\\d|3[01])\\..+",
87
+ onetwoseven = "127\\..+",
88
+ removeRegex = new RegExp("^(?:" + ten + "|" + oneninetwo +
89
+ "|" + oneseventwo + "|" + onetwoseven + ")");
90
+
91
+ const results = search(
92
+ input,
93
+ regex,
94
+ removeLocal ? removeRegex : null,
95
+ sort ? ipSort : null,
96
+ unique
97
+ );
98
+
99
+ if (displayTotal) {
100
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
84
101
  } else {
85
- return "";
102
+ return results.join("\n");
86
103
  }
87
104
  }
88
105
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search } from "../lib/Extract.mjs";
9
+ import { hexadecimalSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract MAC addresses operation
@@ -25,9 +26,19 @@ class ExtractMACAddresses extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "Display total",
29
- "type": "boolean",
30
- "value": false
29
+ name: "Display total",
30
+ type: "boolean",
31
+ value: false
32
+ },
33
+ {
34
+ name: "Sort",
35
+ type: "boolean",
36
+ value: false
37
+ },
38
+ {
39
+ name: "Unique",
40
+ type: "boolean",
41
+ value: false
31
42
  }
32
43
  ];
33
44
  }
@@ -38,10 +49,21 @@ class ExtractMACAddresses extends Operation {
38
49
  * @returns {string}
39
50
  */
40
51
  run(input, args) {
41
- const displayTotal = args[0],
42
- regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig;
52
+ const [displayTotal, sort, unique] = args,
53
+ regex = /[A-F\d]{2}(?:[:-][A-F\d]{2}){5}/ig,
54
+ results = search(
55
+ input,
56
+ regex,
57
+ null,
58
+ sort ? hexadecimalSort : null,
59
+ unique
60
+ );
43
61
 
44
- return search(input, regex, null, displayTotal);
62
+ if (displayTotal) {
63
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
64
+ } else {
65
+ return results.join("\n");
66
+ }
45
67
  }
46
68
 
47
69
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import { search, URL_REGEX } from "../lib/Extract.mjs";
9
+ import { caseInsensitiveSort } from "../lib/Sort.mjs";
9
10
 
10
11
  /**
11
12
  * Extract URLs operation
@@ -25,9 +26,19 @@ class ExtractURLs extends Operation {
25
26
  this.outputType = "string";
26
27
  this.args = [
27
28
  {
28
- "name": "Display total",
29
- "type": "boolean",
30
- "value": false
29
+ name: "Display total",
30
+ type: "boolean",
31
+ value: false
32
+ },
33
+ {
34
+ name: "Sort",
35
+ type: "boolean",
36
+ value: false
37
+ },
38
+ {
39
+ name: "Unique",
40
+ type: "boolean",
41
+ value: false
31
42
  }
32
43
  ];
33
44
  }
@@ -38,8 +49,20 @@ class ExtractURLs extends Operation {
38
49
  * @returns {string}
39
50
  */
40
51
  run(input, args) {
41
- const displayTotal = args[0];
42
- return search(input, URL_REGEX, null, displayTotal);
52
+ const [displayTotal, sort, unique] = args;
53
+ const results = search(
54
+ input,
55
+ URL_REGEX,
56
+ null,
57
+ sort ? caseInsensitiveSort : null,
58
+ unique
59
+ );
60
+
61
+ if (displayTotal) {
62
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
63
+ } else {
64
+ return results.join("\n");
65
+ }
43
66
  }
44
67
 
45
68
  }
@@ -7,6 +7,7 @@
7
7
  import Operation from "../Operation.mjs";
8
8
  import Utils from "../Utils.mjs";
9
9
  import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs";
10
+ import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs";
10
11
 
11
12
  /**
12
13
  * Sort operation
@@ -57,120 +58,19 @@ class Sort extends Operation {
57
58
  if (order === "Alphabetical (case sensitive)") {
58
59
  sorted = sorted.sort();
59
60
  } else if (order === "Alphabetical (case insensitive)") {
60
- sorted = sorted.sort(Sort._caseInsensitiveSort);
61
+ sorted = sorted.sort(caseInsensitiveSort);
61
62
  } else if (order === "IP address") {
62
- sorted = sorted.sort(Sort._ipSort);
63
+ sorted = sorted.sort(ipSort);
63
64
  } else if (order === "Numeric") {
64
- sorted = sorted.sort(Sort._numericSort);
65
+ sorted = sorted.sort(numericSort);
65
66
  } else if (order === "Numeric (hexadecimal)") {
66
- sorted = sorted.sort(Sort._hexadecimalSort);
67
+ sorted = sorted.sort(hexadecimalSort);
67
68
  }
68
69
 
69
70
  if (sortReverse) sorted.reverse();
70
71
  return sorted.join(delim);
71
72
  }
72
73
 
73
- /**
74
- * Comparison operation for sorting of strings ignoring case.
75
- *
76
- * @private
77
- * @param {string} a
78
- * @param {string} b
79
- * @returns {number}
80
- */
81
- static _caseInsensitiveSort(a, b) {
82
- return a.toLowerCase().localeCompare(b.toLowerCase());
83
- }
84
-
85
-
86
- /**
87
- * Comparison operation for sorting of IPv4 addresses.
88
- *
89
- * @private
90
- * @param {string} a
91
- * @param {string} b
92
- * @returns {number}
93
- */
94
- static _ipSort(a, b) {
95
- let a_ = a.split("."),
96
- b_ = b.split(".");
97
-
98
- a_ = a_[0] * 0x1000000 + a_[1] * 0x10000 + a_[2] * 0x100 + a_[3] * 1;
99
- b_ = b_[0] * 0x1000000 + b_[1] * 0x10000 + b_[2] * 0x100 + b_[3] * 1;
100
-
101
- if (isNaN(a_) && !isNaN(b_)) return 1;
102
- if (!isNaN(a_) && isNaN(b_)) return -1;
103
- if (isNaN(a_) && isNaN(b_)) return a.localeCompare(b);
104
-
105
- return a_ - b_;
106
- }
107
-
108
- /**
109
- * Comparison operation for sorting of numeric values.
110
- *
111
- * @author Chris van Marle
112
- * @private
113
- * @param {string} a
114
- * @param {string} b
115
- * @returns {number}
116
- */
117
- static _numericSort(a, b) {
118
- const a_ = a.split(/([^\d]+)/),
119
- b_ = b.split(/([^\d]+)/);
120
-
121
- for (let i = 0; i < a_.length && i < b.length; ++i) {
122
- if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
123
- if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
124
- if (isNaN(a_[i]) && isNaN(b_[i])) {
125
- const ret = a_[i].localeCompare(b_[i]); // Compare strings
126
- if (ret !== 0) return ret;
127
- }
128
- if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
129
- if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
130
- }
131
- }
132
-
133
- return a.localeCompare(b);
134
- }
135
-
136
- /**
137
- * Comparison operation for sorting of hexadecimal values.
138
- *
139
- * @author Chris van Marle
140
- * @private
141
- * @param {string} a
142
- * @param {string} b
143
- * @returns {number}
144
- */
145
- static _hexadecimalSort(a, b) {
146
- let a_ = a.split(/([^\da-f]+)/i),
147
- b_ = b.split(/([^\da-f]+)/i);
148
-
149
- a_ = a_.map(v => {
150
- const t = parseInt(v, 16);
151
- return isNaN(t) ? v : t;
152
- });
153
-
154
- b_ = b_.map(v => {
155
- const t = parseInt(v, 16);
156
- return isNaN(t) ? v : t;
157
- });
158
-
159
- for (let i = 0; i < a_.length && i < b.length; ++i) {
160
- if (isNaN(a_[i]) && !isNaN(b_[i])) return 1; // Numbers after non-numbers
161
- if (!isNaN(a_[i]) && isNaN(b_[i])) return -1;
162
- if (isNaN(a_[i]) && isNaN(b_[i])) {
163
- const ret = a_[i].localeCompare(b_[i]); // Compare strings
164
- if (ret !== 0) return ret;
165
- }
166
- if (!isNaN(a_[i]) && !isNaN(b_[i])) { // Compare numbers
167
- if (a_[i] - b_[i] !== 0) return a_[i] - b_[i];
168
- }
169
- }
170
-
171
- return a.localeCompare(b);
172
- }
173
-
174
74
  }
175
75
 
176
76
  export default Sort;
@@ -7,6 +7,7 @@
7
7
  import Operation from "../Operation.mjs";
8
8
  import XRegExp from "xregexp";
9
9
  import { search } from "../lib/Extract.mjs";
10
+ import { caseInsensitiveSort } from "../lib/Sort.mjs";
10
11
 
11
12
  /**
12
13
  * Strings operation
@@ -27,27 +28,37 @@ class Strings extends Operation {
27
28
  this.outputType = "string";
28
29
  this.args = [
29
30
  {
30
- "name": "Encoding",
31
- "type": "option",
32
- "value": ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
31
+ name: "Encoding",
32
+ type: "option",
33
+ value: ["Single byte", "16-bit littleendian", "16-bit bigendian", "All"]
33
34
  },
34
35
  {
35
- "name": "Minimum length",
36
- "type": "number",
37
- "value": 4
36
+ name: "Minimum length",
37
+ type: "number",
38
+ value: 4
38
39
  },
39
40
  {
40
- "name": "Match",
41
- "type": "option",
42
- "value": [
41
+ name: "Match",
42
+ type: "option",
43
+ value: [
43
44
  "[ASCII]", "Alphanumeric + punctuation (A)", "All printable chars (A)", "Null-terminated strings (A)",
44
45
  "[Unicode]", "Alphanumeric + punctuation (U)", "All printable chars (U)", "Null-terminated strings (U)"
45
46
  ]
46
47
  },
47
48
  {
48
- "name": "Display total",
49
- "type": "boolean",
50
- "value": false
49
+ name: "Display total",
50
+ type: "boolean",
51
+ value: false
52
+ },
53
+ {
54
+ name: "Sort",
55
+ type: "boolean",
56
+ value: false
57
+ },
58
+ {
59
+ name: "Unique",
60
+ type: "boolean",
61
+ value: false
51
62
  }
52
63
  ];
53
64
  }
@@ -58,7 +69,7 @@ class Strings extends Operation {
58
69
  * @returns {string}
59
70
  */
60
71
  run(input, args) {
61
- const [encoding, minLen, matchType, displayTotal] = args,
72
+ const [encoding, minLen, matchType, displayTotal, sort, unique] = args,
62
73
  alphanumeric = "A-Z\\d",
63
74
  punctuation = "/\\-:.,_$%'\"()<>= !\\[\\]{}@",
64
75
  printable = "\x20-\x7e",
@@ -108,8 +119,19 @@ class Strings extends Operation {
108
119
  }
109
120
 
110
121
  const regex = new XRegExp(strings, "ig");
122
+ const results = search(
123
+ input,
124
+ regex,
125
+ null,
126
+ sort ? caseInsensitiveSort : null,
127
+ unique
128
+ );
111
129
 
112
- return search(input, regex, null, displayTotal);
130
+ if (displayTotal) {
131
+ return `Total found: ${results.length}\n\n${results.join("\n")}`;
132
+ } else {
133
+ return results.join("\n");
134
+ }
113
135
  }
114
136
 
115
137
  }
@@ -26,9 +26,14 @@ class Unique extends Operation {
26
26
  this.outputType = "string";
27
27
  this.args = [
28
28
  {
29
- "name": "Delimiter",
30
- "type": "option",
31
- "value": INPUT_DELIM_OPTIONS
29
+ name: "Delimiter",
30
+ type: "option",
31
+ value: INPUT_DELIM_OPTIONS
32
+ },
33
+ {
34
+ name: "Display count",
35
+ type: "boolean",
36
+ value: false
32
37
  }
33
38
  ];
34
39
  }
@@ -39,8 +44,23 @@ class Unique extends Operation {
39
44
  * @returns {string}
40
45
  */
41
46
  run(input, args) {
42
- const delim = Utils.charRep(args[0]);
43
- return input.split(delim).unique().join(delim);
47
+ const delim = Utils.charRep(args[0]),
48
+ count = args[1];
49
+
50
+ if (count) {
51
+ const valMap = input.split(delim).reduce((acc, curr) => {
52
+ if (Object.prototype.hasOwnProperty.call(acc, curr)) {
53
+ acc[curr]++;
54
+ } else {
55
+ acc[curr] = 1;
56
+ }
57
+ return acc;
58
+ }, {});
59
+
60
+ return Object.keys(valMap).map(val => `${valMap[val]} ${val}`).join(delim);
61
+ } else {
62
+ return input.split(delim).unique().join(delim);
63
+ }
44
64
  }
45
65
 
46
66
  }
@@ -471,7 +471,7 @@ color: white;
471
471
  }),
472
472
 
473
473
  it("Extract dates", () => {
474
- assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992\n");
474
+ assert.strictEqual(chef.extractDates("Don't Look a Gift Horse In The Mouth 01/02/1992").toString(), "01/02/1992");
475
475
  }),
476
476
 
477
477
  it("Filter", () => {
@@ -859,7 +859,7 @@ pCGTErs=
859
859
  }),
860
860
 
861
861
  it("SQL Beautify", () => {
862
- const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F
862
+ const result = chef.SQLBeautify(`SELECT MONTH, ID, RAIN_I, TEMP_F
863
863
  FROM STATS;`);
864
864
  const expected = `SELECT MONTH,
865
865
  ID,
@@ -879,8 +879,7 @@ FROM STATS;`;
879
879
  const result = chef.strings("smothering ampersand abreast", {displayTotal: true});
880
880
  const expected = `Total found: 1
881
881
 
882
- smothering ampersand abreast
883
- `;
882
+ smothering ampersand abreast`;
884
883
  assert.strictEqual(result.toString(), expected);
885
884
  }),
886
885
 
@@ -11,7 +11,7 @@ TestRegister.addTests([
11
11
  {
12
12
  name: "Extract email address",
13
13
  input: "email@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com email@example.name\nemail@example.museum email@example.co.jp firstname-lastname@example.com",
14
- expectedOutput: "email@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com\nemail@example.name\nemail@example.museum\nemail@example.co.jp\nfirstname-lastname@example.com\n",
14
+ expectedOutput: "email@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com\nemail@example.name\nemail@example.museum\nemail@example.co.jp\nfirstname-lastname@example.com",
15
15
  recipeConfig: [
16
16
  {
17
17
  "op": "Extract email addresses",
@@ -22,7 +22,7 @@ TestRegister.addTests([
22
22
  {
23
23
  name: "Extract email address - Display total",
24
24
  input: "email@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com email@example.name\nemail@example.museum email@example.co.jp firstname-lastname@example.com",
25
- expectedOutput: "Total found: 11\n\nemail@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com\nemail@example.name\nemail@example.museum\nemail@example.co.jp\nfirstname-lastname@example.com\n",
25
+ expectedOutput: "Total found: 11\n\nemail@example.com\nfirstname.lastname@example.com\nemail@subdomain.example.com\nfirstname+lastname@example.com\n1234567890@example.com\nemail@example-one.com\n_______@example.com\nemail@example.name\nemail@example.museum\nemail@example.co.jp\nfirstname-lastname@example.com",
26
26
  recipeConfig: [
27
27
  {
28
28
  "op": "Extract email addresses",
@@ -33,7 +33,7 @@ TestRegister.addTests([
33
33
  {
34
34
  name: "Extract email address (Internationalized)",
35
35
  input: "\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9 \u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c \u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc Jos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com and Jos\u1ec5Silva@google.com\nFoO@BaR.CoM, john@192.168.10.100\ng\xf3mez@junk.br and Abc.123@example.com.\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com",
36
- expectedOutput: "\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9\n\u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nJos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com\nJos\u1ec5Silva@google.com\nFoO@BaR.CoM\njohn@192.168.10.100\ng\xf3mez@junk.br\nAbc.123@example.com\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com\n",
36
+ expectedOutput: "\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9\n\u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nJos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com\nJos\u1ec5Silva@google.com\nFoO@BaR.CoM\njohn@192.168.10.100\ng\xf3mez@junk.br\nAbc.123@example.com\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com",
37
37
  recipeConfig: [
38
38
  {
39
39
  "op": "Extract email addresses",
@@ -44,7 +44,7 @@ TestRegister.addTests([
44
44
  {
45
45
  name: "Extract email address - Display total (Internationalized)",
46
46
  input: "\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9 \u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c \u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc Jos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com and Jos\u1ec5Silva@google.com\nFoO@BaR.CoM, john@192.168.10.100\ng\xf3mez@junk.br and Abc.123@example.com.\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com",
47
- expectedOutput: "Total found: 19\n\n\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9\n\u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nJos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com\nJos\u1ec5Silva@google.com\nFoO@BaR.CoM\njohn@192.168.10.100\ng\xf3mez@junk.br\nAbc.123@example.com\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com\n",
47
+ expectedOutput: "Total found: 19\n\n\u4f0a\u662d\u5091@\u90f5\u4ef6.\u5546\u52d9\n\u093e\u092e@\u092e\u094b\u0939\u0928.\u0908\u0928\u094d\u092b\u094b\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nJos\u1ec5Silv\u1ec5@googl\u1ec5.com\nJos\u1ec5Silv\u1ec5@google.com\nJos\u1ec5Silva@google.com\nFoO@BaR.CoM\njohn@192.168.10.100\ng\xf3mez@junk.br\nAbc.123@example.com\nuser+mailbox/department=shipping@example.com\n\u7528\u6237@\u4f8b\u5b50.\u5e7f\u544a\n\u0909\u092a\u092f\u094b\u0917\u0915\u0930\u094d\u0924\u093e@\u0909\u0926\u093e\u0939\u0930\u0923.\u0915\u0949\u092e\n\u044e\u0437\u0435\u0440@\u0435\u043a\u0437\u0430\u043c\u043f\u043b.\u043a\u043e\u043c\n\u03b8\u03c3\u03b5\u03c1@\u03b5\u03c7\u03b1\u03bc\u03c0\u03bb\u03b5.\u03c8\u03bf\u03bc\nD\xf6rte@S\xf6rensen.example.com\n\u0430\u0434\u0436\u0430\u0439@\u044d\u043a\u0437\u0430\u043c\u043f\u043b.\u0440\u0443\u0441\ntest@xn--bcher-kva.com",
48
48
  recipeConfig: [
49
49
  {
50
50
  "op": "Extract email addresses",