fixes XSS in trail popups

This commit is contained in:
Christian Beutel
2025-01-12 15:29:07 +01:00
parent 3f7913f506
commit 860e634a03

View File

@@ -46,7 +46,7 @@ export function createMarkerFromWaypoint(waypoint: Waypoint, onDragEnd?: (marker
const nameElement = document.createElement("b"); const nameElement = document.createElement("b");
nameElement.textContent = waypoint.name ?? "-"; nameElement.textContent = waypoint.name ?? "-";
if(waypoint.name?.length) { if (waypoint.name?.length) {
nameElement.classList.add("ml-2") nameElement.classList.add("ml-2")
} }
spanElement.appendChild(nameElement); spanElement.appendChild(nameElement);
@@ -111,31 +111,87 @@ export function createPopupFromTrail(trail: Trail) {
: get(theme) === "light" : get(theme) === "light"
? emptyStateTrailLight ? emptyStateTrailLight
: emptyStateTrailDark; : emptyStateTrailDark;
const popup = new M.Popup({ maxWidth: "320px" }); const popup = new M.Popup({ maxWidth: "320px", closeButton: false });
popup.setHTML( // Create a container element for the popup content
`<a href="/map/trail/${trail.id}" data-sveltekit-preload-data="off"> const linkElement = document.createElement("a");
<li class="flex items-center gap-4 cursor-pointer text-content max-w-80"> linkElement.href = `/map/trail/${trail.id}`; // Set href safely
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${thumbnail}" alt=""> linkElement.setAttribute("data-sveltekit-preload-data", "off");
</div>
<div> // Create a list item element
<h4 class="font-semibold text-lg">${trail.name}</h4> const listItem = document.createElement("li");
<div class="flex gap-x-4"> listItem.className = "flex items-center gap-4 cursor-pointer text-content max-w-80";
${trail.location ? `<h5><i class="fa fa-location-dot mr-2"></i>${trail.location}</h5>` : ""}
<h5><i class="fa fa-gauge mr-2"></i>${get(_)(trail.difficulty as string)}</h5> // Create the image container
</div> const imageContainer = document.createElement("div");
<div class="grid grid-cols-2 mt-2 gap-x-4 gap-y-2 text-sm text-gray-500 flex-wrap"><span class="shrink-0"><i imageContainer.className = "shrink-0";
class="fa fa-left-right mr-2"></i>${formatDistance(
trail.distance, // Create the image element
)}</span><span class="shrink-0"><i class="fa fa-clock mr-2"></i>${formatTimeHHMM( const img = document.createElement("img");
trail.duration, img.className = "h-14 w-14 object-cover rounded-xl";
)}</span><span class="shrink-0"><i class="fa fa-arrow-trend-up mr-2"></i>${formatElevation( img.src = thumbnail; // Set image source safely
trail.elevation_gain, img.alt = ""; // Always include a safe alt attribute
)}</span></span> <span class="shrink-0"><i class="fa fa-arrow-trend-down mr-2"></i>${formatElevation( imageContainer.appendChild(img);
trail.elevation_loss,
)}</span></div> // Create the text container
</div> const textContainer = document.createElement("div");
</li>
</a>`) // Add trail name
const trailName = document.createElement("h4");
trailName.className = "font-semibold text-lg";
trailName.textContent = trail.name; // Set trail name safely
textContainer.appendChild(trailName);
// Add location and difficulty, if available
if (trail.location || trail.difficulty) {
const detailsContainer = document.createElement("div");
detailsContainer.className = "flex gap-x-4";
if (trail.location) {
const locationElement = document.createElement("h5");
locationElement.innerHTML = `<i class="fa fa-location-dot mr-2"></i>`; // Safe static icon
locationElement.appendChild(document.createTextNode(trail.location)); // Safely append location text
detailsContainer.appendChild(locationElement);
}
const difficultyElement = document.createElement("h5");
difficultyElement.innerHTML = `<i class="fa fa-gauge mr-2"></i>`; // Safe static icon
difficultyElement.appendChild(document.createTextNode(get(_)(trail.difficulty as string))); // Safely append difficulty
detailsContainer.appendChild(difficultyElement);
textContainer.appendChild(detailsContainer);
}
// Create the grid container for additional stats
const statsContainer = document.createElement("div");
statsContainer.className =
"grid grid-cols-2 mt-2 gap-x-4 gap-y-2 text-sm text-gray-500 flex-wrap";
const stats = [
{ icon: "fa-left-right", value: formatDistance(trail.distance) },
{ icon: "fa-clock", value: formatTimeHHMM(trail.duration) },
{ icon: "fa-arrow-trend-up", value: formatElevation(trail.elevation_gain) },
{ icon: "fa-arrow-trend-down", value: formatElevation(trail.elevation_loss) },
];
// Loop through stats and add them
stats.forEach(({ icon, value }) => {
const statElement = document.createElement("span");
statElement.className = "shrink-0";
statElement.innerHTML = `<i class="fa ${icon} mr-2"></i>`; // Safe static icon
statElement.appendChild(document.createTextNode(value)); // Safely append stat value
statsContainer.appendChild(statElement);
});
textContainer.appendChild(statsContainer);
// Assemble the popup
listItem.appendChild(imageContainer);
listItem.appendChild(textContainer);
linkElement.appendChild(listItem);
// Safely set the content using setDOMContent
popup.setDOMContent(linkElement);
return popup; return popup;
} }