cyberchef 9.37.2 → 9.38.1

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +7 -2
  2. package/Gruntfile.js +2 -1
  3. package/package.json +4 -2
  4. package/src/core/config/Categories.json +1 -0
  5. package/src/core/config/OperationConfig.json +106 -3
  6. package/src/core/config/modules/Default.mjs +2 -0
  7. package/src/core/config/scripts/newMinorVersion.mjs +144 -0
  8. package/src/core/lib/Binary.mjs +9 -5
  9. package/src/core/lib/Extract.mjs +16 -11
  10. package/src/core/lib/FileSignatures.mjs +12 -12
  11. package/src/core/lib/Protocol.mjs +47 -0
  12. package/src/core/lib/Sort.mjs +105 -0
  13. package/src/core/lib/Stream.mjs +22 -12
  14. package/src/core/operations/ExtractDates.mjs +7 -1
  15. package/src/core/operations/ExtractDomains.mjs +29 -5
  16. package/src/core/operations/ExtractEmailAddresses.mjs +29 -5
  17. package/src/core/operations/ExtractFilePaths.mjs +38 -14
  18. package/src/core/operations/ExtractIPAddresses.mjs +44 -27
  19. package/src/core/operations/ExtractMACAddresses.mjs +28 -6
  20. package/src/core/operations/ExtractURLs.mjs +28 -5
  21. package/src/core/operations/ParseTCP.mjs +245 -0
  22. package/src/core/operations/ParseUDP.mjs +29 -24
  23. package/src/core/operations/Sort.mjs +5 -105
  24. package/src/core/operations/Strings.mjs +36 -14
  25. package/src/core/operations/ToBase45.mjs +4 -0
  26. package/src/core/operations/ToHex.mjs +2 -2
  27. package/src/core/operations/Unique.mjs +25 -5
  28. package/src/core/operations/index.mjs +2 -0
  29. package/src/node/index.mjs +5 -0
  30. package/src/web/App.mjs +10 -5
  31. package/src/web/html/index.html +12 -10
  32. package/src/web/stylesheets/components/_pane.css +11 -4
  33. package/src/web/stylesheets/layout/_controls.css +4 -0
  34. package/src/web/stylesheets/layout/_io.css +1 -2
  35. package/src/web/stylesheets/layout/_structure.css +2 -2
  36. package/src/web/waiters/OperationsWaiter.mjs +5 -1
  37. package/src/web/waiters/OutputWaiter.mjs +2 -0
  38. package/src/web/waiters/WindowWaiter.mjs +2 -2
  39. package/tests/node/tests/operations.mjs +3 -4
  40. package/tests/operations/index.mjs +1 -0
  41. package/tests/operations/tests/ExtractEmailAddresses.mjs +4 -4
  42. package/tests/operations/tests/ParseTCP.mjs +44 -0
  43. package/tests/operations/tests/ParseUDP.mjs +5 -18
  44. package/webpack.config.js +11 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @author n1474335 [n1474335@gmail.com]
