packetsnitch 1.5.599
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/.eslintrc.json +28 -0
- package/.webpack/x64/main/index.js +2 -0
- package/.webpack/x64/main/index.js.map +1 -0
- package/.webpack/x64/renderer/assets/css/rubikglitch.woff2 +0 -0
- package/.webpack/x64/renderer/assets/css/style.css +1916 -0
- package/.webpack/x64/renderer/assets/images/loading.gif +0 -0
- package/.webpack/x64/renderer/assets/images/logo.webp +0 -0
- package/.webpack/x64/renderer/assets/images/packet-snitch-tag.webp +0 -0
- package/.webpack/x64/renderer/main_window/index.html +3 -0
- package/.webpack/x64/renderer/main_window/index.js +3 -0
- package/.webpack/x64/renderer/main_window/index.js.LICENSE.txt +36 -0
- package/.webpack/x64/renderer/main_window/index.js.map +1 -0
- package/.webpack/x64/renderer/main_window/preload.js +2 -0
- package/.webpack/x64/renderer/main_window/preload.js.map +1 -0
- package/backend/common/GeoLite2-City.mmdb +0 -0
- package/backend/common/mac-vendors-export.csv +56923 -0
- package/backend/common/service-names-port-numbers.csv +15368 -0
- package/backend/requirements.txt +14 -0
- package/backend/snitch.py +3611 -0
- package/forge.config.js +80 -0
- package/package.json +102 -0
- package/ps-icon.ico +0 -0
- package/snitch.spec +44 -0
- package/src/assets/css/rubikglitch.woff2 +0 -0
- package/src/assets/css/style.css +1916 -0
- package/src/assets/images/loading.gif +0 -0
- package/src/assets/images/logo.webp +0 -0
- package/src/assets/images/packet-snitch-tag.webp +0 -0
- package/src/back-comm.js +70 -0
- package/src/decoders.js +579 -0
- package/src/filter.js +461 -0
- package/src/front.js +10 -0
- package/src/index.html +1036 -0
- package/src/logging.js +150 -0
- package/src/main.js +571 -0
- package/src/preload.js +73 -0
- package/src/renderer.js +30 -0
- package/src/ui/common-frontend.js +13 -0
- package/src/ui/context-menu.js +88 -0
- package/src/ui/decoders.js +1 -0
- package/src/ui/main-frontend.js +4957 -0
- package/src/ui/panels/crypt-panel.js +565 -0
- package/src/ui/panels/data-panel.js +151 -0
- package/src/ui/panels/data-tools-panel.js +939 -0
- package/src/ui/panels/install-screen.js +59 -0
- package/src/ui/panels/keystore-panel.js +1248 -0
- package/src/ui/panels/list-panel.js +403 -0
- package/src/ui/panels/stats-panel.js +351 -0
- package/src/ui/panels/summary-panel.js +63 -0
- package/webpack.main.config.js +11 -0
- package/webpack.plugins.js +13 -0
- package/webpack.preload.config.js +7 -0
- package/webpack.renderer.config.js +30 -0
- package/webpack.rules.js +35 -0
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
const CryptoJS = require("crypto-js");
|
|
2
|
+
const { sha3_256, sha3_512 } = require("js-sha3");
|
|
3
|
+
const whirlpool = require("whirlpool-js");
|
|
4
|
+
|
|
5
|
+
// ── Conv tab constants ────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const DATA_TOOLS_TEXT_MIME_PRINTABLE_THRESHOLD = 0.9;
|
|
8
|
+
const DATA_TOOLS_ENTROPY_HIGH_THRESHOLD = 6.8;
|
|
9
|
+
const DATA_TOOLS_ENTROPY_MEDIUM_THRESHOLD = 4.5;
|
|
10
|
+
const DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES = 4096;
|
|
11
|
+
const DATA_TOOLS_CONTEXT_BASE64_MIN_LENGTH = 12;
|
|
12
|
+
const DATA_TOOLS_TEXT_ENCODER = new TextEncoder();
|
|
13
|
+
const DATA_TOOLS_HEX_BYTE_RE = /^[0-9a-fA-F]{2}$/;
|
|
14
|
+
|
|
15
|
+
const CONV_CONVERSIONS_SUBTAB = "conversions";
|
|
16
|
+
const CONV_HASHES_SUBTAB = "hashes";
|
|
17
|
+
const CONV_DECODES_SUBTAB = "decodes";
|
|
18
|
+
|
|
19
|
+
const VALID_CONV_SUBTABS = [
|
|
20
|
+
CONV_CONVERSIONS_SUBTAB,
|
|
21
|
+
CONV_HASHES_SUBTAB,
|
|
22
|
+
CONV_DECODES_SUBTAB,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const HASH_IDS = [
|
|
26
|
+
"data-tools-md5-output",
|
|
27
|
+
"data-tools-sha1-output",
|
|
28
|
+
"data-tools-sha256-output",
|
|
29
|
+
"data-tools-sha384-output",
|
|
30
|
+
"data-tools-sha512-output",
|
|
31
|
+
"data-tools-sha3-256-output",
|
|
32
|
+
"data-tools-sha3-512-output",
|
|
33
|
+
"data-tools-ripemd160-output",
|
|
34
|
+
"data-tools-whirlpool-output",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// ── Conv tab state ────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
let activeConvSubtab = CONV_CONVERSIONS_SUBTAB;
|
|
40
|
+
let activeDataToolsProtoResult = null;
|
|
41
|
+
|
|
42
|
+
// ── Injected dependencies (set via initConvPanel) ─────────────────────────────
|
|
43
|
+
|
|
44
|
+
let _writeLogEntry = () => {};
|
|
45
|
+
let _statusUpdate = () => {};
|
|
46
|
+
let _setActiveMainTab = () => {};
|
|
47
|
+
|
|
48
|
+
function initConvPanel({ writeLogEntry, statusUpdate, setActiveMainTab }) {
|
|
49
|
+
_writeLogEntry = writeLogEntry;
|
|
50
|
+
_statusUpdate = statusUpdate;
|
|
51
|
+
_setActiveMainTab = setActiveMainTab;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── State accessors ───────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
function getActiveConvSubtab() {
|
|
57
|
+
return activeConvSubtab;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getActiveDataToolsProtoResult() {
|
|
61
|
+
return activeDataToolsProtoResult;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Input parsing ─────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
function parseDataToolsInput(format, rawInput) {
|
|
67
|
+
if (!rawInput || rawInput.trim() === "") {
|
|
68
|
+
throw new Error("Enter input data first.");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (format === "hex") {
|
|
72
|
+
const normalized = rawInput
|
|
73
|
+
.replace(/0x/gi, "")
|
|
74
|
+
.replace(/[\s,:;-]+/g, "")
|
|
75
|
+
.trim();
|
|
76
|
+
if (!normalized) throw new Error("No hex bytes were found.");
|
|
77
|
+
if (!/^[0-9a-fA-F]+$/.test(normalized)) {
|
|
78
|
+
throw new Error("Hex input can only contain 0-9 and A-F.");
|
|
79
|
+
}
|
|
80
|
+
if (normalized.length % 2 !== 0) {
|
|
81
|
+
throw new Error("Hex input must contain an even number of characters.");
|
|
82
|
+
}
|
|
83
|
+
const bytes = new Uint8Array(normalized.length / 2);
|
|
84
|
+
for (let i = 0; i < normalized.length; i += 2) {
|
|
85
|
+
bytes[i / 2] = parseInt(normalized.slice(i, i + 2), 16);
|
|
86
|
+
}
|
|
87
|
+
return bytes;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (format === "binary") {
|
|
91
|
+
const normalized = rawInput.replace(/\s+/g, "");
|
|
92
|
+
if (!normalized) throw new Error("No binary bits were found.");
|
|
93
|
+
if (!/^[01]+$/.test(normalized)) {
|
|
94
|
+
throw new Error("Binary input can only contain 0 and 1.");
|
|
95
|
+
}
|
|
96
|
+
if (normalized.length % 8 !== 0) {
|
|
97
|
+
throw new Error("Binary input must be grouped into full 8-bit bytes.");
|
|
98
|
+
}
|
|
99
|
+
const bytes = new Uint8Array(normalized.length / 8);
|
|
100
|
+
for (let i = 0; i < normalized.length; i += 8) {
|
|
101
|
+
bytes[i / 8] = parseInt(normalized.slice(i, i + 8), 2);
|
|
102
|
+
}
|
|
103
|
+
return bytes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (format === "base64") {
|
|
107
|
+
const normalized = rawInput
|
|
108
|
+
.trim()
|
|
109
|
+
.replace(/^data:[^;]+;base64,/i, "")
|
|
110
|
+
.replace(/\s+/g, "");
|
|
111
|
+
if (!normalized) throw new Error("No base64 content was found.");
|
|
112
|
+
let decoded = "";
|
|
113
|
+
try {
|
|
114
|
+
decoded = atob(normalized);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error("Invalid base64 input.");
|
|
117
|
+
}
|
|
118
|
+
const bytes = new Uint8Array(decoded.length);
|
|
119
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
120
|
+
bytes[i] = decoded.charCodeAt(i);
|
|
121
|
+
}
|
|
122
|
+
return bytes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (format === "decimal") {
|
|
126
|
+
const tokens = rawInput.split(/[\s,]+/).filter(Boolean);
|
|
127
|
+
if (!tokens.length) throw new Error("No decimal byte values were found.");
|
|
128
|
+
const values = tokens.map((token) => {
|
|
129
|
+
const parsed = Number(token);
|
|
130
|
+
if (!/^\d+$/.test(token) || parsed > 255) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Each decimal value must be a non-negative integer between 0 and 255.",
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return parsed;
|
|
136
|
+
});
|
|
137
|
+
return Uint8Array.from(values);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ascii / utf-8 fallback
|
|
141
|
+
return new TextEncoder().encode(rawInput);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function bytesToBase64(bytes) {
|
|
145
|
+
let binary = "";
|
|
146
|
+
bytes.forEach((byte) => {
|
|
147
|
+
binary += String.fromCharCode(byte);
|
|
148
|
+
});
|
|
149
|
+
return btoa(binary);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function bytesToPrintableAscii(bytes) {
|
|
153
|
+
return [...bytes]
|
|
154
|
+
.map((byte) =>
|
|
155
|
+
byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".",
|
|
156
|
+
)
|
|
157
|
+
.join("");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function bytesToBigIntDecimal(bytes) {
|
|
161
|
+
let total = 0n;
|
|
162
|
+
bytes.forEach((byte) => {
|
|
163
|
+
total = (total << 8n) + BigInt(byte);
|
|
164
|
+
});
|
|
165
|
+
return total.toString(10);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function calculateShannonEntropy(bytes) {
|
|
169
|
+
if (!bytes.length) return 0;
|
|
170
|
+
const counts = new Array(256).fill(0);
|
|
171
|
+
bytes.forEach((byte) => {
|
|
172
|
+
counts[byte] += 1;
|
|
173
|
+
});
|
|
174
|
+
let entropy = 0;
|
|
175
|
+
counts.forEach((count) => {
|
|
176
|
+
if (!count) return;
|
|
177
|
+
const p = count / bytes.length;
|
|
178
|
+
entropy -= p * Math.log2(p);
|
|
179
|
+
});
|
|
180
|
+
return entropy;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function inferMimeType(bytes) {
|
|
184
|
+
if (!bytes || !bytes.length) return "application/octet-stream";
|
|
185
|
+
|
|
186
|
+
const startsWith = (signature) =>
|
|
187
|
+
signature.every((value, index) => bytes[index] === value);
|
|
188
|
+
if (startsWith([0x89, 0x50, 0x4e, 0x47])) return "image/png";
|
|
189
|
+
if (startsWith([0xff, 0xd8, 0xff])) return "image/jpeg";
|
|
190
|
+
if (startsWith([0x47, 0x49, 0x46, 0x38])) return "image/gif";
|
|
191
|
+
if (startsWith([0x25, 0x50, 0x44, 0x46])) return "application/pdf";
|
|
192
|
+
if (startsWith([0x50, 0x4b, 0x03, 0x04])) return "application/zip";
|
|
193
|
+
if (startsWith([0x1f, 0x8b])) return "application/gzip";
|
|
194
|
+
if (startsWith([0x7f, 0x45, 0x4c, 0x46])) return "application/x-elf";
|
|
195
|
+
|
|
196
|
+
const utf8Text = new TextDecoder().decode(bytes);
|
|
197
|
+
const trimmed = utf8Text.trim();
|
|
198
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
199
|
+
try {
|
|
200
|
+
JSON.parse(trimmed);
|
|
201
|
+
return "application/json";
|
|
202
|
+
} catch {
|
|
203
|
+
// Keep evaluating as plain text/binary.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const printableChars = [...utf8Text].filter((ch) => {
|
|
208
|
+
const code = ch.charCodeAt(0);
|
|
209
|
+
return (
|
|
210
|
+
(code >= 32 && code <= 126) || ch === "\n" || ch === "\r" || ch === "\t"
|
|
211
|
+
);
|
|
212
|
+
}).length;
|
|
213
|
+
if (
|
|
214
|
+
utf8Text.length > 0 &&
|
|
215
|
+
printableChars / utf8Text.length > DATA_TOOLS_TEXT_MIME_PRINTABLE_THRESHOLD
|
|
216
|
+
) {
|
|
217
|
+
return "text/plain; charset=utf-8";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return "application/octet-stream";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getEntropyLabel(entropy) {
|
|
224
|
+
if (entropy >= DATA_TOOLS_ENTROPY_HIGH_THRESHOLD) return "High";
|
|
225
|
+
if (entropy >= DATA_TOOLS_ENTROPY_MEDIUM_THRESHOLD) return "Medium";
|
|
226
|
+
return "Low";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Hash outputs ──────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
function resetHashOutputs() {
|
|
232
|
+
document.getElementById("data-tools-hash-input-reading").value = "";
|
|
233
|
+
for (const id of HASH_IDS) {
|
|
234
|
+
document.getElementById(id).value = "";
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function bytesToCharString(bytes) {
|
|
239
|
+
const CHUNK_SIZE = 0x8000;
|
|
240
|
+
let result = "";
|
|
241
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
242
|
+
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
|
|
243
|
+
result += String.fromCharCode(...chunk);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function formatHashInputReading(bytes) {
|
|
249
|
+
return [...bytes]
|
|
250
|
+
.map((byte) => {
|
|
251
|
+
if (byte === 0x5c) return "\\\\";
|
|
252
|
+
if (byte === 0x0a) return "\\n";
|
|
253
|
+
if (byte === 0x0d) return "\\r";
|
|
254
|
+
if (byte === 0x09) return "\\t";
|
|
255
|
+
if (byte >= 0x20 && byte <= 0x7e) return String.fromCharCode(byte);
|
|
256
|
+
return `\\x${byte.toString(16).padStart(2, "0").toUpperCase()}`;
|
|
257
|
+
})
|
|
258
|
+
.join("");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setHashInputReadingFromBytes(bytes) {
|
|
262
|
+
document.getElementById("data-tools-hash-input-reading").value =
|
|
263
|
+
formatHashInputReading(bytes);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function computeDataToolsHashes(bytes) {
|
|
267
|
+
const wordArray = CryptoJS.lib.WordArray.create(bytes);
|
|
268
|
+
const byteString = bytesToCharString(bytes);
|
|
269
|
+
|
|
270
|
+
document.getElementById("data-tools-md5-output").value =
|
|
271
|
+
CryptoJS.MD5(wordArray).toString(CryptoJS.enc.Hex);
|
|
272
|
+
document.getElementById("data-tools-sha1-output").value =
|
|
273
|
+
CryptoJS.SHA1(wordArray).toString(CryptoJS.enc.Hex);
|
|
274
|
+
document.getElementById("data-tools-sha256-output").value =
|
|
275
|
+
CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Hex);
|
|
276
|
+
document.getElementById("data-tools-sha384-output").value =
|
|
277
|
+
CryptoJS.SHA384(wordArray).toString(CryptoJS.enc.Hex);
|
|
278
|
+
document.getElementById("data-tools-sha512-output").value =
|
|
279
|
+
CryptoJS.SHA512(wordArray).toString(CryptoJS.enc.Hex);
|
|
280
|
+
document.getElementById("data-tools-sha3-256-output").value = sha3_256(bytes);
|
|
281
|
+
document.getElementById("data-tools-sha3-512-output").value = sha3_512(bytes);
|
|
282
|
+
document.getElementById("data-tools-ripemd160-output").value =
|
|
283
|
+
CryptoJS.RIPEMD160(wordArray).toString(CryptoJS.enc.Hex);
|
|
284
|
+
const whirlpoolHash =
|
|
285
|
+
bytes.length > 0 ? whirlpool.encSync(byteString, "hex") : "";
|
|
286
|
+
document.getElementById("data-tools-whirlpool-output").value = whirlpoolHash;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function runDataToolsHashesFromInput() {
|
|
290
|
+
const hashInput = document.getElementById("data-tools-hash-input-reading").value;
|
|
291
|
+
const bytes = parseHashInputReadingBytes(hashInput);
|
|
292
|
+
computeDataToolsHashes(bytes);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function parseHashInputReadingBytes(input) {
|
|
296
|
+
const bytes = [];
|
|
297
|
+
let plainStart = 0;
|
|
298
|
+
const flushPlain = (end) => {
|
|
299
|
+
if (end <= plainStart) return;
|
|
300
|
+
bytes.push(...DATA_TOOLS_TEXT_ENCODER.encode(input.slice(plainStart, end)));
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
for (let i = 0; i < input.length; i++) {
|
|
304
|
+
if (input[i] !== "\\") continue;
|
|
305
|
+
flushPlain(i);
|
|
306
|
+
const next = input[i + 1];
|
|
307
|
+
if (next === "n") {
|
|
308
|
+
bytes.push(0x0a);
|
|
309
|
+
i += 1;
|
|
310
|
+
} else if (next === "r") {
|
|
311
|
+
bytes.push(0x0d);
|
|
312
|
+
i += 1;
|
|
313
|
+
} else if (next === "t") {
|
|
314
|
+
bytes.push(0x09);
|
|
315
|
+
i += 1;
|
|
316
|
+
} else if (next === "\\") {
|
|
317
|
+
bytes.push(0x5c);
|
|
318
|
+
i += 1;
|
|
319
|
+
} else if (
|
|
320
|
+
next === "x" &&
|
|
321
|
+
i + 3 < input.length &&
|
|
322
|
+
DATA_TOOLS_HEX_BYTE_RE.test(input.slice(i + 2, i + 4))
|
|
323
|
+
) {
|
|
324
|
+
bytes.push(parseInt(input.slice(i + 2, i + 4), 16));
|
|
325
|
+
i += 3;
|
|
326
|
+
} else {
|
|
327
|
+
bytes.push(0x5c);
|
|
328
|
+
}
|
|
329
|
+
plainStart = i + 1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
flushPlain(input.length);
|
|
333
|
+
return new Uint8Array(bytes);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Conversions panel ─────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
function resetDataToolsOutputs() {
|
|
339
|
+
document.getElementById("data-tools-hex-output").value = "";
|
|
340
|
+
document.getElementById("data-tools-binary-output").value = "";
|
|
341
|
+
document.getElementById("data-tools-decimal-output").value = "";
|
|
342
|
+
document.getElementById("data-tools-decimal-integer-output").value = "";
|
|
343
|
+
document.getElementById("data-tools-ascii-output").value = "";
|
|
344
|
+
document.getElementById("data-tools-base64-output").value = "";
|
|
345
|
+
document.getElementById("data-tools-byte-length").textContent =
|
|
346
|
+
"Byte Length: 0";
|
|
347
|
+
document.getElementById("data-tools-mime-type").textContent =
|
|
348
|
+
"MIME Type: Unknown";
|
|
349
|
+
document.getElementById("data-tools-entropy").textContent =
|
|
350
|
+
"Shannon Entropy: 0.00 (Low)";
|
|
351
|
+
resetHashOutputs();
|
|
352
|
+
clearProtoDecoderOutput();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function runDataToolsConversion() {
|
|
356
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
357
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
358
|
+
const errorEl = document.getElementById("data-tools-error");
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const bytes = parseDataToolsInput(formatEl.value, inputEl.value);
|
|
362
|
+
const hexSpaced = [...bytes]
|
|
363
|
+
.map((byte) => byte.toString(16).padStart(2, "0").toUpperCase())
|
|
364
|
+
.join(" ");
|
|
365
|
+
const binarySpaced = [...bytes]
|
|
366
|
+
.map((byte) => byte.toString(2).padStart(8, "0"))
|
|
367
|
+
.join(" ");
|
|
368
|
+
const decimalBytes = [...bytes].join(" ");
|
|
369
|
+
const asciiPreview = bytesToPrintableAscii(bytes);
|
|
370
|
+
const base64Value = bytesToBase64(bytes);
|
|
371
|
+
const entropy = calculateShannonEntropy(bytes);
|
|
372
|
+
const entropyLabel = getEntropyLabel(entropy);
|
|
373
|
+
const decimalInteger =
|
|
374
|
+
bytes.length > DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES
|
|
375
|
+
? `Input exceeds ${DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES} bytes for decimal integer display`
|
|
376
|
+
: bytesToBigIntDecimal(bytes);
|
|
377
|
+
|
|
378
|
+
document.getElementById("data-tools-hex-output").value = hexSpaced;
|
|
379
|
+
document.getElementById("data-tools-binary-output").value = binarySpaced;
|
|
380
|
+
document.getElementById("data-tools-decimal-output").value = decimalBytes;
|
|
381
|
+
document.getElementById("data-tools-decimal-integer-output").value =
|
|
382
|
+
decimalInteger;
|
|
383
|
+
document.getElementById("data-tools-ascii-output").value = asciiPreview;
|
|
384
|
+
document.getElementById("data-tools-base64-output").value = base64Value;
|
|
385
|
+
document.getElementById("data-tools-byte-length").textContent =
|
|
386
|
+
`Byte Length: ${bytes.length}`;
|
|
387
|
+
document.getElementById("data-tools-mime-type").textContent =
|
|
388
|
+
`MIME Type: ${inferMimeType(bytes)}`;
|
|
389
|
+
document.getElementById("data-tools-entropy").textContent =
|
|
390
|
+
`Shannon Entropy: ${entropy.toFixed(2)} (${entropyLabel})`;
|
|
391
|
+
errorEl.textContent = "";
|
|
392
|
+
setHashInputReadingFromBytes(bytes);
|
|
393
|
+
computeDataToolsHashes(bytes);
|
|
394
|
+
runProtoDecoder(bytes);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
resetDataToolsOutputs();
|
|
397
|
+
errorEl.textContent =
|
|
398
|
+
error && typeof error === "object" && "message" in error
|
|
399
|
+
? error.message
|
|
400
|
+
: String(error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ── Protocol decoders for the Conv tab ───────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
function decodeHttpFromBytes(bytes) {
|
|
407
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
408
|
+
const lines = text.split(/\r?\n/);
|
|
409
|
+
if (!lines.length) return null;
|
|
410
|
+
const firstLine = lines[0].trim();
|
|
411
|
+
const requestMatch = firstLine.match(
|
|
412
|
+
/^([A-Z]+)\s+(\S+)\s+(HTTP\/[\d.]+)$/,
|
|
413
|
+
);
|
|
414
|
+
const responseMatch = firstLine.match(/^(HTTP\/[\d.]+)\s+(\d{3})\s*(.*)/);
|
|
415
|
+
if (!requestMatch && !responseMatch) return null;
|
|
416
|
+
|
|
417
|
+
const emptyLineIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "");
|
|
418
|
+
const headerLines = lines.slice(
|
|
419
|
+
1,
|
|
420
|
+
emptyLineIdx > 0 ? emptyLineIdx : lines.length,
|
|
421
|
+
);
|
|
422
|
+
const headers = {};
|
|
423
|
+
headerLines.forEach((hl) => {
|
|
424
|
+
const idx = hl.indexOf(":");
|
|
425
|
+
if (idx > 0) {
|
|
426
|
+
headers[hl.slice(0, idx).trim()] = hl.slice(idx + 1).trim();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const fields = [];
|
|
431
|
+
if (requestMatch) {
|
|
432
|
+
fields.push(
|
|
433
|
+
{ name: "Type", value: "Request" },
|
|
434
|
+
{ name: "Method", value: requestMatch[1] },
|
|
435
|
+
{ name: "URL", value: requestMatch[2] },
|
|
436
|
+
{ name: "Version", value: requestMatch[3] },
|
|
437
|
+
);
|
|
438
|
+
[
|
|
439
|
+
"Host",
|
|
440
|
+
"User-Agent",
|
|
441
|
+
"Content-Type",
|
|
442
|
+
"Content-Length",
|
|
443
|
+
"Accept",
|
|
444
|
+
"Accept-Encoding",
|
|
445
|
+
"Connection",
|
|
446
|
+
"Authorization",
|
|
447
|
+
"Referer",
|
|
448
|
+
"Cookie",
|
|
449
|
+
].forEach((h) => {
|
|
450
|
+
if (headers[h]) fields.push({ name: h, value: headers[h] });
|
|
451
|
+
});
|
|
452
|
+
} else {
|
|
453
|
+
fields.push(
|
|
454
|
+
{ name: "Type", value: "Response" },
|
|
455
|
+
{ name: "Version", value: responseMatch[1] },
|
|
456
|
+
{ name: "Status Code", value: responseMatch[2] },
|
|
457
|
+
{ name: "Status Message", value: responseMatch[3] || "—" },
|
|
458
|
+
);
|
|
459
|
+
[
|
|
460
|
+
"Server",
|
|
461
|
+
"Content-Type",
|
|
462
|
+
"Content-Length",
|
|
463
|
+
"Content-Encoding",
|
|
464
|
+
"Transfer-Encoding",
|
|
465
|
+
"Connection",
|
|
466
|
+
"Location",
|
|
467
|
+
"Set-Cookie",
|
|
468
|
+
"Cache-Control",
|
|
469
|
+
"Date",
|
|
470
|
+
].forEach((h) => {
|
|
471
|
+
if (headers[h]) fields.push({ name: h, value: headers[h] });
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (emptyLineIdx > 0 && emptyLineIdx < lines.length - 1) {
|
|
475
|
+
const body = lines
|
|
476
|
+
.slice(emptyLineIdx + 1)
|
|
477
|
+
.join("\n")
|
|
478
|
+
.trim();
|
|
479
|
+
if (body) {
|
|
480
|
+
fields.push({
|
|
481
|
+
name: "Body (preview)",
|
|
482
|
+
value: body.length > 200 ? body.slice(0, 200) + "…" : body,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return { protocol: "HTTP", fields };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function decodeTelnetFromBytes(bytes) {
|
|
490
|
+
const IAC = 0xff;
|
|
491
|
+
const WILL = 0xfb,
|
|
492
|
+
WONT = 0xfc,
|
|
493
|
+
DO = 0xfd,
|
|
494
|
+
DONT = 0xfe;
|
|
495
|
+
const SB = 0xfa,
|
|
496
|
+
SE = 0xf0;
|
|
497
|
+
const optionNames = {
|
|
498
|
+
0: "Binary",
|
|
499
|
+
1: "Echo",
|
|
500
|
+
3: "Suppress Go Ahead",
|
|
501
|
+
5: "Status",
|
|
502
|
+
24: "Terminal Type",
|
|
503
|
+
31: "Window Size",
|
|
504
|
+
32: "Terminal Speed",
|
|
505
|
+
34: "Linemode",
|
|
506
|
+
39: "New Environment",
|
|
507
|
+
};
|
|
508
|
+
const negotiations = [];
|
|
509
|
+
let text = "";
|
|
510
|
+
let i = 0;
|
|
511
|
+
let hasIac = false;
|
|
512
|
+
while (i < bytes.length) {
|
|
513
|
+
if (bytes[i] === IAC) {
|
|
514
|
+
hasIac = true;
|
|
515
|
+
i++;
|
|
516
|
+
if (i >= bytes.length) break;
|
|
517
|
+
const cmd = bytes[i++];
|
|
518
|
+
if (cmd === WILL || cmd === WONT || cmd === DO || cmd === DONT) {
|
|
519
|
+
if (i < bytes.length) {
|
|
520
|
+
const opt = bytes[i++];
|
|
521
|
+
const cmdName =
|
|
522
|
+
cmd === WILL
|
|
523
|
+
? "WILL"
|
|
524
|
+
: cmd === WONT
|
|
525
|
+
? "WONT"
|
|
526
|
+
: cmd === DO
|
|
527
|
+
? "DO"
|
|
528
|
+
: "DONT";
|
|
529
|
+
negotiations.push(`${cmdName} ${optionNames[opt] ?? `Option ${opt}`}`);
|
|
530
|
+
}
|
|
531
|
+
} else if (cmd === SB) {
|
|
532
|
+
while (i < bytes.length) {
|
|
533
|
+
if (bytes[i] === IAC && i + 1 < bytes.length && bytes[i + 1] === SE) {
|
|
534
|
+
i += 2;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
i++;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const b = bytes[i++];
|
|
542
|
+
if (b >= 32 && b < 127) text += String.fromCharCode(b);
|
|
543
|
+
else if (b === 10) text += "\n";
|
|
544
|
+
else if (b === 13) text += "\r";
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (!hasIac && !text.trim()) return null;
|
|
548
|
+
const fields = [];
|
|
549
|
+
if (negotiations.length) {
|
|
550
|
+
fields.push({ name: "Negotiations", value: negotiations.join(", ") });
|
|
551
|
+
}
|
|
552
|
+
if (text.trim()) {
|
|
553
|
+
const t = text.trim();
|
|
554
|
+
fields.push({
|
|
555
|
+
name: "Text",
|
|
556
|
+
value: t.length > 500 ? t.slice(0, 500) + "…" : t,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
if (!fields.length) return null;
|
|
560
|
+
return { protocol: "Telnet", fields };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function decodeSshFromBytes(bytes) {
|
|
564
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(
|
|
565
|
+
bytes.slice(0, 512),
|
|
566
|
+
);
|
|
567
|
+
const bannerMatch = text.match(/^SSH-([\S]+)\r?\n/);
|
|
568
|
+
if (!bannerMatch) return null;
|
|
569
|
+
const versionStr = bannerMatch[1];
|
|
570
|
+
const dashIdx = versionStr.indexOf("-");
|
|
571
|
+
const protocolVersion =
|
|
572
|
+
dashIdx >= 0 ? versionStr.slice(0, dashIdx) : versionStr;
|
|
573
|
+
const softwareVersion = dashIdx >= 0 ? versionStr.slice(dashIdx + 1) : "—";
|
|
574
|
+
const fields = [
|
|
575
|
+
{ name: "Protocol Version", value: protocolVersion },
|
|
576
|
+
{ name: "Software Version", value: softwareVersion },
|
|
577
|
+
];
|
|
578
|
+
const bannerEnd = text.indexOf("\n");
|
|
579
|
+
if (bannerEnd > 0 && bytes.length > bannerEnd + 1) {
|
|
580
|
+
fields.push({
|
|
581
|
+
name: "Additional Data",
|
|
582
|
+
value: `${bytes.length - bannerEnd - 1} bytes (key exchange)`,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return { protocol: "SSH / OpenSSH", fields };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function decodePop3FromBytes(bytes) {
|
|
589
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
590
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
591
|
+
if (!lines.length) return null;
|
|
592
|
+
const POP3_COMMANDS = new Set([
|
|
593
|
+
"USER",
|
|
594
|
+
"PASS",
|
|
595
|
+
"STAT",
|
|
596
|
+
"LIST",
|
|
597
|
+
"RETR",
|
|
598
|
+
"DELE",
|
|
599
|
+
"NOOP",
|
|
600
|
+
"RSET",
|
|
601
|
+
"QUIT",
|
|
602
|
+
"APOP",
|
|
603
|
+
"TOP",
|
|
604
|
+
"UIDL",
|
|
605
|
+
]);
|
|
606
|
+
const fields = [];
|
|
607
|
+
let detected = false;
|
|
608
|
+
for (const line of lines) {
|
|
609
|
+
if (line.startsWith("+OK")) {
|
|
610
|
+
fields.push({ name: "Response", value: "+OK" });
|
|
611
|
+
const msg = line.slice(3).trim();
|
|
612
|
+
if (msg) fields.push({ name: "Message", value: msg });
|
|
613
|
+
detected = true;
|
|
614
|
+
} else if (line.startsWith("-ERR")) {
|
|
615
|
+
fields.push({ name: "Response", value: "-ERR" });
|
|
616
|
+
const msg = line.slice(4).trim();
|
|
617
|
+
if (msg) fields.push({ name: "Error", value: msg });
|
|
618
|
+
detected = true;
|
|
619
|
+
} else {
|
|
620
|
+
const parts = line.split(/\s+/);
|
|
621
|
+
const cmd = parts[0].toUpperCase();
|
|
622
|
+
if (POP3_COMMANDS.has(cmd)) {
|
|
623
|
+
fields.push({ name: "Command", value: cmd });
|
|
624
|
+
if (parts.length > 1) {
|
|
625
|
+
fields.push({ name: "Argument", value: parts.slice(1).join(" ") });
|
|
626
|
+
}
|
|
627
|
+
detected = true;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (fields.length >= 10) break;
|
|
631
|
+
}
|
|
632
|
+
if (!detected) return null;
|
|
633
|
+
return { protocol: "POP3", fields };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function decodeImapFromBytes(bytes) {
|
|
637
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
638
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
639
|
+
if (!lines.length) return null;
|
|
640
|
+
const IMAP_STATUSES = new Set(["OK", "NO", "BAD", "PREAUTH", "BYE"]);
|
|
641
|
+
const IMAP_COMMANDS = new Set([
|
|
642
|
+
"CAPABILITY",
|
|
643
|
+
"NOOP",
|
|
644
|
+
"LOGOUT",
|
|
645
|
+
"AUTHENTICATE",
|
|
646
|
+
"LOGIN",
|
|
647
|
+
"SELECT",
|
|
648
|
+
"EXAMINE",
|
|
649
|
+
"CREATE",
|
|
650
|
+
"DELETE",
|
|
651
|
+
"RENAME",
|
|
652
|
+
"SUBSCRIBE",
|
|
653
|
+
"UNSUBSCRIBE",
|
|
654
|
+
"LIST",
|
|
655
|
+
"LSUB",
|
|
656
|
+
"STATUS",
|
|
657
|
+
"APPEND",
|
|
658
|
+
"CHECK",
|
|
659
|
+
"CLOSE",
|
|
660
|
+
"EXPUNGE",
|
|
661
|
+
"SEARCH",
|
|
662
|
+
"FETCH",
|
|
663
|
+
"STORE",
|
|
664
|
+
"COPY",
|
|
665
|
+
"UID",
|
|
666
|
+
"IDLE",
|
|
667
|
+
]);
|
|
668
|
+
const fields = [];
|
|
669
|
+
let detected = false;
|
|
670
|
+
for (const line of lines) {
|
|
671
|
+
if (line.startsWith("* ")) {
|
|
672
|
+
const val = line.slice(2).trim();
|
|
673
|
+
fields.push({
|
|
674
|
+
name: "Untagged",
|
|
675
|
+
value: val.length > 100 ? val.slice(0, 100) + "…" : val,
|
|
676
|
+
});
|
|
677
|
+
detected = true;
|
|
678
|
+
} else if (line.startsWith("+ ")) {
|
|
679
|
+
fields.push({ name: "Continuation", value: line.slice(2).trim() });
|
|
680
|
+
detected = true;
|
|
681
|
+
} else {
|
|
682
|
+
const m = line.match(/^(\S+)\s+(\S+)\s*(.*)/);
|
|
683
|
+
if (m) {
|
|
684
|
+
const tag = m[1];
|
|
685
|
+
const word = m[2].toUpperCase();
|
|
686
|
+
const rest = m[3];
|
|
687
|
+
if (IMAP_STATUSES.has(word)) {
|
|
688
|
+
const val = `${word} ${rest}`.trim();
|
|
689
|
+
fields.push({
|
|
690
|
+
name: `[${tag}] Status`,
|
|
691
|
+
value: val.length > 100 ? val.slice(0, 100) + "…" : val,
|
|
692
|
+
});
|
|
693
|
+
detected = true;
|
|
694
|
+
} else if (IMAP_COMMANDS.has(word)) {
|
|
695
|
+
fields.push({ name: `[${tag}] Command`, value: word });
|
|
696
|
+
if (rest) {
|
|
697
|
+
fields.push({
|
|
698
|
+
name: "Arguments",
|
|
699
|
+
value: rest.length > 100 ? rest.slice(0, 100) + "…" : rest,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
detected = true;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (fields.length >= 12) break;
|
|
707
|
+
}
|
|
708
|
+
if (!detected) return null;
|
|
709
|
+
return { protocol: "IMAP", fields };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function decodeSmtpFromBytes(bytes) {
|
|
713
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
714
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
715
|
+
if (!lines.length) return null;
|
|
716
|
+
const SMTP_COMMANDS = new Set([
|
|
717
|
+
"HELO",
|
|
718
|
+
"EHLO",
|
|
719
|
+
"MAIL",
|
|
720
|
+
"RCPT",
|
|
721
|
+
"DATA",
|
|
722
|
+
"RSET",
|
|
723
|
+
"VRFY",
|
|
724
|
+
"EXPN",
|
|
725
|
+
"NOOP",
|
|
726
|
+
"QUIT",
|
|
727
|
+
"AUTH",
|
|
728
|
+
"STARTTLS",
|
|
729
|
+
]);
|
|
730
|
+
const fields = [];
|
|
731
|
+
let detected = false;
|
|
732
|
+
for (const line of lines) {
|
|
733
|
+
const rm = line.match(/^(\d{3})([\s-])(.*)/);
|
|
734
|
+
if (rm) {
|
|
735
|
+
const label = `Response ${rm[1]}${rm[2] === "-" ? " (cont.)" : ""}`;
|
|
736
|
+
fields.push({ name: label, value: rm[3] });
|
|
737
|
+
detected = true;
|
|
738
|
+
} else {
|
|
739
|
+
const parts = line.split(/\s+/);
|
|
740
|
+
const cmd = parts[0].toUpperCase();
|
|
741
|
+
if (SMTP_COMMANDS.has(cmd)) {
|
|
742
|
+
fields.push({ name: "Command", value: cmd });
|
|
743
|
+
if (parts.length > 1) {
|
|
744
|
+
const arg = parts.slice(1).join(" ");
|
|
745
|
+
fields.push({
|
|
746
|
+
name: "Argument",
|
|
747
|
+
value: arg.length > 100 ? arg.slice(0, 100) + "…" : arg,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
detected = true;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (fields.length >= 12) break;
|
|
754
|
+
}
|
|
755
|
+
if (!detected) return null;
|
|
756
|
+
return { protocol: "SMTP", fields };
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function autoDetectProtoFromBytes(bytes) {
|
|
760
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(
|
|
761
|
+
bytes.slice(0, 256),
|
|
762
|
+
);
|
|
763
|
+
if (/^SSH-/.test(text)) return "ssh";
|
|
764
|
+
if (
|
|
765
|
+
/^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH|CONNECT|TRACE)\s/.test(text) ||
|
|
766
|
+
/^HTTP\/[\d.]+ \d{3}/.test(text)
|
|
767
|
+
)
|
|
768
|
+
return "http";
|
|
769
|
+
if (
|
|
770
|
+
/^(HELO|EHLO|MAIL FROM|RCPT TO|DATA|QUIT)\b/i.test(text) ||
|
|
771
|
+
/^\d{3}[\s-]/.test(text)
|
|
772
|
+
)
|
|
773
|
+
return "smtp";
|
|
774
|
+
if (
|
|
775
|
+
/^\+OK/.test(text) ||
|
|
776
|
+
/^-ERR/.test(text) ||
|
|
777
|
+
/^(USER|PASS|STAT|LIST|RETR|DELE|QUIT)\b/i.test(text)
|
|
778
|
+
)
|
|
779
|
+
return "pop3";
|
|
780
|
+
if (
|
|
781
|
+
/^\* /.test(text) ||
|
|
782
|
+
/^\+ /.test(text) ||
|
|
783
|
+
/^\S+ (OK|NO|BAD|PREAUTH|BYE)\b/i.test(text) ||
|
|
784
|
+
/^\S+ (SELECT|LOGIN|FETCH|AUTHENTICATE)\b/i.test(text)
|
|
785
|
+
)
|
|
786
|
+
return "imap";
|
|
787
|
+
// Telnet: require IAC (0xFF) followed by a valid command byte (0xF0–0xFF)
|
|
788
|
+
const TELNET_COMMANDS = new Set([
|
|
789
|
+
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
|
|
790
|
+
0xfc, 0xfd, 0xfe, 0xff,
|
|
791
|
+
]);
|
|
792
|
+
for (let i = 0; i + 1 < bytes.length; i++) {
|
|
793
|
+
if (bytes[i] === 0xff && TELNET_COMMANDS.has(bytes[i + 1])) return "telnet";
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function renderProtoDecoderOutput(result, selectedProtocol, protocol) {
|
|
799
|
+
const protoOutput = document.getElementById("data-tools-proto-output");
|
|
800
|
+
if (!protoOutput) return;
|
|
801
|
+
activeDataToolsProtoResult = result || null;
|
|
802
|
+
protoOutput.innerHTML = "";
|
|
803
|
+
if (!result) {
|
|
804
|
+
const span = document.createElement("span");
|
|
805
|
+
span.className = "data-tools-proto-none";
|
|
806
|
+
span.textContent =
|
|
807
|
+
selectedProtocol === "auto"
|
|
808
|
+
? "No known protocol detected"
|
|
809
|
+
: `Could not decode as ${(protocol || selectedProtocol).toUpperCase()}`;
|
|
810
|
+
protoOutput.appendChild(span);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const table = document.createElement("table");
|
|
814
|
+
table.className = "data-tools-proto-table";
|
|
815
|
+
const headerRow = document.createElement("tr");
|
|
816
|
+
const th1 = document.createElement("th");
|
|
817
|
+
th1.textContent = `${result.protocol} Field`;
|
|
818
|
+
const th2 = document.createElement("th");
|
|
819
|
+
th2.textContent = "Value";
|
|
820
|
+
headerRow.appendChild(th1);
|
|
821
|
+
headerRow.appendChild(th2);
|
|
822
|
+
table.appendChild(headerRow);
|
|
823
|
+
result.fields.forEach((field) => {
|
|
824
|
+
const tr = document.createElement("tr");
|
|
825
|
+
const tdName = document.createElement("td");
|
|
826
|
+
tdName.textContent = field.name;
|
|
827
|
+
const tdVal = document.createElement("td");
|
|
828
|
+
tdVal.textContent = field.value;
|
|
829
|
+
tr.appendChild(tdName);
|
|
830
|
+
tr.appendChild(tdVal);
|
|
831
|
+
table.appendChild(tr);
|
|
832
|
+
});
|
|
833
|
+
protoOutput.appendChild(table);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function runProtoDecoder(bytes) {
|
|
837
|
+
const selectEl = document.getElementById("data-tools-proto-select");
|
|
838
|
+
const selectedProtocol = selectEl ? selectEl.value : "auto";
|
|
839
|
+
let protocol = selectedProtocol;
|
|
840
|
+
if (protocol === "auto") {
|
|
841
|
+
protocol = autoDetectProtoFromBytes(bytes);
|
|
842
|
+
}
|
|
843
|
+
let result = null;
|
|
844
|
+
switch (protocol) {
|
|
845
|
+
case "http":
|
|
846
|
+
result = decodeHttpFromBytes(bytes);
|
|
847
|
+
break;
|
|
848
|
+
case "telnet":
|
|
849
|
+
result = decodeTelnetFromBytes(bytes);
|
|
850
|
+
break;
|
|
851
|
+
case "ssh":
|
|
852
|
+
result = decodeSshFromBytes(bytes);
|
|
853
|
+
break;
|
|
854
|
+
case "pop3":
|
|
855
|
+
result = decodePop3FromBytes(bytes);
|
|
856
|
+
break;
|
|
857
|
+
case "imap":
|
|
858
|
+
result = decodeImapFromBytes(bytes);
|
|
859
|
+
break;
|
|
860
|
+
case "smtp":
|
|
861
|
+
result = decodeSmtpFromBytes(bytes);
|
|
862
|
+
break;
|
|
863
|
+
default:
|
|
864
|
+
protocol = null;
|
|
865
|
+
}
|
|
866
|
+
renderProtoDecoderOutput(result, selectedProtocol, protocol);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function clearProtoDecoderOutput() {
|
|
870
|
+
const protoOutput = document.getElementById("data-tools-proto-output");
|
|
871
|
+
if (protoOutput) protoOutput.innerHTML = "";
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// ── Conv tab navigation ───────────────────────────────────────────────────────
|
|
875
|
+
|
|
876
|
+
function showDataTools(tabName = CONV_CONVERSIONS_SUBTAB) {
|
|
877
|
+
_setActiveMainTab("data-tools");
|
|
878
|
+
_statusUpdate("Status: Displaying data conversion tools");
|
|
879
|
+
_writeLogEntry("User opened data conversion tools view");
|
|
880
|
+
document.getElementById("packetInfoPane").style.display = "none";
|
|
881
|
+
document.getElementById("packetPayloadPane").style.display = "none";
|
|
882
|
+
document.getElementById("summary_box").style.display = "none";
|
|
883
|
+
document.getElementById("stats_box").style.display = "none";
|
|
884
|
+
document.getElementById("list_box").style.display = "none";
|
|
885
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
886
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
887
|
+
document.getElementById("rightside").style.display = "none";
|
|
888
|
+
document.getElementById("data_tools_box").style.display = "flex";
|
|
889
|
+
setConvSubtab(tabName);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function setConvSubtab(tabName) {
|
|
893
|
+
activeConvSubtab = tabName;
|
|
894
|
+
const conversionsActive = tabName === CONV_CONVERSIONS_SUBTAB;
|
|
895
|
+
const hashesActive = tabName === CONV_HASHES_SUBTAB;
|
|
896
|
+
const decodesActive = tabName === CONV_DECODES_SUBTAB;
|
|
897
|
+
document
|
|
898
|
+
.getElementById("conv-subtab-conversions")
|
|
899
|
+
.classList.toggle("active", conversionsActive);
|
|
900
|
+
document
|
|
901
|
+
.getElementById("conv-subtab-hashes")
|
|
902
|
+
.classList.toggle("active", hashesActive);
|
|
903
|
+
document
|
|
904
|
+
.getElementById("conv-subtab-decodes")
|
|
905
|
+
.classList.toggle("active", decodesActive);
|
|
906
|
+
document.getElementById("conv-conversions-panel").hidden = !conversionsActive;
|
|
907
|
+
document.getElementById("conv-hashes-panel").hidden = !hashesActive;
|
|
908
|
+
document.getElementById("conv-decodes-panel").hidden = !decodesActive;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
912
|
+
|
|
913
|
+
module.exports = {
|
|
914
|
+
id: "data-tools",
|
|
915
|
+
initConvPanel,
|
|
916
|
+
// Constants
|
|
917
|
+
CONV_CONVERSIONS_SUBTAB,
|
|
918
|
+
CONV_HASHES_SUBTAB,
|
|
919
|
+
CONV_DECODES_SUBTAB,
|
|
920
|
+
VALID_CONV_SUBTABS,
|
|
921
|
+
DATA_TOOLS_CONTEXT_BASE64_MIN_LENGTH,
|
|
922
|
+
DATA_TOOLS_TEXT_MIME_PRINTABLE_THRESHOLD,
|
|
923
|
+
DATA_TOOLS_ENTROPY_HIGH_THRESHOLD,
|
|
924
|
+
DATA_TOOLS_ENTROPY_MEDIUM_THRESHOLD,
|
|
925
|
+
DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES,
|
|
926
|
+
// State accessors
|
|
927
|
+
getActiveConvSubtab,
|
|
928
|
+
getActiveDataToolsProtoResult,
|
|
929
|
+
// Functions
|
|
930
|
+
parseDataToolsInput,
|
|
931
|
+
bytesToPrintableAscii,
|
|
932
|
+
decodeHttpFromBytes,
|
|
933
|
+
resetDataToolsOutputs,
|
|
934
|
+
runProtoDecoder,
|
|
935
|
+
runDataToolsConversion,
|
|
936
|
+
runDataToolsHashesFromInput,
|
|
937
|
+
showDataTools,
|
|
938
|
+
setConvSubtab,
|
|
939
|
+
};
|