cyberchef 9.37.3 → 9.38.2
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/CHANGELOG.md +7 -2
- package/Gruntfile.js +2 -1
- package/package.json +4 -2
- package/src/core/config/Categories.json +1 -0
- package/src/core/config/OperationConfig.json +30 -2
- package/src/core/config/modules/Default.mjs +2 -0
- package/src/core/config/scripts/newMinorVersion.mjs +144 -0
- package/src/core/lib/Binary.mjs +9 -5
- package/src/core/lib/FileSignatures.mjs +12 -12
- package/src/core/lib/Protocol.mjs +47 -0
- package/src/core/lib/Stream.mjs +22 -12
- package/src/core/operations/ParseTCP.mjs +245 -0
- package/src/core/operations/ParseUDP.mjs +29 -24
- package/src/core/operations/Substitute.mjs +5 -5
- package/src/core/operations/ToBase45.mjs +4 -0
- package/src/core/operations/ToHex.mjs +2 -2
- package/src/core/operations/index.mjs +2 -0
- package/src/node/index.mjs +5 -0
- package/src/web/App.mjs +10 -5
- package/src/web/html/index.html +12 -10
- package/src/web/stylesheets/components/_pane.css +11 -4
- package/src/web/stylesheets/layout/_controls.css +4 -0
- package/src/web/stylesheets/layout/_io.css +1 -2
- package/src/web/stylesheets/layout/_structure.css +2 -2
- package/src/web/waiters/OperationsWaiter.mjs +5 -1
- package/src/web/waiters/OutputWaiter.mjs +2 -0
- package/src/web/waiters/WindowWaiter.mjs +2 -2
- package/tests/operations/index.mjs +1 -0
- package/tests/operations/tests/ParseTCP.mjs +44 -0
- package/tests/operations/tests/ParseUDP.mjs +5 -18
- 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 {
|
|
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 = "
|
|
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 {
|
|
42
|
+
* @param {string} input
|
|
43
|
+
* @param {Object[]} args
|
|
35
44
|
* @returns {Object}
|
|
36
45
|
*/
|
|
37
46
|
run(input, args) {
|
|
38
|
-
|
|
39
|
-
|
|
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":
|
|
67
|
+
"Checksum": "0x" + toHexFast(s.getBytes(2))
|
|
49
68
|
};
|
|
50
69
|
// Parse data if present
|
|
51
70
|
if (s.hasMore()) {
|
|
52
|
-
UDPPacket.Data =
|
|
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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -44,8 +44,8 @@ class Substitute extends Operation {
|
|
|
44
44
|
* @returns {string}
|
|
45
45
|
*/
|
|
46
46
|
run(input, args) {
|
|
47
|
-
const plaintext = Utils.expandAlphRange(args[0])
|
|
48
|
-
ciphertext = Utils.expandAlphRange(args[1])
|
|
47
|
+
const plaintext = Utils.expandAlphRange([...args[0]]),
|
|
48
|
+
ciphertext = Utils.expandAlphRange([...args[1]]);
|
|
49
49
|
let output = "",
|
|
50
50
|
index = -1;
|
|
51
51
|
|
|
@@ -53,9 +53,9 @@ class Substitute extends Operation {
|
|
|
53
53
|
output = "Warning: Plaintext and Ciphertext lengths differ\n\n";
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
for (
|
|
57
|
-
index = plaintext.indexOf(
|
|
58
|
-
output += index > -1 && index < ciphertext.length ? ciphertext[index] :
|
|
56
|
+
for (const character of input) {
|
|
57
|
+
index = plaintext.indexOf(character);
|
|
58
|
+
output += index > -1 && index < ciphertext.length ? ciphertext[index] : character;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return output;
|
|
@@ -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
|
|
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
|
}
|
|
@@ -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,
|
package/src/node/index.mjs
CHANGED
|
@@ -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,
|
package/src/web/App.mjs
CHANGED
|
@@ -57,7 +57,7 @@ class App {
|
|
|
57
57
|
this.populateOperationsList();
|
|
58
58
|
this.manager.setup();
|
|
59
59
|
this.manager.output.saveBombe();
|
|
60
|
-
this.
|
|
60
|
+
this.adjustComponentSizes();
|
|
61
61
|
this.setCompileMessage();
|
|
62
62
|
|
|
63
63
|
log.debug("App loaded");
|
|
@@ -295,9 +295,7 @@ class App {
|
|
|
295
295
|
gutterSize: 4,
|
|
296
296
|
expandToMin: true,
|
|
297
297
|
onDrag: debounce(function() {
|
|
298
|
-
this.
|
|
299
|
-
this.manager.input.calcMaxTabs();
|
|
300
|
-
this.manager.output.calcMaxTabs();
|
|
298
|
+
this.adjustComponentSizes();
|
|
301
299
|
}, 50, "dragSplitter", this, [])
|
|
302
300
|
});
|
|
303
301
|
|
|
@@ -307,7 +305,7 @@ class App {
|
|
|
307
305
|
minSize: minimise ? [0, 0] : [100, 100]
|
|
308
306
|
});
|
|
309
307
|
|
|
310
|
-
this.
|
|
308
|
+
this.adjustComponentSizes();
|
|
311
309
|
}
|
|
312
310
|
|
|
313
311
|
|
|
@@ -581,6 +579,13 @@ class App {
|
|
|
581
579
|
resetLayout() {
|
|
582
580
|
this.columnSplitter.setSizes([20, 30, 50]);
|
|
583
581
|
this.ioSplitter.setSizes([50, 50]);
|
|
582
|
+
this.adjustComponentSizes();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Adjust components to fit their containers.
|
|
587
|
+
*/
|
|
588
|
+
adjustComponentSizes() {
|
|
584
589
|
this.manager.recipe.adjustWidth();
|
|
585
590
|
this.manager.input.calcMaxTabs();
|
|
586
591
|
this.manager.output.calcMaxTabs();
|
package/src/web/html/index.html
CHANGED
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
<div id="recipe" class="split split-horizontal no-select">
|
|
177
177
|
<div class="title no-select">
|
|
178
178
|
Recipe
|
|
179
|
-
<span class="
|
|
179
|
+
<span class="pane-controls hide-on-maximised-output">
|
|
180
180
|
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
|
|
181
181
|
<i class="material-icons">save</i>
|
|
182
182
|
</button>
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
</div>
|
|
191
191
|
<ul id="rec-list" class="list-area no-select"></ul>
|
|
192
192
|
|
|
193
|
-
<div id="controls" class="no-select">
|
|
193
|
+
<div id="controls" class="no-select hide-on-maximised-output">
|
|
194
194
|
<div id="controls-content" class="d-flex align-items-center">
|
|
195
195
|
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
|
|
196
196
|
Step
|
|
@@ -217,7 +217,10 @@
|
|
|
217
217
|
<div id="input" class="split no-select">
|
|
218
218
|
<div class="title no-select">
|
|
219
219
|
<label for="input-text">Input</label>
|
|
220
|
-
<span class="
|
|
220
|
+
<span class="pane-controls">
|
|
221
|
+
<div class="io-info" id="input-files-info"></div>
|
|
222
|
+
<div class="io-info" id="input-selection-info"></div>
|
|
223
|
+
<div class="io-info" id="input-info"></div>
|
|
221
224
|
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
|
|
222
225
|
<i class="material-icons">add</i>
|
|
223
226
|
</button>
|
|
@@ -236,9 +239,7 @@
|
|
|
236
239
|
<i class="material-icons">view_compact</i>
|
|
237
240
|
</button>
|
|
238
241
|
</span>
|
|
239
|
-
|
|
240
|
-
<div class="io-info" id="input-info"></div>
|
|
241
|
-
<div class="io-info" id="input-selection-info"></div>
|
|
242
|
+
|
|
242
243
|
</div>
|
|
243
244
|
<div id="input-tabs-wrapper" style="display: none;" class="no-select">
|
|
244
245
|
<span id="btn-previous-input-tab" class="input-tab-buttons">
|
|
@@ -288,7 +289,10 @@
|
|
|
288
289
|
<div id="output" class="split">
|
|
289
290
|
<div class="title no-select">
|
|
290
291
|
<label for="output-text">Output</label>
|
|
291
|
-
<span class="
|
|
292
|
+
<span class="pane-controls">
|
|
293
|
+
<div class="io-info" id="bake-info"></div>
|
|
294
|
+
<div class="io-info" id="output-selection-info"></div>
|
|
295
|
+
<div class="io-info" id="output-info"></div>
|
|
292
296
|
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
|
|
293
297
|
<i class="material-icons">archive</i>
|
|
294
298
|
</button>
|
|
@@ -308,9 +312,7 @@
|
|
|
308
312
|
<i class="material-icons">fullscreen</i>
|
|
309
313
|
</button>
|
|
310
314
|
</span>
|
|
311
|
-
|
|
312
|
-
<div class="io-info" id="output-info"></div>
|
|
313
|
-
<div class="io-info" id="output-selection-info"></div>
|
|
315
|
+
|
|
314
316
|
<button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
|
|
315
317
|
<svg width="22" height="22" viewBox="0 0 24 24">
|
|
316
318
|
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
|
|
@@ -24,9 +24,16 @@
|
|
|
24
24
|
line-height: calc(var(--title-height) - 14px);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
.pane-controls {
|
|
28
|
+
position: absolute;
|
|
29
|
+
right: 8px;
|
|
30
|
+
top: 8px;
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.pane-controls .btn {
|
|
36
|
+
margin-left: 2px;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
.list-area {
|
|
@@ -107,4 +114,4 @@
|
|
|
107
114
|
|
|
108
115
|
#files .card-header .float-right a:hover {
|
|
109
116
|
text-decoration: none;
|
|
110
|
-
}
|
|
117
|
+
}
|
|
@@ -39,8 +39,8 @@ div#output {
|
|
|
39
39
|
|
|
40
40
|
.split {
|
|
41
41
|
box-sizing: border-box;
|
|
42
|
-
/* overflow: auto;
|
|
43
|
-
Removed to enable Background Magic button pulse to overflow.
|
|
42
|
+
/* overflow: auto; */
|
|
43
|
+
/* Removed to enable Background Magic button pulse to overflow.
|
|
44
44
|
Replace this rule if it seems to be causing problems. */
|
|
45
45
|
position: relative;
|
|
46
46
|
}
|
|
@@ -109,11 +109,15 @@ class OperationsWaiter {
|
|
|
109
109
|
const matchedOps = [];
|
|
110
110
|
const matchedDescs = [];
|
|
111
111
|
|
|
112
|
+
// Create version with no whitespace for the fuzzy match
|
|
113
|
+
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
|
|
114
|
+
const inStrNWS = inStr.replace(/\s/g, "");
|
|
115
|
+
|
|
112
116
|
for (const opName in this.app.operations) {
|
|
113
117
|
const op = this.app.operations[opName];
|
|
114
118
|
|
|
115
119
|
// Match op name using fuzzy match
|
|
116
|
-
const [nameMatch, score, idxs] = fuzzyMatch(
|
|
120
|
+
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
|
|
117
121
|
|
|
118
122
|
// Match description based on exact match
|
|
119
123
|
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
|
|
@@ -1373,6 +1373,7 @@ class OutputWaiter {
|
|
|
1373
1373
|
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
|
|
1374
1374
|
|
|
1375
1375
|
if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
|
|
1376
|
+
document.body.classList.add("output-maximised");
|
|
1376
1377
|
this.app.initialiseSplitter(true);
|
|
1377
1378
|
this.app.columnSplitter.collapse(0);
|
|
1378
1379
|
this.app.columnSplitter.collapse(1);
|
|
@@ -1381,6 +1382,7 @@ class OutputWaiter {
|
|
|
1381
1382
|
$(el).attr("data-original-title", "Restore output pane");
|
|
1382
1383
|
el.querySelector("i").innerHTML = "fullscreen_exit";
|
|
1383
1384
|
} else {
|
|
1385
|
+
document.body.classList.remove("output-maximised");
|
|
1384
1386
|
$(el).attr("data-original-title", "Maximise output pane");
|
|
1385
1387
|
el.querySelector("i").innerHTML = "fullscreen";
|
|
1386
1388
|
this.app.initialiseSplitter(false);
|
|
@@ -23,11 +23,11 @@ class WindowWaiter {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Handler for window resize events.
|
|
26
|
-
* Resets
|
|
26
|
+
* Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
|
|
27
27
|
* continuous resetting).
|
|
28
28
|
*/
|
|
29
29
|
windowResize() {
|
|
30
|
-
debounce(this.app.
|
|
30
|
+
debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
|