3
+ * @copyright Crown Copyright 2022
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import Stream from "../lib/Stream.mjs";
9
+ import {toHexFast, fromHex} from "../lib/Hex.mjs";
10
+ import {toBinary} from "../lib/Binary.mjs";
11
+ import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
12
+ import Utils from "../Utils.mjs";
13
+ import OperationError from "../errors/OperationError.mjs";
14
+ import BigNumber from "bignumber.js";
15
+
16
+ /**
17
+ * Parse TCP operation
18
+ */
19
+ class ParseTCP extends Operation {
20
+
21
+ /**
22
+ * ParseTCP constructor
23
+ */
24
+ constructor() {
25
+ super();
26
+
27
+ this.name = "Parse TCP";
28
+ this.module = "Default";
29
+ this.description = "Parses a TCP header and payload (if present).";
30
+ this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
31
+ this.inputType = "string";
32
+ this.outputType = "json";
33
+ this.presentType = "html";
34
+ this.args = [
35
+ {
36
+ name: "Input format",
37
+ type: "option",
38
+ value: ["Hex", "Raw"]
39
+ }
40
+ ];
41
+ }
42
+
43
+ /**
44
+ * @param {string} input
45
+ * @param {Object[]} args
46
+ * @returns {html}
47
+ */
48
+ run(input, args) {
49
+ const format = args[0];
50
+
51
+ if (format === "Hex") {
52
+ input = fromHex(input);
53
+ } else if (format === "Raw") {
54
+ input = Utils.strToArrayBuffer(input);
55
+ } else {
56
+ throw new OperationError("Unrecognised input format.");
57
+ }
58
+
59
+ const s = new Stream(new Uint8Array(input));
60
+ if (s.length < 20) {
61
+ throw new OperationError("Need at least 20 bytes for a TCP Header");
62
+ }
63
+
64
+ // Parse Header
65
+ const TCPPacket = {
66
+ "Source port": s.readInt(2),
67
+ "Destination port": s.readInt(2),
68
+ "Sequence number": bytesToLargeNumber(s.getBytes(4)),
69
+ "Acknowledgement number": s.readInt(4),
70
+ "Data offset": s.readBits(4),
71
+ "Flags": {
72
+ "Reserved": toBinary(s.readBits(3), "", 3),
73
+ "NS": s.readBits(1),
74
+ "CWR": s.readBits(1),
75
+ "ECE": s.readBits(1),
76
+ "URG": s.readBits(1),
77
+ "ACK": s.readBits(1),
78
+ "PSH": s.readBits(1),
79
+ "RST": s.readBits(1),
80
+ "SYN": s.readBits(1),
81
+ "FIN": s.readBits(1),
82
+ },
83
+ "Window size": s.readInt(2),
84
+ "Checksum": "0x" + toHexFast(s.getBytes(2)),
85
+ "Urgent pointer": "0x" + toHexFast(s.getBytes(2))
86
+ };
87
+
88
+ // Parse options if present
89
+ let windowScaleShift = 0;
90
+ if (TCPPacket["Data offset"] > 5) {
91
+ let remainingLength = TCPPacket["Data offset"] * 4 - 20;
92
+
93
+ const options = {};
94
+ while (remainingLength > 0) {
95
+ const option = {
96
+ "Kind": s.readInt(1)
97
+ };
98
+
99
+ let opt = { name: "Reserved", length: true };
100
+ if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
101
+ opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
102
+ }
103
+
104
+ // Add Length and Value fields
105
+ if (opt.length) {
106
+ option.Length = s.readInt(1);
107
+
108
+ if (option.Length > 2) {
109
+ if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
110
+ option.Value = opt.parser(s.getBytes(option.Length - 2));
111
+ } else {
112
+ option.Value = option.Length <= 6 ?
113
+ s.readInt(option.Length - 2):
114
+ "0x" + toHexFast(s.getBytes(option.Length - 2));
115
+ }
116
+
117
+ // Store Window Scale shift for later
118
+ if (option.Kind === 3 && option.Value) {
119
+ windowScaleShift = option.Value["Shift count"];
120
+ }
121
+ }
122
+ }
123
+ options[opt.name] = option;
124
+
125
+ const length = option.Length || 1;
126
+ remainingLength -= length;
127
+ }
128
+ TCPPacket.Options = options;
129
+ }
130
+
131
+ if (s.hasMore()) {
132
+ TCPPacket.Data = "0x" + toHexFast(s.getBytes());
133
+ }
134
+
135
+ // Improve values
136
+ TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
137
+ const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
138
+ TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
139
+
140
+ return TCPPacket;
141
+ }
142
+
143
+ /**
144
+ * Displays the TCP Packet in a tabular style
145
+ * @param {Object} data
146
+ * @returns {html}
147
+ */
148
+ present(data) {
149
+ return objToTable(data);
150
+ }
151
+
152
+ }
153
+
154
+ // Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
155
+ // on 2022-05-30
156
+ const TCP_OPTION_KIND_LOOKUP = {
157
+ 0: { name: "End of Option List", length: false },
158
+ 1: { name: "No-Operation", length: false },
159
+ 2: { name: "Maximum Segment Size", length: true },
160
+ 3: { name: "Window Scale", length: true, parser: windowScaleParser },
161
+ 4: { name: "SACK Permitted", length: true },
162
+ 5: { name: "SACK", length: true },
163
+ 6: { name: "Echo (obsoleted by option 8)", length: true },
164
+ 7: { name: "Echo Reply (obsoleted by option 8)", length: true },
165
+ 8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
166
+ 9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
167
+ 10: { name: "Partial Order Service Profile (obsolete)", length: true },
168
+ 11: { name: "CC (obsolete)", length: true },
169
+ 12: { name: "CC.NEW (obsolete)", length: true },
170
+ 13: { name: "CC.ECHO (obsolete)", length: true },
171
+ 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
172
+ 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
173
+ 16: { name: "Skeeter", length: true },
174
+ 17: { name: "Bubba", length: true },
175
+ 18: { name: "Trailer Checksum Option", length: true },
176
+ 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
177
+ 20: { name: "SCPS Capabilities", length: true },
178
+ 21: { name: "Selective Negative Acknowledgements", length: true },
179
+ 22: { name: "Record Boundaries", length: true },
180
+ 23: { name: "Corruption experienced", length: true },
181
+ 24: { name: "SNAP", length: true },
182
+ 25: { name: "Unassigned (released 2000-12-18)", length: true },
183
+ 26: { name: "TCP Compression Filter", length: true },
184
+ 27: { name: "Quick-Start Response", length: true },
185
+ 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
186
+ 29: { name: "TCP Authentication Option (TCP-AO)", length: true },
187
+ 30: { name: "Multipath TCP (MPTCP)", length: true },
188
+ 69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
189
+ 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
190
+ 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
191
+ 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
192
+ 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
193
+ 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
194
+ 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
195
+ };
196
+
197
+ /**
198
+ * Parses the TCP Alternate Checksum Request field
199
+ * @param {Uint8Array} data
200
+ */
201
+ function tcpAlternateChecksumParser(data) {
202
+ const lookup = {
203
+ 0: "TCP Checksum",
204
+ 1: "8-bit Fletchers's algorithm",
205
+ 2: "16-bit Fletchers's algorithm",
206
+ 3: "Redundant Checksum Avoidance"
207
+ }[data[0]];
208
+
209
+ return `${lookup} (0x${toHexFast(data)})`;
210
+ }
211
+
212
+ /**
213
+ * Parses the TCP Timestamp field
214
+ * @param {Uint8Array} data
215
+ */
216
+ function tcpTimestampParser(data) {
217
+ const s = new Stream(data);
218
+
219
+ if (s.length !== 8)
220
+ return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
221
+
222
+ const tsval = bytesToLargeNumber(s.getBytes(4)),
223
+ tsecr = bytesToLargeNumber(s.getBytes(4));
224
+
225
+ return {
226
+ "Current Timestamp": tsval,
227
+ "Echo Reply": tsecr
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Parses the Window Scale field
233
+ * @param {Uint8Array} data
234
+ */
235
+ function windowScaleParser(data) {
236
+ if (data.length !== 1)
237
+ return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
238
+
239
+ return {
240
+ "Shift count": data[0],
241
+ "Multiplier": 1 << data[0]
242
+ };
243
+ }
244
+
245
+ export default ParseTCP;
@@ -6,7 +6,9 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import Stream from "../lib/Stream.mjs";
9
- import {toHex} from "../lib/Hex.mjs";
9
+ import {toHexFast, fromHex} from "../lib/Hex.mjs";
10
+ import {objToTable} from "../lib/Protocol.mjs";
11
+ import Utils from "../Utils.mjs";
10
12
  import OperationError from "../errors/OperationError.mjs";
11
13
 
12
14
  /**
@@ -24,58 +26,61 @@ class ParseUDP extends Operation {
24
26
  this.module = "Default";
25
27
  this.description = "Parses a UDP header and payload (if present).";
26
28
  this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
27
- this.inputType = "ArrayBuffer";
29
+ this.inputType = "string";
28
30
  this.outputType = "json";
29
31
  this.presentType = "html";
30
- this.args = [];
32
+ this.args = [
33
+ {
34
+ name: "Input format",
35
+ type: "option",
36
+ value: ["Hex", "Raw"]
37
+ }
38
+ ];
31
39
  }
32
40
 
33
41
  /**
34
- * @param {ArrayBuffer} input
42
+ * @param {string} input
43
+ * @param {Object[]} args
35
44
  * @returns {Object}
36
45
  */
37
46
  run(input, args) {
38
- if (input.byteLength < 8) {
39
- throw new OperationError("Need 8 bytes for a UDP Header");
47
+ const format = args[0];
48
+
49
+ if (format === "Hex") {
50
+ input = fromHex(input);
51
+ } else if (format === "Raw") {
52
+ input = Utils.strToArrayBuffer(input);
53
+ } else {
54
+ throw new OperationError("Unrecognised input format.");
40
55
  }
41
56
 
42
57
  const s = new Stream(new Uint8Array(input));
58
+ if (s.length < 8) {
59
+ throw new OperationError("Need 8 bytes for a UDP Header");
60
+ }
61
+
43
62
  // Parse Header
44
63
  const UDPPacket = {
45
64
  "Source port": s.readInt(2),
46
65
  "Destination port": s.readInt(2),
47
66
  "Length": s.readInt(2),
48
- "Checksum": toHex(s.getBytes(2), "")
67
+ "Checksum": "0x" + toHexFast(s.getBytes(2))
49
68
  };
50
69
  // Parse data if present
51
70
  if (s.hasMore()) {
52
- UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
71
+ UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
53
72
  }
54
73
 
55
74
  return UDPPacket;
56
75
  }
57
76
 
58
77
  /**
59
- * Displays the UDP Packet in a table style
78
+ * Displays the UDP Packet in a tabular style
60
79
  * @param {Object} data
61
80
  * @returns {html}
62
81
  */
63
82
  present(data) {
64
- const html = [];
65
- html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
66
- html.push("<tr>");
67
- html.push("<th>Field</th>");
68
- html.push("<th>Value</th>");
69
- html.push("</tr>");
70
-
71
- for (const key in data) {
72
- html.push("<tr>");
73
- html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
74
- html.push("<td>" + data[key] + "</td>");
75
- html.push("</tr>");
76
- }
77
- html.push("</table>");
78
- return html.join("");
83
+ return objToTable(data);
79
84
  }
80
85
 
81
86
  }
@@ -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
  }
@@ -65,6 +65,10 @@ class ToBase45 extends Operation {
65
65
 
66
66
  if (chars < 2) {
67
67
  res.push("0");
68
+ chars++;
69
+ }
70
+ if (pair.length > 1 && chars < 3) {
71
+ res.push("0");
68
72
  }
69
73
  }
70
74
 
@@ -67,7 +67,7 @@ class ToHex extends Operation {
67
67
  * @returns {Object[]} pos
68
68
  */
69
69
  highlight(pos, args) {
70
- let delim, commaLen;
70
+ let delim, commaLen = 0;
71
71
  if (args[0] === "0x with comma") {
72
72
  delim = "0x";
73
73
  commaLen = 1;
@@ -86,7 +86,7 @@ class ToHex extends Operation {
86
86
  pos[0].start = pos[0].start * (2 + len) + countLF(pos[0].start);
87
87
  pos[0].end = pos[0].end * (2 + len) + countLF(pos[0].end);
88
88
 
89
- // if the deliminators are not prepended, trim the trailing deliminator
89
+ // if the delimiters are not prepended, trim the trailing delimiter
90
90
  if (!(delim === "0x" || delim === "\\x")) {
91
91
  pos[0].end -= delim.length;
92
92
  }
@@ -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
  }
@@ -229,6 +229,7 @@ import ParseIPv6Address from "./ParseIPv6Address.mjs";
229
229
  import ParseObjectIDTimestamp from "./ParseObjectIDTimestamp.mjs";
230
230
  import ParseQRCode from "./ParseQRCode.mjs";
231
231
  import ParseSSHHostKey from "./ParseSSHHostKey.mjs";
232
+ import ParseTCP from "./ParseTCP.mjs";
232
233
  import ParseTLV from "./ParseTLV.mjs";
233
234
  import ParseUDP from "./ParseUDP.mjs";
234
235
  import ParseUNIXFilePermissions from "./ParseUNIXFilePermissions.mjs";
@@ -601,6 +602,7 @@ export {
601
602
  ParseObjectIDTimestamp,
602
603
  ParseQRCode,
603
604
  ParseSSHHostKey,
605
+ ParseTCP,
604
606
  ParseTLV,
605
607
  ParseUDP,
606
608
  ParseUNIXFilePermissions,
@@ -230,6 +230,7 @@ import {
230
230
  ParseObjectIDTimestamp as core_ParseObjectIDTimestamp,
231
231
  ParseQRCode as core_ParseQRCode,
232
232
  ParseSSHHostKey as core_ParseSSHHostKey,
233
+ ParseTCP as core_ParseTCP,
233
234
  ParseTLV as core_ParseTLV,
234
235
  ParseUDP as core_ParseUDP,
235
236
  ParseUNIXFilePermissions as core_ParseUNIXFilePermissions,
@@ -602,6 +603,7 @@ function generateChef() {
602
603
  "parseObjectIDTimestamp": _wrap(core_ParseObjectIDTimestamp),
603
604
  "parseQRCode": _wrap(core_ParseQRCode),
604
605
  "parseSSHHostKey": _wrap(core_ParseSSHHostKey),
606
+ "parseTCP": _wrap(core_ParseTCP),
605
607
  "parseTLV": _wrap(core_ParseTLV),
606
608
  "parseUDP": _wrap(core_ParseUDP),
607
609
  "parseUNIXFilePermissions": _wrap(core_ParseUNIXFilePermissions),
@@ -991,6 +993,7 @@ const parseIPv6Address = chef.parseIPv6Address;
991
993
  const parseObjectIDTimestamp = chef.parseObjectIDTimestamp;
992
994
  const parseQRCode = chef.parseQRCode;
993
995
  const parseSSHHostKey = chef.parseSSHHostKey;
996
+ const parseTCP = chef.parseTCP;
994
997
  const parseTLV = chef.parseTLV;
995
998
  const parseUDP = chef.parseUDP;
996
999
  const parseUNIXFilePermissions = chef.parseUNIXFilePermissions;
@@ -1365,6 +1368,7 @@ const operations = [
1365
1368
  parseObjectIDTimestamp,
1366
1369
  parseQRCode,
1367
1370
  parseSSHHostKey,
1371
+ parseTCP,
1368
1372
  parseTLV,
1369
1373
  parseUDP,
1370
1374
  parseUNIXFilePermissions,
@@ -1743,6 +1747,7 @@ export {
1743
1747
  parseObjectIDTimestamp,
1744
1748
  parseQRCode,
1745
1749
  parseSSHHostKey,
1750
+ parseTCP,
1746
1751
  parseTLV,
1747
1752
  parseUDP,
1748
1753
  parseUNIXFilePermissions,