restincode/memorial.html
Richard Nixon cbbecc27c6 fix(memorial): sanitize remaining XSS-prone render paths
The references fix in #331 covered only one of the render paths.
Apply the same DOM-construction pattern to personal info, obituary,
social media, contributions, gallery and the help-link fallbacks,
plus the people grid in index.html.

maintext is still rendered as HTML to preserve existing formatted
bios; flagged for a DOMPurify follow-up.
2026-05-17 16:08:45 +01:00

275 lines
9.3 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/" + encodeURIComponent(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 "#";
}
}
function txt(value) {
return document.createTextNode(value == null ? "" : String(value));
}
$.getJSON(person, function (data) {
var fullname = data.firstname + " " + data.lastname;
if (data.handle != "") {
fullname += " (" + data.handle + ")";
}
document.title = document.title + fullname;
$("#subtitle").append(txt(fullname));
$("#memorial-name").text(fullname);
$(".birth").append(txt(data.birth));
$(".death").append(txt(data.death));
if (data.affiliations == null || data.affiliations == "") {
$(".affiliations").append(txt("None"));
} else {
$(".affiliations").append(txt(data.affiliations));
}
if (data.obituary != null && data.obituary != "") {
$(".obituary").append(
$("<a>").attr("href", safeUrl(data.obituary)).text("Obituary"),
);
}
var issueUrl;
if (data.issue == null || data.issue == "") {
issueUrl =
"https://github.com/restincode/restincode/blob/master/CONTRIBUTING.md";
} else {
issueUrl =
"https://github.com/restincode/restincode/issues/" +
encodeURIComponent(data.issue);
}
var safeIssueUrl = safeUrl(issueUrl);
function helpLink() {
return $("<a>").attr("href", safeIssueUrl).text("here.");
}
if (data.mainimage == "") {
$(".memorial-main-image")
.attr("src", "/images/face-silhouette-clipart.png")
.attr("alt", "placeholder photo");
} else {
$(".memorial-main-image")
.attr("src", safeUrl(data.mainimage))
.attr("alt", "Main photo of " + fullname);
}
if (data.maintext == "") {
$("#memorial-text").append(
$("<p>").append(
"No information has been submitted for this person. Help us by submitting ",
helpLink(),
),
);
} else {
// maintext is intentionally rendered as HTML for formatted bios.
// Residual XSS risk is mitigated by PR review; track follow-up
// to integrate DOMPurify for defense-in-depth.
$("#memorial-text").append(data.maintext);
}
if (
!Array.isArray(data.socialmedialinks) ||
!data.socialmedialinks.length
) {
$("#social-media").append(
$("<p>").append(
"No social media links have been submitted for this person. Help us by submitting ",
helpLink(),
),
);
} else {
for (var s = 0; s < data.socialmedialinks.length; s++) {
var sitename = data.socialmedialinks[s]["sitename"];
var siteurl = data.socialmedialinks[s]["siteurl"];
$("#social-media").append(
$("<a>").attr("href", safeUrl(siteurl)).text(sitename),
);
}
}
if (!Array.isArray(data.references) || !data.references.length) {
$("#references").append(
$("<p>").append(
"No references have been submitted for this person. Help us by submitting ",
helpLink(),
),
);
} 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>").append(
"No contributions have been submitted for this person. Help us by submitting ",
helpLink(),
),
);
} 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 $li = $("<li>");
if (url == "") {
$li.append($("<b>").text(title));
} else {
$li.append($("<a>").attr("href", safeUrl(url)).text(title));
}
$li.append(txt(" - " + text));
$("#contributions-list").append($li);
}
}
if (!Array.isArray(data.gallery) || !data.gallery.length) {
$("#nogallery").append(
$("<p>").append(
"No images have been submitted for this person yet. Help us by submitting ",
helpLink(),
),
);
} 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"];
$("#gallery").append(
$("<figure>")
.addClass("memorial-gallery")
.append(
$("<img>")
.attr("src", imgurl)
.attr("alt", imgtitle)
.attr("loading", "lazy"),
$("<figcaption>").text(caption),
),
);
}
}
$(".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>