@oscarpalmer/toretto 0.28.0 → 0.30.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/dist/attribute/index.js +13 -1
- package/dist/html/index.js +63 -0
- package/dist/html/sanitize.js +40 -0
- package/dist/index.js +3 -6
- package/dist/internal/attribute.js +52 -25
- package/dist/toretto.full.js +760 -1026
- package/package.json +12 -12
- package/src/attribute/index.ts +87 -6
- package/src/{html.ts → html/index.ts} +52 -37
- package/src/html/sanitize.ts +83 -0
- package/src/index.ts +7 -2
- package/src/internal/attribute.ts +111 -121
- package/types/attribute/index.d.ts +57 -1
- package/types/{html.d.ts → html/index.d.ts} +4 -5
- package/types/html/sanitize.d.ts +2 -0
- package/types/index.d.ts +2 -2
- package/types/internal/attribute.d.ts +4 -55
- package/dist/html.js +0 -55
- package/dist/internal/sanitize.js +0 -30
- package/src/internal/sanitize.ts +0 -67
- package/types/internal/sanitize.d.ts +0 -13
package/dist/attribute/index.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { getAttribute, getAttributes } from "./get.js";
|
|
2
|
-
import { booleanAttributes, isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute } from "../internal/attribute.js";
|
|
2
|
+
import { booleanAttributes, isBadAttribute as isBadAttribute$1, isBooleanAttribute as isBooleanAttribute$1, isEmptyNonBooleanAttribute as isEmptyNonBooleanAttribute$1, isInvalidBooleanAttribute as isInvalidBooleanAttribute$1 } from "../internal/attribute.js";
|
|
3
3
|
import { setAttribute, setAttributes, setProperties, setProperty } from "./set.js";
|
|
4
|
+
function isBadAttribute(first, second) {
|
|
5
|
+
return isBadAttribute$1(first, second, true);
|
|
6
|
+
}
|
|
7
|
+
function isBooleanAttribute(first) {
|
|
8
|
+
return isBooleanAttribute$1(first, true);
|
|
9
|
+
}
|
|
10
|
+
function isEmptyNonBooleanAttribute(first, second) {
|
|
11
|
+
return isEmptyNonBooleanAttribute$1(first, second, true);
|
|
12
|
+
}
|
|
13
|
+
function isInvalidBooleanAttribute(first, second) {
|
|
14
|
+
return isInvalidBooleanAttribute$1(first, second, true);
|
|
15
|
+
}
|
|
4
16
|
export { booleanAttributes, getAttribute, getAttributes, isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute, setAttribute, setAttributes, setProperties, setProperty };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { sanitizeNodes } from "./sanitize.js";
|
|
2
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
3
|
+
function createHtml(value) {
|
|
4
|
+
const html$1 = getParser().parseFromString(typeof value === "string" ? value : value.innerHTML, HTML_PARSE_TYPE);
|
|
5
|
+
html$1.body.normalize();
|
|
6
|
+
sanitizeNodes([html$1.body], 0);
|
|
7
|
+
return html$1.body.innerHTML;
|
|
8
|
+
}
|
|
9
|
+
function createTemplate(value, options) {
|
|
10
|
+
const template = document.createElement(TEMPLATE_TAG);
|
|
11
|
+
template.innerHTML = createHtml(value);
|
|
12
|
+
if (typeof value === "string" && options.cache) templates[value] = template;
|
|
13
|
+
return template;
|
|
14
|
+
}
|
|
15
|
+
function getNodes(value, options) {
|
|
16
|
+
if (typeof value !== "string" && !(value instanceof HTMLTemplateElement)) return [];
|
|
17
|
+
const template = getTemplate(value, options);
|
|
18
|
+
return template == null ? [] : [...template.content.cloneNode(true).childNodes];
|
|
19
|
+
}
|
|
20
|
+
function getOptions(input) {
|
|
21
|
+
const options = isPlainObject(input) ? input : {};
|
|
22
|
+
options.cache = typeof options.cache === "boolean" ? options.cache : true;
|
|
23
|
+
return options;
|
|
24
|
+
}
|
|
25
|
+
function getParser() {
|
|
26
|
+
parser ??= new DOMParser();
|
|
27
|
+
return parser;
|
|
28
|
+
}
|
|
29
|
+
function getTemplate(value, options) {
|
|
30
|
+
if (value instanceof HTMLTemplateElement) return createTemplate(value, options);
|
|
31
|
+
if (typeof value !== "string" || value.trim().length === 0) return;
|
|
32
|
+
let template = templates[value];
|
|
33
|
+
if (template != null) return template;
|
|
34
|
+
const element = EXPRESSION_ID.test(value) ? document.querySelector(`#${value}`) : null;
|
|
35
|
+
template = element instanceof HTMLTemplateElement ? element : createTemplate(value, options);
|
|
36
|
+
return template;
|
|
37
|
+
}
|
|
38
|
+
var html = ((value, options) => {
|
|
39
|
+
return getNodes(value, getOptions(options));
|
|
40
|
+
});
|
|
41
|
+
html.clear = () => {
|
|
42
|
+
templates = {};
|
|
43
|
+
};
|
|
44
|
+
html.remove = (template) => {
|
|
45
|
+
if (typeof template !== "string" || templates[template] == null) return;
|
|
46
|
+
const keys = Object.keys(templates);
|
|
47
|
+
const { length } = keys;
|
|
48
|
+
const updated = {};
|
|
49
|
+
for (let index = 0; index < length; index += 1) {
|
|
50
|
+
const key = keys[index];
|
|
51
|
+
if (key !== template) updated[key] = templates[key];
|
|
52
|
+
}
|
|
53
|
+
templates = updated;
|
|
54
|
+
};
|
|
55
|
+
function sanitize(value) {
|
|
56
|
+
return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
|
|
57
|
+
}
|
|
58
|
+
var EXPRESSION_ID = /^[a-z][\w-]*$/i;
|
|
59
|
+
var HTML_PARSE_TYPE = "text/html";
|
|
60
|
+
var TEMPLATE_TAG = "template";
|
|
61
|
+
var parser;
|
|
62
|
+
var templates = {};
|
|
63
|
+
export { html, sanitize };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isBadAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute } from "../internal/attribute.js";
|
|
2
|
+
function handleElement(element, depth) {
|
|
3
|
+
if (isClobbered(element)) {
|
|
4
|
+
element.remove();
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
if (depth === 0) {
|
|
8
|
+
const scripts = element.querySelectorAll("script");
|
|
9
|
+
for (const script of scripts) script.remove();
|
|
10
|
+
}
|
|
11
|
+
sanitizeAttributes(element, [...element.attributes]);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
function isClobbered(element) {
|
|
15
|
+
return element instanceof HTMLFormElement && (typeof element.nodeName !== "string" || typeof element.textContent !== "string" || typeof element.removeChild !== "function" || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== "function" || typeof element.setAttribute !== "function" || typeof element.namespaceURI !== "string" || typeof element.insertBefore !== "function" || typeof element.hasChildNodes !== "function");
|
|
16
|
+
}
|
|
17
|
+
function sanitizeAttributes(element, attributes) {
|
|
18
|
+
const { length } = attributes;
|
|
19
|
+
for (let index = 0; index < length; index += 1) {
|
|
20
|
+
const { name, value } = attributes[index];
|
|
21
|
+
if (isBadAttribute(name, value, false) || isEmptyNonBooleanAttribute(name, value, false)) element.removeAttribute(name);
|
|
22
|
+
else if (isInvalidBooleanAttribute(name, value, false)) element.setAttribute(name, "");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function sanitizeNodes(nodes, depth) {
|
|
26
|
+
const actual = nodes.filter((node) => node instanceof Node);
|
|
27
|
+
let { length } = nodes;
|
|
28
|
+
for (let index = 0; index < length; index += 1) {
|
|
29
|
+
const node = actual[index];
|
|
30
|
+
if (node instanceof Element && handleElement(node, depth)) {
|
|
31
|
+
actual.splice(index, 1);
|
|
32
|
+
length -= 1;
|
|
33
|
+
index -= 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (node.hasChildNodes()) sanitizeNodes([...node.childNodes], depth + 1);
|
|
37
|
+
}
|
|
38
|
+
return nodes;
|
|
39
|
+
}
|
|
40
|
+
export { sanitizeAttributes, sanitizeNodes };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { isEventTarget, isHTMLOrSVGElement } from "./internal/is.js";
|
|
2
|
-
import {
|
|
3
|
-
import { booleanAttributes, isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute } from "./internal/attribute.js";
|
|
4
|
-
import { setAttribute, setAttributes, setProperties, setProperty } from "./attribute/set.js";
|
|
5
|
-
import "./attribute/index.js";
|
|
2
|
+
import { isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute } from "./attribute/index.js";
|
|
6
3
|
import { isChildNode, isInDocument } from "./is.js";
|
|
7
4
|
import { getData, setData } from "./data.js";
|
|
8
5
|
import { dispatch, getPosition, off, on } from "./event/index.js";
|
|
9
6
|
import { findAncestor, findRelatives } from "./find/relative.js";
|
|
10
7
|
import { $ as findElement, $$ as findElements, getElementUnderPointer } from "./find/index.js";
|
|
11
8
|
import { getFocusable, getTabbable, isFocusable, isTabbable } from "./focusable.js";
|
|
12
|
-
import { html, sanitize } from "./html.js";
|
|
9
|
+
import { html, sanitize } from "./html/index.js";
|
|
13
10
|
import touch_default from "./touch.js";
|
|
14
11
|
import { getStyle, getStyles, getTextDirection, setStyle, setStyles, toggleStyles } from "./style.js";
|
|
15
|
-
export { findElement as $, findElement, findElements as $$, findElements,
|
|
12
|
+
export { findElement as $, findElement, findElements as $$, findElements, dispatch, findAncestor, findRelatives, getData, getElementUnderPointer, getFocusable, getPosition, getStyle, getStyles, getTabbable, getTextDirection, html, isBadAttribute, isBooleanAttribute, isChildNode, isEmptyNonBooleanAttribute, isEventTarget, isFocusable, isHTMLOrSVGElement, isInDocument, isInvalidBooleanAttribute, isTabbable, off, on, sanitize, setData, setStyle, setStyles, touch_default as supportsTouch, toggleStyles };
|
|
@@ -1,40 +1,59 @@
|
|
|
1
1
|
import { isHTMLOrSVGElement } from "./is.js";
|
|
2
2
|
import { getString } from "@oscarpalmer/atoms/string";
|
|
3
3
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
4
|
+
function badAttributeHandler(name, value) {
|
|
5
|
+
if (name == null || value == null) return true;
|
|
6
|
+
if (EXPRESSION_CLOBBERED_NAME.test(name) && (value in document || value in formElement) || EXPRESSION_EVENT_NAME.test(name)) return true;
|
|
7
|
+
if (EXPRESSION_SKIP_NAME.test(name) || EXPRESSION_URI_VALUE.test(value) || isValidSourceAttribute(name, value)) return false;
|
|
8
|
+
return EXPRESSION_DATA_OR_SCRIPT.test(value);
|
|
9
|
+
}
|
|
10
|
+
function booleanAttributeHandler(name, value) {
|
|
11
|
+
if (name == null || value == null) return true;
|
|
12
|
+
if (!booleanAttributesSet.has(name)) return false;
|
|
13
|
+
const normalized = value.toLowerCase().trim();
|
|
14
|
+
return !(normalized.length === 0 || normalized === name);
|
|
15
|
+
}
|
|
16
|
+
function decodeAttribute(value) {
|
|
17
|
+
textArea ??= document.createElement("textarea");
|
|
18
|
+
textArea.innerHTML = value;
|
|
19
|
+
return decodeURIComponent(textArea.value);
|
|
20
|
+
}
|
|
21
|
+
function handleAttribute(callback, decode, first, second) {
|
|
22
|
+
let name;
|
|
23
|
+
let value;
|
|
24
|
+
if (isAttribute(first)) {
|
|
25
|
+
name = first.name;
|
|
26
|
+
value = String(first.value);
|
|
27
|
+
} else if (typeof first === "string" && typeof second === "string") {
|
|
28
|
+
name = first;
|
|
29
|
+
value = second;
|
|
30
|
+
}
|
|
31
|
+
if (decode && value != null) value = decodeAttribute(value);
|
|
32
|
+
return callback(name, value?.replace(EXPRESSION_WHITESPACE, ""));
|
|
33
|
+
}
|
|
4
34
|
function isAttribute(value) {
|
|
5
35
|
return value instanceof Attr || isPlainObject(value) && typeof value.name === "string" && typeof value.value === "string";
|
|
6
36
|
}
|
|
7
|
-
function isBadAttribute(first, second) {
|
|
8
|
-
return
|
|
37
|
+
function isBadAttribute(first, second, decode) {
|
|
38
|
+
return handleAttribute(badAttributeHandler, decode, first, second);
|
|
9
39
|
}
|
|
10
|
-
function isBooleanAttribute(
|
|
11
|
-
return
|
|
40
|
+
function isBooleanAttribute(first, decode) {
|
|
41
|
+
return handleAttribute((name) => booleanAttributesSet.has(name?.toLowerCase()), decode, first, "");
|
|
12
42
|
}
|
|
13
|
-
function isEmptyNonBooleanAttribute(first, second) {
|
|
14
|
-
return
|
|
43
|
+
function isEmptyNonBooleanAttribute(first, second, decode) {
|
|
44
|
+
return handleAttribute((name, value) => name != null && value != null && !booleanAttributesSet.has(name) && value.trim().length === 0, decode, first, second);
|
|
15
45
|
}
|
|
16
|
-
function isInvalidBooleanAttribute(first, second) {
|
|
17
|
-
return
|
|
18
|
-
if (attribute == null) return true;
|
|
19
|
-
if (!booleanAttributes.includes(attribute.name)) return false;
|
|
20
|
-
const normalized = String(attribute.value).toLowerCase().trim();
|
|
21
|
-
return !(normalized.length === 0 || normalized === attribute.name);
|
|
22
|
-
}, first, second);
|
|
46
|
+
function isInvalidBooleanAttribute(first, second, decode) {
|
|
47
|
+
return handleAttribute(booleanAttributeHandler, decode, first, second);
|
|
23
48
|
}
|
|
24
49
|
function isProperty(value) {
|
|
25
50
|
return isPlainObject(value) && typeof value.name === "string";
|
|
26
51
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
if (isAttribute(first)) attribute = first;
|
|
30
|
-
else if (typeof first === "string" && typeof second === "string") attribute = {
|
|
31
|
-
name: first,
|
|
32
|
-
value: second
|
|
33
|
-
};
|
|
34
|
-
return callback(attribute);
|
|
52
|
+
function isValidSourceAttribute(name, value) {
|
|
53
|
+
return EXPRESSION_SOURCE_NAME.test(name) && EXPRESSION_SOURCE_VALUE.test(value);
|
|
35
54
|
}
|
|
36
55
|
function updateAttribute(element, name, value) {
|
|
37
|
-
const isBoolean =
|
|
56
|
+
const isBoolean = booleanAttributesSet.has(name.toLowerCase());
|
|
38
57
|
if (isBoolean) updateProperty(element, name, value);
|
|
39
58
|
if (isBoolean ? value !== true : value == null) element.removeAttribute(name);
|
|
40
59
|
else element.setAttribute(name, isBoolean ? "" : getString(value));
|
|
@@ -59,9 +78,14 @@ function updateValues(element, values) {
|
|
|
59
78
|
else updateAttribute(element, entry[0], entry[1]);
|
|
60
79
|
}
|
|
61
80
|
}
|
|
62
|
-
var
|
|
63
|
-
var
|
|
64
|
-
var
|
|
81
|
+
var EXPRESSION_CLOBBERED_NAME = /^(id|name)$/i;
|
|
82
|
+
var EXPRESSION_DATA_OR_SCRIPT = /^(?:data|\w+script):/i;
|
|
83
|
+
var EXPRESSION_EVENT_NAME = /^on/i;
|
|
84
|
+
var EXPRESSION_SKIP_NAME = /^(aria-[-\w]+|data-[-\w.\u00B7-\uFFFF]+)$/i;
|
|
85
|
+
var EXPRESSION_SOURCE_NAME = /^src$/i;
|
|
86
|
+
var EXPRESSION_SOURCE_VALUE = /^data:/i;
|
|
87
|
+
var EXPRESSION_URI_VALUE = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
88
|
+
var EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
65
89
|
const booleanAttributes = Object.freeze([
|
|
66
90
|
"async",
|
|
67
91
|
"autofocus",
|
|
@@ -88,4 +112,7 @@ const booleanAttributes = Object.freeze([
|
|
|
88
112
|
"reversed",
|
|
89
113
|
"selected"
|
|
90
114
|
]);
|
|
115
|
+
var booleanAttributesSet = new Set(booleanAttributes);
|
|
116
|
+
var formElement = document.createElement("form");
|
|
117
|
+
var textArea;
|
|
91
118
|
export { booleanAttributes, isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute, isProperty, updateValue, updateValues };
|