diff --git a/README.md b/README.md index 795ea97..e2f69ea 100644 --- a/README.md +++ b/README.md @@ -213,4 +213,4 @@ summary for Real World Scenario ## Security -Like [similar tools](https://handlebarsjs.com/guide/#html-escaping), ghtml does not prevent all kinds of XSS attacks. It is the responsibility of developers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values (especially when they contain expressions), and relying on unsanitized user-provided URIs. +Like [similar tools](https://handlebarsjs.com/guide/#html-escaping), ghtml does not prevent all kinds of XSS attacks. It is the responsibility of developers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values, and relying on unsanitized user-provided URIs. diff --git a/src/html.js b/src/html.js index ab9d218..9ded78e 100644 --- a/src/html.js +++ b/src/html.js @@ -1,4 +1,4 @@ -const escapeRegExp = /["&'<=>`]/; +const escapeRegExp = /["&'<=>]/; const escapeFunction = (string) => { let escaped = ""; @@ -30,10 +30,6 @@ const escapeFunction = (string) => { escaped += string.slice(start, end) + ">"; start = end + 1; continue; - case 96: // ` - escaped += string.slice(start, end) + "`"; - start = end + 1; - continue; } } diff --git a/test/index.js b/test/index.js index 63a2e73..59ec5e1 100644 --- a/test/index.js +++ b/test/index.js @@ -2,7 +2,6 @@ import { html, htmlGenerator, htmlAsyncGenerator } from "../src/index.js"; import { readFile } from "node:fs/promises"; import { readFileSync } from "node:fs"; import test from "node:test"; -import assert from "node:assert"; const conditionTrue = true; const conditionFalse = false; @@ -33,75 +32,75 @@ const generatorPromiseExample = function* () { yield; }; -test("renders empty input", () => { - assert.strictEqual(html({ raw: [""] }), ""); +test("renders empty input", (t) => { + t.assert.strictEqual(html({ raw: [""] }), ""); }); -test("renders empty input", () => { - assert.strictEqual(html`${""}`, ""); +test("renders empty input", (t) => { + t.assert.strictEqual(html`${""}`, ""); }); -test("renders normal input", () => { - assert.strictEqual(html`Hey, ${username}!`, `Hey, ${username}!`); +test("renders normal input", (t) => { + t.assert.strictEqual(html`Hey, ${username}!`, `Hey, ${username}!`); }); -test("renders undefined and null as empty string", () => { - assert.strictEqual(html`

${null}${undefined}

`, "

"); +test("renders undefined and null as empty string", (t) => { + t.assert.strictEqual(html`

${null}${undefined}

`, "

"); }); -test("renders safe content", () => { - assert.strictEqual( +test("renders safe content", (t) => { + t.assert.strictEqual( html`

${descriptionSafe}

`, "

This is a safe description.

", ); }); -test("renders unsafe content", () => { - assert.strictEqual( +test("renders unsafe content", (t) => { + t.assert.strictEqual( html`

${descriptionUnsafe}

`, `

<script>alert('This is an unsafe description.')</script>

`, ); }); -test("renders unsafe content /2", () => { - assert.strictEqual( - html`

${`${descriptionUnsafe}"&\``}

`, - `

<script>alert('This is an unsafe description.')</script>"&`

`, +test("renders unsafe content /2", (t) => { + t.assert.strictEqual( + html`

${`${descriptionUnsafe}"&`}

`, + `

<script>alert('This is an unsafe description.')</script>"&

