Skip to content

Commit b07ea9b

Browse files
committed
Introduce HTML sanitization
1 parent 4132f76 commit b07ea9b

3 files changed

Lines changed: 244 additions & 6 deletions

File tree

compile-markdown.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,32 @@ import * as marked from "marked";
1717
import * as markedGfmHeadingId from "marked-gfm-heading-id";
1818
import * as shiki from "shiki";
1919

20+
import sanitizeHtml from "sanitize-html";
21+
2022
// Utility for reading files to strings.
2123
function readFile(path) {
2224
return fs.readFileSync(path, { encoding: "utf-8" });
2325
}
2426

27+
// Utility for escaping HTML tags.
28+
function escapeHtml(text) {
29+
return text
30+
.replaceAll("&", "&")
31+
.replaceAll("<", "&lt;")
32+
.replaceAll(">", "&gt;")
33+
.replaceAll("\"", "&quot;")
34+
.replaceAll("'", "&#39;");
35+
}
36+
37+
// Utility for removing HTML tags.
38+
function removeHtml(text) {
39+
return sanitizeHtml(text, {
40+
allowedTags: [],
41+
allowedAttributes: {},
42+
disallowedTagsMode: "discard"
43+
});
44+
};
45+
2546
// Fetch arguments.
2647
const args = process.argv.slice(2);
2748

@@ -67,11 +88,14 @@ let mainHtml = await marked.parse(readFile(input), {
6788
return;
6889
}
6990

91+
// Escape token text.
92+
const tokenText = escapeHtml(token.text);
93+
7094
// Absolute link; make it open in a new tab.
7195
if (/^[a-z]+:\/\//i.test(token.href)) {
7296
token.type = "html";
7397
token.text = /* HTML */ `
74-
<a href="${token.href}" target="_blank">${token.text}</a>
98+
<a href="${token.href}" target="_blank">${tokenText}</a>
7599
`.trim();
76100

77101
return;
@@ -105,7 +129,7 @@ let mainHtml = await marked.parse(readFile(input), {
105129

106130
token.type = "html";
107131
token.text = /* HTML */ `
108-
<a href="${href}" target="_blank">${token.text}</a>
132+
<a href="${href}" target="_blank">${tokenText}</a>
109133
`.trim();
110134

111135
return;
@@ -177,7 +201,7 @@ let tocHtml = "";
177201
for (const heading of headings) {
178202
tocHtml += /* HTML */ `
179203
<a href="#${heading.id}" style="margin-left: ${heading.level - 1}em">
180-
- ${heading.text}
204+
- ${removeHtml(heading.text)}
181205
</a>
182206
<br>
183207
`;
@@ -230,14 +254,14 @@ for (const entry of nav) {
230254
let navHtml = "";
231255

232256
for (const [_, category] of Object.entries(categories)) {
233-
navHtml += /* HTML */ `<h2>${category.name}</h2>`;
257+
navHtml += /* HTML */ `<h2>${escapeHtml(category.name)}</h2>`;
234258

235259
for (const file of category.files) {
236260
// Current file, highlighted in bold.
237261
if (input === file.path) {
238262
navHtml += /* HTML */ `
239263
<li id="dei-currentpage">
240-
<b>${file.title}</b>
264+
<b>${removeHtml(file.title)}</b>
241265
<br>
242266
</li>
243267
`;
@@ -246,7 +270,7 @@ for (const [_, category] of Object.entries(categories)) {
246270
else {
247271
navHtml += /* HTML */ `
248272
<li>
249-
<a href="${file.href}">${file.title}</a>
273+
<a href="${file.href}">${removeHtml(file.title)}</a>
250274
<br>
251275
</li>
252276
`;

package-lock.json

Lines changed: 213 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"@types/node": "^24.3.0",
77
"marked": "^16.2.0",
88
"marked-gfm-heading-id": "^4.1.2",
9+
"sanitize-html": "^2.17.0",
910
"shiki": "^3.11.0"
1011
}
1112
}

0 commit comments

Comments
 (0)