-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtml.test.js
More file actions
137 lines (117 loc) · 4.38 KB
/
html.test.js
File metadata and controls
137 lines (117 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* eslint-disable no-restricted-syntax */
import assert from 'node:assert';
import { describe, test, before } from 'node:test';
import { escapeHTML, escapeAttrName, stringifyAttr, html } from './html.js';
// Mock DOM Attr class for Node environment
class MockAttr {
constructor(name, value) {
this.name = name;
this.value = value;
}
}
// Polyfill global Attr for the instanceof check
before(() => {
globalThis.Attr = MockAttr;
});
describe('Security Escaping Utils', () => {
describe('escapeHTML', () => {
test('escapes the "Big 5" special characters', () => {
const input = '< > & " \'';
const expected = '< > & " '';
assert.strictEqual(escapeHTML(input), expected);
});
test('handles mixed content correctly', () => {
const input = '<div title="test">User & Co</div>';
const expected = '<div title="test">User & Co</div>';
assert.strictEqual(escapeHTML(input), expected);
});
test('preserves existing entities (idempotency)', () => {
// Your lookahead prevents double-escaping
const input = '& < © {';
assert.strictEqual(escapeHTML(input), input);
});
test('escapes bare ampersands but leaves entities alone', () => {
const input = 'Ben & Jerry vs Ben & Jerry';
const expected = 'Ben & Jerry vs Ben & Jerry';
assert.strictEqual(escapeHTML(input), expected);
});
test('handles null/undefined/non-string', () => {
assert.strictEqual(escapeHTML(null), '');
assert.strictEqual(escapeHTML(undefined), '');
assert.strictEqual(escapeHTML(123), '123');
assert.strictEqual(escapeHTML({ toString: () => '<br>' }), '<br>');
});
});
describe('escapeAttrName', () => {
test('passes valid names through unchanged', () => {
assert.strictEqual(escapeAttrName('data-id'), 'data-id');
assert.strictEqual(escapeAttrName('xml:lang'), 'xml:lang');
});
test('hex-encodes space characters', () => {
const input = 'on click';
const expected = 'on_0020_click';
assert.strictEqual(escapeAttrName(input), expected);
});
test('hex-encodes control characters', () => {
const input = 'data\x00key'; // Null byte
const expected = 'data_0000_key';
assert.strictEqual(escapeAttrName(input), expected);
});
test('hex-encodes delimiters', () => {
const input = 'user"name';
const expected = 'user_0022_name';
assert.strictEqual(escapeAttrName(input), expected);
});
test('hex-encodes injection attempts', () => {
const input = '><script>';
const expected = '_003e__003c_script_003e_';
assert.strictEqual(escapeAttrName(input), expected);
});
});
describe('stringifyAttr', () => {
test('serializes a valid Attr object', () => {
const attr = new MockAttr('id', 'main-content');
assert.strictEqual(stringifyAttr(attr), 'id="main-content"');
});
test('escapes unsafe values', () => {
const attr = new MockAttr('data-user', 'User "Name" <Admin>');
// Value should use entities
const expected = 'data-user="User "Name" <Admin>"';
assert.strictEqual(stringifyAttr(attr), expected);
});
test('escapes unsafe names (Hex)', () => {
const attr = new MockAttr('data key', 'value');
// Name should use hex encoding
const expected = 'data_0020_key="value"';
assert.strictEqual(stringifyAttr(attr), expected);
});
test('returns empty string for non-Attr objects', () => {
const pojo = { name: 'id', value: 'test' };
assert.strictEqual(stringifyAttr(pojo), '');
assert.strictEqual(stringifyAttr(null), '');
});
test('handles mixed injection attempt', () => {
const attr = new MockAttr('><img src=x', '"><script>');
// Name hex encoded, Value entity escaped
const expected = '_003e__003c_img_0020_src_003d_x=""><script>"';
assert.strictEqual(stringifyAttr(attr), expected);
});
});
});
describe('html tagged template', () => {
test('escapes interpolated values', () => {
const malicious = '<script>';
const output = html`<div>${malicious}</div>`;
assert.strictEqual(output, '<div><script></div>');
});
test('does not escape static template parts', () => {
const output = html`<span title="static"></span>`;
assert.strictEqual(output, '<span title="static"></span>');
});
test('handles multiple interpolations', () => {
const a = '<';
const b = '>';
const output = html`Start ${a} Middle ${b} End`;
assert.strictEqual(output, 'Start < Middle > End');
});
});