fixes XSS in trail popups
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user