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,4957 @@
|
|
|
1
|
+
import "../assets/css/style.css";
|
|
2
|
+
const CryptoJS = require("crypto-js");
|
|
3
|
+
const { sha3_256, sha3_512 } = require("js-sha3");
|
|
4
|
+
const whirlpool = require("whirlpool-js");
|
|
5
|
+
const { filterPackets, validateFilterSyntax } = require("../filter");
|
|
6
|
+
const { initializeLogging } = require("../logging");
|
|
7
|
+
const { initializeContextMenu } = require("./context-menu");
|
|
8
|
+
const {
|
|
9
|
+
createTable,
|
|
10
|
+
renderDnsTable,
|
|
11
|
+
renderIcmpTable,
|
|
12
|
+
renderSnmpTable,
|
|
13
|
+
renderDhcpTable,
|
|
14
|
+
renderNtpTable,
|
|
15
|
+
renderSipTable,
|
|
16
|
+
renderHttpTable,
|
|
17
|
+
renderFtpTable,
|
|
18
|
+
renderSmtpTable,
|
|
19
|
+
renderPop3Table,
|
|
20
|
+
renderImapTable,
|
|
21
|
+
renderTelnetTable,
|
|
22
|
+
renderIrcTable,
|
|
23
|
+
renderMtpTable,
|
|
24
|
+
renderLdapTable,
|
|
25
|
+
renderMysqlTable,
|
|
26
|
+
renderPostgresqlTable,
|
|
27
|
+
renderXmppTable,
|
|
28
|
+
renderSmbTable,
|
|
29
|
+
renderMqttTable,
|
|
30
|
+
renderRtspTable,
|
|
31
|
+
renderTftpTable,
|
|
32
|
+
renderBgpTable,
|
|
33
|
+
renderHttp2Table,
|
|
34
|
+
renderNntpTable,
|
|
35
|
+
renderRadiusTable,
|
|
36
|
+
} = require("./decoders");
|
|
37
|
+
const { createCryptPanel } = require("./panels/crypt-panel");
|
|
38
|
+
const {
|
|
39
|
+
createKeystorePanel,
|
|
40
|
+
CRYPT_KEYSTORE_MODE_SESSION,
|
|
41
|
+
CRYPT_KEYSTORE_MODE_PERSISTENT,
|
|
42
|
+
SESSION_KEYCHAIN_LABEL,
|
|
43
|
+
} = require("./panels/keystore-panel");
|
|
44
|
+
const { createStatsPanel } = require("./panels/stats-panel");
|
|
45
|
+
const { createListPanel } = require("./panels/list-panel");
|
|
46
|
+
const { createSummaryPanel } = require("./panels/summary-panel");
|
|
47
|
+
const { initializeInstallScreen } = require("./panels/install-screen");
|
|
48
|
+
const { createDataPanel } = require("./panels/data-panel");
|
|
49
|
+
const psVer = require("../../package.json").version;
|
|
50
|
+
const {
|
|
51
|
+
initConvPanel,
|
|
52
|
+
CONV_CONVERSIONS_SUBTAB,
|
|
53
|
+
CONV_HASHES_SUBTAB,
|
|
54
|
+
CONV_DECODES_SUBTAB,
|
|
55
|
+
VALID_CONV_SUBTABS,
|
|
56
|
+
DATA_TOOLS_CONTEXT_BASE64_MIN_LENGTH,
|
|
57
|
+
DATA_TOOLS_TEXT_MIME_PRINTABLE_THRESHOLD,
|
|
58
|
+
DATA_TOOLS_ENTROPY_HIGH_THRESHOLD,
|
|
59
|
+
DATA_TOOLS_ENTROPY_MEDIUM_THRESHOLD,
|
|
60
|
+
DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES,
|
|
61
|
+
getActiveConvSubtab,
|
|
62
|
+
getActiveDataToolsProtoResult,
|
|
63
|
+
setConvSubtab,
|
|
64
|
+
runDataToolsHashesFromInput,
|
|
65
|
+
} = require("./panels/data-tools-panel");
|
|
66
|
+
|
|
67
|
+
// Cache frequently accessed DOM elements to avoid repeated lookups
|
|
68
|
+
const domCache = {};
|
|
69
|
+
function getCachedElement(id) {
|
|
70
|
+
if (!domCache[id]) {
|
|
71
|
+
domCache[id] = document.getElementById(id);
|
|
72
|
+
}
|
|
73
|
+
return domCache[id];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const SESSION_FILE_SCHEMA_VERSION = 1;
|
|
77
|
+
const SESSION_CAPTURE_KEY = "Capture Data";
|
|
78
|
+
const SESSION_STATE_KEY = "Session State";
|
|
79
|
+
const MAIN_TAB_SUMMARY = "summary";
|
|
80
|
+
const MAIN_TAB_DATA = "data";
|
|
81
|
+
const MAIN_TAB_STATS = "stats";
|
|
82
|
+
const MAIN_TAB_LIST = "list";
|
|
83
|
+
const MAIN_TAB_NOTES = "notes";
|
|
84
|
+
const MAIN_TAB_DATA_TOOLS = "data-tools";
|
|
85
|
+
const MAIN_TAB_CRYPT = "crypt";
|
|
86
|
+
const MAIN_TAB_KEYSTORE = "keystore";
|
|
87
|
+
const NOTE_DEFAULT_COLOR = "#4caf50";
|
|
88
|
+
const NOTE_FALLBACK_COLORS = [
|
|
89
|
+
"#4caf50",
|
|
90
|
+
"#ff9800",
|
|
91
|
+
"#2196f3",
|
|
92
|
+
"#9c27b0",
|
|
93
|
+
"#e91e63",
|
|
94
|
+
"#ffc107",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Global variables for DOM elements and state
|
|
98
|
+
let capturedPackets = {}; // Stores parsed packet data from JSON
|
|
99
|
+
let jsonCapture = ""; // Stringified JSON capture for pretty display
|
|
100
|
+
let currentIp;
|
|
101
|
+
let finalSummary = ""; // Stores the summary section from JSON
|
|
102
|
+
const status = getCachedElement("status"); // Status bar element
|
|
103
|
+
let hostsList = ["0.0.0.0"]; // List of hosts found in capture
|
|
104
|
+
const hostFilterEl = getCachedElement("host_filter"); // Host filter dropdown
|
|
105
|
+
let packetsForHost = []; // Packets for the currently selected host
|
|
106
|
+
let index = 0; // Navigation index for packets
|
|
107
|
+
let activePacketCursor = 0;
|
|
108
|
+
let bookmarkList = []; // List of bookmarks (host:packet index)
|
|
109
|
+
let activeBookmark = {}; // Current bookmark object
|
|
110
|
+
let isFileLoaded = false;
|
|
111
|
+
let jsonOfPackets;
|
|
112
|
+
let filteredPackets;
|
|
113
|
+
let currentPacketKey;
|
|
114
|
+
let startTime;
|
|
115
|
+
const filterInputEl = getCachedElement("filterStr");
|
|
116
|
+
const filterHighlightEl = getCachedElement("filterStr-highlight");
|
|
117
|
+
const filterClearButtonEl = getCachedElement("filterStr-clear");
|
|
118
|
+
const filterHistorySelectEl = getCachedElement("filter-history-select");
|
|
119
|
+
const filterHistory = [];
|
|
120
|
+
const CONTEXT_IPV4_REGEX =
|
|
121
|
+
/\b(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}\b/;
|
|
122
|
+
const STRICT_IPV4_REGEX =
|
|
123
|
+
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
|
124
|
+
const CONTEXT_MAC_REGEX = /\b([0-9A-Fa-f]{2}([-:])){5}[0-9A-Fa-f]{2}\b/;
|
|
125
|
+
const CONTEXT_MIME_REGEX = /^[\w.+-]+\/[\w.+-]+$/;
|
|
126
|
+
const CRYPT_SSL_SUBTAB = "ssl";
|
|
127
|
+
const CRYPT_PGP_SUBTAB = "pgp";
|
|
128
|
+
const CRYPT_OPENSSH_SUBTAB = "openssh";
|
|
129
|
+
const VALID_MAIN_TABS = [
|
|
130
|
+
MAIN_TAB_SUMMARY,
|
|
131
|
+
MAIN_TAB_DATA,
|
|
132
|
+
MAIN_TAB_STATS,
|
|
133
|
+
MAIN_TAB_LIST,
|
|
134
|
+
MAIN_TAB_NOTES,
|
|
135
|
+
MAIN_TAB_DATA_TOOLS,
|
|
136
|
+
MAIN_TAB_CRYPT,
|
|
137
|
+
MAIN_TAB_KEYSTORE,
|
|
138
|
+
];
|
|
139
|
+
const VALID_CRYPT_SUBTABS = [CRYPT_SSL_SUBTAB, CRYPT_PGP_SUBTAB, CRYPT_OPENSSH_SUBTAB];
|
|
140
|
+
let activeMainTab = MAIN_TAB_SUMMARY;
|
|
141
|
+
let activeCryptSubtab = CRYPT_SSL_SUBTAB;
|
|
142
|
+
let activeDataToolsProtoResult = null;
|
|
143
|
+
let keystorePanel;
|
|
144
|
+
let notesList = [];
|
|
145
|
+
let selectedNoteId = null;
|
|
146
|
+
let noteIdCounter = 0;
|
|
147
|
+
|
|
148
|
+
initializeInstallScreen({
|
|
149
|
+
installapi: window.installapi,
|
|
150
|
+
documentRef: document,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const {
|
|
154
|
+
initializeActivityLog,
|
|
155
|
+
writeLogEntry,
|
|
156
|
+
writeBackendErrorLogEntry,
|
|
157
|
+
logErrorEntry,
|
|
158
|
+
} = initializeLogging({
|
|
159
|
+
logapi: window.logapi,
|
|
160
|
+
documentRef: document,
|
|
161
|
+
consoleRef: console,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const { showStats } = createStatsPanel({
|
|
165
|
+
documentRef: document,
|
|
166
|
+
statusUpdate,
|
|
167
|
+
writeLogEntry,
|
|
168
|
+
setActiveMainTab: (tab) => {
|
|
169
|
+
activeMainTab = tab;
|
|
170
|
+
},
|
|
171
|
+
mainTabStats: MAIN_TAB_STATS,
|
|
172
|
+
getJsonCapture: () => jsonCapture,
|
|
173
|
+
getCapturedPackets: () => capturedPackets,
|
|
174
|
+
filterInputEl,
|
|
175
|
+
syncFilterHighlight,
|
|
176
|
+
runFilterQuery,
|
|
177
|
+
getFilteredPackets: () => filteredPackets,
|
|
178
|
+
setPacketsForHost: (packets) => {
|
|
179
|
+
packetsForHost = packets;
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const summaryPanel = createSummaryPanel({
|
|
184
|
+
documentRef: document,
|
|
185
|
+
getJsonCapture: () => jsonCapture,
|
|
186
|
+
getFinalSummary: () => finalSummary,
|
|
187
|
+
setActiveMainTab: (tab) => {
|
|
188
|
+
activeMainTab = tab;
|
|
189
|
+
},
|
|
190
|
+
mainTabSummary: MAIN_TAB_SUMMARY,
|
|
191
|
+
statusUpdate,
|
|
192
|
+
fileLoaded,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const { showSummary, showSummaryLoading, clearSummaryContent } = summaryPanel;
|
|
196
|
+
const { initializeDataView, bindDataPanelEvents, logCurrentPacketDisplay } =
|
|
197
|
+
createDataPanel({
|
|
198
|
+
constants: {
|
|
199
|
+
MAIN_TAB_DATA,
|
|
200
|
+
},
|
|
201
|
+
documentRef: document,
|
|
202
|
+
statusUpdate,
|
|
203
|
+
writeLogEntry,
|
|
204
|
+
doError,
|
|
205
|
+
getIsFileLoaded: () => isFileLoaded,
|
|
206
|
+
getJsonCapture: () => jsonCapture,
|
|
207
|
+
getHostFilterValue: () => hostFilterEl.value,
|
|
208
|
+
getHostsList: () => hostsList,
|
|
209
|
+
getFilterInputValue: () => filterInputEl.value,
|
|
210
|
+
getFilteredPackets: () => filteredPackets,
|
|
211
|
+
getPacketsForHost: () => packetsForHost,
|
|
212
|
+
setActiveMainTab: (tab) => {
|
|
213
|
+
activeMainTab = tab;
|
|
214
|
+
},
|
|
215
|
+
handlePacketNavigation: (navAction, navBookmark) =>
|
|
216
|
+
handlePacketNavigation(navAction, navBookmark),
|
|
217
|
+
getIndex: () => index,
|
|
218
|
+
setIndex: (nextIndex) => {
|
|
219
|
+
index = nextIndex;
|
|
220
|
+
},
|
|
221
|
+
setActivePacketCursor,
|
|
222
|
+
setCurrentIp: (nextIp) => {
|
|
223
|
+
currentIp = nextIp;
|
|
224
|
+
},
|
|
225
|
+
setCurrentPacketKey: (nextPacketKey) => {
|
|
226
|
+
currentPacketKey = nextPacketKey;
|
|
227
|
+
},
|
|
228
|
+
getCurrentPacketKey: () => currentPacketKey,
|
|
229
|
+
syncBookmarkDropdown,
|
|
230
|
+
infoPanel,
|
|
231
|
+
popHexGrid,
|
|
232
|
+
populateDataTypes,
|
|
233
|
+
});
|
|
234
|
+
bindDataPanelEvents();
|
|
235
|
+
|
|
236
|
+
function getPacketTimeframe() {
|
|
237
|
+
if (!capturedPackets || typeof capturedPackets !== "object") return null;
|
|
238
|
+
const packetTimes = [];
|
|
239
|
+
if (!capturedPackets["Host"]) return null;
|
|
240
|
+
for (const host of Object.keys(capturedPackets["Host"])) {
|
|
241
|
+
const hostPackets = capturedPackets["Host"][host];
|
|
242
|
+
if (!Array.isArray(hostPackets)) continue;
|
|
243
|
+
hostPackets.forEach((packet) => {
|
|
244
|
+
const packetTime = packet?.["Packet Info"]?.["Packet Timestamp"];
|
|
245
|
+
if (packetTime) {
|
|
246
|
+
packetTimes.push(packetTime);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (packetTimes.length === 0) return null;
|
|
251
|
+
const parsedTimes = packetTimes
|
|
252
|
+
.map((time) => ({
|
|
253
|
+
raw: time,
|
|
254
|
+
value: Date.parse(time),
|
|
255
|
+
}))
|
|
256
|
+
.filter((item) => !Number.isNaN(item.value))
|
|
257
|
+
.sort((a, b) => a.value - b.value);
|
|
258
|
+
if (parsedTimes.length < 1) return null;
|
|
259
|
+
return {
|
|
260
|
+
first: parsedTimes[0].raw,
|
|
261
|
+
last: parsedTimes[parsedTimes.length - 1].raw,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
void initializeActivityLog();
|
|
266
|
+
|
|
267
|
+
popHexGrid("00".repeat(256));
|
|
268
|
+
// Set up file upload handler for JSON capture
|
|
269
|
+
document
|
|
270
|
+
.getElementById("json-upload")
|
|
271
|
+
.addEventListener("change", function (event) {
|
|
272
|
+
const file = event.target.files[0];
|
|
273
|
+
if (file) {
|
|
274
|
+
startTime = performance.now();
|
|
275
|
+
statusUpdate("Processing file: " + file.name);
|
|
276
|
+
writeLogEntry(
|
|
277
|
+
`User selected JSON file name=${file.name} size_bytes=${file.size}`,
|
|
278
|
+
);
|
|
279
|
+
processFile(file);
|
|
280
|
+
isFileLoaded = true;
|
|
281
|
+
event.target.value = ""; // Reset so the same file can be loaded again
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
document
|
|
286
|
+
.getElementById("pcap-filename")
|
|
287
|
+
.addEventListener("click", function (event) {
|
|
288
|
+
window.getfileapi
|
|
289
|
+
.selectFile()
|
|
290
|
+
.then((filePath) => {
|
|
291
|
+
if (filePath) {
|
|
292
|
+
writeLogEntry(`User selected PCAP file path=${filePath}`);
|
|
293
|
+
window.fsize
|
|
294
|
+
.getFSize()
|
|
295
|
+
.then((fileSize) => {
|
|
296
|
+
// Update the UI with the file size
|
|
297
|
+
const fileSizeKb = (fileSize / 1024).toFixed(2);
|
|
298
|
+
document.getElementById("pcap-size").textContent =
|
|
299
|
+
`PCAP size: ${fileSizeKb}kb`;
|
|
300
|
+
writeLogEntry(
|
|
301
|
+
`Capture size recorded bytes=${fileSize} kilobytes=${fileSizeKb}`,
|
|
302
|
+
);
|
|
303
|
+
})
|
|
304
|
+
.catch((error) => {
|
|
305
|
+
// Handle any errors (e.g., file not found)
|
|
306
|
+
console.error("Error fetching file size:", error);
|
|
307
|
+
logErrorEntry("file-size-fetch", error);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
runSnitch(filePath);
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
.catch((error) => {
|
|
314
|
+
doError("Error selecting PCAP file!");
|
|
315
|
+
logErrorEntry("pcap-select", error);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
function isValidJson(str) {
|
|
320
|
+
try {
|
|
321
|
+
JSON.parse(str);
|
|
322
|
+
return true;
|
|
323
|
+
} catch (e) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Chunked JSON parsing for large files to avoid blocking the UI
|
|
329
|
+
function parseJsonChunked(jsonString, chunkSize = 65536) {
|
|
330
|
+
return new Promise((resolve, reject) => {
|
|
331
|
+
try {
|
|
332
|
+
// For smaller files, parse directly
|
|
333
|
+
if (jsonString.length < chunkSize * 2) {
|
|
334
|
+
resolve(JSON.parse(jsonString));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// For large files, use setTimeout to yield to the main thread
|
|
339
|
+
let position = 0;
|
|
340
|
+
const length = jsonString.length;
|
|
341
|
+
let result = "";
|
|
342
|
+
const stack = [];
|
|
343
|
+
let inString = false;
|
|
344
|
+
let escape = false;
|
|
345
|
+
|
|
346
|
+
function processChunk() {
|
|
347
|
+
const end = Math.min(position + chunkSize, length);
|
|
348
|
+
|
|
349
|
+
for (; position < end; position++) {
|
|
350
|
+
const char = jsonString[position];
|
|
351
|
+
if (escape) {
|
|
352
|
+
escape = false;
|
|
353
|
+
} else if (char === "\\") {
|
|
354
|
+
escape = true;
|
|
355
|
+
} else if (char === '"') {
|
|
356
|
+
inString = !inString;
|
|
357
|
+
} else if (!inString) {
|
|
358
|
+
if (char === "{" || char === "[") {
|
|
359
|
+
stack.push(char);
|
|
360
|
+
} else if (char === "}" || char === "]") {
|
|
361
|
+
stack.pop();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
result += char;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (position < length) {
|
|
368
|
+
// Yield to main thread and continue
|
|
369
|
+
setTimeout(processChunk, 0);
|
|
370
|
+
} else {
|
|
371
|
+
resolve(JSON.parse(result));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
setTimeout(processChunk, 0);
|
|
376
|
+
} catch (e) {
|
|
377
|
+
reject(e);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function fileLoaded(isLoaded) {
|
|
383
|
+
isFileLoaded = isLoaded;
|
|
384
|
+
if (isLoaded) {
|
|
385
|
+
const loadEndTime = performance.now();
|
|
386
|
+
document.getElementById("load-time").textContent =
|
|
387
|
+
"Load time: " +
|
|
388
|
+
((loadEndTime - startTime) / 1000).toFixed(2) +
|
|
389
|
+
" seconds";
|
|
390
|
+
filterInputEl.disabled = false;
|
|
391
|
+
filterHistorySelectEl.disabled = false;
|
|
392
|
+
document.getElementById("summary-btn").style.opacity = "1";
|
|
393
|
+
document.getElementById("data-btn").style.opacity = "1";
|
|
394
|
+
document.getElementById("data-tools-btn").style.opacity = "1";
|
|
395
|
+
document.getElementById("crypt-btn").style.opacity = "1";
|
|
396
|
+
document.getElementById("keystore-btn").style.opacity = "1";
|
|
397
|
+
document.getElementById("tab-btns").style.opacity = "1";
|
|
398
|
+
document.getElementById("prev-btn").style.opacity = "1";
|
|
399
|
+
document.getElementById("next-btn").style.opacity = "1";
|
|
400
|
+
document.getElementById("log-btn").style.opacity = "1";
|
|
401
|
+
document.getElementById("stats-btn").style.opacity = "1";
|
|
402
|
+
document.getElementById("list-btn").style.opacity = "1";
|
|
403
|
+
document.getElementById("notes-btn").style.opacity = "1";
|
|
404
|
+
document.getElementById("json-lab").style.display = "none";
|
|
405
|
+
document.getElementById("pcap-lab").style.display = "none";
|
|
406
|
+
document.getElementById("llm-toggle").style.display = "none";
|
|
407
|
+
writeLogEntry(
|
|
408
|
+
`Initial file load completed seconds=${((loadEndTime - startTime) / 1000).toFixed(2)}`,
|
|
409
|
+
);
|
|
410
|
+
} else {
|
|
411
|
+
filterInputEl.disabled = true;
|
|
412
|
+
filterHistorySelectEl.disabled = true;
|
|
413
|
+
document.getElementById("json-lab").style.display = "block";
|
|
414
|
+
document.getElementById("pcap-lab").style.display = "block";
|
|
415
|
+
document.getElementById("log-btn").style.opacity = "0";
|
|
416
|
+
document.getElementById("stats-btn").style.opacity = "0";
|
|
417
|
+
document.getElementById("list-btn").style.opacity = "0";
|
|
418
|
+
document.getElementById("notes-btn").style.opacity = "0";
|
|
419
|
+
document.getElementById("crypt-btn").style.opacity = "0";
|
|
420
|
+
document.getElementById("keystore-btn").style.opacity = "0";
|
|
421
|
+
}
|
|
422
|
+
updateFilterClearButtonState();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function escapeHtml(text) {
|
|
426
|
+
return text
|
|
427
|
+
.replace(/&/g, "&")
|
|
428
|
+
.replace(/</g, "<")
|
|
429
|
+
.replace(/>/g, ">")
|
|
430
|
+
.replace(/"/g, """)
|
|
431
|
+
.replace(/'/g, "'");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function decorateExpressionSegment(segmentText) {
|
|
435
|
+
if (!segmentText) return "";
|
|
436
|
+
|
|
437
|
+
const colonIndex = segmentText.indexOf(":");
|
|
438
|
+
if (colonIndex === -1) {
|
|
439
|
+
return `<span class="query-token-value">${escapeHtml(segmentText)}</span>`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const keyText = segmentText.slice(0, colonIndex);
|
|
443
|
+
const valueText = segmentText.slice(colonIndex + 1);
|
|
444
|
+
const cmpMatch = valueText.match(/^(\s*)(>=|<=|==|!=|>|<)(\s*)(.*)$/);
|
|
445
|
+
|
|
446
|
+
let valueHtml = "";
|
|
447
|
+
if (cmpMatch) {
|
|
448
|
+
valueHtml =
|
|
449
|
+
escapeHtml(cmpMatch[1]) +
|
|
450
|
+
`<span class="query-token-operator">${escapeHtml(cmpMatch[2])}</span>` +
|
|
451
|
+
escapeHtml(cmpMatch[3]) +
|
|
452
|
+
`<span class="query-token-value">${escapeHtml(cmpMatch[4])}</span>`;
|
|
453
|
+
} else {
|
|
454
|
+
valueHtml = `<span class="query-token-value">${escapeHtml(valueText)}</span>`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
`<span class="query-token-key">${escapeHtml(keyText)}</span>` +
|
|
459
|
+
'<span class="query-token-colon">:</span>' +
|
|
460
|
+
valueHtml
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function renderHighlightedQuery(query) {
|
|
465
|
+
const source = query || "";
|
|
466
|
+
if (!source) return " ";
|
|
467
|
+
|
|
468
|
+
// Query grammar tokens: logical OR/AND operators and grouping parentheses.
|
|
469
|
+
const tokenRegex = /(\|\||&&|\(|\)|!(?!=))/g;
|
|
470
|
+
let cursor = 0;
|
|
471
|
+
let html = "";
|
|
472
|
+
let tokenMatch = tokenRegex.exec(source);
|
|
473
|
+
|
|
474
|
+
while (tokenMatch !== null) {
|
|
475
|
+
const segmentText = source.slice(cursor, tokenMatch.index);
|
|
476
|
+
html += decorateExpressionSegment(segmentText);
|
|
477
|
+
|
|
478
|
+
const tokenText = tokenMatch[0];
|
|
479
|
+
const tokenClass =
|
|
480
|
+
tokenText === "(" || tokenText === ")" ? "paren" : "logic";
|
|
481
|
+
html += `<span class="query-token-${tokenClass}">${escapeHtml(tokenText)}</span>`;
|
|
482
|
+
cursor = tokenRegex.lastIndex;
|
|
483
|
+
tokenMatch = tokenRegex.exec(source);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
html += decorateExpressionSegment(source.slice(cursor));
|
|
487
|
+
return html;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function syncFilterHighlight() {
|
|
491
|
+
filterHighlightEl.innerHTML = renderHighlightedQuery(filterInputEl.value);
|
|
492
|
+
syncFilterHighlightScroll();
|
|
493
|
+
updateFilterClearButtonState();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function syncFilterHighlightScroll() {
|
|
497
|
+
filterHighlightEl.scrollLeft = filterInputEl.scrollLeft;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function updateFilterClearButtonState() {
|
|
501
|
+
filterClearButtonEl.disabled = !canClearFilterQuery();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function canClearFilterQuery() {
|
|
505
|
+
return !filterInputEl.disabled && filterInputEl.value.trim() !== "";
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function renderFilterHistory() {
|
|
509
|
+
filterHistorySelectEl.replaceChildren();
|
|
510
|
+
|
|
511
|
+
const placeholderOption = document.createElement("option");
|
|
512
|
+
placeholderOption.value = "";
|
|
513
|
+
placeholderOption.textContent = filterHistory.length
|
|
514
|
+
? "Previous queries"
|
|
515
|
+
: "No previous queries";
|
|
516
|
+
placeholderOption.selected = true;
|
|
517
|
+
filterHistorySelectEl.appendChild(placeholderOption);
|
|
518
|
+
|
|
519
|
+
filterHistory.forEach((query) => {
|
|
520
|
+
const queryOption = document.createElement("option");
|
|
521
|
+
queryOption.value = query;
|
|
522
|
+
queryOption.textContent = query;
|
|
523
|
+
queryOption.title = query;
|
|
524
|
+
filterHistorySelectEl.appendChild(queryOption);
|
|
525
|
+
});
|
|
526
|
+
filterHistorySelectEl.value = "";
|
|
527
|
+
filterHistorySelectEl.disabled = !isFileLoaded;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function addFilterHistory(query) {
|
|
531
|
+
const normalizedQuery = query.trim();
|
|
532
|
+
if (!normalizedQuery) return;
|
|
533
|
+
const existingIndex = filterHistory.indexOf(normalizedQuery);
|
|
534
|
+
if (existingIndex !== -1) {
|
|
535
|
+
filterHistory.splice(existingIndex, 1);
|
|
536
|
+
}
|
|
537
|
+
filterHistory.unshift(normalizedQuery);
|
|
538
|
+
renderFilterHistory();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function runFilterQuery(filterQuery, options = {}) {
|
|
542
|
+
const { trackHistory = true } = options;
|
|
543
|
+
try {
|
|
544
|
+
validateFilterSyntax(filterQuery);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
logErrorEntry("filter-syntax", error);
|
|
547
|
+
writeLogEntry(`User query rejected query="${filterQuery}"`);
|
|
548
|
+
doError(`Invalid filter syntax: ${error.message}`);
|
|
549
|
+
statusUpdate("Status: Invalid filter syntax");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (trackHistory) {
|
|
554
|
+
addFilterHistory(filterQuery);
|
|
555
|
+
}
|
|
556
|
+
filteredPackets = filterPackets(capturedPackets, filterQuery);
|
|
557
|
+
writeLogEntry(`User executed query="${filterQuery}"`);
|
|
558
|
+
|
|
559
|
+
if (filteredPackets === undefined || filteredPackets.length === 0) {
|
|
560
|
+
hideAllData();
|
|
561
|
+
statusUpdate("Status: No packets match the filter criteria");
|
|
562
|
+
writeLogEntry("User query returned 0 packets");
|
|
563
|
+
} else {
|
|
564
|
+
statusUpdate(
|
|
565
|
+
"Status: Displaying " +
|
|
566
|
+
filteredPackets.length +
|
|
567
|
+
" packets matching filter",
|
|
568
|
+
);
|
|
569
|
+
writeLogEntry(`User query returned packets=${filteredPackets.length}`);
|
|
570
|
+
handlePacketNavigation("filtered", null);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function clearFilterQuery() {
|
|
575
|
+
if (!canClearFilterQuery()) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
filterInputEl.value = "";
|
|
579
|
+
syncFilterHighlight();
|
|
580
|
+
filterHistorySelectEl.value = "";
|
|
581
|
+
filterInputEl.focus();
|
|
582
|
+
runFilterQuery("");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function deepCloneSessionData(value, fallback) {
|
|
586
|
+
try {
|
|
587
|
+
return JSON.parse(JSON.stringify(value));
|
|
588
|
+
} catch {
|
|
589
|
+
return fallback;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function normalizeNoteColor(colorValue) {
|
|
594
|
+
const normalized =
|
|
595
|
+
typeof colorValue === "string" ? colorValue.trim().toLowerCase() : "";
|
|
596
|
+
if (/^#[0-9a-f]{6}$/.test(normalized)) return normalized;
|
|
597
|
+
return NOTE_DEFAULT_COLOR;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function generateNoteId() {
|
|
601
|
+
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
602
|
+
return globalThis.crypto.randomUUID();
|
|
603
|
+
}
|
|
604
|
+
noteIdCounter += 1;
|
|
605
|
+
return `note-${Date.now()}-${noteIdCounter}`;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function createNoteEntry(text = "", color = NOTE_DEFAULT_COLOR) {
|
|
609
|
+
return {
|
|
610
|
+
id: generateNoteId(),
|
|
611
|
+
text: typeof text === "string" ? text : String(text || ""),
|
|
612
|
+
color: normalizeNoteColor(color),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function getSelectedNoteEntry() {
|
|
617
|
+
return notesList.find((entry) => entry.id === selectedNoteId) || null;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function renderNotesList() {
|
|
621
|
+
const notesSelectEl = document.getElementById("notes-select");
|
|
622
|
+
const notesEditorEl = document.getElementById("notes-editor");
|
|
623
|
+
const newNoteColorEl = document.getElementById("notes-new-color");
|
|
624
|
+
if (!notesSelectEl || !notesEditorEl || !newNoteColorEl) return;
|
|
625
|
+
|
|
626
|
+
notesSelectEl.replaceChildren();
|
|
627
|
+
if (!notesList.length) {
|
|
628
|
+
selectedNoteId = null;
|
|
629
|
+
notesEditorEl.value = "";
|
|
630
|
+
notesEditorEl.disabled = true;
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (!getSelectedNoteEntry()) {
|
|
635
|
+
selectedNoteId = notesList[0].id;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
notesList.forEach((noteEntry, noteIndex) => {
|
|
639
|
+
const optionEl = document.createElement("option");
|
|
640
|
+
const previewText = String(noteEntry.text || "")
|
|
641
|
+
.replace(/\s+/g, " ")
|
|
642
|
+
.trim();
|
|
643
|
+
optionEl.value = noteEntry.id;
|
|
644
|
+
optionEl.textContent = `${noteIndex + 1}. ${previewText || "(empty note)"}`;
|
|
645
|
+
optionEl.style.borderLeft = `8px solid ${normalizeNoteColor(noteEntry.color)}`;
|
|
646
|
+
notesSelectEl.appendChild(optionEl);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
notesSelectEl.value = selectedNoteId;
|
|
650
|
+
const selectedNoteEntry = getSelectedNoteEntry();
|
|
651
|
+
notesEditorEl.disabled = !selectedNoteEntry;
|
|
652
|
+
notesEditorEl.value = selectedNoteEntry ? selectedNoteEntry.text : "";
|
|
653
|
+
newNoteColorEl.value = selectedNoteEntry
|
|
654
|
+
? normalizeNoteColor(selectedNoteEntry.color)
|
|
655
|
+
: NOTE_DEFAULT_COLOR;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function addNote(text, color = NOTE_DEFAULT_COLOR, sourceLabel = "manual") {
|
|
659
|
+
const normalizedText =
|
|
660
|
+
typeof text === "string" ? text.trim() : String(text || "").trim();
|
|
661
|
+
if (!normalizedText) {
|
|
662
|
+
statusUpdate("Status: No note text to add");
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
const noteEntry = createNoteEntry(normalizedText, color);
|
|
666
|
+
notesList.unshift(noteEntry);
|
|
667
|
+
selectedNoteId = noteEntry.id;
|
|
668
|
+
renderNotesList();
|
|
669
|
+
statusUpdate("Status: Note added");
|
|
670
|
+
writeLogEntry(`Note added source=${sourceLabel} length=${normalizedText.length}`);
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function removeSelectedNote() {
|
|
675
|
+
const selectedNoteEntry = getSelectedNoteEntry();
|
|
676
|
+
if (!selectedNoteEntry) {
|
|
677
|
+
statusUpdate("Status: No note selected to remove");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const selectedIndex = notesList.findIndex((entry) => entry.id === selectedNoteEntry.id);
|
|
681
|
+
notesList = notesList.filter((entry) => entry.id !== selectedNoteEntry.id);
|
|
682
|
+
if (notesList.length === 0) {
|
|
683
|
+
selectedNoteId = null;
|
|
684
|
+
} else if (selectedIndex >= notesList.length) {
|
|
685
|
+
selectedNoteId = notesList[notesList.length - 1].id;
|
|
686
|
+
} else {
|
|
687
|
+
selectedNoteId = notesList[Math.max(0, selectedIndex)].id;
|
|
688
|
+
}
|
|
689
|
+
renderNotesList();
|
|
690
|
+
statusUpdate("Status: Note removed");
|
|
691
|
+
writeLogEntry(`Note removed id=${selectedNoteEntry.id}`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function formatNotesForExport() {
|
|
695
|
+
if (!Array.isArray(notesList) || notesList.length === 0) return "";
|
|
696
|
+
return notesList
|
|
697
|
+
.map((noteEntry, noteIndex) => {
|
|
698
|
+
const noteText = String(noteEntry.text || "").trim();
|
|
699
|
+
return [
|
|
700
|
+
`Note ${noteIndex + 1}`,
|
|
701
|
+
`Color: ${normalizeNoteColor(noteEntry.color)}`,
|
|
702
|
+
noteText,
|
|
703
|
+
].join("\n");
|
|
704
|
+
})
|
|
705
|
+
.join("\n\n---\n\n");
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async function saveNotesToDisk() {
|
|
709
|
+
const notesExportText = formatNotesForExport();
|
|
710
|
+
if (!notesExportText) {
|
|
711
|
+
statusUpdate("Status: No notes available to save");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const result = await window.saveapi.saveNotes(notesExportText);
|
|
715
|
+
if (result?.canceled) {
|
|
716
|
+
statusUpdate("Status: Save cancelled");
|
|
717
|
+
} else if (result?.success) {
|
|
718
|
+
statusUpdate("Status: Notes saved successfully");
|
|
719
|
+
writeLogEntry(`Notes saved entries=${notesList.length}`);
|
|
720
|
+
} else {
|
|
721
|
+
const errorMessage =
|
|
722
|
+
result && typeof result === "object" && "error" in result
|
|
723
|
+
? result.error
|
|
724
|
+
: "unknown";
|
|
725
|
+
doError("Notes save failed");
|
|
726
|
+
logErrorEntry("save-notes", errorMessage || "unknown");
|
|
727
|
+
statusUpdate("Status: Notes save failed – " + (errorMessage || "unknown error"));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function buildConvConvertedOutputNoteText() {
|
|
732
|
+
const outputFields = [
|
|
733
|
+
["Hex", "data-tools-hex-output"],
|
|
734
|
+
["Binary", "data-tools-binary-output"],
|
|
735
|
+
["Decimal bytes", "data-tools-decimal-output"],
|
|
736
|
+
["Decimal integer", "data-tools-decimal-integer-output"],
|
|
737
|
+
["ASCII", "data-tools-ascii-output"],
|
|
738
|
+
["Base64", "data-tools-base64-output"],
|
|
739
|
+
];
|
|
740
|
+
const lines = outputFields
|
|
741
|
+
.map(([label, id]) => {
|
|
742
|
+
const value = document.getElementById(id)?.value?.trim() || "";
|
|
743
|
+
return value ? `${label}: ${value}` : "";
|
|
744
|
+
})
|
|
745
|
+
.filter(Boolean);
|
|
746
|
+
return lines.length > 0 ? lines.join("\n") : "";
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function buildConvHashesNoteText() {
|
|
750
|
+
const hashFields = [
|
|
751
|
+
["Input", "data-tools-hash-input-reading"],
|
|
752
|
+
["MD5", "data-tools-md5-output"],
|
|
753
|
+
["SHA-1", "data-tools-sha1-output"],
|
|
754
|
+
["SHA-256", "data-tools-sha256-output"],
|
|
755
|
+
["SHA-384", "data-tools-sha384-output"],
|
|
756
|
+
["SHA-512", "data-tools-sha512-output"],
|
|
757
|
+
["SHA3-256", "data-tools-sha3-256-output"],
|
|
758
|
+
["SHA3-512", "data-tools-sha3-512-output"],
|
|
759
|
+
["RIPEMD-160", "data-tools-ripemd160-output"],
|
|
760
|
+
["Whirlpool", "data-tools-whirlpool-output"],
|
|
761
|
+
];
|
|
762
|
+
const lines = hashFields
|
|
763
|
+
.map(([label, id]) => {
|
|
764
|
+
const value = document.getElementById(id)?.value?.trim() || "";
|
|
765
|
+
return value ? `${label}: ${value}` : "";
|
|
766
|
+
})
|
|
767
|
+
.filter(Boolean);
|
|
768
|
+
return lines.length > 0 ? lines.join("\n") : "";
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function sendTextToNotesFromContextMenu(text, sourceLabel) {
|
|
772
|
+
hideConvertContextMenu();
|
|
773
|
+
const didAdd = addNote(text, NOTE_DEFAULT_COLOR, sourceLabel);
|
|
774
|
+
if (!didAdd) return;
|
|
775
|
+
showNotesWorkspace();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function initializeNotesPanel() {
|
|
779
|
+
const addButtonEl = document.getElementById("notes-add-btn");
|
|
780
|
+
const removeButtonEl = document.getElementById("notes-remove-btn");
|
|
781
|
+
const saveButtonEl = document.getElementById("notes-save-btn");
|
|
782
|
+
const newNoteInputEl = document.getElementById("notes-new-input");
|
|
783
|
+
const newNoteColorEl = document.getElementById("notes-new-color");
|
|
784
|
+
const notesSelectEl = document.getElementById("notes-select");
|
|
785
|
+
const notesEditorEl = document.getElementById("notes-editor");
|
|
786
|
+
if (
|
|
787
|
+
!addButtonEl ||
|
|
788
|
+
!removeButtonEl ||
|
|
789
|
+
!saveButtonEl ||
|
|
790
|
+
!newNoteInputEl ||
|
|
791
|
+
!newNoteColorEl ||
|
|
792
|
+
!notesSelectEl ||
|
|
793
|
+
!notesEditorEl
|
|
794
|
+
) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
newNoteColorEl.value = NOTE_DEFAULT_COLOR;
|
|
798
|
+
|
|
799
|
+
addButtonEl.addEventListener("click", () => {
|
|
800
|
+
const didAdd = addNote(newNoteInputEl.value, newNoteColorEl.value, "notes-panel");
|
|
801
|
+
if (didAdd) {
|
|
802
|
+
newNoteInputEl.value = "";
|
|
803
|
+
newNoteInputEl.focus();
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
saveButtonEl.addEventListener("click", () => {
|
|
807
|
+
void saveNotesToDisk();
|
|
808
|
+
});
|
|
809
|
+
removeButtonEl.addEventListener("click", removeSelectedNote);
|
|
810
|
+
newNoteInputEl.addEventListener("keydown", (event) => {
|
|
811
|
+
if (!(event.ctrlKey || event.metaKey) || event.key !== "Enter") return;
|
|
812
|
+
event.preventDefault();
|
|
813
|
+
const didAdd = addNote(newNoteInputEl.value, newNoteColorEl.value, "notes-panel");
|
|
814
|
+
if (didAdd) {
|
|
815
|
+
newNoteInputEl.value = "";
|
|
816
|
+
newNoteInputEl.focus();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
notesSelectEl.addEventListener("change", () => {
|
|
820
|
+
selectedNoteId = notesSelectEl.value || null;
|
|
821
|
+
renderNotesList();
|
|
822
|
+
});
|
|
823
|
+
notesEditorEl.addEventListener("input", () => {
|
|
824
|
+
const selectedNoteEntry = getSelectedNoteEntry();
|
|
825
|
+
if (!selectedNoteEntry) return;
|
|
826
|
+
selectedNoteEntry.text = notesEditorEl.value;
|
|
827
|
+
const selectedOptionEl =
|
|
828
|
+
notesSelectEl.options[notesSelectEl.selectedIndex] || null;
|
|
829
|
+
if (selectedOptionEl) {
|
|
830
|
+
const previewText = String(selectedNoteEntry.text || "")
|
|
831
|
+
.replace(/\s+/g, " ")
|
|
832
|
+
.trim();
|
|
833
|
+
selectedOptionEl.textContent = `${notesSelectEl.selectedIndex + 1}. ${previewText || "(empty note)"}`;
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
newNoteColorEl.addEventListener("input", () => {
|
|
837
|
+
const selectedNoteEntry = getSelectedNoteEntry();
|
|
838
|
+
if (!selectedNoteEntry) return;
|
|
839
|
+
selectedNoteEntry.color = normalizeNoteColor(newNoteColorEl.value);
|
|
840
|
+
const selectedOptionEl =
|
|
841
|
+
notesSelectEl.options[notesSelectEl.selectedIndex] || null;
|
|
842
|
+
if (selectedOptionEl) {
|
|
843
|
+
selectedOptionEl.style.borderLeft = `8px solid ${selectedNoteEntry.color}`;
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
renderNotesList();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function normalizeLoadedSessionPayload(parsedPayload) {
|
|
851
|
+
if (!parsedPayload || typeof parsedPayload !== "object") {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const hasWrappedCapture =
|
|
856
|
+
parsedPayload[SESSION_CAPTURE_KEY] &&
|
|
857
|
+
typeof parsedPayload[SESSION_CAPTURE_KEY] === "object";
|
|
858
|
+
const captureData = hasWrappedCapture
|
|
859
|
+
? parsedPayload[SESSION_CAPTURE_KEY]
|
|
860
|
+
: parsedPayload;
|
|
861
|
+
const sessionState =
|
|
862
|
+
hasWrappedCapture && parsedPayload[SESSION_STATE_KEY]
|
|
863
|
+
? parsedPayload[SESSION_STATE_KEY]
|
|
864
|
+
: null;
|
|
865
|
+
|
|
866
|
+
if (
|
|
867
|
+
!captureData ||
|
|
868
|
+
typeof captureData !== "object" ||
|
|
869
|
+
!captureData["Host"] ||
|
|
870
|
+
typeof captureData["Host"] !== "object"
|
|
871
|
+
) {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return {
|
|
876
|
+
captureData,
|
|
877
|
+
sessionState:
|
|
878
|
+
sessionState && typeof sessionState === "object" ? sessionState : null,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function rebuildBookmarkDropdown() {
|
|
883
|
+
const selectBookmarkEl = document.getElementById("selectBookmark");
|
|
884
|
+
while (selectBookmarkEl.options.length > 1) {
|
|
885
|
+
selectBookmarkEl.remove(1);
|
|
886
|
+
}
|
|
887
|
+
bookmarkList.forEach((bookmarkKey) => {
|
|
888
|
+
selectBookmarkEl.appendChild(new Option(bookmarkKey, bookmarkKey));
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function getSessionPacketViewMode() {
|
|
893
|
+
if (
|
|
894
|
+
Array.isArray(filteredPackets) &&
|
|
895
|
+
filteredPackets.length > 0 &&
|
|
896
|
+
packetsForHost === filteredPackets
|
|
897
|
+
) {
|
|
898
|
+
return "filtered";
|
|
899
|
+
}
|
|
900
|
+
return "host";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function buildSessionStateSnapshot() {
|
|
904
|
+
const listSearchEl = document.getElementById("list-search");
|
|
905
|
+
const listGroupStreamsEl = document.getElementById("list-group-streams");
|
|
906
|
+
return {
|
|
907
|
+
schemaVersion: SESSION_FILE_SCHEMA_VERSION,
|
|
908
|
+
savedAt: new Date().toISOString(),
|
|
909
|
+
currentFilterQuery: filterInputEl.value || "",
|
|
910
|
+
filterHistory: [...filterHistory],
|
|
911
|
+
currentPacketKey: currentPacketKey || null,
|
|
912
|
+
activePacketCursor: getActivePacketCursor(),
|
|
913
|
+
packetViewMode: getSessionPacketViewMode(),
|
|
914
|
+
selectedHost:
|
|
915
|
+
document.getElementById("target_hosts")?.value || hostFilterEl.value || "",
|
|
916
|
+
bookmarkList: [...bookmarkList],
|
|
917
|
+
sessionKeychainEntries: deepCloneSessionData(
|
|
918
|
+
keystorePanel.getSessionKeychainEntries(),
|
|
919
|
+
[],
|
|
920
|
+
),
|
|
921
|
+
keystoreMode: keystorePanel.getKeystoreMode(),
|
|
922
|
+
notes: deepCloneSessionData(notesList, []),
|
|
923
|
+
tabs: {
|
|
924
|
+
main: activeMainTab,
|
|
925
|
+
conv: getActiveConvSubtab(),
|
|
926
|
+
crypt: activeCryptSubtab,
|
|
927
|
+
listSearch: listSearchEl ? listSearchEl.value : "",
|
|
928
|
+
listGroupStreams: listGroupStreamsEl ? Boolean(listGroupStreamsEl.checked) : false,
|
|
929
|
+
},
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function buildSessionFilePayload() {
|
|
934
|
+
return JSON.stringify(
|
|
935
|
+
{
|
|
936
|
+
[SESSION_CAPTURE_KEY]: capturedPackets,
|
|
937
|
+
[SESSION_STATE_KEY]: buildSessionStateSnapshot(),
|
|
938
|
+
},
|
|
939
|
+
null,
|
|
940
|
+
2,
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async function persistSessionToDisk(sourceLabel = "manual-save") {
|
|
945
|
+
if (
|
|
946
|
+
!capturedPackets ||
|
|
947
|
+
!capturedPackets["Host"] ||
|
|
948
|
+
typeof capturedPackets["Host"] !== "object"
|
|
949
|
+
) {
|
|
950
|
+
statusUpdate("Status: No data loaded to save");
|
|
951
|
+
return { success: false, error: "No loaded capture" };
|
|
952
|
+
}
|
|
953
|
+
const sessionJsonData = buildSessionFilePayload();
|
|
954
|
+
const result = await window.saveapi.saveJson(sessionJsonData);
|
|
955
|
+
if (result.canceled) {
|
|
956
|
+
statusUpdate("Status: Save cancelled");
|
|
957
|
+
} else if (result.success) {
|
|
958
|
+
statusUpdate("Status: Session saved successfully");
|
|
959
|
+
writeLogEntry(`Session saved source=${sourceLabel}`);
|
|
960
|
+
} else {
|
|
961
|
+
const errorMessage =
|
|
962
|
+
result && typeof result === "object" && "error" in result
|
|
963
|
+
? result.error
|
|
964
|
+
: "unknown";
|
|
965
|
+
doError("Save failed");
|
|
966
|
+
logErrorEntry("save-session", errorMessage || "unknown");
|
|
967
|
+
statusUpdate("Status: Save failed – " + (errorMessage || "unknown error"));
|
|
968
|
+
console.error("Save failed:", errorMessage);
|
|
969
|
+
}
|
|
970
|
+
return result;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async function maybePromptSaveSessionOnExit() {
|
|
974
|
+
if (!isFileLoaded || !capturedPackets || !capturedPackets["Host"]) {
|
|
975
|
+
return "discard";
|
|
976
|
+
}
|
|
977
|
+
const dialogEl = document.getElementById("save-session-dialog");
|
|
978
|
+
if (!dialogEl) {
|
|
979
|
+
return window.confirm("Save session before exit?") ? "save" : "cancel";
|
|
980
|
+
}
|
|
981
|
+
dialogEl.hidden = false;
|
|
982
|
+
return new Promise((resolve) => {
|
|
983
|
+
function cleanup(result) {
|
|
984
|
+
dialogEl.hidden = true;
|
|
985
|
+
document
|
|
986
|
+
.getElementById("save-session-save-btn")
|
|
987
|
+
.removeEventListener("click", onSave);
|
|
988
|
+
document
|
|
989
|
+
.getElementById("save-session-discard-btn")
|
|
990
|
+
.removeEventListener("click", onDiscard);
|
|
991
|
+
document
|
|
992
|
+
.getElementById("save-session-cancel-btn")
|
|
993
|
+
.removeEventListener("click", onCancel);
|
|
994
|
+
resolve(result);
|
|
995
|
+
}
|
|
996
|
+
function onSave() { cleanup("save"); }
|
|
997
|
+
function onDiscard() { cleanup("discard"); }
|
|
998
|
+
function onCancel() { cleanup("cancel"); }
|
|
999
|
+
document.getElementById("save-session-save-btn").addEventListener("click", onSave);
|
|
1000
|
+
document.getElementById("save-session-discard-btn").addEventListener("click", onDiscard);
|
|
1001
|
+
document.getElementById("save-session-cancel-btn").addEventListener("click", onCancel);
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async function requestApplicationClose() {
|
|
1006
|
+
const exitAction = await maybePromptSaveSessionOnExit();
|
|
1007
|
+
if (exitAction === "cancel") {
|
|
1008
|
+
statusUpdate("Status: Exit cancelled");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (exitAction === "save") {
|
|
1012
|
+
const saveResult = await persistSessionToDisk("exit-prompt");
|
|
1013
|
+
if (!saveResult?.success) {
|
|
1014
|
+
if (saveResult?.canceled) {
|
|
1015
|
+
statusUpdate("Status: Exit cancelled");
|
|
1016
|
+
} else {
|
|
1017
|
+
statusUpdate("Status: Exit cancelled due to save failure");
|
|
1018
|
+
}
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
window.quitapi.quitApp();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function restoreSessionState(sessionState) {
|
|
1026
|
+
if (!sessionState || typeof sessionState !== "object") return;
|
|
1027
|
+
|
|
1028
|
+
const loadedHistory = Array.isArray(sessionState.filterHistory)
|
|
1029
|
+
? sessionState.filterHistory
|
|
1030
|
+
.filter((query) => typeof query === "string")
|
|
1031
|
+
.map((query) => query.trim())
|
|
1032
|
+
.filter(Boolean)
|
|
1033
|
+
: [];
|
|
1034
|
+
filterHistory.splice(0, filterHistory.length, ...loadedHistory);
|
|
1035
|
+
renderFilterHistory();
|
|
1036
|
+
|
|
1037
|
+
const loadedBookmarks = Array.isArray(sessionState.bookmarkList)
|
|
1038
|
+
? sessionState.bookmarkList.filter(
|
|
1039
|
+
(bookmark) => typeof bookmark === "string" && bookmark.trim() !== "",
|
|
1040
|
+
)
|
|
1041
|
+
: [];
|
|
1042
|
+
bookmarkList = loadedBookmarks;
|
|
1043
|
+
rebuildBookmarkDropdown();
|
|
1044
|
+
|
|
1045
|
+
const loadedSessionEntries = Array.isArray(sessionState.sessionKeychainEntries)
|
|
1046
|
+
? sessionState.sessionKeychainEntries.filter(
|
|
1047
|
+
(entry) => entry && typeof entry === "object",
|
|
1048
|
+
)
|
|
1049
|
+
: [];
|
|
1050
|
+
keystorePanel.restoreSessionState(
|
|
1051
|
+
deepCloneSessionData(loadedSessionEntries, []),
|
|
1052
|
+
sessionState.keystoreMode,
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
const loadedNotes = Array.isArray(sessionState.notes)
|
|
1056
|
+
? sessionState.notes
|
|
1057
|
+
.filter((note) => note && typeof note === "object")
|
|
1058
|
+
.map((note) => ({
|
|
1059
|
+
id:
|
|
1060
|
+
typeof note.id === "string" && note.id.trim()
|
|
1061
|
+
? note.id
|
|
1062
|
+
: generateNoteId(),
|
|
1063
|
+
text: typeof note.text === "string" ? note.text : String(note.text || ""),
|
|
1064
|
+
color: normalizeNoteColor(note.color),
|
|
1065
|
+
}))
|
|
1066
|
+
: [];
|
|
1067
|
+
notesList = loadedNotes;
|
|
1068
|
+
selectedNoteId = notesList.length > 0 ? notesList[0].id : null;
|
|
1069
|
+
renderNotesList();
|
|
1070
|
+
|
|
1071
|
+
const selectedHost = String(sessionState.selectedHost || "").trim();
|
|
1072
|
+
if (selectedHost && capturedPackets?.["Host"]?.[selectedHost]) {
|
|
1073
|
+
const targetHostsEl = document.getElementById("target_hosts");
|
|
1074
|
+
if (targetHostsEl) {
|
|
1075
|
+
targetHostsEl.value = selectedHost;
|
|
1076
|
+
}
|
|
1077
|
+
hostFilterEl.value = selectedHost;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const restoredFilterQuery =
|
|
1081
|
+
typeof sessionState.currentFilterQuery === "string"
|
|
1082
|
+
? sessionState.currentFilterQuery
|
|
1083
|
+
: "";
|
|
1084
|
+
filterInputEl.value = restoredFilterQuery;
|
|
1085
|
+
syncFilterHighlight();
|
|
1086
|
+
if (restoredFilterQuery.trim()) {
|
|
1087
|
+
runFilterQuery(restoredFilterQuery, { trackHistory: false });
|
|
1088
|
+
} else {
|
|
1089
|
+
filteredPackets = [];
|
|
1090
|
+
document.getElementById("filter-returned").textContent = "Filtered Packets: 0";
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
currentPacketKey =
|
|
1094
|
+
typeof sessionState.currentPacketKey === "string"
|
|
1095
|
+
? sessionState.currentPacketKey
|
|
1096
|
+
: null;
|
|
1097
|
+
setActivePacketCursor(sessionState.activePacketCursor);
|
|
1098
|
+
|
|
1099
|
+
const navAction =
|
|
1100
|
+
sessionState.packetViewMode === "filtered" &&
|
|
1101
|
+
Array.isArray(filteredPackets) &&
|
|
1102
|
+
filteredPackets.length > 0
|
|
1103
|
+
? "filtered"
|
|
1104
|
+
: "first-load";
|
|
1105
|
+
handlePacketNavigation(navAction);
|
|
1106
|
+
|
|
1107
|
+
const tabState = sessionState.tabs && typeof sessionState.tabs === "object"
|
|
1108
|
+
? sessionState.tabs
|
|
1109
|
+
: {};
|
|
1110
|
+
const savedMainTab =
|
|
1111
|
+
typeof tabState.main === "string" && VALID_MAIN_TABS.includes(tabState.main)
|
|
1112
|
+
? tabState.main
|
|
1113
|
+
: MAIN_TAB_DATA;
|
|
1114
|
+
const savedConvTab = VALID_CONV_SUBTABS.includes(tabState.conv)
|
|
1115
|
+
? tabState.conv
|
|
1116
|
+
: CONV_CONVERSIONS_SUBTAB;
|
|
1117
|
+
const savedCryptTab = VALID_CRYPT_SUBTABS.includes(tabState.crypt)
|
|
1118
|
+
? tabState.crypt
|
|
1119
|
+
: CRYPT_SSL_SUBTAB;
|
|
1120
|
+
|
|
1121
|
+
if (savedMainTab === MAIN_TAB_SUMMARY) {
|
|
1122
|
+
showSummary();
|
|
1123
|
+
} else if (savedMainTab === MAIN_TAB_STATS) {
|
|
1124
|
+
showStats();
|
|
1125
|
+
} else if (savedMainTab === MAIN_TAB_LIST) {
|
|
1126
|
+
showPacketList();
|
|
1127
|
+
const listSearchEl = document.getElementById("list-search");
|
|
1128
|
+
const listGroupStreamsEl = document.getElementById("list-group-streams");
|
|
1129
|
+
if (listSearchEl && typeof tabState.listSearch === "string") {
|
|
1130
|
+
listSearchEl.value = tabState.listSearch;
|
|
1131
|
+
listSearchEl.dispatchEvent(new Event("input"));
|
|
1132
|
+
}
|
|
1133
|
+
if (listGroupStreamsEl && typeof tabState.listGroupStreams === "boolean") {
|
|
1134
|
+
listGroupStreamsEl.checked = tabState.listGroupStreams;
|
|
1135
|
+
listGroupStreamsEl.dispatchEvent(new Event("change"));
|
|
1136
|
+
}
|
|
1137
|
+
} else if (savedMainTab === MAIN_TAB_NOTES) {
|
|
1138
|
+
showNotesWorkspace();
|
|
1139
|
+
} else if (savedMainTab === MAIN_TAB_DATA_TOOLS) {
|
|
1140
|
+
showDataTools(savedConvTab);
|
|
1141
|
+
} else if (savedMainTab === MAIN_TAB_CRYPT) {
|
|
1142
|
+
showCryptWorkspace(savedCryptTab);
|
|
1143
|
+
} else if (savedMainTab === MAIN_TAB_KEYSTORE && keystorePanel.isUnlocked()) {
|
|
1144
|
+
keystorePanel.showKeystoreWorkspace();
|
|
1145
|
+
} else {
|
|
1146
|
+
initializeDataView();
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (savedMainTab !== MAIN_TAB_DATA_TOOLS) {
|
|
1150
|
+
setConvSubtab(savedConvTab);
|
|
1151
|
+
}
|
|
1152
|
+
if (savedMainTab !== MAIN_TAB_CRYPT) {
|
|
1153
|
+
setCryptSubtab(savedCryptTab);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
writeLogEntry("Session state restored from JSON");
|
|
1157
|
+
statusUpdate("Status: Session restored");
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Reads and parses the JSON file, updates UI and state.
|
|
1162
|
+
* Uses chunked parsing for large files to avoid UI blocking.
|
|
1163
|
+
*/
|
|
1164
|
+
function processFile(file) {
|
|
1165
|
+
let loadedSessionState = null;
|
|
1166
|
+
const reader = new FileReader();
|
|
1167
|
+
reader.onload = (event) => {
|
|
1168
|
+
const mainPanel = getCachedElement("main");
|
|
1169
|
+
if (isValidJson(event.target.result) == false) {
|
|
1170
|
+
console.log("Invalid JSON file");
|
|
1171
|
+
doError("Invalid JSON file, please upload a valid JSON capture!");
|
|
1172
|
+
fileLoaded(false);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
fileLoaded(true);
|
|
1176
|
+
jsonOfPackets = event.target.result;
|
|
1177
|
+
getCachedElement("error-container").style.display = "none";
|
|
1178
|
+
|
|
1179
|
+
// Use chunked parsing for large files (>1MB)
|
|
1180
|
+
const fileSize = event.target.result.length;
|
|
1181
|
+
if (fileSize > 1024 * 1024) {
|
|
1182
|
+
statusUpdate(
|
|
1183
|
+
"Status: Parsing large file (" +
|
|
1184
|
+
(fileSize / 1024 / 1024).toFixed(2) +
|
|
1185
|
+
"MB)...",
|
|
1186
|
+
);
|
|
1187
|
+
parseJsonChunked(event.target.result)
|
|
1188
|
+
.then((parsed) => {
|
|
1189
|
+
const normalizedPayload = normalizeLoadedSessionPayload(parsed);
|
|
1190
|
+
if (!normalizedPayload) {
|
|
1191
|
+
doError("Invalid JSON file, please upload a valid capture/session file!");
|
|
1192
|
+
fileLoaded(false);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
capturedPackets = normalizedPayload.captureData;
|
|
1196
|
+
loadedSessionState = normalizedPayload.sessionState;
|
|
1197
|
+
jsonCapture = JSON.stringify(capturedPackets, null, 2);
|
|
1198
|
+
finalSummary = capturedPackets["Final Summary"] ?? "";
|
|
1199
|
+
finishProcessingFile();
|
|
1200
|
+
})
|
|
1201
|
+
.catch((e) => {
|
|
1202
|
+
console.error("JSON parse error:", e);
|
|
1203
|
+
logErrorEntry("json-parse", e);
|
|
1204
|
+
doError("Error parsing JSON file!");
|
|
1205
|
+
});
|
|
1206
|
+
} else {
|
|
1207
|
+
const normalizedPayload = normalizeLoadedSessionPayload(
|
|
1208
|
+
JSON.parse(event.target.result),
|
|
1209
|
+
);
|
|
1210
|
+
if (!normalizedPayload) {
|
|
1211
|
+
doError("Invalid JSON file, please upload a valid capture/session file!");
|
|
1212
|
+
fileLoaded(false);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
capturedPackets = normalizedPayload.captureData;
|
|
1216
|
+
loadedSessionState = normalizedPayload.sessionState;
|
|
1217
|
+
jsonCapture = JSON.stringify(capturedPackets, null, 2);
|
|
1218
|
+
finalSummary = capturedPackets["Final Summary"] ?? "";
|
|
1219
|
+
finishProcessingFile();
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
function finishProcessingFile() {
|
|
1224
|
+
getCachedElement("target_hosts").hidden = false;
|
|
1225
|
+
getCachedElement("summary-btn").style.display = "block";
|
|
1226
|
+
// Reset host list and dropdowns for the new file
|
|
1227
|
+
hostsList = ["0.0.0.0"];
|
|
1228
|
+
const targetHostsDropdown = getCachedElement("target_hosts");
|
|
1229
|
+
while (targetHostsDropdown.options.length > 0) {
|
|
1230
|
+
targetHostsDropdown.remove(0);
|
|
1231
|
+
}
|
|
1232
|
+
bookmarkList = [];
|
|
1233
|
+
notesList = [];
|
|
1234
|
+
selectedNoteId = null;
|
|
1235
|
+
renderNotesList();
|
|
1236
|
+
const selectBookmarkEl = document.getElementById("selectBookmark");
|
|
1237
|
+
while (selectBookmarkEl.options.length > 1) {
|
|
1238
|
+
selectBookmarkEl.remove(1);
|
|
1239
|
+
}
|
|
1240
|
+
// Populate host dropdown with hosts from JSON
|
|
1241
|
+
for (const host in capturedPackets["Host"]) {
|
|
1242
|
+
hostsList.push(host);
|
|
1243
|
+
const newhost = document.createElement("option");
|
|
1244
|
+
newhost.textContent = host;
|
|
1245
|
+
newhost.value = host;
|
|
1246
|
+
targetHostsDropdown.appendChild(newhost);
|
|
1247
|
+
isFileLoaded = true;
|
|
1248
|
+
}
|
|
1249
|
+
writeLogEntry(`Hosts targeted discovered count=${hostsList.length - 1}`);
|
|
1250
|
+
const keystoreEntryCount = keystorePanel.rebuildSessionEntries();
|
|
1251
|
+
writeLogEntry(
|
|
1252
|
+
`Session keychain auto-populated entries=${keystoreEntryCount}`,
|
|
1253
|
+
);
|
|
1254
|
+
const timeframe = getPacketTimeframe();
|
|
1255
|
+
if (timeframe) {
|
|
1256
|
+
writeLogEntry(
|
|
1257
|
+
`Packet timeframe start="${timeframe.first}" end="${timeframe.last}"`,
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
writeLogEntry(`Total packet count=${totalPacketCount()}`);
|
|
1261
|
+
showSummary();
|
|
1262
|
+
initializeDataView();
|
|
1263
|
+
if (loadedSessionState) {
|
|
1264
|
+
restoreSessionState(loadedSessionState);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
reader.onerror = (error) => {
|
|
1268
|
+
status.textContent = "Status: Error reading file: " + error;
|
|
1269
|
+
logErrorEntry("file-read", error);
|
|
1270
|
+
doError("Error reading file!");
|
|
1271
|
+
};
|
|
1272
|
+
reader.readAsText(file);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Updates the status bar with a message, then resets after 6 seconds.
|
|
1277
|
+
*/
|
|
1278
|
+
function statusUpdate(message) {
|
|
1279
|
+
status.textContent = message;
|
|
1280
|
+
setTimeout(() => {
|
|
1281
|
+
status.textContent = "PacketSnitch " + psVer + ": Ready";
|
|
1282
|
+
}, 6000);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Loads all capturedPackets for a given host IP into packetsForHost.
|
|
1287
|
+
*/
|
|
1288
|
+
function hostPacketInfo(currentIp) {
|
|
1289
|
+
const selected = currentIp;
|
|
1290
|
+
packetsForHost = [];
|
|
1291
|
+
const hostPackets = capturedPackets["Host"][selected];
|
|
1292
|
+
for (const packet in hostPackets) {
|
|
1293
|
+
packetsForHost.push(hostPackets[packet]);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Use event delegation for dynamically created elements
|
|
1298
|
+
// and cache static elements at module load
|
|
1299
|
+
const navButtons = {
|
|
1300
|
+
prev: getCachedElement("prev-btn"),
|
|
1301
|
+
next: getCachedElement("next-btn"),
|
|
1302
|
+
summary: getCachedElement("summary-btn"),
|
|
1303
|
+
data: getCachedElement("data-btn"),
|
|
1304
|
+
setBookmark: getCachedElement("setBookmark"),
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
// Update host filter when a new host is selected from dropdown
|
|
1308
|
+
getCachedElement("target_hosts").addEventListener("change", function () {
|
|
1309
|
+
const selected = getCachedElement("target_hosts").value;
|
|
1310
|
+
let hostFilterEl = getCachedElement("host_filter");
|
|
1311
|
+
filteredPackets = []; // reset filter when host changes
|
|
1312
|
+
writeLogEntry(`Host target changed host=${selected}`);
|
|
1313
|
+
if (hostFilterEl.value !== selected) {
|
|
1314
|
+
hostFilterEl.value = selected;
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
getCachedElement("target_hosts").addEventListener("click", function () {
|
|
1319
|
+
const selected = getCachedElement("target_hosts").value;
|
|
1320
|
+
filteredPackets = filterPackets(
|
|
1321
|
+
capturedPackets,
|
|
1322
|
+
"ip.src.addr: " + selected + "|| ip.dst.addr: " + selected,
|
|
1323
|
+
);
|
|
1324
|
+
writeLogEntry(
|
|
1325
|
+
`Host target clicked host=${selected} packets_returned=${filteredPackets.length}`,
|
|
1326
|
+
);
|
|
1327
|
+
handlePacketNavigation("filtered", null);
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
function parseDataToolsInput(format, rawInput) {
|
|
1331
|
+
if (!rawInput || rawInput.trim() === "") {
|
|
1332
|
+
throw new Error("Enter input data first.");
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (format === "hex") {
|
|
1336
|
+
const normalized = rawInput
|
|
1337
|
+
.replace(/0x/gi, "")
|
|
1338
|
+
.replace(/[\s,:;-]+/g, "")
|
|
1339
|
+
.trim();
|
|
1340
|
+
if (!normalized) throw new Error("No hex bytes were found.");
|
|
1341
|
+
if (!/^[0-9a-fA-F]+$/.test(normalized)) {
|
|
1342
|
+
throw new Error("Hex input can only contain 0-9 and A-F.");
|
|
1343
|
+
}
|
|
1344
|
+
if (normalized.length % 2 !== 0) {
|
|
1345
|
+
throw new Error("Hex input must contain an even number of characters.");
|
|
1346
|
+
}
|
|
1347
|
+
const bytes = new Uint8Array(normalized.length / 2);
|
|
1348
|
+
for (let i = 0; i < normalized.length; i += 2) {
|
|
1349
|
+
bytes[i / 2] = parseInt(normalized.slice(i, i + 2), 16);
|
|
1350
|
+
}
|
|
1351
|
+
return bytes;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (format === "binary") {
|
|
1355
|
+
const normalized = rawInput.replace(/\s+/g, "");
|
|
1356
|
+
if (!normalized) throw new Error("No binary bits were found.");
|
|
1357
|
+
if (!/^[01]+$/.test(normalized)) {
|
|
1358
|
+
throw new Error("Binary input can only contain 0 and 1.");
|
|
1359
|
+
}
|
|
1360
|
+
if (normalized.length % 8 !== 0) {
|
|
1361
|
+
throw new Error("Binary input must be grouped into full 8-bit bytes.");
|
|
1362
|
+
}
|
|
1363
|
+
const bytes = new Uint8Array(normalized.length / 8);
|
|
1364
|
+
for (let i = 0; i < normalized.length; i += 8) {
|
|
1365
|
+
bytes[i / 8] = parseInt(normalized.slice(i, i + 8), 2);
|
|
1366
|
+
}
|
|
1367
|
+
return bytes;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (format === "base64") {
|
|
1371
|
+
const normalized = rawInput
|
|
1372
|
+
.trim()
|
|
1373
|
+
.replace(/^data:[^;]+;base64,/i, "")
|
|
1374
|
+
.replace(/\s+/g, "");
|
|
1375
|
+
if (!normalized) throw new Error("No base64 content was found.");
|
|
1376
|
+
let decoded = "";
|
|
1377
|
+
try {
|
|
1378
|
+
decoded = atob(normalized);
|
|
1379
|
+
} catch {
|
|
1380
|
+
throw new Error("Invalid base64 input.");
|
|
1381
|
+
}
|
|
1382
|
+
const bytes = new Uint8Array(decoded.length);
|
|
1383
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
1384
|
+
bytes[i] = decoded.charCodeAt(i);
|
|
1385
|
+
}
|
|
1386
|
+
return bytes;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (format === "decimal") {
|
|
1390
|
+
const tokens = rawInput.split(/[\s,]+/).filter(Boolean);
|
|
1391
|
+
if (!tokens.length) throw new Error("No decimal byte values were found.");
|
|
1392
|
+
const values = tokens.map((token) => {
|
|
1393
|
+
const parsed = Number(token);
|
|
1394
|
+
if (!/^\d+$/.test(token) || parsed > 255) {
|
|
1395
|
+
throw new Error(
|
|
1396
|
+
"Each decimal value must be a non-negative integer between 0 and 255.",
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
return parsed;
|
|
1400
|
+
});
|
|
1401
|
+
return Uint8Array.from(values);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// ascii / utf-8 fallback
|
|
1405
|
+
return new TextEncoder().encode(rawInput);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function bytesToBase64(bytes) {
|
|
1409
|
+
let binary = "";
|
|
1410
|
+
bytes.forEach((byte) => {
|
|
1411
|
+
binary += String.fromCharCode(byte);
|
|
1412
|
+
});
|
|
1413
|
+
return btoa(binary);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function bytesToPrintableAscii(bytes) {
|
|
1417
|
+
return [...bytes]
|
|
1418
|
+
.map((byte) =>
|
|
1419
|
+
byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".",
|
|
1420
|
+
)
|
|
1421
|
+
.join("");
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function bytesToBigIntDecimal(bytes) {
|
|
1425
|
+
let total = 0n;
|
|
1426
|
+
bytes.forEach((byte) => {
|
|
1427
|
+
total = (total << 8n) + BigInt(byte);
|
|
1428
|
+
});
|
|
1429
|
+
return total.toString(10);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function calculateShannonEntropy(bytes) {
|
|
1433
|
+
if (!bytes.length) return 0;
|
|
1434
|
+
const counts = new Array(256).fill(0);
|
|
1435
|
+
bytes.forEach((byte) => {
|
|
1436
|
+
counts[byte] += 1;
|
|
1437
|
+
});
|
|
1438
|
+
let entropy = 0;
|
|
1439
|
+
counts.forEach((count) => {
|
|
1440
|
+
if (!count) return;
|
|
1441
|
+
const p = count / bytes.length;
|
|
1442
|
+
entropy -= p * Math.log2(p);
|
|
1443
|
+
});
|
|
1444
|
+
return entropy;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
function inferMimeType(bytes) {
|
|
1448
|
+
if (!bytes || !bytes.length) return "application/octet-stream";
|
|
1449
|
+
|
|
1450
|
+
const startsWith = (signature) =>
|
|
1451
|
+
signature.every((value, index) => bytes[index] === value);
|
|
1452
|
+
if (startsWith([0x89, 0x50, 0x4e, 0x47])) return "image/png";
|
|
1453
|
+
if (startsWith([0xff, 0xd8, 0xff])) return "image/jpeg";
|
|
1454
|
+
if (startsWith([0x47, 0x49, 0x46, 0x38])) return "image/gif";
|
|
1455
|
+
if (startsWith([0x25, 0x50, 0x44, 0x46])) return "application/pdf";
|
|
1456
|
+
if (startsWith([0x50, 0x4b, 0x03, 0x04])) return "application/zip";
|
|
1457
|
+
if (startsWith([0x1f, 0x8b])) return "application/gzip";
|
|
1458
|
+
if (startsWith([0x7f, 0x45, 0x4c, 0x46])) return "application/x-elf";
|
|
1459
|
+
|
|
1460
|
+
const utf8Text = new TextDecoder().decode(bytes);
|
|
1461
|
+
const trimmed = utf8Text.trim();
|
|
1462
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1463
|
+
try {
|
|
1464
|
+
JSON.parse(trimmed);
|
|
1465
|
+
return "application/json";
|
|
1466
|
+
} catch {
|
|
1467
|
+
// Keep evaluating as plain text/binary.
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const printableChars = [...utf8Text].filter((ch) => {
|
|
1472
|
+
const code = ch.charCodeAt(0);
|
|
1473
|
+
return (
|
|
1474
|
+
(code >= 32 && code <= 126) || ch === "\n" || ch === "\r" || ch === "\t"
|
|
1475
|
+
);
|
|
1476
|
+
}).length;
|
|
1477
|
+
if (
|
|
1478
|
+
utf8Text.length > 0 &&
|
|
1479
|
+
printableChars / utf8Text.length > DATA_TOOLS_TEXT_MIME_PRINTABLE_THRESHOLD
|
|
1480
|
+
) {
|
|
1481
|
+
return "text/plain; charset=utf-8";
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
return "application/octet-stream";
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function getEntropyLabel(entropy) {
|
|
1488
|
+
if (entropy >= DATA_TOOLS_ENTROPY_HIGH_THRESHOLD) return "High";
|
|
1489
|
+
if (entropy >= DATA_TOOLS_ENTROPY_MEDIUM_THRESHOLD) return "Medium";
|
|
1490
|
+
return "Low";
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function resetDataToolsOutputs() {
|
|
1494
|
+
document.getElementById("data-tools-hex-output").value = "";
|
|
1495
|
+
document.getElementById("data-tools-binary-output").value = "";
|
|
1496
|
+
document.getElementById("data-tools-decimal-output").value = "";
|
|
1497
|
+
document.getElementById("data-tools-decimal-integer-output").value = "";
|
|
1498
|
+
document.getElementById("data-tools-ascii-output").value = "";
|
|
1499
|
+
document.getElementById("data-tools-base64-output").value = "";
|
|
1500
|
+
document.getElementById("data-tools-byte-length").textContent =
|
|
1501
|
+
"Byte Length: 0";
|
|
1502
|
+
document.getElementById("data-tools-mime-type").textContent =
|
|
1503
|
+
"MIME Type: Unknown";
|
|
1504
|
+
document.getElementById("data-tools-entropy").textContent =
|
|
1505
|
+
"Shannon Entropy: 0.00 (Low)";
|
|
1506
|
+
resetHashOutputs();
|
|
1507
|
+
clearProtoDecoderOutput();
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const HASH_IDS = [
|
|
1511
|
+
"data-tools-md5-output",
|
|
1512
|
+
"data-tools-sha1-output",
|
|
1513
|
+
"data-tools-sha256-output",
|
|
1514
|
+
"data-tools-sha384-output",
|
|
1515
|
+
"data-tools-sha512-output",
|
|
1516
|
+
"data-tools-sha3-256-output",
|
|
1517
|
+
"data-tools-sha3-512-output",
|
|
1518
|
+
"data-tools-ripemd160-output",
|
|
1519
|
+
"data-tools-whirlpool-output",
|
|
1520
|
+
];
|
|
1521
|
+
|
|
1522
|
+
function resetHashOutputs() {
|
|
1523
|
+
for (const id of HASH_IDS) {
|
|
1524
|
+
document.getElementById(id).value = "";
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function bytesToCharString(bytes) {
|
|
1529
|
+
const CHUNK_SIZE = 0x8000;
|
|
1530
|
+
let result = "";
|
|
1531
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
1532
|
+
const chunk = bytes.subarray(i, i + CHUNK_SIZE);
|
|
1533
|
+
result += String.fromCharCode(...chunk);
|
|
1534
|
+
}
|
|
1535
|
+
return result;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function computeDataToolsHashes(bytes) {
|
|
1539
|
+
const wordArray = CryptoJS.lib.WordArray.create(bytes);
|
|
1540
|
+
const byteString = bytesToCharString(bytes);
|
|
1541
|
+
|
|
1542
|
+
document.getElementById("data-tools-md5-output").value =
|
|
1543
|
+
CryptoJS.MD5(wordArray).toString(CryptoJS.enc.Hex);
|
|
1544
|
+
document.getElementById("data-tools-sha1-output").value =
|
|
1545
|
+
CryptoJS.SHA1(wordArray).toString(CryptoJS.enc.Hex);
|
|
1546
|
+
document.getElementById("data-tools-sha256-output").value =
|
|
1547
|
+
CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Hex);
|
|
1548
|
+
document.getElementById("data-tools-sha384-output").value =
|
|
1549
|
+
CryptoJS.SHA384(wordArray).toString(CryptoJS.enc.Hex);
|
|
1550
|
+
document.getElementById("data-tools-sha512-output").value =
|
|
1551
|
+
CryptoJS.SHA512(wordArray).toString(CryptoJS.enc.Hex);
|
|
1552
|
+
document.getElementById("data-tools-sha3-256-output").value = sha3_256(bytes);
|
|
1553
|
+
document.getElementById("data-tools-sha3-512-output").value = sha3_512(bytes);
|
|
1554
|
+
document.getElementById("data-tools-ripemd160-output").value =
|
|
1555
|
+
CryptoJS.RIPEMD160(wordArray).toString(CryptoJS.enc.Hex);
|
|
1556
|
+
const whirlpoolHash =
|
|
1557
|
+
bytes.length > 0 ? whirlpool.encSync(byteString, "hex") : "";
|
|
1558
|
+
document.getElementById("data-tools-whirlpool-output").value = whirlpoolHash;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function runDataToolsConversion() {
|
|
1562
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
1563
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
1564
|
+
const errorEl = document.getElementById("data-tools-error");
|
|
1565
|
+
|
|
1566
|
+
try {
|
|
1567
|
+
const bytes = parseDataToolsInput(formatEl.value, inputEl.value);
|
|
1568
|
+
const hexSpaced = [...bytes]
|
|
1569
|
+
.map((byte) => byte.toString(16).padStart(2, "0").toUpperCase())
|
|
1570
|
+
.join(" ");
|
|
1571
|
+
const binarySpaced = [...bytes]
|
|
1572
|
+
.map((byte) => byte.toString(2).padStart(8, "0"))
|
|
1573
|
+
.join(" ");
|
|
1574
|
+
const decimalBytes = [...bytes].join(" ");
|
|
1575
|
+
const asciiPreview = bytesToPrintableAscii(bytes);
|
|
1576
|
+
const base64Value = bytesToBase64(bytes);
|
|
1577
|
+
const entropy = calculateShannonEntropy(bytes);
|
|
1578
|
+
const entropyLabel = getEntropyLabel(entropy);
|
|
1579
|
+
const decimalInteger =
|
|
1580
|
+
bytes.length > DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES
|
|
1581
|
+
? `Input exceeds ${DATA_TOOLS_MAX_DECIMAL_INTEGER_BYTES} bytes for decimal integer display`
|
|
1582
|
+
: bytesToBigIntDecimal(bytes);
|
|
1583
|
+
|
|
1584
|
+
document.getElementById("data-tools-hex-output").value = hexSpaced;
|
|
1585
|
+
document.getElementById("data-tools-binary-output").value = binarySpaced;
|
|
1586
|
+
document.getElementById("data-tools-decimal-output").value = decimalBytes;
|
|
1587
|
+
document.getElementById("data-tools-decimal-integer-output").value =
|
|
1588
|
+
decimalInteger;
|
|
1589
|
+
document.getElementById("data-tools-ascii-output").value = asciiPreview;
|
|
1590
|
+
document.getElementById("data-tools-base64-output").value = base64Value;
|
|
1591
|
+
document.getElementById("data-tools-byte-length").textContent =
|
|
1592
|
+
`Byte Length: ${bytes.length}`;
|
|
1593
|
+
document.getElementById("data-tools-mime-type").textContent =
|
|
1594
|
+
`MIME Type: ${inferMimeType(bytes)}`;
|
|
1595
|
+
document.getElementById("data-tools-entropy").textContent =
|
|
1596
|
+
`Shannon Entropy: ${entropy.toFixed(2)} (${entropyLabel})`;
|
|
1597
|
+
errorEl.textContent = "";
|
|
1598
|
+
computeDataToolsHashes(bytes);
|
|
1599
|
+
runProtoDecoder(bytes);
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
resetDataToolsOutputs();
|
|
1602
|
+
errorEl.textContent =
|
|
1603
|
+
error && typeof error === "object" && "message" in error
|
|
1604
|
+
? error.message
|
|
1605
|
+
: String(error);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// ── Protocol decoders for the Conv tab ───────────────────────────────────────
|
|
1610
|
+
|
|
1611
|
+
function decodeHttpFromBytes(bytes) {
|
|
1612
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
1613
|
+
const lines = text.split(/\r?\n/);
|
|
1614
|
+
if (!lines.length) return null;
|
|
1615
|
+
const firstLine = lines[0].trim();
|
|
1616
|
+
const requestMatch = firstLine.match(
|
|
1617
|
+
/^([A-Z]+)\s+(\S+)\s+(HTTP\/[\d.]+)$/,
|
|
1618
|
+
);
|
|
1619
|
+
const responseMatch = firstLine.match(/^(HTTP\/[\d.]+)\s+(\d{3})\s*(.*)/);
|
|
1620
|
+
if (!requestMatch && !responseMatch) return null;
|
|
1621
|
+
|
|
1622
|
+
const emptyLineIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "");
|
|
1623
|
+
const headerLines = lines.slice(
|
|
1624
|
+
1,
|
|
1625
|
+
emptyLineIdx > 0 ? emptyLineIdx : lines.length,
|
|
1626
|
+
);
|
|
1627
|
+
const headers = {};
|
|
1628
|
+
headerLines.forEach((hl) => {
|
|
1629
|
+
const idx = hl.indexOf(":");
|
|
1630
|
+
if (idx > 0) {
|
|
1631
|
+
headers[hl.slice(0, idx).trim()] = hl.slice(idx + 1).trim();
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
const fields = [];
|
|
1636
|
+
if (requestMatch) {
|
|
1637
|
+
fields.push(
|
|
1638
|
+
{ name: "Type", value: "Request" },
|
|
1639
|
+
{ name: "Method", value: requestMatch[1] },
|
|
1640
|
+
{ name: "URL", value: requestMatch[2] },
|
|
1641
|
+
{ name: "Version", value: requestMatch[3] },
|
|
1642
|
+
);
|
|
1643
|
+
[
|
|
1644
|
+
"Host",
|
|
1645
|
+
"User-Agent",
|
|
1646
|
+
"Content-Type",
|
|
1647
|
+
"Content-Length",
|
|
1648
|
+
"Accept",
|
|
1649
|
+
"Accept-Encoding",
|
|
1650
|
+
"Connection",
|
|
1651
|
+
"Authorization",
|
|
1652
|
+
"Referer",
|
|
1653
|
+
"Cookie",
|
|
1654
|
+
].forEach((h) => {
|
|
1655
|
+
if (headers[h]) fields.push({ name: h, value: headers[h] });
|
|
1656
|
+
});
|
|
1657
|
+
} else {
|
|
1658
|
+
fields.push(
|
|
1659
|
+
{ name: "Type", value: "Response" },
|
|
1660
|
+
{ name: "Version", value: responseMatch[1] },
|
|
1661
|
+
{ name: "Status Code", value: responseMatch[2] },
|
|
1662
|
+
{ name: "Status Message", value: responseMatch[3] || "—" },
|
|
1663
|
+
);
|
|
1664
|
+
[
|
|
1665
|
+
"Server",
|
|
1666
|
+
"Content-Type",
|
|
1667
|
+
"Content-Length",
|
|
1668
|
+
"Content-Encoding",
|
|
1669
|
+
"Transfer-Encoding",
|
|
1670
|
+
"Connection",
|
|
1671
|
+
"Location",
|
|
1672
|
+
"Set-Cookie",
|
|
1673
|
+
"Cache-Control",
|
|
1674
|
+
"Date",
|
|
1675
|
+
].forEach((h) => {
|
|
1676
|
+
if (headers[h]) fields.push({ name: h, value: headers[h] });
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
if (emptyLineIdx > 0 && emptyLineIdx < lines.length - 1) {
|
|
1680
|
+
const body = lines
|
|
1681
|
+
.slice(emptyLineIdx + 1)
|
|
1682
|
+
.join("\n")
|
|
1683
|
+
.trim();
|
|
1684
|
+
if (body) {
|
|
1685
|
+
fields.push({
|
|
1686
|
+
name: "Body (preview)",
|
|
1687
|
+
value: body.length > 200 ? body.slice(0, 200) + "…" : body,
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return { protocol: "HTTP", fields };
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function decodeTelnetFromBytes(bytes) {
|
|
1695
|
+
const IAC = 0xff;
|
|
1696
|
+
const WILL = 0xfb,
|
|
1697
|
+
WONT = 0xfc,
|
|
1698
|
+
DO = 0xfd,
|
|
1699
|
+
DONT = 0xfe;
|
|
1700
|
+
const SB = 0xfa,
|
|
1701
|
+
SE = 0xf0;
|
|
1702
|
+
const optionNames = {
|
|
1703
|
+
0: "Binary",
|
|
1704
|
+
1: "Echo",
|
|
1705
|
+
3: "Suppress Go Ahead",
|
|
1706
|
+
5: "Status",
|
|
1707
|
+
24: "Terminal Type",
|
|
1708
|
+
31: "Window Size",
|
|
1709
|
+
32: "Terminal Speed",
|
|
1710
|
+
34: "Linemode",
|
|
1711
|
+
39: "New Environment",
|
|
1712
|
+
};
|
|
1713
|
+
const negotiations = [];
|
|
1714
|
+
let text = "";
|
|
1715
|
+
let i = 0;
|
|
1716
|
+
let hasIac = false;
|
|
1717
|
+
while (i < bytes.length) {
|
|
1718
|
+
if (bytes[i] === IAC) {
|
|
1719
|
+
hasIac = true;
|
|
1720
|
+
i++;
|
|
1721
|
+
if (i >= bytes.length) break;
|
|
1722
|
+
const cmd = bytes[i++];
|
|
1723
|
+
if (cmd === WILL || cmd === WONT || cmd === DO || cmd === DONT) {
|
|
1724
|
+
if (i < bytes.length) {
|
|
1725
|
+
const opt = bytes[i++];
|
|
1726
|
+
const cmdName =
|
|
1727
|
+
cmd === WILL
|
|
1728
|
+
? "WILL"
|
|
1729
|
+
: cmd === WONT
|
|
1730
|
+
? "WONT"
|
|
1731
|
+
: cmd === DO
|
|
1732
|
+
? "DO"
|
|
1733
|
+
: "DONT";
|
|
1734
|
+
negotiations.push(`${cmdName} ${optionNames[opt] ?? `Option ${opt}`}`);
|
|
1735
|
+
}
|
|
1736
|
+
} else if (cmd === SB) {
|
|
1737
|
+
while (i < bytes.length) {
|
|
1738
|
+
if (bytes[i] === IAC && i + 1 < bytes.length && bytes[i + 1] === SE) {
|
|
1739
|
+
i += 2;
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
i++;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
} else {
|
|
1746
|
+
const b = bytes[i++];
|
|
1747
|
+
if (b >= 32 && b < 127) text += String.fromCharCode(b);
|
|
1748
|
+
else if (b === 10) text += "\n";
|
|
1749
|
+
else if (b === 13) text += "\r";
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (!hasIac && !text.trim()) return null;
|
|
1753
|
+
const fields = [];
|
|
1754
|
+
if (negotiations.length) {
|
|
1755
|
+
fields.push({ name: "Negotiations", value: negotiations.join(", ") });
|
|
1756
|
+
}
|
|
1757
|
+
if (text.trim()) {
|
|
1758
|
+
const t = text.trim();
|
|
1759
|
+
fields.push({
|
|
1760
|
+
name: "Text",
|
|
1761
|
+
value: t.length > 500 ? t.slice(0, 500) + "…" : t,
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
if (!fields.length) return null;
|
|
1765
|
+
return { protocol: "Telnet", fields };
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function decodeSshFromBytes(bytes) {
|
|
1769
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(
|
|
1770
|
+
bytes.slice(0, 512),
|
|
1771
|
+
);
|
|
1772
|
+
const bannerMatch = text.match(/^SSH-([\S]+)\r?\n/);
|
|
1773
|
+
if (!bannerMatch) return null;
|
|
1774
|
+
const versionStr = bannerMatch[1];
|
|
1775
|
+
const dashIdx = versionStr.indexOf("-");
|
|
1776
|
+
const protocolVersion =
|
|
1777
|
+
dashIdx >= 0 ? versionStr.slice(0, dashIdx) : versionStr;
|
|
1778
|
+
const softwareVersion = dashIdx >= 0 ? versionStr.slice(dashIdx + 1) : "—";
|
|
1779
|
+
const fields = [
|
|
1780
|
+
{ name: "Protocol Version", value: protocolVersion },
|
|
1781
|
+
{ name: "Software Version", value: softwareVersion },
|
|
1782
|
+
];
|
|
1783
|
+
const bannerEnd = text.indexOf("\n");
|
|
1784
|
+
if (bannerEnd > 0 && bytes.length > bannerEnd + 1) {
|
|
1785
|
+
fields.push({
|
|
1786
|
+
name: "Additional Data",
|
|
1787
|
+
value: `${bytes.length - bannerEnd - 1} bytes (key exchange)`,
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
return { protocol: "SSH / OpenSSH", fields };
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
function decodePop3FromBytes(bytes) {
|
|
1794
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
1795
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
1796
|
+
if (!lines.length) return null;
|
|
1797
|
+
const POP3_COMMANDS = new Set([
|
|
1798
|
+
"USER",
|
|
1799
|
+
"PASS",
|
|
1800
|
+
"STAT",
|
|
1801
|
+
"LIST",
|
|
1802
|
+
"RETR",
|
|
1803
|
+
"DELE",
|
|
1804
|
+
"NOOP",
|
|
1805
|
+
"RSET",
|
|
1806
|
+
"QUIT",
|
|
1807
|
+
"APOP",
|
|
1808
|
+
"TOP",
|
|
1809
|
+
"UIDL",
|
|
1810
|
+
]);
|
|
1811
|
+
const fields = [];
|
|
1812
|
+
let detected = false;
|
|
1813
|
+
for (const line of lines) {
|
|
1814
|
+
if (line.startsWith("+OK")) {
|
|
1815
|
+
fields.push({ name: "Response", value: "+OK" });
|
|
1816
|
+
const msg = line.slice(3).trim();
|
|
1817
|
+
if (msg) fields.push({ name: "Message", value: msg });
|
|
1818
|
+
detected = true;
|
|
1819
|
+
} else if (line.startsWith("-ERR")) {
|
|
1820
|
+
fields.push({ name: "Response", value: "-ERR" });
|
|
1821
|
+
const msg = line.slice(4).trim();
|
|
1822
|
+
if (msg) fields.push({ name: "Error", value: msg });
|
|
1823
|
+
detected = true;
|
|
1824
|
+
} else {
|
|
1825
|
+
const parts = line.split(/\s+/);
|
|
1826
|
+
const cmd = parts[0].toUpperCase();
|
|
1827
|
+
if (POP3_COMMANDS.has(cmd)) {
|
|
1828
|
+
fields.push({ name: "Command", value: cmd });
|
|
1829
|
+
if (parts.length > 1) {
|
|
1830
|
+
fields.push({ name: "Argument", value: parts.slice(1).join(" ") });
|
|
1831
|
+
}
|
|
1832
|
+
detected = true;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (fields.length >= 10) break;
|
|
1836
|
+
}
|
|
1837
|
+
if (!detected) return null;
|
|
1838
|
+
return { protocol: "POP3", fields };
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
function decodeImapFromBytes(bytes) {
|
|
1842
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
1843
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
1844
|
+
if (!lines.length) return null;
|
|
1845
|
+
const IMAP_STATUSES = new Set(["OK", "NO", "BAD", "PREAUTH", "BYE"]);
|
|
1846
|
+
const IMAP_COMMANDS = new Set([
|
|
1847
|
+
"CAPABILITY",
|
|
1848
|
+
"NOOP",
|
|
1849
|
+
"LOGOUT",
|
|
1850
|
+
"AUTHENTICATE",
|
|
1851
|
+
"LOGIN",
|
|
1852
|
+
"SELECT",
|
|
1853
|
+
"EXAMINE",
|
|
1854
|
+
"CREATE",
|
|
1855
|
+
"DELETE",
|
|
1856
|
+
"RENAME",
|
|
1857
|
+
"SUBSCRIBE",
|
|
1858
|
+
"UNSUBSCRIBE",
|
|
1859
|
+
"LIST",
|
|
1860
|
+
"LSUB",
|
|
1861
|
+
"STATUS",
|
|
1862
|
+
"APPEND",
|
|
1863
|
+
"CHECK",
|
|
1864
|
+
"CLOSE",
|
|
1865
|
+
"EXPUNGE",
|
|
1866
|
+
"SEARCH",
|
|
1867
|
+
"FETCH",
|
|
1868
|
+
"STORE",
|
|
1869
|
+
"COPY",
|
|
1870
|
+
"UID",
|
|
1871
|
+
"IDLE",
|
|
1872
|
+
]);
|
|
1873
|
+
const fields = [];
|
|
1874
|
+
let detected = false;
|
|
1875
|
+
for (const line of lines) {
|
|
1876
|
+
if (line.startsWith("* ")) {
|
|
1877
|
+
const val = line.slice(2).trim();
|
|
1878
|
+
fields.push({
|
|
1879
|
+
name: "Untagged",
|
|
1880
|
+
value: val.length > 100 ? val.slice(0, 100) + "…" : val,
|
|
1881
|
+
});
|
|
1882
|
+
detected = true;
|
|
1883
|
+
} else if (line.startsWith("+ ")) {
|
|
1884
|
+
fields.push({ name: "Continuation", value: line.slice(2).trim() });
|
|
1885
|
+
detected = true;
|
|
1886
|
+
} else {
|
|
1887
|
+
const m = line.match(/^(\S+)\s+(\S+)\s*(.*)/);
|
|
1888
|
+
if (m) {
|
|
1889
|
+
const tag = m[1];
|
|
1890
|
+
const word = m[2].toUpperCase();
|
|
1891
|
+
const rest = m[3];
|
|
1892
|
+
if (IMAP_STATUSES.has(word)) {
|
|
1893
|
+
const val = `${word} ${rest}`.trim();
|
|
1894
|
+
fields.push({
|
|
1895
|
+
name: `[${tag}] Status`,
|
|
1896
|
+
value: val.length > 100 ? val.slice(0, 100) + "…" : val,
|
|
1897
|
+
});
|
|
1898
|
+
detected = true;
|
|
1899
|
+
} else if (IMAP_COMMANDS.has(word)) {
|
|
1900
|
+
fields.push({ name: `[${tag}] Command`, value: word });
|
|
1901
|
+
if (rest) {
|
|
1902
|
+
fields.push({
|
|
1903
|
+
name: "Arguments",
|
|
1904
|
+
value: rest.length > 100 ? rest.slice(0, 100) + "…" : rest,
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
detected = true;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (fields.length >= 12) break;
|
|
1912
|
+
}
|
|
1913
|
+
if (!detected) return null;
|
|
1914
|
+
return { protocol: "IMAP", fields };
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
function decodeSmtpFromBytes(bytes) {
|
|
1918
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
1919
|
+
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
1920
|
+
if (!lines.length) return null;
|
|
1921
|
+
const SMTP_COMMANDS = new Set([
|
|
1922
|
+
"HELO",
|
|
1923
|
+
"EHLO",
|
|
1924
|
+
"MAIL",
|
|
1925
|
+
"RCPT",
|
|
1926
|
+
"DATA",
|
|
1927
|
+
"RSET",
|
|
1928
|
+
"VRFY",
|
|
1929
|
+
"EXPN",
|
|
1930
|
+
"NOOP",
|
|
1931
|
+
"QUIT",
|
|
1932
|
+
"AUTH",
|
|
1933
|
+
"STARTTLS",
|
|
1934
|
+
]);
|
|
1935
|
+
const fields = [];
|
|
1936
|
+
let detected = false;
|
|
1937
|
+
for (const line of lines) {
|
|
1938
|
+
const rm = line.match(/^(\d{3})([\s-])(.*)/);
|
|
1939
|
+
if (rm) {
|
|
1940
|
+
const label = `Response ${rm[1]}${rm[2] === "-" ? " (cont.)" : ""}`;
|
|
1941
|
+
fields.push({ name: label, value: rm[3] });
|
|
1942
|
+
detected = true;
|
|
1943
|
+
} else {
|
|
1944
|
+
const parts = line.split(/\s+/);
|
|
1945
|
+
const cmd = parts[0].toUpperCase();
|
|
1946
|
+
if (SMTP_COMMANDS.has(cmd)) {
|
|
1947
|
+
fields.push({ name: "Command", value: cmd });
|
|
1948
|
+
if (parts.length > 1) {
|
|
1949
|
+
const arg = parts.slice(1).join(" ");
|
|
1950
|
+
fields.push({
|
|
1951
|
+
name: "Argument",
|
|
1952
|
+
value: arg.length > 100 ? arg.slice(0, 100) + "…" : arg,
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
detected = true;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
if (fields.length >= 12) break;
|
|
1959
|
+
}
|
|
1960
|
+
if (!detected) return null;
|
|
1961
|
+
return { protocol: "SMTP", fields };
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function autoDetectProtoFromBytes(bytes) {
|
|
1965
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(
|
|
1966
|
+
bytes.slice(0, 256),
|
|
1967
|
+
);
|
|
1968
|
+
if (/^SSH-/.test(text)) return "ssh";
|
|
1969
|
+
if (
|
|
1970
|
+
/^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH|CONNECT|TRACE)\s/.test(text) ||
|
|
1971
|
+
/^HTTP\/[\d.]+ \d{3}/.test(text)
|
|
1972
|
+
)
|
|
1973
|
+
return "http";
|
|
1974
|
+
if (
|
|
1975
|
+
/^(HELO|EHLO|MAIL FROM|RCPT TO|DATA|QUIT)\b/i.test(text) ||
|
|
1976
|
+
/^\d{3}[\s-]/.test(text)
|
|
1977
|
+
)
|
|
1978
|
+
return "smtp";
|
|
1979
|
+
if (
|
|
1980
|
+
/^\+OK/.test(text) ||
|
|
1981
|
+
/^-ERR/.test(text) ||
|
|
1982
|
+
/^(USER|PASS|STAT|LIST|RETR|DELE|QUIT)\b/i.test(text)
|
|
1983
|
+
)
|
|
1984
|
+
return "pop3";
|
|
1985
|
+
if (
|
|
1986
|
+
/^\* /.test(text) ||
|
|
1987
|
+
/^\+ /.test(text) ||
|
|
1988
|
+
/^\S+ (OK|NO|BAD|PREAUTH|BYE)\b/i.test(text) ||
|
|
1989
|
+
/^\S+ (SELECT|LOGIN|FETCH|AUTHENTICATE)\b/i.test(text)
|
|
1990
|
+
)
|
|
1991
|
+
return "imap";
|
|
1992
|
+
// Telnet: require IAC (0xFF) followed by a valid command byte (0xF0–0xFF)
|
|
1993
|
+
const TELNET_COMMANDS = new Set([
|
|
1994
|
+
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
|
|
1995
|
+
0xfc, 0xfd, 0xfe, 0xff,
|
|
1996
|
+
]);
|
|
1997
|
+
for (let i = 0; i + 1 < bytes.length; i++) {
|
|
1998
|
+
if (bytes[i] === 0xff && TELNET_COMMANDS.has(bytes[i + 1])) return "telnet";
|
|
1999
|
+
}
|
|
2000
|
+
return null;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
function renderProtoDecoderOutput(result, selectedProtocol, protocol) {
|
|
2004
|
+
const protoOutput = document.getElementById("data-tools-proto-output");
|
|
2005
|
+
if (!protoOutput) return;
|
|
2006
|
+
activeDataToolsProtoResult = result || null;
|
|
2007
|
+
protoOutput.innerHTML = "";
|
|
2008
|
+
if (!result) {
|
|
2009
|
+
const span = document.createElement("span");
|
|
2010
|
+
span.className = "data-tools-proto-none";
|
|
2011
|
+
span.textContent =
|
|
2012
|
+
selectedProtocol === "auto"
|
|
2013
|
+
? "No known protocol detected"
|
|
2014
|
+
: `Could not decode as ${(protocol || selectedProtocol).toUpperCase()}`;
|
|
2015
|
+
protoOutput.appendChild(span);
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
const table = document.createElement("table");
|
|
2019
|
+
table.className = "data-tools-proto-table";
|
|
2020
|
+
const headerRow = document.createElement("tr");
|
|
2021
|
+
const th1 = document.createElement("th");
|
|
2022
|
+
th1.textContent = `${result.protocol} Field`;
|
|
2023
|
+
const th2 = document.createElement("th");
|
|
2024
|
+
th2.textContent = "Value";
|
|
2025
|
+
headerRow.appendChild(th1);
|
|
2026
|
+
headerRow.appendChild(th2);
|
|
2027
|
+
table.appendChild(headerRow);
|
|
2028
|
+
result.fields.forEach((field) => {
|
|
2029
|
+
const tr = document.createElement("tr");
|
|
2030
|
+
const tdName = document.createElement("td");
|
|
2031
|
+
tdName.textContent = field.name;
|
|
2032
|
+
const tdVal = document.createElement("td");
|
|
2033
|
+
tdVal.textContent = field.value;
|
|
2034
|
+
tr.appendChild(tdName);
|
|
2035
|
+
tr.appendChild(tdVal);
|
|
2036
|
+
table.appendChild(tr);
|
|
2037
|
+
});
|
|
2038
|
+
protoOutput.appendChild(table);
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function runProtoDecoder(bytes) {
|
|
2042
|
+
const selectEl = document.getElementById("data-tools-proto-select");
|
|
2043
|
+
const selectedProtocol = selectEl ? selectEl.value : "auto";
|
|
2044
|
+
let protocol = selectedProtocol;
|
|
2045
|
+
if (protocol === "auto") {
|
|
2046
|
+
protocol = autoDetectProtoFromBytes(bytes);
|
|
2047
|
+
}
|
|
2048
|
+
let result = null;
|
|
2049
|
+
switch (protocol) {
|
|
2050
|
+
case "http":
|
|
2051
|
+
result = decodeHttpFromBytes(bytes);
|
|
2052
|
+
break;
|
|
2053
|
+
case "telnet":
|
|
2054
|
+
result = decodeTelnetFromBytes(bytes);
|
|
2055
|
+
break;
|
|
2056
|
+
case "ssh":
|
|
2057
|
+
result = decodeSshFromBytes(bytes);
|
|
2058
|
+
break;
|
|
2059
|
+
case "pop3":
|
|
2060
|
+
result = decodePop3FromBytes(bytes);
|
|
2061
|
+
break;
|
|
2062
|
+
case "imap":
|
|
2063
|
+
result = decodeImapFromBytes(bytes);
|
|
2064
|
+
break;
|
|
2065
|
+
case "smtp":
|
|
2066
|
+
result = decodeSmtpFromBytes(bytes);
|
|
2067
|
+
break;
|
|
2068
|
+
default:
|
|
2069
|
+
protocol = null;
|
|
2070
|
+
}
|
|
2071
|
+
renderProtoDecoderOutput(result, selectedProtocol, protocol);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function clearProtoDecoderOutput() {
|
|
2075
|
+
const protoOutput = document.getElementById("data-tools-proto-output");
|
|
2076
|
+
if (protoOutput) protoOutput.innerHTML = "";
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2080
|
+
|
|
2081
|
+
function showDataTools(tabName = CONV_CONVERSIONS_SUBTAB) {
|
|
2082
|
+
activeMainTab = MAIN_TAB_DATA_TOOLS;
|
|
2083
|
+
statusUpdate("Status: Displaying data conversion tools");
|
|
2084
|
+
writeLogEntry("User opened data conversion tools view");
|
|
2085
|
+
document.getElementById("prev-btn").style.display = "none";
|
|
2086
|
+
document.getElementById("next-btn").style.display = "none";
|
|
2087
|
+
document.getElementById("packetInfoPane").style.display = "none";
|
|
2088
|
+
document.getElementById("packetPayloadPane").style.display = "none";
|
|
2089
|
+
document.getElementById("summary_box").style.display = "none";
|
|
2090
|
+
document.getElementById("stats_box").style.display = "none";
|
|
2091
|
+
document.getElementById("list_box").style.display = "none";
|
|
2092
|
+
document.getElementById("notes_box").style.display = "none";
|
|
2093
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
2094
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
2095
|
+
document.getElementById("rightside").style.display = "none";
|
|
2096
|
+
document.getElementById("data_tools_box").style.display = "flex";
|
|
2097
|
+
setConvSubtab(tabName);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
function showNotesWorkspace() {
|
|
2101
|
+
activeMainTab = MAIN_TAB_NOTES;
|
|
2102
|
+
statusUpdate("Status: Displaying session notes");
|
|
2103
|
+
writeLogEntry("User opened notes workspace");
|
|
2104
|
+
document.getElementById("prev-btn").style.display = "none";
|
|
2105
|
+
document.getElementById("next-btn").style.display = "none";
|
|
2106
|
+
document.getElementById("packetInfoPane").style.display = "none";
|
|
2107
|
+
document.getElementById("packetPayloadPane").style.display = "none";
|
|
2108
|
+
document.getElementById("summary_box").style.display = "none";
|
|
2109
|
+
document.getElementById("stats_box").style.display = "none";
|
|
2110
|
+
document.getElementById("list_box").style.display = "none";
|
|
2111
|
+
document.getElementById("data_tools_box").style.display = "none";
|
|
2112
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
2113
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
2114
|
+
document.getElementById("notes_box").style.display = "flex";
|
|
2115
|
+
document.getElementById("rightside").style.display = "block";
|
|
2116
|
+
const rightsideDataEl = document.getElementById("rightside-data");
|
|
2117
|
+
const rightsideNotesEl = document.getElementById("rightside-notes");
|
|
2118
|
+
if (rightsideDataEl) rightsideDataEl.hidden = true;
|
|
2119
|
+
if (rightsideNotesEl) rightsideNotesEl.hidden = false;
|
|
2120
|
+
renderNotesList();
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
function getFirstLineOrFallback(elementId, fallback = "") {
|
|
2124
|
+
const text = document.getElementById(elementId)?.textContent || "";
|
|
2125
|
+
const firstLine = text.split("\n")[0]?.trim();
|
|
2126
|
+
return firstLine || fallback;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
|
|
2130
|
+
const cryptPanel = createCryptPanel({
|
|
2131
|
+
constants: {
|
|
2132
|
+
MAIN_TAB_CRYPT,
|
|
2133
|
+
CRYPT_SSL_SUBTAB,
|
|
2134
|
+
CRYPT_PGP_SUBTAB,
|
|
2135
|
+
CRYPT_OPENSSH_SUBTAB,
|
|
2136
|
+
SESSION_KEYCHAIN_LABEL,
|
|
2137
|
+
STRICT_IPV4_REGEX,
|
|
2138
|
+
},
|
|
2139
|
+
getCapturedPackets: () => capturedPackets,
|
|
2140
|
+
getJsonCapture: () => jsonCapture,
|
|
2141
|
+
setActiveMainTab: (tabName) => {
|
|
2142
|
+
activeMainTab = tabName;
|
|
2143
|
+
},
|
|
2144
|
+
setActiveCryptSubtab: (tabName) => {
|
|
2145
|
+
activeCryptSubtab = tabName;
|
|
2146
|
+
},
|
|
2147
|
+
statusUpdate,
|
|
2148
|
+
writeLogEntry,
|
|
2149
|
+
doError,
|
|
2150
|
+
logErrorEntry,
|
|
2151
|
+
filterInputEl,
|
|
2152
|
+
syncFilterHighlight,
|
|
2153
|
+
runFilterQuery,
|
|
2154
|
+
addSessionKeystoreEntry: (...args) => keystorePanel.addSessionKeystoreEntry(...args),
|
|
2155
|
+
getFirstLineOrFallback,
|
|
2156
|
+
sendDecryptedToConv: ({ hexValue, utf8Value, sourceLabel }) => {
|
|
2157
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
2158
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
2159
|
+
const normalizedHex = String(hexValue || "").trim();
|
|
2160
|
+
const normalizedUtf8 = String(utf8Value || "");
|
|
2161
|
+
if (normalizedHex) {
|
|
2162
|
+
inputEl.value = normalizedHex;
|
|
2163
|
+
formatEl.value = "hex";
|
|
2164
|
+
} else {
|
|
2165
|
+
inputEl.value = normalizedUtf8;
|
|
2166
|
+
formatEl.value = "ascii";
|
|
2167
|
+
}
|
|
2168
|
+
showDataTools(CONV_CONVERSIONS_SUBTAB);
|
|
2169
|
+
runDataToolsConversion();
|
|
2170
|
+
},
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
const {
|
|
2174
|
+
setCryptSubtab,
|
|
2175
|
+
applyCryptCertificateText,
|
|
2176
|
+
applyCryptPrivateKeyText,
|
|
2177
|
+
readCryptTextFile,
|
|
2178
|
+
applyCryptFilterForActiveEntry,
|
|
2179
|
+
loadEncounteredCertificateIntoCrypt,
|
|
2180
|
+
refreshCryptEncounteredEntries,
|
|
2181
|
+
showCryptWorkspace,
|
|
2182
|
+
decryptActiveEntryWithLoadedKey,
|
|
2183
|
+
sendDecryptedPayloadToConvTab,
|
|
2184
|
+
clearCryptDecryptionOutput,
|
|
2185
|
+
} = cryptPanel;
|
|
2186
|
+
|
|
2187
|
+
const listPanel = createListPanel({
|
|
2188
|
+
constants: {
|
|
2189
|
+
MAIN_TAB_LIST,
|
|
2190
|
+
},
|
|
2191
|
+
getJsonCapture: () => jsonCapture,
|
|
2192
|
+
getCapturedPackets: () => capturedPackets,
|
|
2193
|
+
getBookmarkList: () => bookmarkList,
|
|
2194
|
+
setActiveMainTab: (tabName) => {
|
|
2195
|
+
activeMainTab = tabName;
|
|
2196
|
+
},
|
|
2197
|
+
statusUpdate,
|
|
2198
|
+
writeLogEntry,
|
|
2199
|
+
hostFilterEl,
|
|
2200
|
+
filterInputEl,
|
|
2201
|
+
syncFilterHighlight,
|
|
2202
|
+
runFilterQuery,
|
|
2203
|
+
getFilteredPackets: () => filteredPackets,
|
|
2204
|
+
setPacketsForHost: (packets) => {
|
|
2205
|
+
packetsForHost = packets;
|
|
2206
|
+
},
|
|
2207
|
+
setIndex: (nextIndex) => {
|
|
2208
|
+
index = nextIndex;
|
|
2209
|
+
},
|
|
2210
|
+
setCurrentIp: (nextCurrentIp) => {
|
|
2211
|
+
currentIp = nextCurrentIp;
|
|
2212
|
+
},
|
|
2213
|
+
setCurrentPacketKey: (packetKey) => {
|
|
2214
|
+
currentPacketKey = packetKey;
|
|
2215
|
+
},
|
|
2216
|
+
syncBookmarkDropdown,
|
|
2217
|
+
setActivePacketCursor,
|
|
2218
|
+
showAllData,
|
|
2219
|
+
infoPanel,
|
|
2220
|
+
popHexGrid,
|
|
2221
|
+
populateDataTypes,
|
|
2222
|
+
});
|
|
2223
|
+
|
|
2224
|
+
const { showPacketList } = listPanel;
|
|
2225
|
+
let activeContextConversionText = "";
|
|
2226
|
+
let activeContextTarget = null;
|
|
2227
|
+
let activeContextPasteTarget = null;
|
|
2228
|
+
let activeContextFilterQueries = {};
|
|
2229
|
+
let activeContextCookieJarText = "";
|
|
2230
|
+
const convertContextMenuEl = getCachedElement("convert-context-menu");
|
|
2231
|
+
const convertContextButtons = {
|
|
2232
|
+
copy: getCachedElement("ctx-copy"),
|
|
2233
|
+
paste: getCachedElement("ctx-paste"),
|
|
2234
|
+
saveJson: getCachedElement("ctx-save-json"),
|
|
2235
|
+
exportPacket: getCachedElement("ctx-export-packet"),
|
|
2236
|
+
exportPayload: getCachedElement("ctx-export-payload"),
|
|
2237
|
+
hex: getCachedElement("convert-context-hex"),
|
|
2238
|
+
binary: getCachedElement("convert-context-binary"),
|
|
2239
|
+
base64: getCachedElement("convert-context-base64"),
|
|
2240
|
+
decimal: getCachedElement("convert-context-decimal"),
|
|
2241
|
+
ascii: getCachedElement("convert-context-ascii"),
|
|
2242
|
+
loadCursorAscii: getCachedElement("convert-context-load-cursor-ascii"),
|
|
2243
|
+
loadPayload: getCachedElement("convert-context-load-payload"),
|
|
2244
|
+
copyHex: getCachedElement("convert-context-copy-hex"),
|
|
2245
|
+
copyAscii: getCachedElement("convert-context-copy-ascii"),
|
|
2246
|
+
copyRaw: getCachedElement("convert-context-copy-raw"),
|
|
2247
|
+
filterIp: getCachedElement("ctx-filter-ip"),
|
|
2248
|
+
filterPort: getCachedElement("ctx-filter-port"),
|
|
2249
|
+
filterMac: getCachedElement("ctx-filter-mac"),
|
|
2250
|
+
filterProtocol: getCachedElement("ctx-filter-protocol"),
|
|
2251
|
+
filterMime: getCachedElement("ctx-filter-mime"),
|
|
2252
|
+
filterOrIp: getCachedElement("ctx-filter-or-ip"),
|
|
2253
|
+
filterOrPort: getCachedElement("ctx-filter-or-port"),
|
|
2254
|
+
filterOrMac: getCachedElement("ctx-filter-or-mac"),
|
|
2255
|
+
filterOrProtocol: getCachedElement("ctx-filter-or-protocol"),
|
|
2256
|
+
filterOrMime: getCachedElement("ctx-filter-or-mime"),
|
|
2257
|
+
filterNotIp: getCachedElement("ctx-filter-not-ip"),
|
|
2258
|
+
filterNotPort: getCachedElement("ctx-filter-not-port"),
|
|
2259
|
+
filterNotMac: getCachedElement("ctx-filter-not-mac"),
|
|
2260
|
+
filterNotProtocol: getCachedElement("ctx-filter-not-protocol"),
|
|
2261
|
+
filterNotMime: getCachedElement("ctx-filter-not-mime"),
|
|
2262
|
+
filterParenOpen: getCachedElement("ctx-filter-paren-open"),
|
|
2263
|
+
filterParenClose: getCachedElement("ctx-filter-paren-close"),
|
|
2264
|
+
filterParenWrap: getCachedElement("ctx-filter-paren-wrap"),
|
|
2265
|
+
filterClearIp: getCachedElement("ctx-filter-clear-ip"),
|
|
2266
|
+
filterClearPort: getCachedElement("ctx-filter-clear-port"),
|
|
2267
|
+
filterClearMac: getCachedElement("ctx-filter-clear-mac"),
|
|
2268
|
+
filterClearProtocol: getCachedElement("ctx-filter-clear-protocol"),
|
|
2269
|
+
filterClearMime: getCachedElement("ctx-filter-clear-mime"),
|
|
2270
|
+
keystorePasswordSession: getCachedElement("ctx-keystore-password-session"),
|
|
2271
|
+
keystorePasswordPersistent: getCachedElement("ctx-keystore-password-persistent"),
|
|
2272
|
+
keystoreKeySession: getCachedElement("ctx-keystore-key-session"),
|
|
2273
|
+
keystoreKeyPersistent: getCachedElement("ctx-keystore-key-persistent"),
|
|
2274
|
+
keystoreCertSession: getCachedElement("ctx-keystore-cert-session"),
|
|
2275
|
+
keystoreCertPersistent: getCachedElement("ctx-keystore-cert-persistent"),
|
|
2276
|
+
keystoreCookieSession: getCachedElement("ctx-keystore-cookie-session"),
|
|
2277
|
+
keystoreCookiePersistent: getCachedElement("ctx-keystore-cookie-persistent"),
|
|
2278
|
+
keystoreUriSession: getCachedElement("ctx-keystore-uri-session"),
|
|
2279
|
+
keystoreUriPersistent: getCachedElement("ctx-keystore-uri-persistent"),
|
|
2280
|
+
copyCookieJar: getCachedElement("ctx-copy-cookie-jar"),
|
|
2281
|
+
saveCookieJar: getCachedElement("ctx-save-cookie-jar"),
|
|
2282
|
+
notesSendData: getCachedElement("ctx-notes-send-data"),
|
|
2283
|
+
notesSendConvOutput: getCachedElement("ctx-notes-send-conv-output"),
|
|
2284
|
+
notesSendConvHashes: getCachedElement("ctx-notes-send-conv-hashes"),
|
|
2285
|
+
httpFileSave: getCachedElement("ctx-http-file-save"),
|
|
2286
|
+
httpFileLoad: getCachedElement("ctx-http-file-load"),
|
|
2287
|
+
httpFilePreview: getCachedElement("ctx-http-file-preview"),
|
|
2288
|
+
};
|
|
2289
|
+
const convertContextSubmenus = {
|
|
2290
|
+
copy: getCachedElement("ctx-copy-submenu"),
|
|
2291
|
+
convert: getCachedElement("ctx-convert-submenu"),
|
|
2292
|
+
filter: getCachedElement("ctx-filter-submenu"),
|
|
2293
|
+
filterAnd: getCachedElement("ctx-filter-and-submenu"),
|
|
2294
|
+
filterOr: getCachedElement("ctx-filter-or-submenu"),
|
|
2295
|
+
filterNot: getCachedElement("ctx-filter-not-submenu"),
|
|
2296
|
+
filterParentheses: getCachedElement("ctx-filter-parentheses-submenu"),
|
|
2297
|
+
filterClear: getCachedElement("ctx-filter-clear-submenu"),
|
|
2298
|
+
notes: getCachedElement("ctx-notes-submenu"),
|
|
2299
|
+
export: getCachedElement("ctx-export-submenu"),
|
|
2300
|
+
keystore: getCachedElement("ctx-keystore-submenu"),
|
|
2301
|
+
keystorePassword: getCachedElement("ctx-keystore-password-submenu"),
|
|
2302
|
+
keystoreKey: getCachedElement("ctx-keystore-key-submenu"),
|
|
2303
|
+
keystoreCert: getCachedElement("ctx-keystore-cert-submenu"),
|
|
2304
|
+
keystoreCookie: getCachedElement("ctx-keystore-cookie-submenu"),
|
|
2305
|
+
keystoreUri: getCachedElement("ctx-keystore-uri-submenu"),
|
|
2306
|
+
httpFile: getCachedElement("ctx-http-file-submenu"),
|
|
2307
|
+
};
|
|
2308
|
+
const convertContextDividerEl = getCachedElement("convert-context-divider");
|
|
2309
|
+
const convertContextSaveDividerEl = getCachedElement(
|
|
2310
|
+
"convert-context-save-divider",
|
|
2311
|
+
);
|
|
2312
|
+
const convertContextSubmenuEls = Array.from(
|
|
2313
|
+
convertContextMenuEl.querySelectorAll(".ctx-submenu"),
|
|
2314
|
+
);
|
|
2315
|
+
|
|
2316
|
+
function resetConvertContextSubmenuPositions() {
|
|
2317
|
+
convertContextSubmenuEls.forEach((submenuEl) => {
|
|
2318
|
+
submenuEl.classList.remove("ctx-submenu-flip-x", "ctx-submenu-flip-y");
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
function updateConvertContextSubmenuPositions() {
|
|
2323
|
+
const viewportPadding = 8;
|
|
2324
|
+
resetConvertContextSubmenuPositions();
|
|
2325
|
+
|
|
2326
|
+
convertContextSubmenuEls.forEach((submenuEl) => {
|
|
2327
|
+
if (submenuEl.style.display === "none") return;
|
|
2328
|
+
// Use :scope > to get only the direct child panel, not a grandchild's.
|
|
2329
|
+
const submenuPanelEl = submenuEl.querySelector(":scope > .ctx-submenu-panel");
|
|
2330
|
+
if (!submenuPanelEl) return;
|
|
2331
|
+
|
|
2332
|
+
// Temporarily reveal every ancestor .ctx-submenu-panel so that this
|
|
2333
|
+
// element has a real viewport position when getBoundingClientRect() is
|
|
2334
|
+
// called. Without this, panels at depth > 1 are inside a hidden
|
|
2335
|
+
// ancestor and always return zero-area rects, making the overflow
|
|
2336
|
+
// calculations completely wrong for those levels.
|
|
2337
|
+
const revealedAncestors = [];
|
|
2338
|
+
let node = submenuEl.parentElement;
|
|
2339
|
+
while (node && node !== convertContextMenuEl) {
|
|
2340
|
+
if (
|
|
2341
|
+
node.classList.contains("ctx-submenu-panel") &&
|
|
2342
|
+
node.style.display !== "block"
|
|
2343
|
+
) {
|
|
2344
|
+
revealedAncestors.push({
|
|
2345
|
+
el: node,
|
|
2346
|
+
previousDisplay: node.style.display,
|
|
2347
|
+
previousVisibility: node.style.visibility,
|
|
2348
|
+
previousPointerEvents: node.style.pointerEvents,
|
|
2349
|
+
});
|
|
2350
|
+
node.style.display = "block";
|
|
2351
|
+
node.style.visibility = "hidden";
|
|
2352
|
+
node.style.pointerEvents = "none";
|
|
2353
|
+
}
|
|
2354
|
+
node = node.parentElement;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
const previousDisplay = submenuPanelEl.style.display;
|
|
2358
|
+
const previousVisibility = submenuPanelEl.style.visibility;
|
|
2359
|
+
const previousPointerEvents = submenuPanelEl.style.pointerEvents;
|
|
2360
|
+
submenuPanelEl.style.display = "block";
|
|
2361
|
+
submenuPanelEl.style.visibility = "hidden";
|
|
2362
|
+
submenuPanelEl.style.pointerEvents = "none";
|
|
2363
|
+
|
|
2364
|
+
const submenuRect = submenuEl.getBoundingClientRect();
|
|
2365
|
+
const panelRect = submenuPanelEl.getBoundingClientRect();
|
|
2366
|
+
const wouldOverflowRight =
|
|
2367
|
+
submenuRect.right + panelRect.width > window.innerWidth - viewportPadding;
|
|
2368
|
+
const wouldOverflowBottom =
|
|
2369
|
+
submenuRect.top + panelRect.height > window.innerHeight - viewportPadding;
|
|
2370
|
+
const hasRoomAbove =
|
|
2371
|
+
submenuRect.bottom - panelRect.height >= viewportPadding;
|
|
2372
|
+
|
|
2373
|
+
if (wouldOverflowRight) {
|
|
2374
|
+
submenuEl.classList.add("ctx-submenu-flip-x");
|
|
2375
|
+
}
|
|
2376
|
+
if (wouldOverflowBottom && hasRoomAbove) {
|
|
2377
|
+
submenuEl.classList.add("ctx-submenu-flip-y");
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
submenuPanelEl.style.display = previousDisplay;
|
|
2381
|
+
submenuPanelEl.style.visibility = previousVisibility;
|
|
2382
|
+
submenuPanelEl.style.pointerEvents = previousPointerEvents;
|
|
2383
|
+
|
|
2384
|
+
// Restore ancestor panels in reverse order (innermost first).
|
|
2385
|
+
for (let i = revealedAncestors.length - 1; i >= 0; i--) {
|
|
2386
|
+
const ancestor = revealedAncestors[i];
|
|
2387
|
+
ancestor.el.style.display = ancestor.previousDisplay;
|
|
2388
|
+
ancestor.el.style.visibility = ancestor.previousVisibility;
|
|
2389
|
+
ancestor.el.style.pointerEvents = ancestor.previousPointerEvents;
|
|
2390
|
+
}
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
function hideConvertContextMenu() {
|
|
2395
|
+
activeContextConversionText = "";
|
|
2396
|
+
activeContextTarget = null;
|
|
2397
|
+
activeContextPasteTarget = null;
|
|
2398
|
+
activeContextFilterQueries = {};
|
|
2399
|
+
activeContextCookieJarText = "";
|
|
2400
|
+
resetConvertContextSubmenuPositions();
|
|
2401
|
+
convertContextMenuEl.hidden = true;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
function normalizeContextToken(value) {
|
|
2405
|
+
if (value === null || value === undefined) return "";
|
|
2406
|
+
return String(value).replace(/\s+/g, " ").trim();
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
function extractContextIp(value) {
|
|
2410
|
+
const normalized = normalizeContextToken(value);
|
|
2411
|
+
const match = normalized.match(CONTEXT_IPV4_REGEX);
|
|
2412
|
+
return match ? match[0] : "";
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
function extractContextPort(value, allowStandaloneNumber = false) {
|
|
2416
|
+
const normalized = normalizeContextToken(value);
|
|
2417
|
+
const ipPortMatch = normalized.match(
|
|
2418
|
+
/\b(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}:(\d{1,5})\b/,
|
|
2419
|
+
);
|
|
2420
|
+
if (ipPortMatch) {
|
|
2421
|
+
const ipPortValue = Number.parseInt(ipPortMatch[4], 10);
|
|
2422
|
+
return ipPortValue >= 0 && ipPortValue <= 65535 ? String(ipPortValue) : "";
|
|
2423
|
+
}
|
|
2424
|
+
if (!allowStandaloneNumber) return "";
|
|
2425
|
+
const portMatch = normalized.match(/^\d{1,5}$/);
|
|
2426
|
+
if (!portMatch) return "";
|
|
2427
|
+
const portValue = Number.parseInt(normalized, 10);
|
|
2428
|
+
return portValue >= 0 && portValue <= 65535 ? String(portValue) : "";
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
function extractContextMac(value) {
|
|
2432
|
+
const normalized = normalizeContextToken(value);
|
|
2433
|
+
const match = normalized.match(CONTEXT_MAC_REGEX);
|
|
2434
|
+
return match ? match[0].toLowerCase() : "";
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
function extractContextMimeType(value) {
|
|
2438
|
+
const normalized = normalizeContextToken(value);
|
|
2439
|
+
const labelStripped = normalized
|
|
2440
|
+
.replace(/^mime(?:\s+type)?\s*:\s*/i, "")
|
|
2441
|
+
.trim();
|
|
2442
|
+
if (!labelStripped) return "";
|
|
2443
|
+
const mimeBase = labelStripped.split(";")[0].trim();
|
|
2444
|
+
return CONTEXT_MIME_REGEX.test(mimeBase) ? mimeBase.toLowerCase() : "";
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
function extractContextProtocol(value) {
|
|
2448
|
+
const normalized = normalizeContextToken(value);
|
|
2449
|
+
const labelStripped = normalized
|
|
2450
|
+
.replace(/^protocol(?:\s+name)?\s*:\s*/i, "")
|
|
2451
|
+
.replace(/^app(?:lication)?\s+protocol\s*:\s*/i, "")
|
|
2452
|
+
.replace(/^transport\s+protocol\s*:\s*/i, "")
|
|
2453
|
+
.trim();
|
|
2454
|
+
if (!labelStripped) return "";
|
|
2455
|
+
const protocolMatch = labelStripped.match(/^[a-z][a-z0-9+_-]*$/i);
|
|
2456
|
+
return protocolMatch ? labelStripped.toLowerCase() : "";
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function sanitizeFilterTerm(value) {
|
|
2460
|
+
return normalizeContextToken(value)
|
|
2461
|
+
.replace(/[^a-zA-Z0-9:./+-]/g, "")
|
|
2462
|
+
.trim();
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
function buildContextFilterQueries(target, selectedText, conversionText) {
|
|
2466
|
+
const candidates = [];
|
|
2467
|
+
const addCandidate = (value) => {
|
|
2468
|
+
const normalized = normalizeContextToken(value);
|
|
2469
|
+
if (!normalized) return;
|
|
2470
|
+
if (!candidates.includes(normalized)) candidates.push(normalized);
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
addCandidate(selectedText);
|
|
2474
|
+
addCandidate(conversionText);
|
|
2475
|
+
|
|
2476
|
+
let rowName = "";
|
|
2477
|
+
let rowPortEligible = false;
|
|
2478
|
+
const row = target?.closest?.("tr");
|
|
2479
|
+
if (row) {
|
|
2480
|
+
const cells = row.querySelectorAll("td");
|
|
2481
|
+
rowName = normalizeContextToken(cells[0]?.textContent);
|
|
2482
|
+
const rowValue = normalizeContextToken(cells[1]?.textContent);
|
|
2483
|
+
addCandidate(rowValue);
|
|
2484
|
+
rowPortEligible = /\bport\b/i.test(rowName);
|
|
2485
|
+
if (/^ip\s*:?\s*port$/i.test(rowName) && rowValue) {
|
|
2486
|
+
const bracketedIpv6Match = rowValue.match(/^\[([^\]]+)\]:(\d{1,5})$/);
|
|
2487
|
+
if (bracketedIpv6Match) {
|
|
2488
|
+
addCandidate(bracketedIpv6Match[1]);
|
|
2489
|
+
addCandidate(bracketedIpv6Match[2]);
|
|
2490
|
+
} else {
|
|
2491
|
+
const ipv4PortMatch = rowValue.match(
|
|
2492
|
+
/^((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}):(\d{1,5})$/,
|
|
2493
|
+
);
|
|
2494
|
+
if (ipv4PortMatch) {
|
|
2495
|
+
addCandidate(ipv4PortMatch[1]);
|
|
2496
|
+
addCandidate(ipv4PortMatch[2]);
|
|
2497
|
+
} else {
|
|
2498
|
+
const lastColonIndex = rowValue.lastIndexOf(":");
|
|
2499
|
+
if (lastColonIndex > 0) {
|
|
2500
|
+
const maybePort = rowValue.slice(lastColonIndex + 1).trim();
|
|
2501
|
+
if (/^\d{1,5}$/.test(maybePort)) {
|
|
2502
|
+
addCandidate(maybePort);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
const filterQueries = {};
|
|
2511
|
+
const skipProtocol = /^network\s+class$/i.test(rowName);
|
|
2512
|
+
for (const candidate of candidates) {
|
|
2513
|
+
if (!filterQueries.ip) {
|
|
2514
|
+
const ip = extractContextIp(candidate);
|
|
2515
|
+
if (ip) {
|
|
2516
|
+
const safeIp = sanitizeFilterTerm(ip);
|
|
2517
|
+
filterQueries.ip = `ip.src.addr: ${safeIp} || ip.dst.addr: ${safeIp}`;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
if (!filterQueries.port) {
|
|
2521
|
+
const port = extractContextPort(candidate, rowPortEligible);
|
|
2522
|
+
if (port) {
|
|
2523
|
+
const safePort = sanitizeFilterTerm(port);
|
|
2524
|
+
filterQueries.port =
|
|
2525
|
+
`tcp.src.port: ${safePort} || tcp.dst.port: ${safePort}` +
|
|
2526
|
+
` || udp.src.port: ${safePort} || udp.dst.port: ${safePort}`;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if (!filterQueries.mac) {
|
|
2530
|
+
const mac = extractContextMac(candidate);
|
|
2531
|
+
if (mac) {
|
|
2532
|
+
const safeMac = sanitizeFilterTerm(mac);
|
|
2533
|
+
filterQueries.mac = `ether.src.mac.addr: ${safeMac} || ether.dst.mac.addr: ${safeMac}`;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
if (!filterQueries.protocol && !skipProtocol) {
|
|
2537
|
+
const protocol = extractContextProtocol(candidate);
|
|
2538
|
+
if (protocol) {
|
|
2539
|
+
const safeProtocol = sanitizeFilterTerm(protocol);
|
|
2540
|
+
filterQueries.protocol = `wire.proto: ${safeProtocol} || tcp.proto: ${safeProtocol}`;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
if (!filterQueries.mime) {
|
|
2544
|
+
const mimeType = extractContextMimeType(candidate);
|
|
2545
|
+
if (mimeType) {
|
|
2546
|
+
const safeMimeType = sanitizeFilterTerm(mimeType);
|
|
2547
|
+
filterQueries.mime = `mime.type: ${safeMimeType}`;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
return filterQueries;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
function getTrimmedSelectionText() {
|
|
2556
|
+
return window.getSelection()?.toString().trim() || "";
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
function looksLikeBase64(text) {
|
|
2560
|
+
const normalized = text.replace(/\s+/g, "");
|
|
2561
|
+
return (
|
|
2562
|
+
normalized.length >= DATA_TOOLS_CONTEXT_BASE64_MIN_LENGTH &&
|
|
2563
|
+
normalized.length % 4 === 0 &&
|
|
2564
|
+
/^[A-Za-z0-9+/]*={0,2}$/.test(normalized) &&
|
|
2565
|
+
normalized.replace(/=/g, "").length > 0
|
|
2566
|
+
);
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
function detectConvertibleFormats(text) {
|
|
2570
|
+
const formats = [];
|
|
2571
|
+
const value = text.trim();
|
|
2572
|
+
if (!value) return formats;
|
|
2573
|
+
|
|
2574
|
+
const canParse = (format) => {
|
|
2575
|
+
try {
|
|
2576
|
+
parseDataToolsInput(format, value);
|
|
2577
|
+
return true;
|
|
2578
|
+
} catch {
|
|
2579
|
+
return false;
|
|
2580
|
+
}
|
|
2581
|
+
};
|
|
2582
|
+
|
|
2583
|
+
if (canParse("hex")) formats.push("hex");
|
|
2584
|
+
if (canParse("binary")) formats.push("binary");
|
|
2585
|
+
if (canParse("decimal")) formats.push("decimal");
|
|
2586
|
+
if (looksLikeBase64(value) && canParse("base64")) formats.push("base64");
|
|
2587
|
+
if (formats.length > 0) formats.push("ascii");
|
|
2588
|
+
|
|
2589
|
+
return formats;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
function splitCookieHeaderEntries(headerValue) {
|
|
2593
|
+
if (typeof headerValue !== "string" || !headerValue.trim()) return [];
|
|
2594
|
+
const entries = [];
|
|
2595
|
+
let currentEntry = "";
|
|
2596
|
+
let inQuotes = false;
|
|
2597
|
+
let isEscaped = false;
|
|
2598
|
+
|
|
2599
|
+
for (const character of headerValue) {
|
|
2600
|
+
if (isEscaped) {
|
|
2601
|
+
currentEntry += character;
|
|
2602
|
+
isEscaped = false;
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
if (character === "\\" && inQuotes) {
|
|
2606
|
+
currentEntry += character;
|
|
2607
|
+
isEscaped = true;
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
if (character === '"') {
|
|
2611
|
+
inQuotes = !inQuotes;
|
|
2612
|
+
currentEntry += character;
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
if (character === ";" && !inQuotes) {
|
|
2616
|
+
const trimmedEntry = currentEntry.trim();
|
|
2617
|
+
if (trimmedEntry) entries.push(trimmedEntry);
|
|
2618
|
+
currentEntry = "";
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
currentEntry += character;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
const trimmedEntry = currentEntry.trim();
|
|
2625
|
+
if (trimmedEntry) entries.push(trimmedEntry);
|
|
2626
|
+
return entries;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
function extractCookieJarEntriesFromHttpFields(fields) {
|
|
2630
|
+
if (!Array.isArray(fields)) return [];
|
|
2631
|
+
const cookieEntries = [];
|
|
2632
|
+
const addCookieEntry = (entry) => {
|
|
2633
|
+
const normalizedEntry =
|
|
2634
|
+
typeof entry === "string" ? entry.trim() : String(entry || "").trim();
|
|
2635
|
+
if (!normalizedEntry || !normalizedEntry.includes("=")) return;
|
|
2636
|
+
if (!cookieEntries.includes(normalizedEntry)) {
|
|
2637
|
+
cookieEntries.push(normalizedEntry);
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
|
|
2641
|
+
fields.forEach((field) => {
|
|
2642
|
+
const fieldName = String(field?.name || "").trim().toLowerCase();
|
|
2643
|
+
const fieldValue = typeof field?.value === "string" ? field.value.trim() : "";
|
|
2644
|
+
if (!fieldValue) return;
|
|
2645
|
+
if (fieldName === "cookie") {
|
|
2646
|
+
splitCookieHeaderEntries(fieldValue).forEach((cookieEntry) => {
|
|
2647
|
+
addCookieEntry(cookieEntry);
|
|
2648
|
+
});
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
if (fieldName === "set-cookie") {
|
|
2652
|
+
addCookieEntry(fieldValue);
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
|
|
2656
|
+
return cookieEntries;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
keystorePanel = createKeystorePanel({
|
|
2660
|
+
statusUpdate,
|
|
2661
|
+
writeLogEntry,
|
|
2662
|
+
doError,
|
|
2663
|
+
logErrorEntry,
|
|
2664
|
+
getCapturedPackets: () => capturedPackets,
|
|
2665
|
+
getJsonCapture: () => jsonCapture,
|
|
2666
|
+
setActiveMainTab: (tabName) => {
|
|
2667
|
+
activeMainTab = tabName;
|
|
2668
|
+
},
|
|
2669
|
+
MAIN_TAB_KEYSTORE,
|
|
2670
|
+
parseDataToolsInput,
|
|
2671
|
+
decodeHttpFromBytes,
|
|
2672
|
+
extractCookieJarEntriesFromHttpFields,
|
|
2673
|
+
getTrimmedSelectionText,
|
|
2674
|
+
hideConvertContextMenu,
|
|
2675
|
+
getActiveContextConversionText: () => activeContextConversionText,
|
|
2676
|
+
getApplyCryptCertificateText: () => applyCryptCertificateText,
|
|
2677
|
+
getApplyCryptPrivateKeyText: () => applyCryptPrivateKeyText,
|
|
2678
|
+
openExternalUrl: (url) => window.browserapi.openExternalUrl(url),
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
function buildCookieJarTextFromHttpFields(fields) {
|
|
2682
|
+
return extractCookieJarEntriesFromHttpFields(fields).join("\n");
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function getCookieJarTextForCurrentPacket() {
|
|
2686
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
2687
|
+
if (!payloadHex) return "";
|
|
2688
|
+
try {
|
|
2689
|
+
const bytes = parseDataToolsInput("hex", payloadHex);
|
|
2690
|
+
const decodedHttp = decodeHttpFromBytes(bytes);
|
|
2691
|
+
return decodedHttp?.protocol === "HTTP"
|
|
2692
|
+
? buildCookieJarTextFromHttpFields(decodedHttp.fields)
|
|
2693
|
+
: "";
|
|
2694
|
+
} catch {
|
|
2695
|
+
// Context-menu cookie actions are best-effort for the active packet.
|
|
2696
|
+
// Fallback to no cookie jar when payload decode fails.
|
|
2697
|
+
return "";
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function getCookieJarTextForContextTarget(target) {
|
|
2702
|
+
if (target?.closest?.("#data-tools-proto-output")) {
|
|
2703
|
+
const dataToolsCookieJarText =
|
|
2704
|
+
getActiveDataToolsProtoResult()?.protocol === "HTTP"
|
|
2705
|
+
? buildCookieJarTextFromHttpFields(getActiveDataToolsProtoResult().fields)
|
|
2706
|
+
: "";
|
|
2707
|
+
if (dataToolsCookieJarText) return dataToolsCookieJarText;
|
|
2708
|
+
}
|
|
2709
|
+
return getCookieJarTextForCurrentPacket();
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
function getConversionTextFromTarget(target) {
|
|
2713
|
+
const selectedText = window.getSelection()?.toString().trim();
|
|
2714
|
+
if (selectedText) return selectedText;
|
|
2715
|
+
|
|
2716
|
+
const directValue =
|
|
2717
|
+
target && "value" in target && typeof target.value === "string"
|
|
2718
|
+
? target.value.trim()
|
|
2719
|
+
: "";
|
|
2720
|
+
if (directValue) return directValue;
|
|
2721
|
+
|
|
2722
|
+
if (target?.classList?.contains("griditem")) {
|
|
2723
|
+
return target.textContent.trim();
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
const textContent = target?.textContent ? target.textContent.trim() : "";
|
|
2727
|
+
if (!textContent) return "";
|
|
2728
|
+
|
|
2729
|
+
if (textContent.includes(":")) {
|
|
2730
|
+
const prefix = textContent.split(":")[0]?.trim();
|
|
2731
|
+
const looksLikeLabel = /^[A-Za-z][\w\s-]*$/.test(prefix);
|
|
2732
|
+
// Keep full suffix so values containing additional colons (IPv6/timestamps)
|
|
2733
|
+
// are preserved, e.g. "Label: fe80::1" or "Time: 12:34:56".
|
|
2734
|
+
if (looksLikeLabel) {
|
|
2735
|
+
const suffix = textContent.split(":").slice(1).join(":").trim();
|
|
2736
|
+
if (suffix) return suffix;
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
return textContent;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function getPasteTargetFromContextTarget(target) {
|
|
2744
|
+
if (!(target instanceof Element)) return null;
|
|
2745
|
+
const editableTarget = target.closest(
|
|
2746
|
+
'input, textarea, [contenteditable="true"], [contenteditable=""]',
|
|
2747
|
+
);
|
|
2748
|
+
if (!editableTarget) return null;
|
|
2749
|
+
|
|
2750
|
+
if ("readOnly" in editableTarget && editableTarget.readOnly) return null;
|
|
2751
|
+
if ("disabled" in editableTarget && editableTarget.disabled) return null;
|
|
2752
|
+
|
|
2753
|
+
if (editableTarget.tagName === "INPUT") {
|
|
2754
|
+
const disallowedInputTypes = new Set([
|
|
2755
|
+
"button",
|
|
2756
|
+
"checkbox",
|
|
2757
|
+
"color",
|
|
2758
|
+
"file",
|
|
2759
|
+
"hidden",
|
|
2760
|
+
"image",
|
|
2761
|
+
"radio",
|
|
2762
|
+
"range",
|
|
2763
|
+
"reset",
|
|
2764
|
+
"submit",
|
|
2765
|
+
]);
|
|
2766
|
+
const inputType = (editableTarget.type || "text").toLowerCase();
|
|
2767
|
+
if (disallowedInputTypes.has(inputType)) return null;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
return editableTarget;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
function showConvertContextMenu(
|
|
2774
|
+
x,
|
|
2775
|
+
y,
|
|
2776
|
+
sourceText,
|
|
2777
|
+
formats,
|
|
2778
|
+
{
|
|
2779
|
+
isHexViewTarget = false,
|
|
2780
|
+
target = null,
|
|
2781
|
+
pasteTarget = null,
|
|
2782
|
+
showCopySelection = false,
|
|
2783
|
+
showPaste = true,
|
|
2784
|
+
showSaveJson = true,
|
|
2785
|
+
filterQueries = {},
|
|
2786
|
+
cookieJarText = "",
|
|
2787
|
+
showManualKeystoreUri = false,
|
|
2788
|
+
} = {},
|
|
2789
|
+
) {
|
|
2790
|
+
activeContextConversionText = sourceText;
|
|
2791
|
+
activeContextTarget = target;
|
|
2792
|
+
activeContextPasteTarget = pasteTarget;
|
|
2793
|
+
activeContextFilterQueries = filterQueries;
|
|
2794
|
+
activeContextCookieJarText = cookieJarText;
|
|
2795
|
+
|
|
2796
|
+
convertContextButtons.copy.style.display = showCopySelection
|
|
2797
|
+
? "block"
|
|
2798
|
+
: "none";
|
|
2799
|
+
convertContextButtons.paste.style.display = showPaste ? "block" : "none";
|
|
2800
|
+
convertContextButtons.saveJson.style.display = showSaveJson
|
|
2801
|
+
? "block"
|
|
2802
|
+
: "none";
|
|
2803
|
+
const hasPacketToExport = Boolean(
|
|
2804
|
+
getCurrentPacketForExport(packetsForHost, getActivePacketCursor()),
|
|
2805
|
+
);
|
|
2806
|
+
const currentPayloadHex = getCurrentRawPayloadHex();
|
|
2807
|
+
const hasPayloadToExport = Boolean(currentPayloadHex);
|
|
2808
|
+
const hasHttpBody = Boolean(getCurrentHttpBodyHex());
|
|
2809
|
+
convertContextButtons.exportPacket.style.display = hasPacketToExport
|
|
2810
|
+
? "block"
|
|
2811
|
+
: "none";
|
|
2812
|
+
convertContextButtons.exportPayload.style.display = hasPayloadToExport
|
|
2813
|
+
? "block"
|
|
2814
|
+
: "none";
|
|
2815
|
+
convertContextButtons.httpFileSave.style.display = hasHttpBody
|
|
2816
|
+
? "block"
|
|
2817
|
+
: "none";
|
|
2818
|
+
convertContextButtons.httpFileLoad.style.display = hasHttpBody
|
|
2819
|
+
? "block"
|
|
2820
|
+
: "none";
|
|
2821
|
+
convertContextButtons.httpFilePreview.style.display = hasHttpBody
|
|
2822
|
+
? "block"
|
|
2823
|
+
: "none";
|
|
2824
|
+
|
|
2825
|
+
["hex", "binary", "base64", "decimal", "ascii"].forEach((format) => {
|
|
2826
|
+
convertContextButtons[format].style.display = formats.includes(format)
|
|
2827
|
+
? "block"
|
|
2828
|
+
: "none";
|
|
2829
|
+
});
|
|
2830
|
+
convertContextButtons.copyHex.style.display = isHexViewTarget
|
|
2831
|
+
? "block"
|
|
2832
|
+
: "none";
|
|
2833
|
+
convertContextButtons.copyAscii.style.display = isHexViewTarget
|
|
2834
|
+
? "block"
|
|
2835
|
+
: "none";
|
|
2836
|
+
convertContextButtons.copyRaw.style.display = isHexViewTarget
|
|
2837
|
+
? "block"
|
|
2838
|
+
: "none";
|
|
2839
|
+
convertContextButtons.copyCookieJar.style.display = cookieJarText
|
|
2840
|
+
? "block"
|
|
2841
|
+
: "none";
|
|
2842
|
+
convertContextButtons.saveCookieJar.style.display = cookieJarText
|
|
2843
|
+
? "block"
|
|
2844
|
+
: "none";
|
|
2845
|
+
convertContextButtons.loadPayload.style.display = hasPayloadToExport
|
|
2846
|
+
? "block"
|
|
2847
|
+
: "none";
|
|
2848
|
+
const cursorByteIndex = Number.parseInt(target?.dataset?.byteIndex ?? "-1", 10);
|
|
2849
|
+
const hasCursorAsciiValue = Boolean(
|
|
2850
|
+
target?.classList?.contains("griditem") &&
|
|
2851
|
+
getCursorAsciiContextLoadData(currentPayloadHex, cursorByteIndex),
|
|
2852
|
+
);
|
|
2853
|
+
convertContextButtons.loadCursorAscii.style.display = hasCursorAsciiValue
|
|
2854
|
+
? "block"
|
|
2855
|
+
: "none";
|
|
2856
|
+
convertContextButtons.filterIp.style.display = filterQueries.ip
|
|
2857
|
+
? "block"
|
|
2858
|
+
: "none";
|
|
2859
|
+
convertContextButtons.filterPort.style.display = filterQueries.port
|
|
2860
|
+
? "block"
|
|
2861
|
+
: "none";
|
|
2862
|
+
convertContextButtons.filterMac.style.display = filterQueries.mac
|
|
2863
|
+
? "block"
|
|
2864
|
+
: "none";
|
|
2865
|
+
convertContextButtons.filterProtocol.style.display = filterQueries.protocol
|
|
2866
|
+
? "block"
|
|
2867
|
+
: "none";
|
|
2868
|
+
convertContextButtons.filterMime.style.display = filterQueries.mime
|
|
2869
|
+
? "block"
|
|
2870
|
+
: "none";
|
|
2871
|
+
convertContextButtons.filterOrIp.style.display = filterQueries.ip
|
|
2872
|
+
? "block"
|
|
2873
|
+
: "none";
|
|
2874
|
+
convertContextButtons.filterOrPort.style.display = filterQueries.port
|
|
2875
|
+
? "block"
|
|
2876
|
+
: "none";
|
|
2877
|
+
convertContextButtons.filterOrMac.style.display = filterQueries.mac
|
|
2878
|
+
? "block"
|
|
2879
|
+
: "none";
|
|
2880
|
+
convertContextButtons.filterOrProtocol.style.display = filterQueries.protocol
|
|
2881
|
+
? "block"
|
|
2882
|
+
: "none";
|
|
2883
|
+
convertContextButtons.filterOrMime.style.display = filterQueries.mime
|
|
2884
|
+
? "block"
|
|
2885
|
+
: "none";
|
|
2886
|
+
convertContextButtons.filterNotIp.style.display = filterQueries.ip
|
|
2887
|
+
? "block"
|
|
2888
|
+
: "none";
|
|
2889
|
+
convertContextButtons.filterNotPort.style.display = filterQueries.port
|
|
2890
|
+
? "block"
|
|
2891
|
+
: "none";
|
|
2892
|
+
convertContextButtons.filterNotMac.style.display = filterQueries.mac
|
|
2893
|
+
? "block"
|
|
2894
|
+
: "none";
|
|
2895
|
+
convertContextButtons.filterNotProtocol.style.display = filterQueries.protocol
|
|
2896
|
+
? "block"
|
|
2897
|
+
: "none";
|
|
2898
|
+
convertContextButtons.filterNotMime.style.display = filterQueries.mime
|
|
2899
|
+
? "block"
|
|
2900
|
+
: "none";
|
|
2901
|
+
convertContextButtons.filterParenOpen.style.display = "block";
|
|
2902
|
+
convertContextButtons.filterParenClose.style.display = "block";
|
|
2903
|
+
convertContextButtons.filterParenWrap.style.display = "block";
|
|
2904
|
+
convertContextButtons.filterClearIp.style.display = filterQueries.ip
|
|
2905
|
+
? "block"
|
|
2906
|
+
: "none";
|
|
2907
|
+
convertContextButtons.filterClearPort.style.display = filterQueries.port
|
|
2908
|
+
? "block"
|
|
2909
|
+
: "none";
|
|
2910
|
+
convertContextButtons.filterClearMac.style.display = filterQueries.mac
|
|
2911
|
+
? "block"
|
|
2912
|
+
: "none";
|
|
2913
|
+
convertContextButtons.filterClearProtocol.style.display =
|
|
2914
|
+
filterQueries.protocol ? "block" : "none";
|
|
2915
|
+
convertContextButtons.filterClearMime.style.display = filterQueries.mime
|
|
2916
|
+
? "block"
|
|
2917
|
+
: "none";
|
|
2918
|
+
const hasCookieActions = Boolean(cookieJarText);
|
|
2919
|
+
const hasContextDataForNotes = Boolean(
|
|
2920
|
+
(sourceText && sourceText.trim()) || getTrimmedSelectionText(),
|
|
2921
|
+
);
|
|
2922
|
+
const hasConvOutputForNotes = Boolean(buildConvConvertedOutputNoteText());
|
|
2923
|
+
const hasConvHashesForNotes = Boolean(buildConvHashesNoteText());
|
|
2924
|
+
convertContextButtons.notesSendData.style.display = hasContextDataForNotes
|
|
2925
|
+
? "block"
|
|
2926
|
+
: "none";
|
|
2927
|
+
convertContextButtons.notesSendConvOutput.style.display = hasConvOutputForNotes
|
|
2928
|
+
? "block"
|
|
2929
|
+
: "none";
|
|
2930
|
+
convertContextButtons.notesSendConvHashes.style.display = hasConvHashesForNotes
|
|
2931
|
+
? "block"
|
|
2932
|
+
: "none";
|
|
2933
|
+
const hasNotesActions =
|
|
2934
|
+
hasContextDataForNotes || hasConvOutputForNotes || hasConvHashesForNotes;
|
|
2935
|
+
const hasCopyActions = showCopySelection || isHexViewTarget || hasCookieActions;
|
|
2936
|
+
const hasClipboardActions = hasCopyActions || showPaste;
|
|
2937
|
+
const hasGeneralActions = hasClipboardActions;
|
|
2938
|
+
const hasDataTypeActions =
|
|
2939
|
+
formats.length > 0 || hasPayloadToExport || hasCursorAsciiValue;
|
|
2940
|
+
const hasFilterActions = Object.values(filterQueries).some(Boolean);
|
|
2941
|
+
const hasContextTextKeystoreActions = showCopySelection || Boolean(sourceText);
|
|
2942
|
+
const hasKeystoreActions = hasContextTextKeystoreActions || showManualKeystoreUri;
|
|
2943
|
+
const hasExportActions =
|
|
2944
|
+
showSaveJson || hasPacketToExport || hasPayloadToExport || hasCookieActions;
|
|
2945
|
+
convertContextSubmenus.copy.style.display = hasCopyActions ? "block" : "none";
|
|
2946
|
+
convertContextSubmenus.convert.style.display = hasDataTypeActions
|
|
2947
|
+
? "block"
|
|
2948
|
+
: "none";
|
|
2949
|
+
convertContextSubmenus.filter.style.display = hasFilterActions
|
|
2950
|
+
? "block"
|
|
2951
|
+
: "none";
|
|
2952
|
+
convertContextSubmenus.filterAnd.style.display = hasFilterActions
|
|
2953
|
+
? "block"
|
|
2954
|
+
: "none";
|
|
2955
|
+
convertContextSubmenus.filterOr.style.display = hasFilterActions
|
|
2956
|
+
? "block"
|
|
2957
|
+
: "none";
|
|
2958
|
+
convertContextSubmenus.filterNot.style.display = hasFilterActions
|
|
2959
|
+
? "block"
|
|
2960
|
+
: "none";
|
|
2961
|
+
convertContextSubmenus.filterParentheses.style.display = hasFilterActions
|
|
2962
|
+
? "block"
|
|
2963
|
+
: "none";
|
|
2964
|
+
convertContextSubmenus.filterClear.style.display = hasFilterActions
|
|
2965
|
+
? "block"
|
|
2966
|
+
: "none";
|
|
2967
|
+
convertContextSubmenus.notes.style.display = hasNotesActions ? "block" : "none";
|
|
2968
|
+
convertContextSubmenus.keystore.style.display = hasKeystoreActions
|
|
2969
|
+
? "block"
|
|
2970
|
+
: "none";
|
|
2971
|
+
convertContextSubmenus.keystorePassword.style.display = hasContextTextKeystoreActions
|
|
2972
|
+
? "block"
|
|
2973
|
+
: "none";
|
|
2974
|
+
convertContextSubmenus.keystoreKey.style.display = hasContextTextKeystoreActions
|
|
2975
|
+
? "block"
|
|
2976
|
+
: "none";
|
|
2977
|
+
convertContextSubmenus.keystoreCert.style.display = hasContextTextKeystoreActions
|
|
2978
|
+
? "block"
|
|
2979
|
+
: "none";
|
|
2980
|
+
convertContextSubmenus.keystoreCookie.style.display = hasContextTextKeystoreActions
|
|
2981
|
+
? "block"
|
|
2982
|
+
: "none";
|
|
2983
|
+
convertContextSubmenus.keystoreUri.style.display = showManualKeystoreUri
|
|
2984
|
+
? "block"
|
|
2985
|
+
: "none";
|
|
2986
|
+
convertContextSubmenus.export.style.display = hasExportActions
|
|
2987
|
+
? "block"
|
|
2988
|
+
: "none";
|
|
2989
|
+
convertContextSubmenus.httpFile.style.display = hasHttpBody ? "block" : "none";
|
|
2990
|
+
if (
|
|
2991
|
+
!hasGeneralActions &&
|
|
2992
|
+
!hasDataTypeActions &&
|
|
2993
|
+
!isHexViewTarget &&
|
|
2994
|
+
!hasFilterActions &&
|
|
2995
|
+
!hasCookieActions &&
|
|
2996
|
+
!hasNotesActions &&
|
|
2997
|
+
!hasKeystoreActions &&
|
|
2998
|
+
!hasExportActions &&
|
|
2999
|
+
!hasHttpBody
|
|
3000
|
+
) {
|
|
3001
|
+
hideConvertContextMenu();
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
convertContextDividerEl.style.display =
|
|
3005
|
+
hasClipboardActions &&
|
|
3006
|
+
(hasDataTypeActions ||
|
|
3007
|
+
isHexViewTarget ||
|
|
3008
|
+
hasFilterActions ||
|
|
3009
|
+
hasExportActions ||
|
|
3010
|
+
hasHttpBody)
|
|
3011
|
+
? "block"
|
|
3012
|
+
: "none";
|
|
3013
|
+
convertContextSaveDividerEl.style.display =
|
|
3014
|
+
(hasExportActions || hasHttpBody) &&
|
|
3015
|
+
(hasClipboardActions ||
|
|
3016
|
+
hasDataTypeActions ||
|
|
3017
|
+
isHexViewTarget ||
|
|
3018
|
+
hasFilterActions ||
|
|
3019
|
+
hasCookieActions ||
|
|
3020
|
+
hasKeystoreActions)
|
|
3021
|
+
? "block"
|
|
3022
|
+
: "none";
|
|
3023
|
+
|
|
3024
|
+
convertContextMenuEl.hidden = false;
|
|
3025
|
+
const menuWidth = convertContextMenuEl.offsetWidth;
|
|
3026
|
+
const menuHeight = convertContextMenuEl.offsetHeight;
|
|
3027
|
+
const boundedX = Math.max(8, Math.min(x, window.innerWidth - menuWidth - 8));
|
|
3028
|
+
const boundedY = Math.max(
|
|
3029
|
+
8,
|
|
3030
|
+
Math.min(y, window.innerHeight - menuHeight - 8),
|
|
3031
|
+
);
|
|
3032
|
+
convertContextMenuEl.style.left = `${boundedX}px`;
|
|
3033
|
+
convertContextMenuEl.style.top = `${boundedY}px`;
|
|
3034
|
+
updateConvertContextSubmenuPositions();
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
function loadContextValueIntoDataTools(format) {
|
|
3038
|
+
if (!activeContextConversionText) return;
|
|
3039
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
3040
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
3041
|
+
inputEl.value = activeContextConversionText;
|
|
3042
|
+
formatEl.value = format;
|
|
3043
|
+
showDataTools();
|
|
3044
|
+
runDataToolsConversion();
|
|
3045
|
+
hideConvertContextMenu();
|
|
3046
|
+
writeLogEntry(`Context conversion loaded format=${format}`);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
function loadRawPayloadIntoDataToolsFromContextMenu() {
|
|
3050
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
3051
|
+
hideConvertContextMenu();
|
|
3052
|
+
if (!payloadHex) {
|
|
3053
|
+
statusUpdate("Status: No raw payload available to load");
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
3057
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
3058
|
+
inputEl.value = payloadHex;
|
|
3059
|
+
formatEl.value = "hex";
|
|
3060
|
+
showDataTools();
|
|
3061
|
+
runDataToolsConversion();
|
|
3062
|
+
writeLogEntry("Context conversion loaded raw payload into Conv tab");
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
function getCursorAsciiContextLoadData(payloadHex, byteIndex) {
|
|
3066
|
+
if (byteIndex < 0 || !payloadHex) return null;
|
|
3067
|
+
const decodedAscii = hexToAscii(payloadHex);
|
|
3068
|
+
let printableSequence = "";
|
|
3069
|
+
for (let i = byteIndex; i < decodedAscii.length; i++) {
|
|
3070
|
+
const charCode = decodedAscii.charCodeAt(i);
|
|
3071
|
+
if (!isPrintable(charCode)) break;
|
|
3072
|
+
printableSequence += decodedAscii[i];
|
|
3073
|
+
}
|
|
3074
|
+
if (printableSequence) {
|
|
3075
|
+
return { value: printableSequence, format: "ascii" };
|
|
3076
|
+
}
|
|
3077
|
+
const hexOffset = byteIndex * 2;
|
|
3078
|
+
const hexPair = payloadHex.slice(hexOffset, hexOffset + 2);
|
|
3079
|
+
if (hexPair.length !== 2 || !/^[0-9A-Fa-f]{2}$/.test(hexPair)) return null;
|
|
3080
|
+
return { value: hexPair.toUpperCase(), format: "hex" };
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
function loadCursorAsciiIntoDataToolsFromContextMenu() {
|
|
3084
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
3085
|
+
const byteIndex = Number.parseInt(
|
|
3086
|
+
activeContextTarget?.dataset?.byteIndex ?? "-1",
|
|
3087
|
+
10,
|
|
3088
|
+
);
|
|
3089
|
+
const cursorAsciiLoadData = getCursorAsciiContextLoadData(payloadHex, byteIndex);
|
|
3090
|
+
hideConvertContextMenu();
|
|
3091
|
+
if (!cursorAsciiLoadData) {
|
|
3092
|
+
statusUpdate("Status: No cursor ASCII value available to load");
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
3096
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
3097
|
+
inputEl.value = cursorAsciiLoadData.value;
|
|
3098
|
+
formatEl.value = cursorAsciiLoadData.format;
|
|
3099
|
+
showDataTools();
|
|
3100
|
+
runDataToolsConversion();
|
|
3101
|
+
writeLogEntry(
|
|
3102
|
+
`Context conversion loaded cursor ASCII into Conv tab format=${cursorAsciiLoadData.format}`,
|
|
3103
|
+
);
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
function getActivePacketCursor() {
|
|
3107
|
+
return Number.isInteger(activePacketCursor) && activePacketCursor >= 0
|
|
3108
|
+
? activePacketCursor
|
|
3109
|
+
: null;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
function setActivePacketCursor(nextIndex) {
|
|
3113
|
+
const parsedIndex = Number.parseInt(nextIndex, 10);
|
|
3114
|
+
activePacketCursor =
|
|
3115
|
+
Number.isNaN(parsedIndex) || parsedIndex < 0 ? null : parsedIndex;
|
|
3116
|
+
return activePacketCursor;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
function getCurrentRawPayloadHex() {
|
|
3120
|
+
const packetCursor = getActivePacketCursor();
|
|
3121
|
+
const payloadHex =
|
|
3122
|
+
packetsForHost?.[packetCursor]?.["Packet Info"]?.["Raw data"]?.[
|
|
3123
|
+
"Payload"
|
|
3124
|
+
]?.["Hex Encoded"];
|
|
3125
|
+
return typeof payloadHex === "string" ? payloadHex : "";
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
function getCurrentHttpData() {
|
|
3129
|
+
const cursor = getActivePacketCursor();
|
|
3130
|
+
if (cursor === null) return null;
|
|
3131
|
+
const packetInfo = packetsForHost?.[cursor]?.["Packet Info"];
|
|
3132
|
+
if (!packetInfo) return null;
|
|
3133
|
+
const protocol = packetInfo["Protocol"] || "TCP";
|
|
3134
|
+
return packetInfo[protocol]?.["HTTP"] || null;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
function extractHttpBodyHex(payloadHex) {
|
|
3138
|
+
if (!payloadHex) return "";
|
|
3139
|
+
// Locate the HTTP header/body separator in hex space.
|
|
3140
|
+
// RFC 7230 mandates \r\n\r\n which encodes as "0d0a0d0a".
|
|
3141
|
+
const lower = payloadHex.toLowerCase();
|
|
3142
|
+
const sepIdx = lower.indexOf("0d0a0d0a");
|
|
3143
|
+
if (sepIdx === -1) return "";
|
|
3144
|
+
const bodyStart = sepIdx + 8; // skip past the 4-byte CRLFCRLF separator
|
|
3145
|
+
if (bodyStart >= payloadHex.length) return "";
|
|
3146
|
+
return payloadHex.slice(bodyStart);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
function getCurrentHttpBodyHex() {
|
|
3150
|
+
return extractHttpBodyHex(getCurrentRawPayloadHex());
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
function getCurrentPacketForExport(packetSet, packetIndex) {
|
|
3154
|
+
if (!Number.isInteger(packetIndex) || packetIndex < 0) {
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
return packetSet?.[packetIndex] || null;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
async function copyTextToClipboard(text, label) {
|
|
3161
|
+
if (!text) {
|
|
3162
|
+
statusUpdate(`Status: No ${label.toLowerCase()} available to copy`);
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
try {
|
|
3167
|
+
await navigator.clipboard.writeText(text);
|
|
3168
|
+
} catch {
|
|
3169
|
+
const fallbackInput = document.createElement("textarea");
|
|
3170
|
+
fallbackInput.value = text;
|
|
3171
|
+
fallbackInput.style.position = "fixed";
|
|
3172
|
+
fallbackInput.style.left = "-9999px";
|
|
3173
|
+
document.body.appendChild(fallbackInput);
|
|
3174
|
+
fallbackInput.focus();
|
|
3175
|
+
fallbackInput.select();
|
|
3176
|
+
document.execCommand("copy");
|
|
3177
|
+
document.body.removeChild(fallbackInput);
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
statusUpdate(`Status: Copied ${label} to clipboard`);
|
|
3181
|
+
writeLogEntry(`Copied ${label} length=${text.length}`);
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
function getAsciiPreviewForHexOffset(payloadHex, byteIndex) {
|
|
3185
|
+
if (byteIndex < 0) return "";
|
|
3186
|
+
const decodedAscii = hexToAscii(payloadHex);
|
|
3187
|
+
let printableSequence = "";
|
|
3188
|
+
for (let i = byteIndex; i < decodedAscii.length; i++) {
|
|
3189
|
+
const charCode = decodedAscii.charCodeAt(i);
|
|
3190
|
+
if (!isPrintable(charCode)) break;
|
|
3191
|
+
printableSequence += decodedAscii[i];
|
|
3192
|
+
}
|
|
3193
|
+
if (printableSequence.length > 0) return printableSequence;
|
|
3194
|
+
const fallbackCode = decodedAscii.charCodeAt(byteIndex);
|
|
3195
|
+
if (Number.isNaN(fallbackCode)) return "";
|
|
3196
|
+
return isPrintable(fallbackCode) ? decodedAscii[byteIndex] : ".";
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
async function copyHexFromContext() {
|
|
3200
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
3201
|
+
const hexValue = activeContextTarget?.classList?.contains("griditem")
|
|
3202
|
+
? activeContextTarget.textContent.trim()
|
|
3203
|
+
: payloadHex;
|
|
3204
|
+
await copyTextToClipboard(hexValue, "Hex");
|
|
3205
|
+
hideConvertContextMenu();
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
async function copyAsciiFromContext() {
|
|
3209
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
3210
|
+
const byteIndex = Number.parseInt(
|
|
3211
|
+
activeContextTarget?.dataset?.byteIndex ?? "-1",
|
|
3212
|
+
10,
|
|
3213
|
+
);
|
|
3214
|
+
const fullPayloadAscii = payloadHex
|
|
3215
|
+
? bytesToPrintableAscii(parseDataToolsInput("hex", payloadHex))
|
|
3216
|
+
: "";
|
|
3217
|
+
const asciiValue = activeContextTarget?.classList?.contains("griditem")
|
|
3218
|
+
? getAsciiPreviewForHexOffset(payloadHex, byteIndex)
|
|
3219
|
+
: fullPayloadAscii;
|
|
3220
|
+
await copyTextToClipboard(asciiValue, "ASCII");
|
|
3221
|
+
hideConvertContextMenu();
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
async function copyRawPayloadFromContext() {
|
|
3225
|
+
await copyTextToClipboard(getCurrentRawPayloadHex(), "Raw payload");
|
|
3226
|
+
hideConvertContextMenu();
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
function copySelectedTextFromContextMenu() {
|
|
3230
|
+
const selectedText = getTrimmedSelectionText();
|
|
3231
|
+
hideConvertContextMenu();
|
|
3232
|
+
if (!selectedText) {
|
|
3233
|
+
statusUpdate("Status: No text selected to copy");
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
navigator.clipboard
|
|
3237
|
+
.writeText(selectedText)
|
|
3238
|
+
.then(() => {
|
|
3239
|
+
statusUpdate("Status: Copied selected text to clipboard");
|
|
3240
|
+
writeLogEntry(`Copied selected text length=${selectedText.length}`);
|
|
3241
|
+
})
|
|
3242
|
+
.catch((error) => {
|
|
3243
|
+
console.error("Copy failed:", error);
|
|
3244
|
+
statusUpdate("Status: Copy failed – clipboard access denied");
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
async function copyCookieJarFromContextMenu() {
|
|
3249
|
+
const cookieJarText = activeContextCookieJarText;
|
|
3250
|
+
hideConvertContextMenu();
|
|
3251
|
+
await copyTextToClipboard(cookieJarText, "Cookie Jar");
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
function pasteTextFromContextMenu() {
|
|
3255
|
+
const pasteTarget = activeContextPasteTarget;
|
|
3256
|
+
hideConvertContextMenu();
|
|
3257
|
+
if (!pasteTarget) {
|
|
3258
|
+
statusUpdate("Status: Paste unavailable for this target");
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
navigator.clipboard
|
|
3262
|
+
.readText()
|
|
3263
|
+
.then((text) => {
|
|
3264
|
+
if (
|
|
3265
|
+
pasteTarget.tagName === "INPUT" ||
|
|
3266
|
+
pasteTarget.tagName === "TEXTAREA"
|
|
3267
|
+
) {
|
|
3268
|
+
const hasSelectionRange =
|
|
3269
|
+
typeof pasteTarget.selectionStart === "number" &&
|
|
3270
|
+
typeof pasteTarget.selectionEnd === "number";
|
|
3271
|
+
const start = hasSelectionRange
|
|
3272
|
+
? pasteTarget.selectionStart
|
|
3273
|
+
: pasteTarget.value.length;
|
|
3274
|
+
const end = hasSelectionRange
|
|
3275
|
+
? pasteTarget.selectionEnd
|
|
3276
|
+
: pasteTarget.value.length;
|
|
3277
|
+
const current = pasteTarget.value;
|
|
3278
|
+
pasteTarget.value =
|
|
3279
|
+
current.substring(0, start) + text + current.substring(end);
|
|
3280
|
+
if (hasSelectionRange) {
|
|
3281
|
+
pasteTarget.selectionStart = pasteTarget.selectionEnd =
|
|
3282
|
+
start + text.length;
|
|
3283
|
+
}
|
|
3284
|
+
pasteTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
if (pasteTarget.isContentEditable) {
|
|
3289
|
+
pasteTarget.focus();
|
|
3290
|
+
const selection = window.getSelection();
|
|
3291
|
+
if (selection && selection.rangeCount > 0) {
|
|
3292
|
+
const range = selection.getRangeAt(0);
|
|
3293
|
+
range.deleteContents();
|
|
3294
|
+
const textNode = document.createTextNode(text);
|
|
3295
|
+
range.insertNode(textNode);
|
|
3296
|
+
range.setStartAfter(textNode);
|
|
3297
|
+
range.setEndAfter(textNode);
|
|
3298
|
+
selection.removeAllRanges();
|
|
3299
|
+
selection.addRange(range);
|
|
3300
|
+
} else {
|
|
3301
|
+
pasteTarget.textContent = (pasteTarget.textContent || "") + text;
|
|
3302
|
+
}
|
|
3303
|
+
pasteTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3304
|
+
}
|
|
3305
|
+
})
|
|
3306
|
+
.catch((error) => {
|
|
3307
|
+
console.error("Paste failed:", error);
|
|
3308
|
+
statusUpdate("Status: Paste failed – clipboard access denied");
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
function saveJsonFromContextMenu() {
|
|
3313
|
+
hideConvertContextMenu();
|
|
3314
|
+
void persistSessionToDisk("context-menu");
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
function exportCurrentPacketFromContextMenu() {
|
|
3318
|
+
hideConvertContextMenu();
|
|
3319
|
+
const currentPacket = getCurrentPacketForExport(
|
|
3320
|
+
packetsForHost,
|
|
3321
|
+
getActivePacketCursor(),
|
|
3322
|
+
);
|
|
3323
|
+
if (!currentPacket) {
|
|
3324
|
+
statusUpdate("Status: No packet selected to export");
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
window.saveapi.savePacket(currentPacket).then((result) => {
|
|
3328
|
+
if (result.canceled) {
|
|
3329
|
+
statusUpdate("Status: Export cancelled");
|
|
3330
|
+
} else if (result.success) {
|
|
3331
|
+
statusUpdate("Status: Packet exported successfully");
|
|
3332
|
+
writeLogEntry("Context menu packet export completed");
|
|
3333
|
+
} else {
|
|
3334
|
+
const errorMessage =
|
|
3335
|
+
result && typeof result === "object" && "error" in result
|
|
3336
|
+
? result.error
|
|
3337
|
+
: "unknown";
|
|
3338
|
+
doError("Packet export failed");
|
|
3339
|
+
logErrorEntry("export-packet", errorMessage || "unknown");
|
|
3340
|
+
statusUpdate(
|
|
3341
|
+
"Status: Packet export failed – " + (errorMessage || "unknown error"),
|
|
3342
|
+
);
|
|
3343
|
+
console.error("Packet export failed:", errorMessage);
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
function exportCurrentPayloadFromContextMenu() {
|
|
3349
|
+
hideConvertContextMenu();
|
|
3350
|
+
const payloadHex = getCurrentRawPayloadHex();
|
|
3351
|
+
if (!payloadHex) {
|
|
3352
|
+
statusUpdate("Status: No payload available to export");
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
window.saveapi.savePayload(payloadHex).then((result) => {
|
|
3356
|
+
if (result.canceled) {
|
|
3357
|
+
statusUpdate("Status: Export cancelled");
|
|
3358
|
+
} else if (result.success) {
|
|
3359
|
+
statusUpdate("Status: Payload exported successfully");
|
|
3360
|
+
writeLogEntry("Context menu payload export completed");
|
|
3361
|
+
} else {
|
|
3362
|
+
const errorMessage =
|
|
3363
|
+
result && typeof result === "object" && "error" in result
|
|
3364
|
+
? result.error
|
|
3365
|
+
: "unknown";
|
|
3366
|
+
doError("Payload export failed");
|
|
3367
|
+
logErrorEntry("export-payload", errorMessage || "unknown");
|
|
3368
|
+
statusUpdate(
|
|
3369
|
+
"Status: Payload export failed – " + (errorMessage || "unknown error"),
|
|
3370
|
+
);
|
|
3371
|
+
console.error("Payload export failed:", errorMessage);
|
|
3372
|
+
}
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
function saveCookieJarFromContextMenu() {
|
|
3377
|
+
const cookieJarText = activeContextCookieJarText;
|
|
3378
|
+
hideConvertContextMenu();
|
|
3379
|
+
if (!cookieJarText) {
|
|
3380
|
+
statusUpdate("Status: No cookie jar available to save");
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
window.saveapi.saveCookieJar(cookieJarText).then((result) => {
|
|
3384
|
+
if (result.canceled) {
|
|
3385
|
+
statusUpdate("Status: Save cancelled");
|
|
3386
|
+
} else if (result.success) {
|
|
3387
|
+
statusUpdate("Status: Cookie jar saved successfully");
|
|
3388
|
+
writeLogEntry("Context menu cookie jar save completed");
|
|
3389
|
+
} else {
|
|
3390
|
+
const errorMessage =
|
|
3391
|
+
result && typeof result === "object" && "error" in result
|
|
3392
|
+
? result.error
|
|
3393
|
+
: "unknown";
|
|
3394
|
+
doError("Cookie jar save failed");
|
|
3395
|
+
logErrorEntry("save-cookie-jar", errorMessage || "unknown");
|
|
3396
|
+
statusUpdate(
|
|
3397
|
+
"Status: Cookie jar save failed – " + (errorMessage || "unknown error"),
|
|
3398
|
+
);
|
|
3399
|
+
console.error("Cookie jar save failed:", errorMessage);
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function getHttpContentTypeForCurrentPacket() {
|
|
3405
|
+
const httpData = getCurrentHttpData();
|
|
3406
|
+
return (httpData && httpData["Content-Type"]) || "application/octet-stream";
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
function saveHttpBodyFromContextMenu() {
|
|
3410
|
+
hideConvertContextMenu();
|
|
3411
|
+
const bodyHex = getCurrentHttpBodyHex();
|
|
3412
|
+
if (!bodyHex) {
|
|
3413
|
+
statusUpdate("Status: No HTTP body available to save");
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
const contentType = getHttpContentTypeForCurrentPacket();
|
|
3417
|
+
window.saveapi.saveHttpBody(bodyHex, contentType).then((result) => {
|
|
3418
|
+
if (result.canceled) {
|
|
3419
|
+
statusUpdate("Status: Save cancelled");
|
|
3420
|
+
} else if (result.success) {
|
|
3421
|
+
statusUpdate("Status: HTTP body saved successfully");
|
|
3422
|
+
writeLogEntry("Context menu HTTP body save completed");
|
|
3423
|
+
} else {
|
|
3424
|
+
const errorMessage =
|
|
3425
|
+
result && typeof result === "object" && "error" in result
|
|
3426
|
+
? result.error
|
|
3427
|
+
: "unknown";
|
|
3428
|
+
doError("HTTP body save failed");
|
|
3429
|
+
logErrorEntry("http-body-save", errorMessage || "unknown");
|
|
3430
|
+
statusUpdate(
|
|
3431
|
+
"Status: HTTP body save failed – " + (errorMessage || "unknown error"),
|
|
3432
|
+
);
|
|
3433
|
+
console.error("HTTP body save failed:", errorMessage);
|
|
3434
|
+
}
|
|
3435
|
+
});
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
function loadHttpBodyIntoConvTabFromContextMenu() {
|
|
3439
|
+
const bodyHex = getCurrentHttpBodyHex();
|
|
3440
|
+
hideConvertContextMenu();
|
|
3441
|
+
if (!bodyHex) {
|
|
3442
|
+
statusUpdate("Status: No HTTP body available to load");
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
3446
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
3447
|
+
inputEl.value = bodyHex;
|
|
3448
|
+
formatEl.value = "hex";
|
|
3449
|
+
showDataTools();
|
|
3450
|
+
runDataToolsConversion();
|
|
3451
|
+
writeLogEntry("Context menu loaded HTTP body into Conv tab");
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
function previewHttpBodyInBrowserFromContextMenu() {
|
|
3455
|
+
hideConvertContextMenu();
|
|
3456
|
+
const bodyHex = getCurrentHttpBodyHex();
|
|
3457
|
+
if (!bodyHex) {
|
|
3458
|
+
statusUpdate("Status: No HTTP body available to preview");
|
|
3459
|
+
return;
|
|
3460
|
+
}
|
|
3461
|
+
const contentType = getHttpContentTypeForCurrentPacket();
|
|
3462
|
+
window.previewapi.previewHttpBody(bodyHex, contentType).then((result) => {
|
|
3463
|
+
if (result.success) {
|
|
3464
|
+
statusUpdate("Status: HTTP body opened in browser");
|
|
3465
|
+
writeLogEntry("Context menu HTTP body browser preview launched");
|
|
3466
|
+
} else {
|
|
3467
|
+
const errorMessage =
|
|
3468
|
+
result && typeof result === "object" && "error" in result
|
|
3469
|
+
? result.error
|
|
3470
|
+
: "unknown";
|
|
3471
|
+
doError("HTTP body preview failed");
|
|
3472
|
+
logErrorEntry("http-body-preview", errorMessage || "unknown");
|
|
3473
|
+
statusUpdate(
|
|
3474
|
+
"Status: HTTP body preview failed – " + (errorMessage || "unknown error"),
|
|
3475
|
+
);
|
|
3476
|
+
console.error("HTTP body preview failed:", errorMessage);
|
|
3477
|
+
}
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
function appendFilterQueryFromContextMenu(
|
|
3482
|
+
type,
|
|
3483
|
+
joinOperator = "&&",
|
|
3484
|
+
negate = false,
|
|
3485
|
+
) {
|
|
3486
|
+
const query = activeContextFilterQueries[type];
|
|
3487
|
+
hideConvertContextMenu();
|
|
3488
|
+
if (!query) {
|
|
3489
|
+
statusUpdate("Status: No matching filter value found for this selection");
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
if (joinOperator !== "&&" && joinOperator !== "||") {
|
|
3493
|
+
statusUpdate("Status: Could not add filter query — please try again");
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
const queryToInsert = negate ? `!(${query})` : query;
|
|
3497
|
+
const existingQuery = filterInputEl.value.trim();
|
|
3498
|
+
const wrappedQuery = negate
|
|
3499
|
+
? queryToInsert
|
|
3500
|
+
: queryToInsert.includes("||") || queryToInsert.includes("&&")
|
|
3501
|
+
? `(${queryToInsert})`
|
|
3502
|
+
: queryToInsert;
|
|
3503
|
+
if (!existingQuery) {
|
|
3504
|
+
filterInputEl.value = queryToInsert;
|
|
3505
|
+
} else if (/(?:\|\||&&)\s*$/.test(existingQuery)) {
|
|
3506
|
+
filterInputEl.value = `${existingQuery} ${wrappedQuery}`;
|
|
3507
|
+
} else {
|
|
3508
|
+
filterInputEl.value = `${existingQuery} ${joinOperator} ${wrappedQuery}`;
|
|
3509
|
+
}
|
|
3510
|
+
syncFilterHighlight();
|
|
3511
|
+
filterInputEl.focus();
|
|
3512
|
+
statusUpdate("Status: Filter query populated — press Enter to apply");
|
|
3513
|
+
writeLogEntry(
|
|
3514
|
+
`Context menu filter populated type=${type} negated=${negate} query="${filterInputEl.value}"`,
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
function clearAndFilterQueryFromContextMenu(type) {
|
|
3519
|
+
const query = activeContextFilterQueries[type];
|
|
3520
|
+
hideConvertContextMenu();
|
|
3521
|
+
if (!query) {
|
|
3522
|
+
statusUpdate("Status: No matching filter value found for this selection");
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
filterInputEl.value = query;
|
|
3526
|
+
syncFilterHighlight();
|
|
3527
|
+
filterInputEl.focus();
|
|
3528
|
+
statusUpdate("Status: Filter query populated — press Enter to apply");
|
|
3529
|
+
writeLogEntry(
|
|
3530
|
+
`Context menu filter cleared and populated type=${type} query="${filterInputEl.value}"`,
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
function appendParenthesisTokenFromContextMenu(token) {
|
|
3535
|
+
hideConvertContextMenu();
|
|
3536
|
+
if (token !== "(" && token !== ")") {
|
|
3537
|
+
statusUpdate("Status: Could not append parenthesis — please try again");
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
filterInputEl.value = `${filterInputEl.value}${token}`;
|
|
3541
|
+
syncFilterHighlight();
|
|
3542
|
+
filterInputEl.focus();
|
|
3543
|
+
statusUpdate("Status: Filter query updated — press Enter to apply");
|
|
3544
|
+
writeLogEntry(
|
|
3545
|
+
`Context menu filter appended token="${token}" query="${filterInputEl.value}"`,
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
function wrapCurrentFilterWithParenthesesFromContextMenu() {
|
|
3550
|
+
hideConvertContextMenu();
|
|
3551
|
+
const existingQuery = filterInputEl.value.trim();
|
|
3552
|
+
if (!existingQuery) {
|
|
3553
|
+
statusUpdate("Status: No filter query available to wrap");
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
filterInputEl.value = `(${existingQuery})`;
|
|
3557
|
+
syncFilterHighlight();
|
|
3558
|
+
filterInputEl.focus();
|
|
3559
|
+
statusUpdate("Status: Filter query updated — press Enter to apply");
|
|
3560
|
+
writeLogEntry(`Context menu filter wrapped query="${filterInputEl.value}"`);
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
initConvPanel({
|
|
3564
|
+
writeLogEntry,
|
|
3565
|
+
statusUpdate,
|
|
3566
|
+
setActiveMainTab: (tab) => {
|
|
3567
|
+
activeMainTab = tab;
|
|
3568
|
+
},
|
|
3569
|
+
});
|
|
3570
|
+
initializeNotesPanel();
|
|
3571
|
+
document.getElementById("close-btn").addEventListener("click", () => {
|
|
3572
|
+
void requestApplicationClose();
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3575
|
+
// Show capture stats when stats button is clicked
|
|
3576
|
+
document.getElementById("stats-btn").addEventListener("click", function () {
|
|
3577
|
+
if (!isFileLoaded) {
|
|
3578
|
+
doError("Please upload a JSON file before accessing packet statistics.");
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
showStats();
|
|
3582
|
+
});
|
|
3583
|
+
|
|
3584
|
+
// Show data conversion tools when data tools button is clicked
|
|
3585
|
+
document
|
|
3586
|
+
.getElementById("data-tools-btn")
|
|
3587
|
+
.addEventListener("click", function () {
|
|
3588
|
+
showDataTools();
|
|
3589
|
+
});
|
|
3590
|
+
|
|
3591
|
+
document.getElementById("crypt-btn").addEventListener("click", function () {
|
|
3592
|
+
if (!isFileLoaded) {
|
|
3593
|
+
doError("Please upload a JSON file before accessing crypt tools.");
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
showCryptWorkspace();
|
|
3597
|
+
});
|
|
3598
|
+
|
|
3599
|
+
document.getElementById("keystore-btn").addEventListener("click", async function () {
|
|
3600
|
+
if (!isFileLoaded) {
|
|
3601
|
+
doError("Please upload a JSON file before accessing the keystore.");
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
const unlocked = await keystorePanel.unlockPersistentKeystoreAndLoad();
|
|
3605
|
+
if (!unlocked) return;
|
|
3606
|
+
keystorePanel.showKeystoreWorkspace();
|
|
3607
|
+
});
|
|
3608
|
+
document
|
|
3609
|
+
.getElementById("crypt-keystore-unlock-confirm-btn")
|
|
3610
|
+
.addEventListener("click", keystorePanel.submitKeystoreUnlockDialog);
|
|
3611
|
+
document
|
|
3612
|
+
.getElementById("crypt-keystore-unlock-cancel-btn")
|
|
3613
|
+
.addEventListener("click", () => keystorePanel.resolveKeystoreUnlockPassword(null));
|
|
3614
|
+
document
|
|
3615
|
+
.getElementById("crypt-keystore-unlock-password")
|
|
3616
|
+
.addEventListener("keydown", (event) => {
|
|
3617
|
+
if (event.key !== "Enter") return;
|
|
3618
|
+
keystorePanel.submitKeystoreUnlockDialog();
|
|
3619
|
+
});
|
|
3620
|
+
document
|
|
3621
|
+
.getElementById("crypt-keystore-unlock-password-confirm")
|
|
3622
|
+
.addEventListener("keydown", (event) => {
|
|
3623
|
+
if (event.key !== "Enter") return;
|
|
3624
|
+
keystorePanel.submitKeystoreUnlockDialog();
|
|
3625
|
+
});
|
|
3626
|
+
document
|
|
3627
|
+
.getElementById("crypt-keystore-manual-uri-confirm-btn")
|
|
3628
|
+
.addEventListener("click", keystorePanel.submitManualUriFromContextMenuDialog);
|
|
3629
|
+
document
|
|
3630
|
+
.getElementById("crypt-keystore-manual-uri-cancel-btn")
|
|
3631
|
+
.addEventListener("click", () =>
|
|
3632
|
+
keystorePanel.resolveManualUriFromContextMenuDialog(null),
|
|
3633
|
+
);
|
|
3634
|
+
document
|
|
3635
|
+
.getElementById("crypt-keystore-manual-uri-input")
|
|
3636
|
+
.addEventListener("keydown", (event) => {
|
|
3637
|
+
if (event.key !== "Enter") return;
|
|
3638
|
+
keystorePanel.submitManualUriFromContextMenuDialog();
|
|
3639
|
+
});
|
|
3640
|
+
|
|
3641
|
+
// Show packet list when list button is clicked
|
|
3642
|
+
document.getElementById("list-btn").addEventListener("click", function () {
|
|
3643
|
+
if (!isFileLoaded) {
|
|
3644
|
+
doError("Please upload a JSON file before accessing the packet list.");
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
showPacketList();
|
|
3648
|
+
});
|
|
3649
|
+
document.getElementById("notes-btn").addEventListener("click", function () {
|
|
3650
|
+
if (!isFileLoaded) {
|
|
3651
|
+
doError("Please upload a JSON file before accessing notes.");
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
showNotesWorkspace();
|
|
3655
|
+
});
|
|
3656
|
+
|
|
3657
|
+
document
|
|
3658
|
+
.getElementById("conv-subtab-conversions")
|
|
3659
|
+
.addEventListener("click", () => setConvSubtab(CONV_CONVERSIONS_SUBTAB));
|
|
3660
|
+
document
|
|
3661
|
+
.getElementById("conv-subtab-hashes")
|
|
3662
|
+
.addEventListener("click", () => setConvSubtab(CONV_HASHES_SUBTAB));
|
|
3663
|
+
document
|
|
3664
|
+
.getElementById("conv-subtab-decodes")
|
|
3665
|
+
.addEventListener("click", () => setConvSubtab(CONV_DECODES_SUBTAB));
|
|
3666
|
+
|
|
3667
|
+
document
|
|
3668
|
+
.getElementById("crypt-subtab-ssl")
|
|
3669
|
+
.addEventListener("click", () => setCryptSubtab(CRYPT_SSL_SUBTAB));
|
|
3670
|
+
document
|
|
3671
|
+
.getElementById("crypt-subtab-pgp")
|
|
3672
|
+
.addEventListener("click", () => setCryptSubtab(CRYPT_PGP_SUBTAB));
|
|
3673
|
+
document
|
|
3674
|
+
.getElementById("crypt-subtab-openssh")
|
|
3675
|
+
.addEventListener("click", () => setCryptSubtab(CRYPT_OPENSSH_SUBTAB));
|
|
3676
|
+
document.getElementById("crypt-refresh-btn").addEventListener("click", () => {
|
|
3677
|
+
refreshCryptEncounteredEntries();
|
|
3678
|
+
});
|
|
3679
|
+
document
|
|
3680
|
+
.getElementById("crypt-encountered-list")
|
|
3681
|
+
.addEventListener("change", function () {
|
|
3682
|
+
const selectedIndex = Number(this.value);
|
|
3683
|
+
cryptPanel.selectEncounteredEntry(selectedIndex);
|
|
3684
|
+
});
|
|
3685
|
+
document
|
|
3686
|
+
.getElementById("crypt-apply-filter-btn")
|
|
3687
|
+
.addEventListener("click", applyCryptFilterForActiveEntry);
|
|
3688
|
+
document
|
|
3689
|
+
.getElementById("crypt-load-encountered-cert-btn")
|
|
3690
|
+
.addEventListener("click", loadEncounteredCertificateIntoCrypt);
|
|
3691
|
+
|
|
3692
|
+
document
|
|
3693
|
+
.getElementById("crypt-load-cert-file-btn")
|
|
3694
|
+
.addEventListener("click", () =>
|
|
3695
|
+
document.getElementById("crypt-cert-file-input").click(),
|
|
3696
|
+
);
|
|
3697
|
+
document
|
|
3698
|
+
.getElementById("crypt-cert-file-input")
|
|
3699
|
+
.addEventListener("change", function () {
|
|
3700
|
+
readCryptTextFile(this, applyCryptCertificateText);
|
|
3701
|
+
this.value = "";
|
|
3702
|
+
});
|
|
3703
|
+
document
|
|
3704
|
+
.getElementById("crypt-use-cert-input-btn")
|
|
3705
|
+
.addEventListener("click", () =>
|
|
3706
|
+
applyCryptCertificateText(
|
|
3707
|
+
document.getElementById("crypt-cert-input").value,
|
|
3708
|
+
"pasted text",
|
|
3709
|
+
),
|
|
3710
|
+
);
|
|
3711
|
+
document.getElementById("crypt-clear-cert-btn").addEventListener("click", () => {
|
|
3712
|
+
applyCryptCertificateText("", "cleared");
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
document
|
|
3716
|
+
.getElementById("crypt-load-key-file-btn")
|
|
3717
|
+
.addEventListener("click", () =>
|
|
3718
|
+
document.getElementById("crypt-key-file-input").click(),
|
|
3719
|
+
);
|
|
3720
|
+
document
|
|
3721
|
+
.getElementById("crypt-key-file-input")
|
|
3722
|
+
.addEventListener("change", function () {
|
|
3723
|
+
readCryptTextFile(this, applyCryptPrivateKeyText);
|
|
3724
|
+
this.value = "";
|
|
3725
|
+
});
|
|
3726
|
+
document
|
|
3727
|
+
.getElementById("crypt-use-key-input-btn")
|
|
3728
|
+
.addEventListener("click", () =>
|
|
3729
|
+
applyCryptPrivateKeyText(
|
|
3730
|
+
document.getElementById("crypt-key-input").value,
|
|
3731
|
+
"pasted text",
|
|
3732
|
+
),
|
|
3733
|
+
);
|
|
3734
|
+
document.getElementById("crypt-clear-key-btn").addEventListener("click", () => {
|
|
3735
|
+
applyCryptPrivateKeyText("", "cleared");
|
|
3736
|
+
});
|
|
3737
|
+
document
|
|
3738
|
+
.getElementById("crypt-decrypt-entry-btn")
|
|
3739
|
+
.addEventListener("click", decryptActiveEntryWithLoadedKey);
|
|
3740
|
+
document
|
|
3741
|
+
.getElementById("crypt-send-decrypted-conv-btn")
|
|
3742
|
+
.addEventListener("click", sendDecryptedPayloadToConvTab);
|
|
3743
|
+
document
|
|
3744
|
+
.getElementById("crypt-clear-decrypted-btn")
|
|
3745
|
+
.addEventListener("click", clearCryptDecryptionOutput);
|
|
3746
|
+
|
|
3747
|
+
document
|
|
3748
|
+
.getElementById("crypt-save-cert-keystore-btn")
|
|
3749
|
+
.addEventListener("click", () => {
|
|
3750
|
+
void keystorePanel.addCryptKeystoreEntry({
|
|
3751
|
+
type: "certificate",
|
|
3752
|
+
label: document.getElementById("crypt-keystore-label").value,
|
|
3753
|
+
source: "crypt-certificate-loader",
|
|
3754
|
+
content: document.getElementById("crypt-cert-input").value,
|
|
3755
|
+
summary: getFirstLineOrFallback(
|
|
3756
|
+
"crypt-cert-preview",
|
|
3757
|
+
"Certificate from loader",
|
|
3758
|
+
),
|
|
3759
|
+
});
|
|
3760
|
+
});
|
|
3761
|
+
document
|
|
3762
|
+
.getElementById("crypt-save-key-keystore-btn")
|
|
3763
|
+
.addEventListener("click", () => {
|
|
3764
|
+
void keystorePanel.addCryptKeystoreEntry({
|
|
3765
|
+
type: "private-key",
|
|
3766
|
+
label: document.getElementById("crypt-keystore-label").value,
|
|
3767
|
+
source: "crypt-private-key-loader",
|
|
3768
|
+
content: document.getElementById("crypt-key-input").value,
|
|
3769
|
+
summary: getFirstLineOrFallback(
|
|
3770
|
+
"crypt-key-preview",
|
|
3771
|
+
"Private key from loader",
|
|
3772
|
+
),
|
|
3773
|
+
});
|
|
3774
|
+
});
|
|
3775
|
+
document
|
|
3776
|
+
.getElementById("crypt-save-secret-keystore-btn")
|
|
3777
|
+
.addEventListener("click", () => {
|
|
3778
|
+
void keystorePanel.addCryptKeystoreEntry({
|
|
3779
|
+
type: "secret",
|
|
3780
|
+
label: document.getElementById("crypt-keystore-label").value,
|
|
3781
|
+
source: "crypt-secret-input",
|
|
3782
|
+
content: document.getElementById("crypt-credential-input").value,
|
|
3783
|
+
summary: "Manual secret/credential entry",
|
|
3784
|
+
});
|
|
3785
|
+
});
|
|
3786
|
+
document
|
|
3787
|
+
.getElementById("crypt-keystore-mode")
|
|
3788
|
+
.addEventListener("change", function () {
|
|
3789
|
+
const selectedMode = String(this.value || CRYPT_KEYSTORE_MODE_SESSION);
|
|
3790
|
+
keystorePanel.setActiveMode(
|
|
3791
|
+
selectedMode === CRYPT_KEYSTORE_MODE_PERSISTENT
|
|
3792
|
+
? CRYPT_KEYSTORE_MODE_PERSISTENT
|
|
3793
|
+
: CRYPT_KEYSTORE_MODE_SESSION,
|
|
3794
|
+
);
|
|
3795
|
+
});
|
|
3796
|
+
document
|
|
3797
|
+
.getElementById("crypt-keystore-list")
|
|
3798
|
+
.addEventListener("change", function () {
|
|
3799
|
+
const activeEntries = keystorePanel.getActiveCryptKeystoreEntries();
|
|
3800
|
+
const selectedIndex = Number(this.value);
|
|
3801
|
+
if (!Number.isFinite(selectedIndex) || !activeEntries[selectedIndex]) {
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
keystorePanel.renderCryptKeystoreDetails(activeEntries[selectedIndex]);
|
|
3805
|
+
});
|
|
3806
|
+
document
|
|
3807
|
+
.getElementById("crypt-load-keystore-entry-btn")
|
|
3808
|
+
.addEventListener("click", () => {
|
|
3809
|
+
void keystorePanel.loadSelectedCryptKeystoreEntry();
|
|
3810
|
+
});
|
|
3811
|
+
document
|
|
3812
|
+
.getElementById("crypt-send-to-persistent-btn")
|
|
3813
|
+
.addEventListener("click", () => {
|
|
3814
|
+
void keystorePanel.sendSelectedSessionEntryToPersistent();
|
|
3815
|
+
});
|
|
3816
|
+
document
|
|
3817
|
+
.getElementById("crypt-delete-keystore-entry-btn")
|
|
3818
|
+
.addEventListener("click", () => {
|
|
3819
|
+
void keystorePanel.deleteSelectedCryptKeystoreEntry();
|
|
3820
|
+
});
|
|
3821
|
+
document
|
|
3822
|
+
.getElementById("crypt-open-link-btn")
|
|
3823
|
+
.addEventListener("click", () => {
|
|
3824
|
+
void keystorePanel.openSelectedKeystoreLinkInBrowser();
|
|
3825
|
+
});
|
|
3826
|
+
|
|
3827
|
+
document
|
|
3828
|
+
.getElementById("data-tools-convert-btn")
|
|
3829
|
+
.addEventListener("click", runDataToolsConversion);
|
|
3830
|
+
document
|
|
3831
|
+
.getElementById("data-tools-hash-input-reading")
|
|
3832
|
+
.addEventListener("input", runDataToolsHashesFromInput);
|
|
3833
|
+
document
|
|
3834
|
+
.getElementById("data-tools-clear-btn")
|
|
3835
|
+
.addEventListener("click", () => {
|
|
3836
|
+
document.getElementById("data-tools-input").value = "";
|
|
3837
|
+
document.getElementById("data-tools-error").textContent = "";
|
|
3838
|
+
resetDataToolsOutputs();
|
|
3839
|
+
});
|
|
3840
|
+
document
|
|
3841
|
+
.getElementById("data-tools-proto-select")
|
|
3842
|
+
.addEventListener("change", () => {
|
|
3843
|
+
const inputEl = document.getElementById("data-tools-input");
|
|
3844
|
+
const formatEl = document.getElementById("data-tools-format");
|
|
3845
|
+
if (!inputEl.value.trim()) return;
|
|
3846
|
+
try {
|
|
3847
|
+
const bytes = parseDataToolsInput(formatEl.value, inputEl.value);
|
|
3848
|
+
runProtoDecoder(bytes);
|
|
3849
|
+
} catch {
|
|
3850
|
+
// ignore parse errors; the error will have been shown on convert
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
convertContextButtons.hex.addEventListener("click", () =>
|
|
3854
|
+
loadContextValueIntoDataTools("hex"),
|
|
3855
|
+
);
|
|
3856
|
+
convertContextButtons.binary.addEventListener("click", () =>
|
|
3857
|
+
loadContextValueIntoDataTools("binary"),
|
|
3858
|
+
);
|
|
3859
|
+
convertContextButtons.base64.addEventListener("click", () =>
|
|
3860
|
+
loadContextValueIntoDataTools("base64"),
|
|
3861
|
+
);
|
|
3862
|
+
convertContextButtons.decimal.addEventListener("click", () =>
|
|
3863
|
+
loadContextValueIntoDataTools("decimal"),
|
|
3864
|
+
);
|
|
3865
|
+
convertContextButtons.ascii.addEventListener("click", () =>
|
|
3866
|
+
loadContextValueIntoDataTools("ascii"),
|
|
3867
|
+
);
|
|
3868
|
+
convertContextButtons.loadPayload.addEventListener("click", () => {
|
|
3869
|
+
loadRawPayloadIntoDataToolsFromContextMenu();
|
|
3870
|
+
});
|
|
3871
|
+
convertContextButtons.loadCursorAscii.addEventListener("click", () => {
|
|
3872
|
+
loadCursorAsciiIntoDataToolsFromContextMenu();
|
|
3873
|
+
});
|
|
3874
|
+
convertContextButtons.copyHex.addEventListener("click", () => {
|
|
3875
|
+
copyHexFromContext();
|
|
3876
|
+
});
|
|
3877
|
+
convertContextButtons.copyAscii.addEventListener("click", () => {
|
|
3878
|
+
copyAsciiFromContext();
|
|
3879
|
+
});
|
|
3880
|
+
convertContextButtons.copyRaw.addEventListener("click", () => {
|
|
3881
|
+
copyRawPayloadFromContext();
|
|
3882
|
+
});
|
|
3883
|
+
convertContextButtons.filterIp.addEventListener("click", () => {
|
|
3884
|
+
appendFilterQueryFromContextMenu("ip", "&&");
|
|
3885
|
+
});
|
|
3886
|
+
convertContextButtons.filterPort.addEventListener("click", () => {
|
|
3887
|
+
appendFilterQueryFromContextMenu("port", "&&");
|
|
3888
|
+
});
|
|
3889
|
+
convertContextButtons.filterMac.addEventListener("click", () => {
|
|
3890
|
+
appendFilterQueryFromContextMenu("mac", "&&");
|
|
3891
|
+
});
|
|
3892
|
+
convertContextButtons.filterProtocol.addEventListener("click", () => {
|
|
3893
|
+
appendFilterQueryFromContextMenu("protocol", "&&");
|
|
3894
|
+
});
|
|
3895
|
+
convertContextButtons.filterMime.addEventListener("click", () => {
|
|
3896
|
+
appendFilterQueryFromContextMenu("mime", "&&");
|
|
3897
|
+
});
|
|
3898
|
+
convertContextButtons.filterOrIp.addEventListener("click", () => {
|
|
3899
|
+
appendFilterQueryFromContextMenu("ip", "||");
|
|
3900
|
+
});
|
|
3901
|
+
convertContextButtons.filterOrPort.addEventListener("click", () => {
|
|
3902
|
+
appendFilterQueryFromContextMenu("port", "||");
|
|
3903
|
+
});
|
|
3904
|
+
convertContextButtons.filterOrMac.addEventListener("click", () => {
|
|
3905
|
+
appendFilterQueryFromContextMenu("mac", "||");
|
|
3906
|
+
});
|
|
3907
|
+
convertContextButtons.filterOrProtocol.addEventListener("click", () => {
|
|
3908
|
+
appendFilterQueryFromContextMenu("protocol", "||");
|
|
3909
|
+
});
|
|
3910
|
+
convertContextButtons.filterOrMime.addEventListener("click", () => {
|
|
3911
|
+
appendFilterQueryFromContextMenu("mime", "||");
|
|
3912
|
+
});
|
|
3913
|
+
convertContextButtons.filterNotIp.addEventListener("click", () => {
|
|
3914
|
+
appendFilterQueryFromContextMenu("ip", "&&", true);
|
|
3915
|
+
});
|
|
3916
|
+
convertContextButtons.filterNotPort.addEventListener("click", () => {
|
|
3917
|
+
appendFilterQueryFromContextMenu("port", "&&", true);
|
|
3918
|
+
});
|
|
3919
|
+
convertContextButtons.filterNotMac.addEventListener("click", () => {
|
|
3920
|
+
appendFilterQueryFromContextMenu("mac", "&&", true);
|
|
3921
|
+
});
|
|
3922
|
+
convertContextButtons.filterNotProtocol.addEventListener("click", () => {
|
|
3923
|
+
appendFilterQueryFromContextMenu("protocol", "&&", true);
|
|
3924
|
+
});
|
|
3925
|
+
convertContextButtons.filterNotMime.addEventListener("click", () => {
|
|
3926
|
+
appendFilterQueryFromContextMenu("mime", "&&", true);
|
|
3927
|
+
});
|
|
3928
|
+
convertContextButtons.filterParenOpen.addEventListener("click", () => {
|
|
3929
|
+
appendParenthesisTokenFromContextMenu("(");
|
|
3930
|
+
});
|
|
3931
|
+
convertContextButtons.filterParenClose.addEventListener("click", () => {
|
|
3932
|
+
appendParenthesisTokenFromContextMenu(")");
|
|
3933
|
+
});
|
|
3934
|
+
convertContextButtons.filterParenWrap.addEventListener("click", () => {
|
|
3935
|
+
wrapCurrentFilterWithParenthesesFromContextMenu();
|
|
3936
|
+
});
|
|
3937
|
+
convertContextButtons.filterClearIp.addEventListener("click", () => {
|
|
3938
|
+
clearAndFilterQueryFromContextMenu("ip");
|
|
3939
|
+
});
|
|
3940
|
+
convertContextButtons.filterClearPort.addEventListener("click", () => {
|
|
3941
|
+
clearAndFilterQueryFromContextMenu("port");
|
|
3942
|
+
});
|
|
3943
|
+
convertContextButtons.filterClearMac.addEventListener("click", () => {
|
|
3944
|
+
clearAndFilterQueryFromContextMenu("mac");
|
|
3945
|
+
});
|
|
3946
|
+
convertContextButtons.filterClearProtocol.addEventListener("click", () => {
|
|
3947
|
+
clearAndFilterQueryFromContextMenu("protocol");
|
|
3948
|
+
});
|
|
3949
|
+
convertContextButtons.filterClearMime.addEventListener("click", () => {
|
|
3950
|
+
clearAndFilterQueryFromContextMenu("mime");
|
|
3951
|
+
});
|
|
3952
|
+
convertContextButtons.copy.addEventListener(
|
|
3953
|
+
"click",
|
|
3954
|
+
copySelectedTextFromContextMenu,
|
|
3955
|
+
);
|
|
3956
|
+
convertContextButtons.copyCookieJar.addEventListener(
|
|
3957
|
+
"click",
|
|
3958
|
+
copyCookieJarFromContextMenu,
|
|
3959
|
+
);
|
|
3960
|
+
convertContextButtons.paste.addEventListener("click", pasteTextFromContextMenu);
|
|
3961
|
+
convertContextButtons.notesSendData.addEventListener("click", () => {
|
|
3962
|
+
const selectedText = getTrimmedSelectionText();
|
|
3963
|
+
sendTextToNotesFromContextMenu(
|
|
3964
|
+
selectedText || activeContextConversionText,
|
|
3965
|
+
"context-data",
|
|
3966
|
+
);
|
|
3967
|
+
});
|
|
3968
|
+
convertContextButtons.notesSendConvOutput.addEventListener("click", () => {
|
|
3969
|
+
sendTextToNotesFromContextMenu(
|
|
3970
|
+
buildConvConvertedOutputNoteText(),
|
|
3971
|
+
"context-conv-output",
|
|
3972
|
+
);
|
|
3973
|
+
});
|
|
3974
|
+
convertContextButtons.notesSendConvHashes.addEventListener("click", () => {
|
|
3975
|
+
sendTextToNotesFromContextMenu(
|
|
3976
|
+
buildConvHashesNoteText(),
|
|
3977
|
+
"context-conv-hashes",
|
|
3978
|
+
);
|
|
3979
|
+
});
|
|
3980
|
+
convertContextButtons.keystorePasswordSession.addEventListener("click", () => {
|
|
3981
|
+
keystorePanel.addToKeystoreFromContextMenu("password", CRYPT_KEYSTORE_MODE_SESSION);
|
|
3982
|
+
});
|
|
3983
|
+
convertContextButtons.keystorePasswordPersistent.addEventListener("click", () => {
|
|
3984
|
+
keystorePanel.addToKeystoreFromContextMenu("password", CRYPT_KEYSTORE_MODE_PERSISTENT);
|
|
3985
|
+
});
|
|
3986
|
+
convertContextButtons.keystoreKeySession.addEventListener("click", () => {
|
|
3987
|
+
keystorePanel.addToKeystoreFromContextMenu("key", CRYPT_KEYSTORE_MODE_SESSION);
|
|
3988
|
+
});
|
|
3989
|
+
convertContextButtons.keystoreKeyPersistent.addEventListener("click", () => {
|
|
3990
|
+
keystorePanel.addToKeystoreFromContextMenu("key", CRYPT_KEYSTORE_MODE_PERSISTENT);
|
|
3991
|
+
});
|
|
3992
|
+
convertContextButtons.keystoreCertSession.addEventListener("click", () => {
|
|
3993
|
+
keystorePanel.addToKeystoreFromContextMenu("cert", CRYPT_KEYSTORE_MODE_SESSION);
|
|
3994
|
+
});
|
|
3995
|
+
convertContextButtons.keystoreCertPersistent.addEventListener("click", () => {
|
|
3996
|
+
keystorePanel.addToKeystoreFromContextMenu("cert", CRYPT_KEYSTORE_MODE_PERSISTENT);
|
|
3997
|
+
});
|
|
3998
|
+
convertContextButtons.keystoreCookieSession.addEventListener("click", () => {
|
|
3999
|
+
keystorePanel.addToKeystoreFromContextMenu("cookie", CRYPT_KEYSTORE_MODE_SESSION);
|
|
4000
|
+
});
|
|
4001
|
+
convertContextButtons.keystoreCookiePersistent.addEventListener("click", () => {
|
|
4002
|
+
keystorePanel.addToKeystoreFromContextMenu("cookie", CRYPT_KEYSTORE_MODE_PERSISTENT);
|
|
4003
|
+
});
|
|
4004
|
+
convertContextButtons.keystoreUriSession.addEventListener("click", () => {
|
|
4005
|
+
void keystorePanel.addManualUriToKeystoreFromContextMenu(
|
|
4006
|
+
CRYPT_KEYSTORE_MODE_SESSION,
|
|
4007
|
+
);
|
|
4008
|
+
});
|
|
4009
|
+
convertContextButtons.keystoreUriPersistent.addEventListener("click", () => {
|
|
4010
|
+
void keystorePanel.addManualUriToKeystoreFromContextMenu(
|
|
4011
|
+
CRYPT_KEYSTORE_MODE_PERSISTENT,
|
|
4012
|
+
);
|
|
4013
|
+
});
|
|
4014
|
+
convertContextButtons.saveJson.addEventListener(
|
|
4015
|
+
"click",
|
|
4016
|
+
saveJsonFromContextMenu,
|
|
4017
|
+
);
|
|
4018
|
+
convertContextButtons.exportPacket.addEventListener(
|
|
4019
|
+
"click",
|
|
4020
|
+
exportCurrentPacketFromContextMenu,
|
|
4021
|
+
);
|
|
4022
|
+
convertContextButtons.exportPayload.addEventListener(
|
|
4023
|
+
"click",
|
|
4024
|
+
exportCurrentPayloadFromContextMenu,
|
|
4025
|
+
);
|
|
4026
|
+
convertContextButtons.saveCookieJar.addEventListener(
|
|
4027
|
+
"click",
|
|
4028
|
+
saveCookieJarFromContextMenu,
|
|
4029
|
+
);
|
|
4030
|
+
convertContextButtons.httpFileSave.addEventListener(
|
|
4031
|
+
"click",
|
|
4032
|
+
saveHttpBodyFromContextMenu,
|
|
4033
|
+
);
|
|
4034
|
+
convertContextButtons.httpFileLoad.addEventListener(
|
|
4035
|
+
"click",
|
|
4036
|
+
loadHttpBodyIntoConvTabFromContextMenu,
|
|
4037
|
+
);
|
|
4038
|
+
convertContextButtons.httpFilePreview.addEventListener(
|
|
4039
|
+
"click",
|
|
4040
|
+
previewHttpBodyInBrowserFromContextMenu,
|
|
4041
|
+
);
|
|
4042
|
+
|
|
4043
|
+
// Handle bookmark selection from dropdown
|
|
4044
|
+
document
|
|
4045
|
+
.getElementById("selectBookmark")
|
|
4046
|
+
.addEventListener("change", function () {
|
|
4047
|
+
const bookmarkHost = document
|
|
4048
|
+
.getElementById("selectBookmark")
|
|
4049
|
+
.value.split(":")[0];
|
|
4050
|
+
index = document.getElementById("selectBookmark").value.split(":")[1];
|
|
4051
|
+
setActivePacketCursor(index);
|
|
4052
|
+
packetsForHost = capturedPackets["Host"][bookmarkHost];
|
|
4053
|
+
activeBookmark["Host"] = bookmarkHost;
|
|
4054
|
+
activeBookmark["Packet"] = index;
|
|
4055
|
+
hostFilterEl.value = bookmarkHost;
|
|
4056
|
+
if (bookmarkHost == undefined || index == undefined) {
|
|
4057
|
+
statusUpdate("Invalid bookmark selection, missing host or packet index");
|
|
4058
|
+
doError("Invalid bookmark selection, missing host or packet index!");
|
|
4059
|
+
} else {
|
|
4060
|
+
document.getElementById("target_hosts").value = bookmarkHost;
|
|
4061
|
+
}
|
|
4062
|
+
handlePacketNavigation("bookmark", activeBookmark);
|
|
4063
|
+
});
|
|
4064
|
+
|
|
4065
|
+
// Add current packet as a bookmark
|
|
4066
|
+
document.getElementById("setBookmark").addEventListener("click", function () {
|
|
4067
|
+
if (!bookmarkList.includes(currentPacketKey)) {
|
|
4068
|
+
if (currentPacketKey != undefined) {
|
|
4069
|
+
bookmarkList.push(currentPacketKey);
|
|
4070
|
+
document
|
|
4071
|
+
.getElementById("selectBookmark")
|
|
4072
|
+
.appendChild(new Option(currentPacketKey, currentPacketKey));
|
|
4073
|
+
writeLogEntry(`Bookmark added key=${currentPacketKey}`);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
});
|
|
4077
|
+
|
|
4078
|
+
// Syncs the bookmark dropdown to reflect whether the given packet key is bookmarked
|
|
4079
|
+
function syncBookmarkDropdown(packetKey) {
|
|
4080
|
+
document.getElementById("selectBookmark").value = bookmarkList.includes(
|
|
4081
|
+
packetKey,
|
|
4082
|
+
)
|
|
4083
|
+
? packetKey
|
|
4084
|
+
: "";
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
// function that returns the total number of packets in the entire capture
|
|
4088
|
+
function totalPacketCount() {
|
|
4089
|
+
let totalCount = 0;
|
|
4090
|
+
if (capturedPackets["Host"] != undefined) {
|
|
4091
|
+
for (const host in capturedPackets["Host"]) {
|
|
4092
|
+
totalCount += capturedPackets["Host"][host].length;
|
|
4093
|
+
}
|
|
4094
|
+
} else {
|
|
4095
|
+
return 0;
|
|
4096
|
+
}
|
|
4097
|
+
return totalCount;
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
/**
|
|
4101
|
+
* Returns the packet array index matching a `sourceIp:packetIndex` key.
|
|
4102
|
+
*/
|
|
4103
|
+
function findPacketIndexByKey(packetSet, packetKey) {
|
|
4104
|
+
if (!Array.isArray(packetSet) || !packetKey || typeof packetKey !== "string") {
|
|
4105
|
+
return -1;
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
const separatorIndex = packetKey.lastIndexOf(":");
|
|
4109
|
+
if (separatorIndex < 0) return -1;
|
|
4110
|
+
|
|
4111
|
+
const sourceIp = packetKey.slice(0, separatorIndex);
|
|
4112
|
+
const packetIndexValue = packetKey.slice(separatorIndex + 1);
|
|
4113
|
+
return packetSet.findIndex((packet) => {
|
|
4114
|
+
const packetInfo = packet?.["Packet Info"];
|
|
4115
|
+
if (!packetInfo) return false;
|
|
4116
|
+
const candidateSourceIp = packetInfo?.["IP"]?.["Source IP"];
|
|
4117
|
+
const candidatePacketIndex = packetInfo?.["Index"];
|
|
4118
|
+
return (
|
|
4119
|
+
String(candidateSourceIp) === sourceIp &&
|
|
4120
|
+
String(candidatePacketIndex) === packetIndexValue
|
|
4121
|
+
);
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
/**
|
|
4126
|
+
* Handles navigation between capturedPackets (next, prev, activeBookmark, first-load).
|
|
4127
|
+
* Updates UI and packet info accordingly.
|
|
4128
|
+
*/
|
|
4129
|
+
function handlePacketNavigation(navAction, navBookmark) {
|
|
4130
|
+
activeMainTab = MAIN_TAB_DATA;
|
|
4131
|
+
const previousPacketKey = currentPacketKey;
|
|
4132
|
+
const previousCursor = getActivePacketCursor();
|
|
4133
|
+
document.getElementById("prev-btn").style.display = "block";
|
|
4134
|
+
document.getElementById("next-btn").style.display = "block";
|
|
4135
|
+
document.getElementById("loading-container").style.display = "none";
|
|
4136
|
+
document.getElementById("summary_box").style.display = "none";
|
|
4137
|
+
document.getElementById("stats_box").style.display = "none";
|
|
4138
|
+
document.getElementById("list_box").style.display = "none";
|
|
4139
|
+
document.getElementById("notes_box").style.display = "none";
|
|
4140
|
+
document.getElementById("data_tools_box").style.display = "none";
|
|
4141
|
+
document.getElementById("crypt_box").style.display = "none";
|
|
4142
|
+
document.getElementById("keystore_box").style.display = "none";
|
|
4143
|
+
document.getElementById("packetInfoPane").style.display = "block";
|
|
4144
|
+
document.getElementById("packetPayloadPane").style.display = "block";
|
|
4145
|
+
document.getElementById("welcome").style.display = "none";
|
|
4146
|
+
showAllData();
|
|
4147
|
+
const rightsideDataEl = document.getElementById("rightside-data");
|
|
4148
|
+
const rightsideNotesEl = document.getElementById("rightside-notes");
|
|
4149
|
+
if (rightsideDataEl) rightsideDataEl.hidden = false;
|
|
4150
|
+
if (rightsideNotesEl) rightsideNotesEl.hidden = true;
|
|
4151
|
+
|
|
4152
|
+
document.getElementById("total-packets").innerHTML =
|
|
4153
|
+
"Total Packets: " + totalPacketCount();
|
|
4154
|
+
if (navAction === undefined) {
|
|
4155
|
+
handlePacketNavigation("first-load");
|
|
4156
|
+
}
|
|
4157
|
+
let packetSet = capturedPackets["Host"][hostFilterEl.value];
|
|
4158
|
+
if (navAction === "filtered") {
|
|
4159
|
+
packetSet = [];
|
|
4160
|
+
document.getElementById("filter-returned").textContent =
|
|
4161
|
+
"Filtered Packets: " + filteredPackets.length;
|
|
4162
|
+
packetSet = filteredPackets;
|
|
4163
|
+
writeLogEntry(
|
|
4164
|
+
`Filtered packet navigation packets_returned=${packetSet.length}`,
|
|
4165
|
+
);
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
if (navAction === "bookmark") {
|
|
4169
|
+
if (
|
|
4170
|
+
navBookmark["Host"] == undefined ||
|
|
4171
|
+
navBookmark["Packet"] == undefined
|
|
4172
|
+
) {
|
|
4173
|
+
statusUpdate("Status: Invalid bookmark data, reverting to first packet");
|
|
4174
|
+
doError("Invalid bookmark data, missing host or packet index!");
|
|
4175
|
+
handlePacketNavigation("first-load");
|
|
4176
|
+
} else {
|
|
4177
|
+
index = navBookmark["Packet"] - 1;
|
|
4178
|
+
setActivePacketCursor(index);
|
|
4179
|
+
|
|
4180
|
+
statusUpdate(
|
|
4181
|
+
"Navigating to bookmark: " +
|
|
4182
|
+
navBookmark["Host"] +
|
|
4183
|
+
" packet " +
|
|
4184
|
+
navBookmark["Packet"],
|
|
4185
|
+
);
|
|
4186
|
+
writeLogEntry(
|
|
4187
|
+
`Navigating bookmark host=${navBookmark["Host"]} packet=${navBookmark["Packet"]}`,
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
} else {
|
|
4191
|
+
const packetIndexFromKey = findPacketIndexByKey(packetSet, previousPacketKey);
|
|
4192
|
+
if (packetIndexFromKey >= 0) {
|
|
4193
|
+
index = packetIndexFromKey;
|
|
4194
|
+
} else if (
|
|
4195
|
+
Number.isInteger(previousCursor) &&
|
|
4196
|
+
previousCursor >= 0 &&
|
|
4197
|
+
previousCursor < packetSet?.length
|
|
4198
|
+
) {
|
|
4199
|
+
index = previousCursor;
|
|
4200
|
+
} else {
|
|
4201
|
+
index = 0;
|
|
4202
|
+
}
|
|
4203
|
+
setActivePacketCursor(index);
|
|
4204
|
+
}
|
|
4205
|
+
if (!packetSet || packetSet.length === 0) {
|
|
4206
|
+
statusUpdate("Status: No packets");
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
4209
|
+
if (
|
|
4210
|
+
packetSet != undefined &&
|
|
4211
|
+
(packetSet.length == 0 || packetSet[0] == undefined)
|
|
4212
|
+
) {
|
|
4213
|
+
statusUpdate("Status: No packet information found for this host");
|
|
4214
|
+
document.getElementById("main").innerHTML = "Please select a json file!";
|
|
4215
|
+
}
|
|
4216
|
+
// in the data main secton, this is where we would
|
|
4217
|
+
// add the packet info for each packet, for now we just
|
|
4218
|
+
// dump the json, we'll format later
|
|
4219
|
+
// packetsForHost[index] is an array of all packet info
|
|
4220
|
+
// for the current host, we want to be able to navigate
|
|
4221
|
+
// through it with next and prev buttons
|
|
4222
|
+
if (packetSet == undefined || packetSet[index] == undefined) {
|
|
4223
|
+
statusUpdate("Status: No packet information found for this host");
|
|
4224
|
+
doError("No packet information found for this host!");
|
|
4225
|
+
return;
|
|
4226
|
+
} else {
|
|
4227
|
+
currentIp = packetSet[index]["Packet Info"]["IP"]["Source IP"];
|
|
4228
|
+
currentPacketKey =
|
|
4229
|
+
currentIp + ":" + packetSet[index]["Packet Info"]["Index"];
|
|
4230
|
+
syncBookmarkDropdown(currentPacketKey);
|
|
4231
|
+
console.log(packetSet[index]);
|
|
4232
|
+
const hexPayload =
|
|
4233
|
+
packetSet[index]["Packet Info"]["Raw data"]["Payload"]["Hex Encoded"];
|
|
4234
|
+
infoPanel(packetSet);
|
|
4235
|
+
popHexGrid(hexPayload);
|
|
4236
|
+
populateDataTypes(packetSet);
|
|
4237
|
+
logCurrentPacketDisplay(navAction || "first-load");
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
function populateDataTypes(p) {
|
|
4241
|
+
const typesListEl = document.getElementById("types-list");
|
|
4242
|
+
typesListEl.textContent = "";
|
|
4243
|
+
const mimeTypeEl = document.getElementById("mime-type");
|
|
4244
|
+
const charsetEl = document.getElementById("charset");
|
|
4245
|
+
const encodingEl = document.getElementById("encoding");
|
|
4246
|
+
const languageEl = document.getElementById("language");
|
|
4247
|
+
encodingEl.textContent = "";
|
|
4248
|
+
languageEl.textContent = "";
|
|
4249
|
+
let encodingText = "";
|
|
4250
|
+
let languageText = "";
|
|
4251
|
+
// packetsForHost = capturedPackets["Host"][hostFilterEl.value];
|
|
4252
|
+
packetsForHost = p;
|
|
4253
|
+
let charsetText = JSON.parse(
|
|
4254
|
+
JSON.stringify(
|
|
4255
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Characters"]["Charset"],
|
|
4256
|
+
),
|
|
4257
|
+
);
|
|
4258
|
+
if (
|
|
4259
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Characters"]["Encoding"] ==
|
|
4260
|
+
"Unavailable for high entropy data"
|
|
4261
|
+
) {
|
|
4262
|
+
encodingText = JSON.parse(
|
|
4263
|
+
JSON.stringify(
|
|
4264
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Characters"]["Encoding"],
|
|
4265
|
+
),
|
|
4266
|
+
);
|
|
4267
|
+
} else {
|
|
4268
|
+
encodingText = JSON.stringify(
|
|
4269
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Characters"]["Encoding"][
|
|
4270
|
+
"encoding"
|
|
4271
|
+
],
|
|
4272
|
+
);
|
|
4273
|
+
languageText = JSON.stringify(
|
|
4274
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Characters"]["Encoding"][
|
|
4275
|
+
"language"
|
|
4276
|
+
],
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
const mimeTypeText = JSON.parse(
|
|
4281
|
+
JSON.stringify(packetsForHost[index]["Extra Info"]["MIME Type"]),
|
|
4282
|
+
);
|
|
4283
|
+
let dataItems = JSON.parse(
|
|
4284
|
+
JSON.stringify(packetsForHost[index]["Extra Info"]["Data Types"]),
|
|
4285
|
+
);
|
|
4286
|
+
let sslDetails = "";
|
|
4287
|
+
if (
|
|
4288
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Server Info"][
|
|
4289
|
+
"Encryption Data"
|
|
4290
|
+
] != "N/A" &&
|
|
4291
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Server Info"][
|
|
4292
|
+
"Encryption Data"
|
|
4293
|
+
] != undefined
|
|
4294
|
+
) {
|
|
4295
|
+
sslDetails =
|
|
4296
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Server Info"][
|
|
4297
|
+
"Encryption Data"
|
|
4298
|
+
]["SSL Version"];
|
|
4299
|
+
const protoName =
|
|
4300
|
+
packetsForHost[index]["Extra Info"]["Traits"]["Network Data"][
|
|
4301
|
+
"Port Protcol"
|
|
4302
|
+
];
|
|
4303
|
+
dataItems = [];
|
|
4304
|
+
dataItems.push(sslDetails + " encrypted stream");
|
|
4305
|
+
dataItems.push(protoName + " protocol data");
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
mimeTypeEl.textContent = "MIME type: " + mimeTypeText;
|
|
4309
|
+
charsetText = charsetText == "" ? "Unknown" : charsetText;
|
|
4310
|
+
encodingText = encodingText == "" ? "Unknown" : encodingText;
|
|
4311
|
+
if (encodingText !== undefined) {
|
|
4312
|
+
encodingEl.textContent =
|
|
4313
|
+
"Payload Encoding: " + encodingText.replace(/"/g, "");
|
|
4314
|
+
}
|
|
4315
|
+
if (languageText !== undefined) {
|
|
4316
|
+
languageEl.textContent =
|
|
4317
|
+
"Payload Language: " + languageText.replace(/"/g, "");
|
|
4318
|
+
}
|
|
4319
|
+
dataItems.forEach((item) => {
|
|
4320
|
+
const listItem = document.createElement("li");
|
|
4321
|
+
listItem.textContent = item;
|
|
4322
|
+
typesListEl.appendChild(listItem);
|
|
4323
|
+
});
|
|
4324
|
+
}
|
|
4325
|
+
// this takes a char code and returns true if it's
|
|
4326
|
+
// a printable ASCII character, false otherwise
|
|
4327
|
+
function isPrintable(charCode) {
|
|
4328
|
+
// ASCII printable: 32 (space) to 126 (~)
|
|
4329
|
+
return charCode >= 32 && charCode <= 126;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
// this changes hex to ASCII
|
|
4333
|
+
function hexToAscii(hex) {
|
|
4334
|
+
let decodedAscii = "";
|
|
4335
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
4336
|
+
decodedAscii += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
|
|
4337
|
+
}
|
|
4338
|
+
return decodedAscii;
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
// trunactes a string to a max length
|
|
4342
|
+
function truncate(str, maxLength) {
|
|
4343
|
+
if (str.length <= maxLength) return str;
|
|
4344
|
+
return str.slice(0, maxLength);
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
// returns a 0 padded hex string of a number with a given length
|
|
4348
|
+
function decToHex(num, pad) {
|
|
4349
|
+
return num.toString(16).padStart(pad, "0");
|
|
4350
|
+
}
|
|
4351
|
+
|
|
4352
|
+
// clears the higlights (its called after the moouse leaves grid)
|
|
4353
|
+
function clearGridHighlights() {
|
|
4354
|
+
document
|
|
4355
|
+
.querySelectorAll(".griditem")
|
|
4356
|
+
.forEach((el) => el.classList.remove("highlight"));
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
/**
|
|
4360
|
+
* Populates the hex grid display with the given hex string.
|
|
4361
|
+
*/
|
|
4362
|
+
function popHexGrid(hex) {
|
|
4363
|
+
// swap it back to ASCII for the fade box
|
|
4364
|
+
const payloadAsciiBox = document.getElementById("payloadascii");
|
|
4365
|
+
const decodedAscii = hexToAscii(hex);
|
|
4366
|
+
document.getElementById("hexg").textContent = "";
|
|
4367
|
+
const hexGridContainer = document.getElementById("hexg");
|
|
4368
|
+
const hexPairs = hex.toUpperCase().match(/.{1,2}/g) || [];
|
|
4369
|
+
// this block populates the grid with boxes for hex codes
|
|
4370
|
+
hexPairs.forEach((hexPair, byteIndex) => {
|
|
4371
|
+
const item = document.createElement("div");
|
|
4372
|
+
item.classList.add("griditem");
|
|
4373
|
+
item.textContent = hexPair;
|
|
4374
|
+
item.dataset.byteIndex = String(byteIndex);
|
|
4375
|
+
hexGridContainer.appendChild(item);
|
|
4376
|
+
});
|
|
4377
|
+
function getPrintableSequence(startIndex) {
|
|
4378
|
+
let result = "";
|
|
4379
|
+
for (let i = startIndex; i < decodedAscii.length; i++) {
|
|
4380
|
+
if (!isPrintable(decodedAscii.charCodeAt(i))) break;
|
|
4381
|
+
result += String.fromCharCode(decodedAscii.charCodeAt(i));
|
|
4382
|
+
}
|
|
4383
|
+
return result;
|
|
4384
|
+
}
|
|
4385
|
+
// Attach event listeners to each grid item
|
|
4386
|
+
document.querySelectorAll(".griditem").forEach((item, idx) => {
|
|
4387
|
+
item.addEventListener("mouseenter", (e) => {
|
|
4388
|
+
//box fade in
|
|
4389
|
+
const hexOffsetDisplay = document.getElementById("asciiOffset");
|
|
4390
|
+
const asciiTextBox = document.getElementById("asciiText");
|
|
4391
|
+
payloadAsciiBox.style.top = e.clientY + 18 + "px";
|
|
4392
|
+
payloadAsciiBox.style.left = e.clientX + 18 + "px";
|
|
4393
|
+
payloadAsciiBox.classList.add("visible");
|
|
4394
|
+
asciiTextBox.innerHTML = "";
|
|
4395
|
+
const printable = getPrintableSequence(idx);
|
|
4396
|
+
window.currentPrintableSequence = printable;
|
|
4397
|
+
// adds only consecutive printable characters to the decodedAscii box
|
|
4398
|
+
asciiTextBox.textContent += truncate(printable, 32);
|
|
4399
|
+
for (let i = 0; i < truncate(printable, 32).length; i++) {
|
|
4400
|
+
const highlightedCell = document.querySelectorAll(".griditem")[idx + i];
|
|
4401
|
+
highlightedCell.classList.add("highlight");
|
|
4402
|
+
}
|
|
4403
|
+
const hexLen = parseInt(truncate(printable, 32).length, 10)
|
|
4404
|
+
.toString(16)
|
|
4405
|
+
.padStart(2, "0")
|
|
4406
|
+
.toUpperCase();
|
|
4407
|
+
const hexOffset = idx.toString(16).padStart(4, "0").toUpperCase();
|
|
4408
|
+
if (printable.length == 0) {
|
|
4409
|
+
asciiTextBox.textContent = "0x" + item.textContent;
|
|
4410
|
+
}
|
|
4411
|
+
hexOffsetDisplay.textContent = "0x" + hexOffset + ":" + hexLen;
|
|
4412
|
+
});
|
|
4413
|
+
});
|
|
4414
|
+
// this fades the box back out and calls the grid clear func
|
|
4415
|
+
document.querySelectorAll(".griditem").forEach((item) => {
|
|
4416
|
+
item.addEventListener("mouseleave", () => {
|
|
4417
|
+
payloadAsciiBox.classList.remove("visible");
|
|
4418
|
+
clearGridHighlights();
|
|
4419
|
+
});
|
|
4420
|
+
});
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
/**
|
|
4424
|
+
* Utility to create a table from data and headers, and append to a container.
|
|
4425
|
+
*/
|
|
4426
|
+
// probably should break this function up into smaller pieces,
|
|
4427
|
+
// but it works for now, it takes the current packet info and
|
|
4428
|
+
// populates the info panel with it, including the side tables
|
|
4429
|
+
// and the main info table, also updates the timestamp and
|
|
4430
|
+
// currentIp:port info at the top
|
|
4431
|
+
function infoPanel(pk) {
|
|
4432
|
+
const infoPaneEl = document.getElementById("packetInfoPane");
|
|
4433
|
+
document.getElementById("rightside").style.display = "block";
|
|
4434
|
+
document.getElementById("leftside").style.display = "block";
|
|
4435
|
+
const infoPaneOrigHtml = infoPaneEl.innerHTML;
|
|
4436
|
+
infoPaneEl.style.display = "block";
|
|
4437
|
+
const p = pk[index];
|
|
4438
|
+
let packetInfoData = p["Packet Info"];
|
|
4439
|
+
let extraInfoData = p["Extra Info"];
|
|
4440
|
+
let packetTimestamp = packetInfoData["Packet Timestamp"];
|
|
4441
|
+
let ipChecksum = packetInfoData["IP"]["IP Checksum"];
|
|
4442
|
+
|
|
4443
|
+
// Determine transport protocol (TCP or UDP); fall back to TCP for older captures
|
|
4444
|
+
const protocol = packetInfoData["Protocol"] || "TCP";
|
|
4445
|
+
const transportData = packetInfoData[protocol] || {};
|
|
4446
|
+
|
|
4447
|
+
const transportChecksum =
|
|
4448
|
+
protocol === "TCP"
|
|
4449
|
+
? transportData["TCP checksum"]
|
|
4450
|
+
: protocol === "UDP"
|
|
4451
|
+
? transportData["UDP checksum"]
|
|
4452
|
+
: protocol === "ICMP"
|
|
4453
|
+
? transportData["ICMP Checksum"]
|
|
4454
|
+
: "N/A";
|
|
4455
|
+
const transportLayerLen =
|
|
4456
|
+
protocol === "TCP"
|
|
4457
|
+
? transportData["TCP layer length"]
|
|
4458
|
+
: protocol === "UDP"
|
|
4459
|
+
? transportData["UDP length"]
|
|
4460
|
+
: protocol === "ICMP"
|
|
4461
|
+
? transportData["Wire length"]
|
|
4462
|
+
: "N/A";
|
|
4463
|
+
const tcpFlags =
|
|
4464
|
+
protocol === "TCP" && transportData["TCP Flag Data"]
|
|
4465
|
+
? transportData["TCP Flag Data"]["Flags"]
|
|
4466
|
+
: "N/A";
|
|
4467
|
+
|
|
4468
|
+
const sourceIpPort =
|
|
4469
|
+
packetInfoData["IP"]["Source IP"] +
|
|
4470
|
+
":" +
|
|
4471
|
+
(transportData["Source port"] ?? "?");
|
|
4472
|
+
const destIpPort =
|
|
4473
|
+
packetInfoData["IP"]["Destination IP"] +
|
|
4474
|
+
":" +
|
|
4475
|
+
(transportData["Destination port"] ?? "?");
|
|
4476
|
+
const etherFrame =
|
|
4477
|
+
typeof packetInfoData["Ethernet Frame"] === "object" &&
|
|
4478
|
+
packetInfoData["Ethernet Frame"] !== null
|
|
4479
|
+
? packetInfoData["Ethernet Frame"]
|
|
4480
|
+
: {};
|
|
4481
|
+
const srcMac = etherFrame["MAC Source"] ?? "N/A";
|
|
4482
|
+
const dstMac = etherFrame["MAC Destination"] ?? "N/A";
|
|
4483
|
+
const srcMacVendor = etherFrame["MAC Source Vendor"] ?? "N/A";
|
|
4484
|
+
const dstMacVendor = etherFrame["MAC Destination Vendor"] ?? "N/A";
|
|
4485
|
+
const ipLayerLen = packetInfoData["IP"]["IP layer length"];
|
|
4486
|
+
const wireLen = transportData["Wire length"];
|
|
4487
|
+
const payloadLen = packetInfoData["Raw data"]["Payload Length"];
|
|
4488
|
+
let sslCert = "";
|
|
4489
|
+
let sslVersion = "";
|
|
4490
|
+
let sslAlgos = "";
|
|
4491
|
+
if (
|
|
4492
|
+
extraInfoData["Traits"]["Server Info"]["Encryption Data"] == "N/A" ||
|
|
4493
|
+
extraInfoData["Traits"]["Server Info"].hasOwnProperty("Encryption Data") ==
|
|
4494
|
+
false
|
|
4495
|
+
) {
|
|
4496
|
+
sslCert = "Not encrypted";
|
|
4497
|
+
sslVersion = "Not encrypted";
|
|
4498
|
+
sslAlgos = "";
|
|
4499
|
+
} else {
|
|
4500
|
+
sslCert =
|
|
4501
|
+
extraInfoData["Traits"]["Server Info"]["Encryption Data"]["SSL Cert"] ??
|
|
4502
|
+
"Not available";
|
|
4503
|
+
sslVersion =
|
|
4504
|
+
extraInfoData["Traits"]["Server Info"]["Encryption Data"][
|
|
4505
|
+
"SSL Version"
|
|
4506
|
+
] ?? "Not available";
|
|
4507
|
+
sslAlgos =
|
|
4508
|
+
extraInfoData["Traits"]["Server Info"]["Encryption Data"][
|
|
4509
|
+
"Encrypted With"
|
|
4510
|
+
].join("<br>Extra algo info: ") ?? "No algorithm information available";
|
|
4511
|
+
}
|
|
4512
|
+
const isDecompressed = extraInfoData["Decompressed"]["Decompressed"];
|
|
4513
|
+
function removeIps(ipList) {
|
|
4514
|
+
const ipRegex =
|
|
4515
|
+
/\b((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\b/;
|
|
4516
|
+
return ipList.filter((item) => !ipRegex.test(item));
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
let dnsHostsHtml;
|
|
4520
|
+
if (
|
|
4521
|
+
extraInfoData["Traits"]["Network Data"]["Hostnames"]["Hostnames"] ==
|
|
4522
|
+
undefined
|
|
4523
|
+
) {
|
|
4524
|
+
dnsHostsHtml = "localhost";
|
|
4525
|
+
} else {
|
|
4526
|
+
dnsHostsHtml =
|
|
4527
|
+
"localhost<br>" +
|
|
4528
|
+
extraInfoData["Traits"]["Network Data"]["Hostnames"]["Hostnames"].join(
|
|
4529
|
+
"<br>",
|
|
4530
|
+
);
|
|
4531
|
+
}
|
|
4532
|
+
const filteredDnsHosts = removeIps(dnsHostsHtml.split("<br>")).join("<br>");
|
|
4533
|
+
dnsHostsHtml = filteredDnsHosts == "" ? "localhost" : filteredDnsHosts;
|
|
4534
|
+
|
|
4535
|
+
const pageTitle = extraInfoData["Traits"]["Server Info"]["Page Title"];
|
|
4536
|
+
const isEncrypted = extraInfoData["Traits"]["Server Info"]["Encrypted"];
|
|
4537
|
+
const protoName = extraInfoData["Traits"]["Network Data"]["Port Protcol"];
|
|
4538
|
+
const protoDescription =
|
|
4539
|
+
extraInfoData["Traits"]["Network Data"]["Port Description"];
|
|
4540
|
+
const srcNetClass =
|
|
4541
|
+
extraInfoData["Traits"]["Network Data"]["Source IP"]["Class"];
|
|
4542
|
+
const dstNetClass =
|
|
4543
|
+
extraInfoData["Traits"]["Network Data"]["Destination IP"]["Class"];
|
|
4544
|
+
document.getElementById("sidedatatable").textContent = "";
|
|
4545
|
+
document.getElementById("protoInfoSrc").textContent = "Source";
|
|
4546
|
+
document.getElementById("protoInfoDest").textContent = "Destination";
|
|
4547
|
+
document.getElementById("comp").textContent = "Unknown";
|
|
4548
|
+
if (isDecompressed == false || isDecompressed == undefined) {
|
|
4549
|
+
const types = extraInfoData["Data Types"];
|
|
4550
|
+
|
|
4551
|
+
types.forEach((type) => {
|
|
4552
|
+
if (type.includes("Zlib") || type.includes("zlib")) {
|
|
4553
|
+
document.getElementById("comp").textContent = "Compressed with zlib";
|
|
4554
|
+
|
|
4555
|
+
console.log("Data type identified: " + type);
|
|
4556
|
+
}
|
|
4557
|
+
if (type.includes("Gzip") || type.includes("gzip")) {
|
|
4558
|
+
document.getElementById("comp").textContent = "Compressed with gzip";
|
|
4559
|
+
}
|
|
4560
|
+
if (type.includes("Zip")) {
|
|
4561
|
+
document.getElementById("comp").textContent = "Compressed with zip";
|
|
4562
|
+
}
|
|
4563
|
+
});
|
|
4564
|
+
}
|
|
4565
|
+
if (isDecompressed == true) {
|
|
4566
|
+
document.getElementById("comp").textContent =
|
|
4567
|
+
"Not regonized as compressed data";
|
|
4568
|
+
}
|
|
4569
|
+
// wireLen
|
|
4570
|
+
if (pageTitle == undefined || pageTitle == "N/A") {
|
|
4571
|
+
document.getElementById("website").textContent =
|
|
4572
|
+
"Not available for this server";
|
|
4573
|
+
} else {
|
|
4574
|
+
document.getElementById("website").textContent = pageTitle;
|
|
4575
|
+
}
|
|
4576
|
+
//document.getElementById("crypt").textContent = isEncrypted;
|
|
4577
|
+
const dnsCollapsedList = dnsHostsHtml.replace(/(<br\s*\/?>\s*)+/gi, "<br>");
|
|
4578
|
+
document.getElementById("dns").innerHTML = dnsCollapsedList;
|
|
4579
|
+
if (sslAlgos == undefined || sslAlgos == "") {
|
|
4580
|
+
//document.getElementById("crypt").innerHTML = sslCert
|
|
4581
|
+
// ? "Encrypted with: " + sslVersion + "<br>" + sslAlgos
|
|
4582
|
+
// : "Not Encrypted";
|
|
4583
|
+
document.getElementById("crypt").innerHTML = "Not encrypted";
|
|
4584
|
+
} else {
|
|
4585
|
+
document.getElementById("crypt").innerHTML =
|
|
4586
|
+
"Encrypted with: " + sslVersion + "<br>" + sslAlgos;
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
if (protoName == "Unknown") {
|
|
4590
|
+
document.getElementById("protocols").innerHTML = "Unknown";
|
|
4591
|
+
} else {
|
|
4592
|
+
document.getElementById("protocols").innerHTML =
|
|
4593
|
+
"Protocol Name: " +
|
|
4594
|
+
protoName +
|
|
4595
|
+
"<br>Protocol Description: " +
|
|
4596
|
+
protoDescription;
|
|
4597
|
+
}
|
|
4598
|
+
const checksumData = [
|
|
4599
|
+
{ name: "IP Checksum", value: ipChecksum },
|
|
4600
|
+
{ name: protocol + " Checksum", value: transportChecksum },
|
|
4601
|
+
{ name: "Flags", value: tcpFlags },
|
|
4602
|
+
{ name: "IP Length", value: ipLayerLen },
|
|
4603
|
+
{ name: protocol + " Length", value: transportLayerLen },
|
|
4604
|
+
{ name: "Wire Length", value: wireLen },
|
|
4605
|
+
{ name: "Payload Length", value: payloadLen },
|
|
4606
|
+
];
|
|
4607
|
+
const checksumHeaders = ["Protocol data", "Details"];
|
|
4608
|
+
createTable(checksumData, checksumHeaders, "sidedatatable");
|
|
4609
|
+
|
|
4610
|
+
// DNS info table (shown for UDP/DNS packets)
|
|
4611
|
+
renderDnsTable(transportData);
|
|
4612
|
+
|
|
4613
|
+
// ICMP info table (shown for ICMP packets)
|
|
4614
|
+
renderIcmpTable(protocol, transportData);
|
|
4615
|
+
|
|
4616
|
+
// SNMP info table (shown for SNMP packets on port 161/162)
|
|
4617
|
+
renderSnmpTable(transportData);
|
|
4618
|
+
|
|
4619
|
+
// DHCP info table (shown for DHCP packets on port 67/68)
|
|
4620
|
+
renderDhcpTable(transportData);
|
|
4621
|
+
|
|
4622
|
+
// NTP info table (shown for NTP packets on port 123)
|
|
4623
|
+
renderNtpTable(transportData);
|
|
4624
|
+
|
|
4625
|
+
// SIP info table (shown for SIP packets on port 5060/5061)
|
|
4626
|
+
renderSipTable(transportData);
|
|
4627
|
+
|
|
4628
|
+
// HTTP info table (shown for HTTP request/response packets)
|
|
4629
|
+
renderHttpTable(transportData);
|
|
4630
|
+
|
|
4631
|
+
// HTTP/2 info table (shown for HTTP/2 frames on any TCP port)
|
|
4632
|
+
renderHttp2Table(transportData);
|
|
4633
|
+
|
|
4634
|
+
// FTP info table (shown for FTP packets on port 20/21)
|
|
4635
|
+
renderFtpTable(transportData);
|
|
4636
|
+
|
|
4637
|
+
// SMTP info table (shown for SMTP packets on port 25/587/465)
|
|
4638
|
+
renderSmtpTable(transportData);
|
|
4639
|
+
|
|
4640
|
+
// POP3 info table (shown for POP3 packets on port 110/995)
|
|
4641
|
+
renderPop3Table(transportData);
|
|
4642
|
+
|
|
4643
|
+
// IMAP info table (shown for IMAP packets on port 143/993)
|
|
4644
|
+
renderImapTable(transportData);
|
|
4645
|
+
|
|
4646
|
+
// Telnet info table (shown for Telnet packets on port 23)
|
|
4647
|
+
renderTelnetTable(transportData);
|
|
4648
|
+
|
|
4649
|
+
// IRC info table (shown for IRC packets on port 6667/6668/6669)
|
|
4650
|
+
renderIrcTable(transportData);
|
|
4651
|
+
|
|
4652
|
+
// MTP info table (shown for MTP/MMS packets on port 1755)
|
|
4653
|
+
renderMtpTable(transportData);
|
|
4654
|
+
|
|
4655
|
+
// LDAP info table (shown for LDAP packets on port 389/636)
|
|
4656
|
+
renderLdapTable(transportData);
|
|
4657
|
+
|
|
4658
|
+
// MySQL info table (shown for MySQL packets on port 3306)
|
|
4659
|
+
renderMysqlTable(transportData);
|
|
4660
|
+
|
|
4661
|
+
// PostgreSQL info table (shown for PostgreSQL packets on port 5432)
|
|
4662
|
+
renderPostgresqlTable(transportData);
|
|
4663
|
+
|
|
4664
|
+
// XMPP info table (shown for XMPP packets on port 5222/5223)
|
|
4665
|
+
renderXmppTable(transportData);
|
|
4666
|
+
|
|
4667
|
+
// SMB info table (shown for SMB packets on port 139/445)
|
|
4668
|
+
renderSmbTable(transportData);
|
|
4669
|
+
|
|
4670
|
+
// MQTT info table (shown for MQTT packets on port 1883/8883)
|
|
4671
|
+
renderMqttTable(transportData);
|
|
4672
|
+
|
|
4673
|
+
// RTSP info table (shown for RTSP packets on port 554)
|
|
4674
|
+
renderRtspTable(transportData);
|
|
4675
|
+
|
|
4676
|
+
// TFTP info table (shown for TFTP packets on UDP port 69)
|
|
4677
|
+
renderTftpTable(transportData);
|
|
4678
|
+
|
|
4679
|
+
// BGP info table (shown for BGP packets on port 179)
|
|
4680
|
+
renderBgpTable(transportData);
|
|
4681
|
+
|
|
4682
|
+
// NNTP info table (shown for NNTP packets on port 119)
|
|
4683
|
+
renderNntpTable(transportData);
|
|
4684
|
+
|
|
4685
|
+
// RADIUS info table (shown for RADIUS packets on port 1812/1813/1645/1646)
|
|
4686
|
+
renderRadiusTable(transportData);
|
|
4687
|
+
|
|
4688
|
+
const ipTableHeaders = ["Packet", "Data"];
|
|
4689
|
+
const srcIpData = [
|
|
4690
|
+
{ name: "IP:Port", value: sourceIpPort },
|
|
4691
|
+
{ name: "MAC", value: srcMac },
|
|
4692
|
+
{ name: "MAC Vendor", value: srcMacVendor },
|
|
4693
|
+
{ name: "Network Class", value: srcNetClass },
|
|
4694
|
+
];
|
|
4695
|
+
createTable(srcIpData, ipTableHeaders, "protoInfoSrc");
|
|
4696
|
+
const dstIpData = [
|
|
4697
|
+
{ name: "IP:Port", value: destIpPort },
|
|
4698
|
+
{ name: "MAC", value: dstMac },
|
|
4699
|
+
{ name: "MAC Vendor", value: dstMacVendor },
|
|
4700
|
+
{ name: "Network Class", value: dstNetClass },
|
|
4701
|
+
];
|
|
4702
|
+
createTable(dstIpData, ipTableHeaders, "protoInfoDest");
|
|
4703
|
+
const entropyValue = extraInfoData["Traits"]["Shannon Entropy"];
|
|
4704
|
+
document.getElementById("timestamp").textContent =
|
|
4705
|
+
"Timestamp " + packetTimestamp;
|
|
4706
|
+
//document.getElementById("ip2ip").textContent = sourceIpPort + " ~ " + destIpPort;
|
|
4707
|
+
document.getElementById("sideloctable").textContent = "";
|
|
4708
|
+
document.getElementById("entropybox").textContent =
|
|
4709
|
+
"\u096F " + entropyValue.toFixed(2);
|
|
4710
|
+
const entropyBoxEl = document.getElementById("entropybox");
|
|
4711
|
+
if (entropyValue >= 6.8) {
|
|
4712
|
+
entropyBoxEl.className = "high";
|
|
4713
|
+
} else if (entropyValue >= 4.5) {
|
|
4714
|
+
entropyBoxEl.className = "med";
|
|
4715
|
+
} else {
|
|
4716
|
+
entropyBoxEl.className = "low";
|
|
4717
|
+
}
|
|
4718
|
+
const secondColumnCells = document.querySelectorAll(
|
|
4719
|
+
"table tr td:nth-child(1), table tr th:nth-child(1)",
|
|
4720
|
+
);
|
|
4721
|
+
secondColumnCells.forEach((cell) => {
|
|
4722
|
+
cell.style.width = "23%";
|
|
4723
|
+
});
|
|
4724
|
+
if (
|
|
4725
|
+
extraInfoData["Traits"]["Network Data"]["Source IP"]["Location"]["City"] ==
|
|
4726
|
+
undefined
|
|
4727
|
+
) {
|
|
4728
|
+
const localnetData = [{ name: "Location", value: "Localnet" }];
|
|
4729
|
+
const localnetHeaders = ["Source Host", "Location"];
|
|
4730
|
+
createTable(localnetData, localnetHeaders, "sideloctable");
|
|
4731
|
+
} else {
|
|
4732
|
+
const srcLocData = [
|
|
4733
|
+
{
|
|
4734
|
+
name: "Country",
|
|
4735
|
+
value:
|
|
4736
|
+
extraInfoData["Traits"]["Network Data"]["Source IP"]["Location"][
|
|
4737
|
+
"Country"
|
|
4738
|
+
],
|
|
4739
|
+
},
|
|
4740
|
+
{
|
|
4741
|
+
name: "City",
|
|
4742
|
+
value:
|
|
4743
|
+
extraInfoData["Traits"]["Network Data"]["Source IP"]["Location"][
|
|
4744
|
+
"City"
|
|
4745
|
+
],
|
|
4746
|
+
},
|
|
4747
|
+
{
|
|
4748
|
+
name: "Timezone",
|
|
4749
|
+
value:
|
|
4750
|
+
extraInfoData["Traits"]["Network Data"]["Source IP"]["Location"][
|
|
4751
|
+
"Time Zone"
|
|
4752
|
+
],
|
|
4753
|
+
},
|
|
4754
|
+
];
|
|
4755
|
+
const srcLocHeaders = ["Source Host", "Location"];
|
|
4756
|
+
createTable(srcLocData, srcLocHeaders, "sideloctable");
|
|
4757
|
+
}
|
|
4758
|
+
if (
|
|
4759
|
+
extraInfoData["Traits"]["Network Data"]["Destination IP"]["Location"][
|
|
4760
|
+
"City"
|
|
4761
|
+
] == undefined
|
|
4762
|
+
) {
|
|
4763
|
+
const localnetData = [{ name: "Location", value: "Localnet" }];
|
|
4764
|
+
const localnetHeaders = ["Destination Host", "Location"];
|
|
4765
|
+
createTable(localnetData, localnetHeaders, "sideloctable");
|
|
4766
|
+
} else {
|
|
4767
|
+
const dstLocData = [
|
|
4768
|
+
{
|
|
4769
|
+
name: "Country",
|
|
4770
|
+
value:
|
|
4771
|
+
extraInfoData["Traits"]["Network Data"]["Destination IP"]["Location"][
|
|
4772
|
+
"Country"
|
|
4773
|
+
],
|
|
4774
|
+
},
|
|
4775
|
+
{
|
|
4776
|
+
name: "City",
|
|
4777
|
+
value:
|
|
4778
|
+
extraInfoData["Traits"]["Network Data"]["Destination IP"]["Location"][
|
|
4779
|
+
"City"
|
|
4780
|
+
],
|
|
4781
|
+
},
|
|
4782
|
+
{
|
|
4783
|
+
name: "Timezone",
|
|
4784
|
+
|
|
4785
|
+
value:
|
|
4786
|
+
extraInfoData["Traits"]["Network Data"]["Destination IP"]["Location"][
|
|
4787
|
+
"Time Zone"
|
|
4788
|
+
],
|
|
4789
|
+
},
|
|
4790
|
+
];
|
|
4791
|
+
const dstLocHeaders = ["Destination Host", "Location"];
|
|
4792
|
+
createTable(dstLocData, dstLocHeaders, "sideloctable");
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
// Save the currently loaded capture plus session state to disk
|
|
4797
|
+
document.getElementById("save-json-btn").addEventListener("click", function () {
|
|
4798
|
+
void persistSessionToDisk("sidebar-button");
|
|
4799
|
+
});
|
|
4800
|
+
|
|
4801
|
+
// the next two have hooks into IPC handlers for main.js
|
|
4802
|
+
// data transactions
|
|
4803
|
+
|
|
4804
|
+
// when the main.js returns our json data from snitch.py
|
|
4805
|
+
window.jsonapi.onJsonData((jsonData) => {
|
|
4806
|
+
document.getElementById("loading-container").style.display = "block";
|
|
4807
|
+
document.getElementById("error-container").style.display = "none";
|
|
4808
|
+
statusUpdate("Loaded data from backend, processing...");
|
|
4809
|
+
writeLogEntry("Backend JSON payload received for processing");
|
|
4810
|
+
processFile(
|
|
4811
|
+
new File([jsonData], "capture.json", { type: "application/json" }),
|
|
4812
|
+
);
|
|
4813
|
+
document.getElementById("loading-container").style.display = "none";
|
|
4814
|
+
const loadEndTime = performance.now();
|
|
4815
|
+
document.getElementById("load-time").textContent =
|
|
4816
|
+
"Load time: " + ((loadEndTime - startTime) / 1000).toFixed(2) + " seconds";
|
|
4817
|
+
});
|
|
4818
|
+
|
|
4819
|
+
// here we create the backend process and hook it to the handler
|
|
4820
|
+
function runSnitch(file) {
|
|
4821
|
+
document.getElementById("loading-container").style.display = "block";
|
|
4822
|
+
showSummaryLoading();
|
|
4823
|
+
document.getElementById("status").textContent =
|
|
4824
|
+
"Status: Running snitch backend, this may take a few minutes...";
|
|
4825
|
+
document.getElementById("error-container").style.display = "none";
|
|
4826
|
+
startTime = performance.now();
|
|
4827
|
+
const useLLM = document.getElementById("use-llm").checked;
|
|
4828
|
+
const fileLabel = typeof file === "string" ? file : file?.name || "unknown";
|
|
4829
|
+
writeLogEntry(
|
|
4830
|
+
`Backend analysis started file=${fileLabel} llm_enabled=${useLLM}`,
|
|
4831
|
+
);
|
|
4832
|
+
window.snitchapi
|
|
4833
|
+
.runBackendCommand(file, useLLM)
|
|
4834
|
+
.then((output) => {})
|
|
4835
|
+
.catch((error) => {
|
|
4836
|
+
doError("Backend run error!", { backend: true });
|
|
4837
|
+
logErrorEntry("backend-run", error);
|
|
4838
|
+
});
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
function doError(message, { backend = false } = {}) {
|
|
4842
|
+
console.error("Error from backend:", message);
|
|
4843
|
+
if (backend) {
|
|
4844
|
+
writeBackendErrorLogEntry(`Error shown message="${message}"`);
|
|
4845
|
+
} else {
|
|
4846
|
+
writeLogEntry(`Error shown message="${message}"`);
|
|
4847
|
+
}
|
|
4848
|
+
const loadingContainerEl = document.getElementById("loading-container");
|
|
4849
|
+
const errorContainerEl = document.getElementById("error-container");
|
|
4850
|
+
clearSummaryContent();
|
|
4851
|
+
loadingContainerEl.style.display = "none";
|
|
4852
|
+
errorContainerEl.style.display = "block";
|
|
4853
|
+
errorContainerEl.textContent = message;
|
|
4854
|
+
errorContainerEl.addEventListener("click", () => {
|
|
4855
|
+
errorContainerEl.style.display = "none";
|
|
4856
|
+
loadingContainerEl.style.display = "none";
|
|
4857
|
+
});
|
|
4858
|
+
}
|
|
4859
|
+
|
|
4860
|
+
function hideAllData() {
|
|
4861
|
+
// document.getElementById("packetInfoPane").textContent =
|
|
4862
|
+
// "No matching packets found.";
|
|
4863
|
+
doError("No packets match the filter criteria!");
|
|
4864
|
+
statusUpdate("Status: No packets match the filter criteria");
|
|
4865
|
+
document.getElementById("data-types").style.display = "none";
|
|
4866
|
+
document.getElementById("protoInfo").style.display = "none";
|
|
4867
|
+
document.getElementById("timestamp").style.display = "none";
|
|
4868
|
+
document.getElementById("rightside").style.display = "none";
|
|
4869
|
+
document.getElementById("active-recon").style.display = "none";
|
|
4870
|
+
document.getElementById("prev-btn").style.opacity = "0";
|
|
4871
|
+
document.getElementById("next-btn").style.opacity = "0";
|
|
4872
|
+
popHexGrid("00".repeat(1));
|
|
4873
|
+
}
|
|
4874
|
+
function showAllData() {
|
|
4875
|
+
document.getElementById("prev-btn").style.opacity = "1";
|
|
4876
|
+
document.getElementById("next-btn").style.opacity = "1";
|
|
4877
|
+
document.getElementById("data-types").style.display = "block";
|
|
4878
|
+
document.getElementById("protoInfo").style.display = "block";
|
|
4879
|
+
document.getElementById("timestamp").style.display = "block";
|
|
4880
|
+
document.getElementById("rightside").style.display = "block";
|
|
4881
|
+
document.getElementById("active-recon").style.display = "block";
|
|
4882
|
+
document.getElementById("hexg").hidden = false;
|
|
4883
|
+
document.getElementById("error-container").style.display = "none";
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
document
|
|
4887
|
+
.getElementById("filterStr")
|
|
4888
|
+
.addEventListener("keydown", function (event) {
|
|
4889
|
+
if (event.key === "Enter") {
|
|
4890
|
+
const filterQuery = filterInputEl.value;
|
|
4891
|
+
runFilterQuery(filterQuery);
|
|
4892
|
+
filterHistorySelectEl.value = "";
|
|
4893
|
+
}
|
|
4894
|
+
});
|
|
4895
|
+
|
|
4896
|
+
filterClearButtonEl.addEventListener("click", clearFilterQuery);
|
|
4897
|
+
|
|
4898
|
+
initializeContextMenu({
|
|
4899
|
+
documentRef: document,
|
|
4900
|
+
windowRef: window,
|
|
4901
|
+
convertContextMenuEl,
|
|
4902
|
+
getPasteTargetFromContextTarget,
|
|
4903
|
+
getTrimmedSelectionText,
|
|
4904
|
+
getConversionTextFromTarget,
|
|
4905
|
+
detectConvertibleFormats,
|
|
4906
|
+
buildContextFilterQueries,
|
|
4907
|
+
getCookieJarTextForContextTarget,
|
|
4908
|
+
showConvertContextMenu,
|
|
4909
|
+
hideConvertContextMenu,
|
|
4910
|
+
});
|
|
4911
|
+
|
|
4912
|
+
filterInputEl.addEventListener("input", syncFilterHighlight);
|
|
4913
|
+
filterInputEl.addEventListener("scroll", syncFilterHighlightScroll);
|
|
4914
|
+
|
|
4915
|
+
filterHistorySelectEl.addEventListener("change", () => {
|
|
4916
|
+
const selectedQuery = filterHistorySelectEl.value;
|
|
4917
|
+
if (!selectedQuery) return;
|
|
4918
|
+
filterInputEl.value = selectedQuery;
|
|
4919
|
+
syncFilterHighlight();
|
|
4920
|
+
runFilterQuery(selectedQuery);
|
|
4921
|
+
filterHistorySelectEl.value = "";
|
|
4922
|
+
});
|
|
4923
|
+
|
|
4924
|
+
renderFilterHistory();
|
|
4925
|
+
syncFilterHighlight();
|
|
4926
|
+
|
|
4927
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
4928
|
+
doError(message + " at " + source + ":" + lineno + ":" + colno);
|
|
4929
|
+
};
|
|
4930
|
+
|
|
4931
|
+
window.onunhandledrejection = (event) => {
|
|
4932
|
+
doError("Unhandled promise error! " + event.reason);
|
|
4933
|
+
};
|
|
4934
|
+
|
|
4935
|
+
window.api.onError((msg) => {
|
|
4936
|
+
console.error("Error from backend:", msg);
|
|
4937
|
+
// Show alert or UI message
|
|
4938
|
+
doError(msg, { backend: true });
|
|
4939
|
+
});
|
|
4940
|
+
|
|
4941
|
+
// On page load, hide packet info and payload panes
|
|
4942
|
+
onload = function () {
|
|
4943
|
+
// document.getElementById("selectBookmark").style.display = "none";
|
|
4944
|
+
hideConvertContextMenu();
|
|
4945
|
+
keystorePanel.resetKeystoreState();
|
|
4946
|
+
setCryptSubtab(CRYPT_SSL_SUBTAB);
|
|
4947
|
+
setConvSubtab(CONV_CONVERSIONS_SUBTAB);
|
|
4948
|
+
document.getElementById("packetInfoPane").style.display = "none";
|
|
4949
|
+
document.getElementById("packetPayloadPane").style.display = "none";
|
|
4950
|
+
document.getElementById("rightside").style.display = "none";
|
|
4951
|
+
const rightsideDataEl = document.getElementById("rightside-data");
|
|
4952
|
+
const rightsideNotesEl = document.getElementById("rightside-notes");
|
|
4953
|
+
if (rightsideDataEl) rightsideDataEl.hidden = false;
|
|
4954
|
+
if (rightsideNotesEl) rightsideNotesEl.hidden = true;
|
|
4955
|
+
document.getElementById("leftside").style.display = "none";
|
|
4956
|
+
document.getElementById("loading-container").style.display = "none";
|
|
4957
|
+
};
|