`, ); }); -test("renders unsafe content /3", () => { +test("renders unsafe content /3", (t) => { // prettier-ignore - assert.strictEqual( + t.assert.strictEqual( html`${"altText`, `altText`, ); }); -test("renders arrays", () => { - assert.strictEqual( +test("renders arrays", (t) => { + t.assert.strictEqual( html`

${[descriptionSafe, descriptionUnsafe]}

`, "

This is a safe description.<script>alert('This is an unsafe description.')</script>

", ); }); -test("bypasses escaping", () => { - assert.strictEqual( +test("bypasses escaping", (t) => { + t.assert.strictEqual( html`

!${[descriptionSafe, descriptionUnsafe]}

`, "

This is a safe description.

", ); }); -test("renders nested html calls", () => { +test("renders nested html calls", (t) => { // prettier-ignore - assert.strictEqual( + t.assert.strictEqual( html`

!${conditionTrue ? html`${descriptionUnsafe}` : ""}

`, "

<script>alert('This is an unsafe description.')</script>

", ); }); -test("renders multiple html calls", () => { - assert.strictEqual( +test("renders multiple html calls", (t) => { + t.assert.strictEqual( html`

!${conditionFalse ? "" : html` ${descriptionSafe} `} @@ -119,14 +118,14 @@ test("renders multiple html calls", () => { ); }); -test("renders multiple html calls with different expression types", () => { +test("renders multiple html calls with different expression types", (t) => { const obj = {}; obj.toString = () => { return "description of the object"; }; // prettier-ignore - assert.strictEqual( + t.assert.strictEqual( html`

!${conditionTrue ? html` ${descriptionSafe} ` : ""} @@ -147,7 +146,7 @@ test("renders multiple html calls with different expression types", () => { ); }); -test("htmlGenerator renders safe content", () => { +test("htmlGenerator renders safe content", (t) => { const generator = htmlGenerator`

${descriptionSafe}!${descriptionUnsafe}G!${htmlGenerator`${array1}`}!${null}${255}

`; let accumulator = ""; @@ -155,13 +154,13 @@ test("htmlGenerator renders safe content", () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.G12345255

", ); }); -test("htmlGenerator renders unsafe content", () => { +test("htmlGenerator renders unsafe content", (t) => { const generator = htmlGenerator`

${descriptionSafe}${descriptionUnsafe}${htmlGenerator`${array1}`}${null}${255}

`; let accumulator = ""; @@ -169,13 +168,13 @@ test("htmlGenerator renders unsafe content", () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.<script>alert('This is an unsafe description.')</script>12345255

", ); }); -test("htmlGenerator works with nested htmlGenerator calls in an array", () => { +test("htmlGenerator works with nested htmlGenerator calls in an array", (t) => { const generator = htmlGenerator``; @@ -185,11 +184,11 @@ test("htmlGenerator works with nested htmlGenerator calls in an array", () => { accumulator += value; } - assert.strictEqual(accumulator, ""); - assert.strictEqual(generator.next().done, true); + t.assert.strictEqual(accumulator, ""); + t.assert.strictEqual(generator.next().done, true); }); -test("htmlGenerator works with other generators (raw)", () => { +test("htmlGenerator works with other generators (raw)", (t) => { const generator = htmlGenerator`
!${generatorExample()}
`; let accumulator = ""; @@ -197,14 +196,14 @@ test("htmlGenerator works with other generators (raw)", () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.12345255

", ); - assert.strictEqual(generator.next().done, true); + t.assert.strictEqual(generator.next().done, true); }); -test("htmlGenerator works with other generators (escaped)", () => { +test("htmlGenerator works with other generators (escaped)", (t) => { const generator = htmlGenerator`
${generatorExample()}
`; let accumulator = ""; @@ -212,14 +211,14 @@ test("htmlGenerator works with other generators (escaped)", () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "
<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>
", ); - assert.strictEqual(generator.next().done, true); + t.assert.strictEqual(generator.next().done, true); }); -test("htmlGenerator works with other generators within an array (raw)", () => { +test("htmlGenerator works with other generators within an array (raw)", (t) => { const generator = htmlGenerator`
!${[generatorExample()]}
`; let accumulator = ""; @@ -227,14 +226,14 @@ test("htmlGenerator works with other generators within an array (raw)", () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.1,2,3,4,5255

", ); - assert.strictEqual(generator.next().done, true); + t.assert.strictEqual(generator.next().done, true); }); -test("htmlGenerator works with other generators within an array (escaped)", () => { +test("htmlGenerator works with other generators within an array (escaped)", (t) => { const generator = htmlGenerator`
${[generatorExample()]}
`; let accumulator = ""; @@ -242,14 +241,14 @@ test("htmlGenerator works with other generators within an array (escaped)", () = accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "
<p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p>
", ); - assert.strictEqual(generator.next().done, true); + t.assert.strictEqual(generator.next().done, true); }); -test("htmlAsyncGenerator renders safe content", async () => { +test("htmlAsyncGenerator renders safe content", async (t) => { const generator = htmlAsyncGenerator`

${descriptionSafe}!${descriptionUnsafe}G!${htmlAsyncGenerator`${array1}`}!${null}${255}

`; let accumulator = ""; @@ -257,13 +256,13 @@ test("htmlAsyncGenerator renders safe content", async () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.G12345255

", ); }); -test("htmlAsyncGenerator renders unsafe content", async () => { +test("htmlAsyncGenerator renders unsafe content", async (t) => { const generator = htmlAsyncGenerator`

${descriptionSafe}${descriptionUnsafe}${htmlAsyncGenerator`${array1}`}${null}${255}

`; let accumulator = ""; @@ -271,13 +270,13 @@ test("htmlAsyncGenerator renders unsafe content", async () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.<script>alert('This is an unsafe description.')</script>12345255

", ); }); -test("htmlAsyncGenerator works with other generators (raw)", async () => { +test("htmlAsyncGenerator works with other generators (raw)", async (t) => { const generator = htmlAsyncGenerator`
!${generatorExample()}
`; let accumulator = ""; @@ -285,13 +284,13 @@ test("htmlAsyncGenerator works with other generators (raw)", async () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "

This is a safe description.12345255

", ); }); -test("htmlAsyncGenerator works with other generators (escaped)", async () => { +test("htmlAsyncGenerator works with other generators (escaped)", async (t) => { const generator = htmlAsyncGenerator`
${generatorExample()}
`; let accumulator = ""; @@ -299,13 +298,13 @@ test("htmlAsyncGenerator works with other generators (escaped)", async () => { accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator, "
<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>
", ); }); -test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array", async () => { +test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array", async (t) => { const generator = htmlAsyncGenerator`!${[1, 2, 3].map((i) => { return htmlAsyncGenerator`${i}:

${readFile("test/test.md", "utf8")}

`; })}`; @@ -315,13 +314,13 @@ test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array" accumulator += value; } - assert.strictEqual( + t.assert.strictEqual( accumulator.replaceAll("\n", "").trim(), "1:

# test.md>

2:

# test.md>

3:

# test.md>

", ); }); -test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => { +test("htmlAsyncGenerator renders chunks with promises (escaped)", async (t) => { const generator = htmlAsyncGenerator``; @@ -331,89 +330,89 @@ test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => { ); let value = await generator.next(); - assert.strictEqual(value.value, ""); value = await generator.next(); - assert.strictEqual(value.done, true); + t.assert.strictEqual(value.done, true); }); -test("htmlAsyncGenerator renders chunks with promises (raw)", async () => { +test("htmlAsyncGenerator renders chunks with promises (raw)", async (t) => { const generator = htmlAsyncGenerator``; const fileContent = readFileSync("test/test.md", "utf8"); let value = await generator.next(); - assert.strictEqual(value.value, ""); value = await generator.next(); - assert.strictEqual(value.done, true); + t.assert.strictEqual(value.done, true); }); -test("htmlAsyncGenerator redners in chuncks", async () => { +test("htmlAsyncGenerator redners in chuncks", async (t) => { const generator = htmlAsyncGenerator``; let value = await generator.next(); - assert.strictEqual(value.value, ""); value = await generator.next(); - assert.strictEqual(value.done, true); + t.assert.strictEqual(value.done, true); }); -test("htmlAsyncGenerator redners in chuncks (raw)", async () => { +test("htmlAsyncGenerator redners in chuncks (raw)", async (t) => { const generator = htmlAsyncGenerator``; let value = await generator.next(); - assert.strictEqual(value.value, ""); value = await generator.next(); - assert.strictEqual(value.done, true); + t.assert.strictEqual(value.done, true); });