restincode/memorial.html
mauvehed 0c4853d88d fix(memorial): sanitize references titles and fallback URL against XSS
Use jQuery DOM construction ($("<a>").attr().text()) instead of string
concatenation for reference links, preventing HTML injection from
untrusted title values. Pass fallback issue URL through safeUrl().
2026-03-15 16:39:03 -05:00

262 lines
8.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<title>Rest In Code - In Memory of</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="author"
content="Connie 'Sunfire' Hill and Rest In Code contributors"
/>
<link rel="stylesheet" href="style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.png" />
<script
src="https://code.jquery.com/jquery-3.7.0.min.js"
integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g="
crossorigin="anonymous"
></script>
<script>
$(function () {
var person;
var params = new URLSearchParams(window.location.search);
if (params.get("name") != null) {
var name = params.get("name");
person = "/people/" + name + ".json";
} else {
person = "/people/person.json";
}
function safeUrl(url) {
try {
var u = new URL(url, window.location.origin);
return u.protocol === "http:" || u.protocol === "https:"
? u.href
: "#";
} catch (e) {
return "#";
}
}
$.getJSON(person, function (data) {
var fullname = data.firstname + " " + data.lastname;
if (data.handle != "") {
fullname += " (" + data.handle + ")";
}
$("title").append(fullname);
$("#subtitle").append(fullname);
$("#memorial-name").append(fullname);
$(".birth").append(data.birth);
$(".death").append(data.death);
$(".issue").append(data.issue);
if (data.affiliations == null || data.affiliations == "") {
$(".affiliations").append("None");
} else {
$(".affiliations").append(data.affiliations);
}
if (data.obituary != null && data.obituary != "") {
$(".obituary").append(
'<a href="' + safeUrl(data.obituary) + '">Obituary</a>',
);
}
if (data.issue == null || data.issue == "") {
data.issue =
"https://github.com/restincode/restincode/blob/master/CONTRIBUTING.md";
} else {
data.issue =
"https://github.com/restincode/restincode/issues/" + data.issue;
}
if (data.mainimage == "") {
$(".memorial-main-image")
.attr("src", "/images/face-silhouette-clipart.png")
.attr("alt", "placeholder photo");
} else {
$(".memorial-main-image")
.attr("src", data.mainimage)
.attr("alt", "Main photo of " + fullname);
}
if (data.maintext == "") {
$("#memorial-text").append(
"<p>No information has been submitted for this person. Help us by submitting <a href='" +
data.issue +
"'>here.</a></p>",
);
} else {
$("#memorial-text").append(data.maintext);
}
if (
!Array.isArray(data.socialmedialinks) ||
!data.socialmedialinks.length
) {
$("#social-media").append(
"<p>No social media links have been submitted for this person. Help us by submitting <a href='" +
data.issue +
"'>here.</a></p>",
);
} else {
for (var s = 0; s < data.socialmedialinks.length; s++) {
var sitename = data.socialmedialinks[s]["sitename"];
var siteurl = data.socialmedialinks[s]["siteurl"];
var social =
'<a href="' + safeUrl(siteurl) + '">' + sitename + "</a>";
$("#social-media").append(social);
}
}
if (!Array.isArray(data.references) || !data.references.length) {
$("#references").append(
$("<p>").append(
"No references have been submitted for this person. Help us by submitting ",
$("<a>").attr("href", safeUrl(data.issue)).text("here."),
),
);
} else {
for (var r = 0; r < data.references.length; r++) {
$("#references").append(
$("<a>")
.attr("href", safeUrl(data.references[r]["url"]))
.text(data.references[r]["title"]),
);
}
}
if (
!Array.isArray(data.contributions) ||
!data.contributions.length
) {
$("#contributions-list").append(
"<li>No contributions have been submitted for this person. Help us by submitting <a href='" +
data.issue +
"'>here.</a></li>",
);
} else {
for (var x = 0; x < data.contributions.length; x++) {
var title = data.contributions[x]["title"];
var url = data.contributions[x]["url"];
var text = data.contributions[x]["description"];
var cont;
if (url == "") {
cont = "<li><b>" + title + "</b> - " + text + "</li>";
} else {
cont =
"<li><a href='" +
safeUrl(url) +
"'>" +
title +
"</a> - " +
text +
"</li>";
}
$("#contributions-list").append(cont);
}
}
if (!Array.isArray(data.gallery) || !data.gallery.length) {
$("#nogallery").append(
"<p>No images have been submitted for this person yet. Help us by submitting <a href='" +
data.issue +
"'>here.</a></p>",
);
} else {
for (var i = 0; i < data.gallery.length; i++) {
var imgurl = safeUrl(data.gallery[i]["url"]);
var imgtitle = data.gallery[i]["title"];
var caption = data.gallery[i]["caption"];
var image =
"<figure class='memorial-gallery'><img src='" +
imgurl +
"' alt='" +
imgtitle +
"' loading='lazy'><figcaption>" +
caption +
"</figcaption></figure>";
$("#gallery").append(image);
}
}
$(".loading-message").remove();
}).fail(function () {
$(".loading-message").replaceWith(
'<p class="error-message">Unable to load memorial data. The person may not exist or there was a network error.</p>',
);
});
});
</script>
</head>
<body>
<a href="#main-content" class="skip-link">Skip to content</a>
<header>
<div class="header-inner">
<h1 id="main-title">
RestInCode - <span id="subtitle">In Memory of </span>
</h1>
<nav class="header-nav">
<a href="about.html">About</a>
<a href="https://github.com/restincode/restincode">Contribute</a>
<a href="https://github.com/restincode/restincode/issues">Contact</a>
</nav>
</div>
</header>
<main id="main-content" class="memorial-layout">
<a href="index.html" class="back-link">&larr; Back to memorials</a>
<h2 id="memorial-name"></h2>
<div class="memorial-content">
<article class="memorial-main">
<img src="#" alt="memorial image" class="memorial-main-image" />
<div id="memorial-text">
<p class="loading-message">Loading memorial...</p>
</div>
<hr />
<h3>Contributions</h3>
<ul id="contributions-list"></ul>
<hr />
<section id="gallery-section" aria-label="Photo gallery">
<h3>Gallery</h3>
<div id="nogallery">
<div id="gallery"></div>
</div>
</section>
</article>
<aside>
<p class="birth"><b>Born:</b></p>
<p class="death"><b>Died:</b></p>
<p class="affiliations"><b>Affiliations:</b><br /></p>
<p class="obituary"></p>
<div id="social-media">
<h4>Social Media</h4>
</div>
<div id="references">
<h4>References</h4>
</div>
</aside>
</div>
</main>
<footer>
<a href="index.html">&larr; Back to memorials</a> | Memorial content
belongs to its respective creators and rights holders. We do our best to
identify and credit them.
</footer>
</body>
</html>