ghtml 1.7.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm i ghtml
12
12
 
13
13
  ### `html`
14
14
 
15
- The `html` function is designed to tag template literals and automatically escape their expressions to prevent XSS attacks. To intentionally bypass escaping for a specific expression, prefix it with `!`.
15
+ The `html` function is designed to tag template literals and automatically escape their expressions. To intentionally bypass escaping a specific expression, prefix it with `!`.
16
16
 
17
17
  ### `htmlGenerator`
18
18
 
@@ -32,7 +32,7 @@ Because they return generators instead of strings, a key difference of `htmlGene
32
32
 
33
33
  ### `includeFile`
34
34
 
35
- Available for Node.js users, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
35
+ Available in Node.js, the `includeFile` function is a wrapper around `readFileSync`. It reads and outputs the content of a file while also caching it in memory for faster future reuse.
36
36
 
37
37
  ## Usage
38
38
 
@@ -41,11 +41,11 @@ Available for Node.js users, the `includeFile` function is a wrapper around `rea
41
41
  ```js
42
42
  import { html } from "ghtml";
43
43
 
44
- const username = '<img src="https://example.com/hacker.png">';
44
+ const username = '<img src="https://example.com/pwned.png">';
45
45
  const greeting = html`<h1>Hello, ${username}!</h1>`;
46
46
 
47
47
  console.log(greeting);
48
- // Output: <h1>Hello, &lt;img src=&quot;https://example.com/hacker.png&quot;&gt;</h1>
48
+ // Output: <h1>Hello, &#60;img src=&#34;https://example.com/pwned.png&#34;&#62;</h1>
49
49
  ```
50
50
 
51
51
  To bypass escaping:
@@ -78,7 +78,7 @@ const htmlString = html`
78
78
  )}
79
79
  </ul>
80
80
  `
81
- : "<p>No data...</p>"}
81
+ : html`<p>No data...</p>`}
82
82
  </div>
83
83
  `;
84
84
  ```
@@ -168,3 +168,7 @@ const logo = includeFile("static/logo.svg");
168
168
  console.log(logo);
169
169
  // Output: content of "static/logo.svg"
170
170
  ```
171
+
172
+ ## Security
173
+
174
+ Like [similar](https://handlebarsjs.com/guide/#html-escaping) [tools](https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities), `ghtml` will not prevent all kinds of XSS attacks. It is the responsibility of consumers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values (especially when they contain expressions), and using unsanitized user-provided URLs.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Replace your template engine with fast JavaScript by leveraging the power of tagged templates.",
4
4
  "author": "Gürgün Dayıoğlu",
5
5
  "license": "MIT",
6
- "version": "1.7.1",
6
+ "version": "2.0.0",
7
7
  "type": "module",
8
8
  "main": "./src/index.js",
9
9
  "exports": {
@@ -22,7 +22,7 @@
22
22
  "devDependencies": {
23
23
  "@fastify/pre-commit": "^2.1.0",
24
24
  "c8": "^9.1.0",
25
- "grules": "^0.17.1",
25
+ "grules": "^0.17.2",
26
26
  "tinybench": "^2.8.0"
27
27
  },
28
28
  "repository": {
package/src/html.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const escapeDictionary = {
2
- '"': "&quot;",
3
- "'": "&apos;",
4
- "&": "&amp;",
5
- "<": "&lt;",
6
- ">": "&gt;",
2
+ '"': "&#34;",
3
+ "&": "&#38;",
4
+ "'": "&#39;",
5
+ "<": "&#60;",
6
+ ">": "&#62;",
7
+ "`": "&#96;",
7
8
  };
8
9
 
