diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 3d26ffa..782f3e4 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -3234,11 +3234,11 @@ sudo make install function renderAcarsCard(msg) { const type = msg.message_type || 'other'; const badge = getAcarsTypeBadge(type); - const desc = msg.label_description || ('Label ' + (msg.label || '?')); + const desc = escapeHtml(msg.label_description || ('Label ' + (msg.label || '?'))); const text = msg.text || msg.msg || ''; - const truncText = text.length > 120 ? text.substring(0, 120) + '...' : text; + const truncText = escapeHtml(text.length > 120 ? text.substring(0, 120) + '...' : text); const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ''; - const flight = msg.flight || ''; + const flight = escapeHtml(msg.flight || ''); let parsedHtml = ''; if (msg.parsed) { @@ -3246,23 +3246,23 @@ sudo make install if (type === 'position' && p.lat !== undefined) { parsedHtml = '
' + p.lat.toFixed(4) + ', ' + p.lon.toFixed(4) + - (p.flight_level ? ' • ' + p.flight_level : '') + - (p.destination ? ' → ' + p.destination : '') + '
'; + (p.flight_level ? ' • ' + escapeHtml(String(p.flight_level)) : '') + + (p.destination ? ' → ' + escapeHtml(String(p.destination)) : '') + ''; } else if (type === 'engine_data') { const parts = []; Object.keys(p).forEach(k => { - parts.push(k + ': ' + p[k].value); + parts.push(escapeHtml(k) + ': ' + escapeHtml(String(p[k].value))); }); if (parts.length) { parsedHtml = '
' + parts.slice(0, 4).join(' | ') + '
'; } } else if (type === 'oooi' && p.origin) { parsedHtml = '
' + - p.origin + ' → ' + p.destination + - (p.out ? ' | OUT ' + p.out : '') + - (p.off ? ' OFF ' + p.off : '') + - (p.on ? ' ON ' + p.on : '') + - (p['in'] ? ' IN ' + p['in'] : '') + '
'; + escapeHtml(String(p.origin)) + ' → ' + escapeHtml(String(p.destination)) + + (p.out ? ' | OUT ' + escapeHtml(String(p.out)) : '') + + (p.off ? ' OFF ' + escapeHtml(String(p.off)) : '') + + (p.on ? ' ON ' + escapeHtml(String(p.on)) : '') + + (p['in'] ? ' IN ' + escapeHtml(String(p['in'])) : '') + ''; } } @@ -3341,6 +3341,8 @@ sudo make install selectedIcao = null; showAircraftDetails(null); updateFlightLookupBtn(); + highlightSidebarMessages(null); + clearAircraftMessages(); } } }); @@ -4096,6 +4098,12 @@ sudo make install const text = data.text || data.msg || ''; const time = new Date().toLocaleTimeString(); + // Escape user-controlled strings for safe innerHTML insertion + const eFlight = escapeHtml(flight); + const eReg = escapeHtml(reg); + const eLabelDesc = escapeHtml(labelDesc || (label ? 'Label: ' + label : '')); + const eText = escapeHtml(text.length > 80 ? text.substring(0, 80) + '...' : text); + // Try to find matching tracked aircraft const matchedIcao = findAircraftIcaoByFlight(flight) || findAircraftIcaoByFlight(data.tail) || @@ -4120,12 +4128,12 @@ sudo make install msg.innerHTML = `
- ${flight}${linkIcon} + ${eFlight}${linkIcon} ${time}
- ${reg ? `
Reg: ${reg}
` : ''} -
${typeBadge} ${labelDesc || (label ? 'Label: ' + label : '')}
- ${text && msgType !== 'link_test' && msgType !== 'handshake' ? `
${text.length > 80 ? text.substring(0, 80) + '...' : text}
` : ''} + ${reg ? `
Reg: ${eReg}
` : ''} +
${typeBadge} ${eLabelDesc}
+ ${text && msgType !== 'link_test' && msgType !== 'handshake' ? `
${eText}
` : ''} `; container.insertBefore(msg, container.firstChild); diff --git a/templates/partials/modes/acars.html b/templates/partials/modes/acars.html index 8fc0169..440a3d7 100644 --- a/templates/partials/modes/acars.html +++ b/templates/partials/modes/acars.html @@ -189,25 +189,25 @@ } function renderAcarsMainCard(data) { - const flight = data.flight || 'UNKNOWN'; + const flight = escapeHtml(data.flight || 'UNKNOWN'); const type = data.message_type || 'other'; const badge = acarsMainTypeBadge(type); - const desc = data.label_description || (data.label ? 'Label: ' + data.label : ''); + const desc = escapeHtml(data.label_description || (data.label ? 'Label: ' + data.label : '')); const text = data.text || data.msg || ''; - const truncText = text.length > 150 ? text.substring(0, 150) + '...' : text; + const truncText = escapeHtml(text.length > 150 ? text.substring(0, 150) + '...' : text); const time = new Date().toLocaleTimeString(); let parsedHtml = ''; if (data.parsed) { const p = data.parsed; if (type === 'position' && p.lat !== undefined) { - parsedHtml = `
${p.lat.toFixed(4)}, ${p.lon.toFixed(4)}${p.flight_level ? ' • ' + p.flight_level : ''}${p.destination ? ' → ' + p.destination : ''}
`; + parsedHtml = `
${p.lat.toFixed(4)}, ${p.lon.toFixed(4)}${p.flight_level ? ' • ' + escapeHtml(String(p.flight_level)) : ''}${p.destination ? ' → ' + escapeHtml(String(p.destination)) : ''}
`; } else if (type === 'engine_data') { const parts = []; - Object.keys(p).forEach(k => parts.push(k + ': ' + p[k].value)); + Object.keys(p).forEach(k => parts.push(escapeHtml(k) + ': ' + escapeHtml(String(p[k].value)))); if (parts.length) parsedHtml = `
${parts.slice(0, 4).join(' | ')}
`; } else if (type === 'oooi' && p.origin) { - parsedHtml = `
${p.origin} → ${p.destination}${p.out ? ' | OUT ' + p.out : ''}${p.off ? ' OFF ' + p.off : ''}${p.on ? ' ON ' + p.on : ''}${p['in'] ? ' IN ' + p['in'] : ''}
`; + parsedHtml = `
${escapeHtml(String(p.origin))} → ${escapeHtml(String(p.destination))}${p.out ? ' | OUT ' + escapeHtml(String(p.out)) : ''}${p.off ? ' OFF ' + escapeHtml(String(p.off)) : ''}${p.on ? ' ON ' + escapeHtml(String(p.on)) : ''}${p['in'] ? ' IN ' + escapeHtml(String(p['in'])) : ''}
`; } } diff --git a/tests/test_acars_translator.py b/tests/test_acars_translator.py index 0fca74b..4340437 100644 --- a/tests/test_acars_translator.py +++ b/tests/test_acars_translator.py @@ -63,8 +63,8 @@ class TestClassifyMessageType: def test_sq_is_squawk(self): assert classify_message_type('SQ') == 'squawk' - def test_underscore_d_is_link_test(self): - assert classify_message_type('_d') == 'link_test' + def test_underscore_d_is_handshake(self): + assert classify_message_type('_d') == 'handshake' def test_q0_is_link_test(self): assert classify_message_type('Q0') == 'link_test' @@ -228,7 +228,7 @@ class TestTranslateMessage: msg = {'label': '_d', 'text': ''} result = translate_message(msg) assert result['label_description'] == 'Demand mode (link test)' - assert result['message_type'] in ('link_test', 'handshake') + assert result['message_type'] == 'handshake' def test_unknown_label(self): msg = {'label': 'ZZ', 'text': 'SOME DATA'} diff --git a/utils/acars_translator.py b/utils/acars_translator.py index 2f2178c..4f2f72c 100644 --- a/utils/acars_translator.py +++ b/utils/acars_translator.py @@ -127,7 +127,7 @@ def classify_message_type(label: str | None, text: str | None = None) -> str: # Position reports if label in ('H1', '20', '15', '16', '30', 'S1'): return 'position' - if label == 'H1' or (text and '#M1BPOS' in text): + if text and '#M1BPOS' in text: return 'position' # Engine / DFDR data @@ -155,7 +155,7 @@ def classify_message_type(label: str | None, text: str | None = None) -> str: return 'squawk' # Link test / handshake - if label in ('_d', 'Q0', 'QA', 'QB', 'QC', 'QD', 'QE', 'QF', 'QG', + if label in ('Q0', 'QA', 'QB', 'QC', 'QD', 'QE', 'QF', 'QG', 'QH', 'QK', 'QM', 'QN', 'QP', 'QQ', 'QR', 'QS', 'QT', 'QX', '4X'): return 'link_test'