Skip to content

Commit dcef878

Browse files
committed
fix: Escape HTML when initializing the editor.
1 parent b2bcb31 commit dcef878

File tree

3 files changed

+56
-3
lines changed

3 files changed

+56
-3
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@
55
This is code editor pattern based on CodeJar and PrismJS.
66

77

8+
---
9+
10+
**Note:**
11+
12+
When prefilling the textarea, please add HTML escaped content to the template to avoid any XSS security issues or nesting errors with existing HTML.
13+
14+
Example to escape content in Python:
15+
16+
import html
17+
escaped_html = html.escape(unescaped_html)
18+
19+
20+
Example to escape content in JavaScript:
21+
22+
const escaped_html = unescaped_html
23+
.replaceAll("&", "&")
24+
.replaceAll("&lt;", "<")
25+
.replaceAll("&gt;", ">")
26+
.replaceAll("&quot;", '"');
27+
28+
---
29+
830
### Options reference
931

1032
| Property | Default Value | Type | Description |

src/code-editor.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import "regenerator-runtime/runtime"; // needed for ``await`` support
22
import Base from "@patternslib/patternslib/src/core/base";
33
import Parser from "@patternslib/patternslib/src/core/parser";
4+
import utils from "@patternslib/patternslib/src/core/utils";
45

56
export const parser = new Parser("code-editor");
67
parser.addArgument("language", null); // programming language to use.
@@ -35,9 +36,17 @@ export default Base.extend({
3536

3637
let el = this.el;
3738
if (["textarea", "input"].includes(this.el.nodeName.toLowerCase())) {
38-
el = document.createElement("pre");
39-
el.innerHTML = `<code contenteditable>${this.el.value}</code>`;
40-
this.el.parentNode.insertBefore(el, this.el);
39+
const unescaped_html = utils.unescape_html(this.el.value);
40+
const language_class = this.options.language
41+
? `language-${this.options.language}`
42+
: "";
43+
const pre_el = document.createElement("pre");
44+
el = document.createElement("code");
45+
el.setAttribute("class", language_class);
46+
el.setAttribute("contenteditable", "");
47+
el.textContent = unescaped_html;
48+
pre_el.append(el);
49+
this.el.parentNode.insertBefore(pre_el, this.el);
4150
this.el.setAttribute("hidden", "");
4251
}
4352

src/code-editor.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,26 @@ describe("pat-code-editor", () => {
1919
expect(document.querySelectorAll("pre code").length).toBe(1);
2020
expect(document.querySelector("textarea").getAttribute("hidden")).toBe("");
2121
});
22+
23+
it("handles escaped content correctly", async () => {
24+
const unescaped = `<hello attribute="value">this & that</hello>`;
25+
const escaped = `&lt;hello attribute=&quot;value&quot;&gt;this &amp; that&lt;/hello&gt;`;
26+
27+
document.body.innerHTML = `
28+
<textarea
29+
class="pat-code-editor"
30+
data-pat-code-editor="language: javascript">
31+
${escaped}
32+
</textarea>
33+
`;
34+
35+
new Pattern(document.querySelector(".pat-code-editor"));
36+
await utils.timeout(1);
37+
38+
const editor_el = document.querySelector("pre code");
39+
expect(editor_el.textContent.trim()).toBe(unescaped);
40+
41+
expect(editor_el.querySelectorAll(".token").length).toBeGreaterThan(0);
42+
expect(editor_el.innerHTML.includes("&lt;")).toBe(true);
43+
});
2244
});

0 commit comments

Comments
 (0)