9
10
  const escapeRegExp = new RegExp(
@@ -19,6 +20,7 @@ const escapeFunction = (string) => {
19
20
 
20
21
  do {
21
22
  const escapedCharacter = escapeDictionary[string[end++]];
23
+
22
24
  if (escapedCharacter) {
23
25
  escaped += string.slice(start, end - 1) + escapedCharacter;
24
26
  start = end;
@@ -44,10 +46,10 @@ const html = ({ raw: literals }, ...expressions) => {
44
46
  const expression = expressions[index];
45
47
  let literal = literals[index];
46
48
  let string =
47
- expression === undefined || expression === null
48
- ? ""
49
- : typeof expression === "string"
50
- ? expression
49
+ typeof expression === "string"
50
+ ? expression
51
+ : expression === undefined || expression === null
52
+ ? ""
51
53
  : arrayIsArray(expression)
52
54
  ? expression.join("")
53
55
  : `${expression}`;
@@ -78,10 +80,10 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
78
80
  let literal = literals[index];
79
81
  let string;
80
82
 
81
- if (expression === undefined || expression === null) {
82
- string = "";
83
- } else if (typeof expression === "string") {
83
+ if (typeof expression === "string") {
84
84
  string = expression;
85
+ } else if (expression === undefined || expression === null) {
86
+ string = "";
85
87
  } else {
86
88
  if (expression[Symbol.iterator]) {
87
89
  const isRaw =
@@ -96,13 +98,13 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
96
98
  }
97
99
 
98
100
  for (expression of expression) {
99
- if (expression === undefined || expression === null) {
100
- continue;
101
- }
102
-
103
101
  if (typeof expression === "string") {
104
102
  string = expression;
105
103
  } else {
104
+ if (expression === undefined || expression === null) {
105
+ continue;
106
+ }
107
+
106
108
  if (expression[Symbol.iterator]) {
107
109
  for (expression of expression) {
108
110
  if (expression === undefined || expression === null) {
@@ -171,10 +173,10 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
171
173
  let literal = literals[index];
172
174
  let string;
173
175
 
174
- if (expression === undefined || expression === null) {
175
- string = "";
176
- } else if (typeof expression === "string") {
176
+ if (typeof expression === "string") {
177
177
  string = expression;
178
+ } else if (expression === undefined || expression === null) {
179
+ string = "";
178
180
  } else {
179
181
  if (expression[Symbol.iterator] || expression[Symbol.asyncIterator]) {
180
182
  const isRaw =
@@ -189,13 +191,13 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
189
191
  }
190
192
 
191
193
  for await (expression of expression) {
192
- if (expression === undefined || expression === null) {
193
- continue;
194
- }
195
-
196
194
  if (typeof expression === "string") {
197
195
  string = expression;
198
196
  } else {
197
+ if (expression === undefined || expression === null) {
198
+ continue;
199
+ }
200
+
199
201
  if (
200
202
  expression[Symbol.iterator] ||
201
203
  expression[Symbol.asyncIterator]
package/test/index.js CHANGED
@@ -59,14 +59,14 @@ test("renders safe content", () => {
59
59
  test("renders unsafe content", () => {
60
60
  assert.strictEqual(
61
61
  html`<p>${descriptionUnsafe}</p>`,
62
- `<p>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>`,
62
+ `<p>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>`,
63
63
  );
64
64
  });
65
65
 
66
66
  test("renders arrays", () => {
67
67
  assert.strictEqual(
68
68
  html`<p>${[descriptionSafe, descriptionUnsafe]}</p>`,
69
- "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</p>",
69
+ "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</p>",
70
70
  );
71
71
  });
72
72
 
@@ -81,7 +81,7 @@ test("renders nested html calls", () => {
81
81
  // prettier-ignore
82
82
  assert.strictEqual(
83
83
  html`<p>!${conditionTrue ? html`<strong>${descriptionUnsafe}</strong>` : ""}</p>`,
84
- "<p><strong>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;</strong></p>",
84
+ "<p><strong>&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;</strong></p>",
85
85
  );
86
86
  });
87
87
 
@@ -156,7 +156,7 @@ test("htmlGenerator renders unsafe content", () => {
156
156
 
157
157
  assert.strictEqual(
158
158
  accumulator,
159
- "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
159
+ "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
160
160
  );
161
161
  });
162
162
 
@@ -199,7 +199,7 @@ test("htmlGenerator works with other generators (escaped)", () => {
199
199
 
200
200
  assert.strictEqual(
201
201
  accumulator,
202
- "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
202
+ "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
203
203
  );
204
204
  assert.strictEqual(generator.next().done, true);
205
205
  });
@@ -229,7 +229,7 @@ test("htmlGenerator works with other generators within an array (escaped)", () =
229
229
 
230
230
  assert.strictEqual(
231
231
  accumulator,
232
- "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;1,2,3,4,5255&lt;/p&gt;</div>",
232
+ "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;1,2,3,4,5255&#60;/p&#62;</div>",
233
233
  );
234
234
  assert.strictEqual(generator.next().done, true);
235
235
  });
@@ -258,7 +258,7 @@ test("htmlAsyncGenerator renders unsafe content", async () => {
258
258
 
259
259
  assert.strictEqual(
260
260
  accumulator,
261
- "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
261
+ "<p>This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255</p>",
262
262
  );
263
263
  });
264
264
 
@@ -286,7 +286,7 @@ test("htmlAsyncGenerator works with other generators (escaped)", async () => {
286
286
 
287
287
  assert.strictEqual(
288
288
  accumulator,
289
- "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
289
+ "<div>&#60;p&#62;This is a safe description.&#60;script&#62;alert(&#39;This is an unsafe description.&#39;)&#60;/script&#62;12345255&#60;/p&#62;</div>",
290
290
  );
291
291
  });
292
292
 
@@ -302,7 +302,7 @@ test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array"
302
302
 
303
303
  assert.strictEqual(
304
304
  accumulator.replaceAll("\n", "").trim(),
305
- "1: <p># test.md&gt;</p>2: <p># test.md&gt;</p>3: <p># test.md&gt;</p>",
305
+ "1: <p># test.md&#62;</p>2: <p># test.md&#62;</p>3: <p># test.md&#62;</p>",
306
306
  );
307
307
  });
308
308
 
@@ -312,7 +312,7 @@ test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => {
312
312
  })}</ul>`;
313
313
  const fileContent = readFileSync("test/test.md", "utf8").replaceAll(
314
314
  ">",
315
- "&gt;",
315
+ "&#62;",
316
316
  );
317
317
 
318
318
  let value = await generator.next();
@@ -372,7 +372,7 @@ test("htmlAsyncGenerator redners in chuncks", async () => {
372
372
  assert.strictEqual(value.value, "<ul>");
373
373
 
374
374
  value = await generator.next();
375
- assert.strictEqual(value.value, "&lt;p&gt;");
375
+ assert.strictEqual(value.value, "&#60;p&#62;");
376
376
 
377
377
  value = await generator.next();
378
378
  assert.strictEqual(value.value, "12");