@shgysk8zer0/polyfills 0.3.2 → 0.3.3

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.
@@ -1,299 +0,0 @@
1
- /**
2
- * @copyright 2023-2024 Chris Zuber <admin@kernvalley.us>
3
- */
4
- import { SanitizerConfig as defaultConfig } from './SanitizerConfigW3C.js';
5
- import { createPolicy } from './trust.js';
6
- import { isObject, getType, callOnce } from './utility.js';
7
- import { urls } from './attributes.js';
8
-
9
- export const supported = () => 'Sanitizer' in globalThis;
10
- export const nativeSupport = supported();
11
-
12
- const isDataAttr = name => name.length > 5 && name.substring(0, 5) === 'data-';
13
-
14
- export const setHTML = function setHTML(el, input, opts = defaultConfig) {
15
- const doc = safeParseHTML(input, opts);
16
- el.replaceChildren(documentToFragment(doc));
17
- };
18
-
19
- const allowProtocols = ['https:', 'blob:'];
20
-
21
- const HTML_NS = 'http://www.w3.org/1999/xhtml';
22
-
23
- if (! allowProtocols.includes(location.protocol)) {
24
- allowProtocols.push(location.protocol);
25
- }
26
-
27
- const policyName = 'sanitizer-raw#html';
28
- const getPolicy = callOnce(() => createPolicy(policyName, { createHTML: input => input }));
29
- const createHTML = input => getPolicy().createHTML(input);
30
-
31
- export function documentToFragment(doc) {
32
- const frag = document.createDocumentFragment();
33
- const clone = doc.cloneNode(true);
34
- frag.append(...clone.head.childNodes, ...clone.body.childNodes);
35
- return frag;
36
- }
37
-
38
- export function addNamesapces(list) {
39
- const mapped = list.map(allow => typeof allow === 'string' ? ({ name: allow, namespace: HTML_NS }) : allow);
40
-
41
- return Object.groupBy(mapped, ({ namespace = HTML_NS }) => namespace);
42
- }
43
-
44
- /**
45
- * Helper function to adapt to changes in spec
46
- */
47
- export function convertSanitizerConfig({
48
- allowAttributes, allowComments, allowElements, allowCustomElements,
49
- blockElements, dropAttributes, dropElements, allowUnknownMarkup, sanitizer,
50
- } = {}, context) {
51
- if (sanitizer instanceof Sanitizer) {
52
- return convertSanitizerConfig(sanitizer.getConfiguration(), context);
53
- } else {
54
- switch (context) {
55
- default:
56
- if (typeof allowAttributes === 'undefined' && typeof dropAttributes === 'undefined') {
57
- allowAttributes = defaultConfig.allowAttributes;
58
- }
59
-
60
- if (typeof allowElements === 'undefined' && typeof dropElements === 'undefined') {
61
- allowElements = defaultConfig.allowElements;
62
- }
63
- return {
64
- allowAttributes, allowComments, allowElements, allowCustomElements,
65
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
66
- };
67
- }
68
- }
69
- }
70
-
71
- export function convertToSanitizerConfig({
72
- allowAttributes, allowComments, allowElements, allowCustomElements,
73
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
74
- } = {}) {
75
- if (typeof allowAttributes === 'undefined' && typeof dropAttributes === 'undefined') {
76
- allowAttributes = defaultConfig.allowAttributes;
77
- }
78
-
79
- if (typeof allowElements === 'undefined' && typeof dropElements === 'undefined') {
80
- allowElements = defaultConfig.allowElements;
81
- }
82
- return {
83
- allowAttributes, allowComments, allowElements, allowCustomElements,
84
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
85
- };
86
- }
87
-
88
- export function safeParseHTML(input, opts = defaultConfig) {
89
- const doc = new DOMParser().parseFromString(createHTML(input), 'text/html');
90
- // Not sure if this will be in spec, but it is necessary
91
- if (Array.isArray(opts.allowElements) && ! opts.allowElements.includes('html') ) {
92
- opts.allowElements = [...new Set([...opts.allowElements, 'html' ,'head', 'body'])];
93
- }
94
- return sanitizeNode(doc, opts);
95
- }
96
-
97
- export function sanitize(input, opts = defaultConfig) {
98
- if (! (input instanceof Node)) {
99
- throw new TypeError('sanitize requires a Document or DocumentFragment');
100
- } else if (input.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
101
- return sanitizeNode(input, opts);
102
- } else if (input.nodeType === Node.DOCUMENT_NODE) {
103
- return sanitizeNode(documentToFragment(input), opts);
104
- } else {
105
- throw new TypeError('sanitize requires a Document or DocumentFragment');
106
- }
107
- }
108
-
109
- export function sanitizeFor(tag, content, opts = defaultConfig) {
110
- const el = document.createElement(tag);
111
- const temp = document.createElement('template');
112
- temp.innerHTML = createHTML(content);
113
- el.append(sanitize(temp.content, opts));
114
- return el;
115
- }
116
-
117
- export function sanitizeNode(root, opts = defaultConfig) {
118
- try {
119
- if (! (root instanceof Node)) {
120
- throw new TypeError(`Expected a Node but got a ${getType(root)}.`);
121
- } else if (! isObject(opts)) {
122
- throw new TypeError(`Expected config to be an object but got ${getType(opts)}.`);
123
- }
124
-
125
- const {
126
- allowElements, allowComments, allowAttributes, allowCustomElements,
127
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
128
- } = convertSanitizerConfig(opts);
129
-
130
- const iter = document.createNodeIterator(
131
- root,
132
- NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
133
- );
134
-
135
- let node = iter.root.nodeType === Node.ELEMENT_NODE
136
- ? iter.root
137
- : iter.nextNode();
138
-
139
- while (node instanceof Node) {
140
- switch(node.nodeType) {
141
- case Node.ELEMENT_NODE: {
142
- if (
143
- ! allowUnknownMarkup
144
- && ( ! (node instanceof HTMLElement) || node instanceof HTMLUnknownElement)
145
- ) {
146
- node.remove();
147
- break;
148
- }
149
-
150
- const tag = node.tagName.toLowerCase();
151
-
152
- if (Array.isArray(dropElements) && dropElements.includes(tag)) {
153
- node.remove();
154
- } else if (Array.isArray(blockElements) && blockElements.includes(tag)) {
155
- if (node.hasChildNodes()) {
156
- node.replaceWith(...node.childNodes);
157
- } else {
158
- node.remove();
159
- }
160
- } else if (tag.includes('-') && ! allowCustomElements) {
161
- node.remove();
162
- } else if (Array.isArray(allowElements) && ! allowElements.includes(tag)) {
163
- node.remove();
164
- } else {
165
- if (node.hasAttributes()) {
166
- node.getAttributeNames().forEach(name => {
167
- const attr = node.getAttributeNode(name);
168
- const { value } = attr;
169
-
170
- if (
171
- urls.includes(name)
172
- && ! allowProtocols.includes(new URL(value, document.baseURI).protocol)
173
- ) {
174
- node.removeAttributeNode(attr);
175
- } else if (isObject(dropAttributes)) {
176
- if (
177
- name in dropAttributes
178
- && ['*', tag].some(sel => dropAttributes[name].includes(sel))
179
- ) {
180
- node.removeAttributeNode(attr);
181
- }
182
- } else if (isObject(allowAttributes)) {
183
- if (
184
- ! ((
185
- name in allowAttributes
186
- && ['*', tag].some(sel => allowAttributes[name].includes(sel))
187
- ) || isDataAttr(name))
188
- ) {
189
- node.removeAttributeNode(attr);
190
- }
191
- }
192
- });
193
- }
194
- }
195
-
196
- break;
197
- }
198
-
199
- case Node.COMMENT_NODE: {
200
- if (! allowComments) {
201
- node.remove();
202
- }
203
-
204
- break;
205
- }
206
- }
207
-
208
- if (node.localName === 'template') {
209
- sanitizeNode(node.content, opts);
210
- }
211
-
212
- node = iter.nextNode();
213
- }
214
-
215
- return root;
216
- } catch(err) {
217
- console.error(err);
218
- root.parentElement.removeChild(root);
219
- }
220
- }
221
-
222
- export function getSantizerUtils(Sanitizer, defaultConfig) {
223
- const polyfill = function polyfill() {
224
- let polyfilled = false;
225
-
226
- if (! supported()) {
227
- globalThis.Sanitizer = Sanitizer;
228
- polyfilled = true;
229
- } else {
230
- if (! (globalThis.Sanitizer.getDefaultConfiguration instanceof Function)) {
231
- globalThis.Sanitizer.getDefaultConfiguration = function() {
232
- return defaultConfig;
233
- };
234
- polyfilled = true;
235
- }
236
-
237
- if (! (globalThis.Sanitizer.prototype.getConfiguration instanceof Function)) {
238
- const configs = new WeakMap();
239
- const SanitizerNative = globalThis.Sanitizer;
240
-
241
- globalThis.Sanitizer = class Sanitizer extends SanitizerNative {
242
- constructor({
243
- allowAttributes, allowComments, allowElements, allowCustomElements,
244
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
245
- } = SanitizerNative.getDefaultConfiguration()) {
246
- super({
247
- allowAttributes, allowComments, allowElements, allowCustomElements,
248
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
249
- });
250
- configs.set(this, {
251
- allowAttributes, allowComments, allowElements, allowCustomElements,
252
- blockElements, dropAttributes, dropElements, allowUnknownMarkup,
253
- });
254
- }
255
-
256
- getConfiguration() {
257
- return configs.get(this);
258
- }
259
- };
260
- polyfilled = true;
261
- }
262
-
263
- if (! (globalThis.Sanitizer.prototype.sanitize instanceof Function)) {
264
- globalThis.Sanitizer.prototype.sanitize = function(input) {
265
- if (! (input instanceof Node)) {
266
- throw new TypeError('`Sanitizer.sanitize()` expects a `Node`');
267
- } else if (! [Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(input.nodeType)) {
268
- throw new TypeError('Expected a Document or DocumentFragment in `Sanitizer.sanitize()`.');
269
- } else {
270
- return sanitize(input, this.getConfiguration());
271
- }
272
- };
273
- polyfilled = true;
274
- }
275
-
276
- if (! (globalThis.Sanitizer.prototype.sanitizeFor instanceof Function)) {
277
- globalThis.Sanitizer.prototype.sanitizeFor = function(element, input) {
278
- const el = document.createElement(element);
279
- setHTML(el, input,this.getConfiguration());
280
- return el;
281
- };
282
- polyfilled = true;
283
- }
284
-
285
- if (! (Element.prototype.setHTML instanceof Function)) {
286
- Element.prototype.setHTML = function(input, opts = defaultConfig) {
287
- setHTML(this, input, opts);
288
- };
289
- polyfilled = true;
290
- }
291
- }
292
-
293
- return polyfilled;
294
- };
295
-
296
- return { setHTML, polyfill };
297
- }
298
-
299
- export const trustPolicies = [policyName];