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'