diff --git a/js/layers/fires.js b/js/layers/fires.js index 01062b0..ebc1ea2 100644 --- a/js/layers/fires.js +++ b/js/layers/fires.js @@ -59,7 +59,7 @@ export default function create(ctx) { if (!loc) continue; const title = ev.title || 'Wildfire'; const category = (ev.categories && ev.categories[0] && ev.categories[0].title) || 'Wildfires'; - const link = ev.link || (ev.id ? `https://eonet.gsfc.nasa.gov/api/v3/events/${ev.id}` : null); + const link = lib.safeUrl(ev.link || (ev.id ? `https://eonet.gsfc.nasa.gov/api/v3/events/${encodeURIComponent(ev.id)}` : '')); const dateTxt = loc.date ? new Date(loc.date).toISOString().replace('T', ' ').replace('.000Z', ' UTC') : '—'; ds.entities.add({ @@ -88,11 +88,11 @@ export default function create(ctx) { }, description: `` + - `` + - `` + + `` + + `` + `` + `
Event${title}
Category${category}
Event${lib.escapeHtml(title)}
Category${lib.escapeHtml(category)}
Last report${dateTxt}
` + - (link ? `

Open event on EONET ↗

` : '') + + (link ? `

Open event on EONET ↗

` : '') + `

Active wildfire event from NASA EONET (real data).

`, }); count++; diff --git a/js/layers/quakes.js b/js/layers/quakes.js index 02f30d1..d8c252a 100644 --- a/js/layers/quakes.js +++ b/js/layers/quakes.js @@ -64,7 +64,8 @@ export default function create(ctx) { const depth = coords[2]; const mag = p.mag; const timeMs = p.time; - const place = p.place || 'Unknown location'; + const place = p.place || 'Unknown location'; // raw: used in name/label (text contexts) + const url = lib.safeUrl(p.url); const size = baseSize(mag); const color = magColor(mag); const recentAtBuild = typeof timeMs === 'number' && (Date.now() - timeMs) < 3_600_000; @@ -93,11 +94,11 @@ export default function create(ctx) { description: `` + `` + - `` + + `` + `` + `` + `
MagnitudeM${mag.toFixed(1)}
Place${place}
Place${lib.escapeHtml(place)}
Depth${depth != null ? depth.toFixed(1) + ' km' : '—'}
Time (UTC)${timeMs != null ? new Date(timeMs).toISOString().replace('T', ' ').replace('.000Z', ' UTC') : '—'}
` + - (p.url ? `

USGS event page ↗

` : ''), + (url ? `

USGS event page ↗

` : ''), }); // Labels only for the notable quakes, to keep the globe legible. diff --git a/js/lib.js b/js/lib.js index 8d6b939..8179c78 100644 --- a/js/lib.js +++ b/js/lib.js @@ -77,6 +77,24 @@ export function altitudeColor(CONFIG, altMeters) { return c.aircraftHigh; } +// Escape a string for safe interpolation into InfoBox description HTML. +// Feeds like USGS/EONET are trusted, but this is defense-in-depth for the +// public deploy (and the InfoBox is a real HTML sink). +export function escapeHtml(s) { + return String(s == null ? '' : s) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +// Only allow http(s) URLs into href attributes (blocks javascript:/data: URLs). +export function safeUrl(u) { + const s = String(u == null ? '' : u).trim(); + return /^https?:\/\//i.test(s) ? s : ''; +} + // CSS hex → Cesium.Color, optional alpha. export function cz(hex, alpha) { const col = Cesium.Color.fromCssColorString(hex);