@oscarpalmer/toretto 0.29.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.js → html/index.js} +5 -5
- 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 +747 -1007
- package/package.json +12 -12
- package/src/attribute/index.ts +87 -6
- package/src/{html.ts → html/index.ts} +7 -7
- 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} +2 -2
- package/types/{internal → html}/sanitize.d.ts +1 -1
- package/types/index.d.ts +2 -2
- package/types/internal/attribute.d.ts +4 -55
- package/dist/internal/sanitize.js +0 -24
- package/src/internal/sanitize.ts +0 -40
package/package.json
CHANGED
|
@@ -8,17 +8,15 @@
|
|
|
8
8
|
},
|
|
9
9
|
"description": "A collection of badass DOM utilities.",
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@
|
|
12
|
-
"@rollup/plugin-typescript": "^12.3",
|
|
13
|
-
"@types/node": "^24.10",
|
|
11
|
+
"@types/node": "^25",
|
|
14
12
|
"@vitest/coverage-istanbul": "^4",
|
|
15
|
-
"jsdom": "^27.
|
|
16
|
-
"oxfmt": "^0.
|
|
17
|
-
"oxlint": "^1.
|
|
18
|
-
"
|
|
13
|
+
"jsdom": "^27.3",
|
|
14
|
+
"oxfmt": "^0.18",
|
|
15
|
+
"oxlint": "^1.33",
|
|
16
|
+
"rolldown": "1.0.0-beta.55",
|
|
19
17
|
"tslib": "^2.8",
|
|
20
18
|
"typescript": "^5.9",
|
|
21
|
-
"vite": "8.0.0-beta.
|
|
19
|
+
"vite": "8.0.0-beta.2",
|
|
22
20
|
"vitest": "^4"
|
|
23
21
|
},
|
|
24
22
|
"exports": {
|
|
@@ -74,6 +72,8 @@
|
|
|
74
72
|
],
|
|
75
73
|
"keywords": [
|
|
76
74
|
"dom",
|
|
75
|
+
"html",
|
|
76
|
+
"sanitization",
|
|
77
77
|
"utility"
|
|
78
78
|
],
|
|
79
79
|
"license": "MIT",
|
|
@@ -84,14 +84,14 @@
|
|
|
84
84
|
"url": "git+https://github.com/oscarpalmer/toretto.git"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
|
-
"build": "npm run clean && npx vite build && npm run
|
|
87
|
+
"build": "npm run clean && npx vite build && npm run rolldown:build && npx tsc",
|
|
88
88
|
"clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
89
|
+
"rolldown:build": "npx rolldown -c",
|
|
90
|
+
"rolldown:watch": "npx rolldown -c --watch",
|
|
91
91
|
"test": "npx vitest --coverage",
|
|
92
92
|
"watch": "npx vite build --watch"
|
|
93
93
|
},
|
|
94
94
|
"type": "module",
|
|
95
95
|
"types": "types/index.d.ts",
|
|
96
|
-
"version": "0.
|
|
96
|
+
"version": "0.30.0"
|
|
97
97
|
}
|
package/src/attribute/index.ts
CHANGED
|
@@ -1,9 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
isInvalidBooleanAttribute,
|
|
1
|
+
import {
|
|
2
|
+
isBadAttribute as internalIsBadAttribute,
|
|
3
|
+
isBooleanAttribute as _isBooleanAttribute,
|
|
4
|
+
isEmptyNonBooleanAttribute as _isEmptyNonBooleanAttribute,
|
|
5
|
+
isInvalidBooleanAttribute as _isInvalidBooleanAttribute,
|
|
7
6
|
} from '../internal/attribute';
|
|
7
|
+
import type {Attribute} from '../models';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Is the attribute considered bad and potentially harmful?
|
|
11
|
+
* @param attribute Attribute to check
|
|
12
|
+
* @returns `true` if attribute is considered bad
|
|
13
|
+
*/
|
|
14
|
+
export function isBadAttribute(attribute: Attr | Attribute): boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Is the attribute considered bad and potentially harmful?
|
|
18
|
+
* @param name Attribute name
|
|
19
|
+
* @param value Attribute value
|
|
20
|
+
* @returns `true` if attribute is considered bad
|
|
21
|
+
*/
|
|
22
|
+
export function isBadAttribute(name: string, value: string): boolean;
|
|
23
|
+
|
|
24
|
+
export function isBadAttribute(first: unknown, second?: unknown): boolean {
|
|
25
|
+
return internalIsBadAttribute(first, second, true);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Is the attribute a boolean attribute?
|
|
30
|
+
* @param name Attribute to check
|
|
31
|
+
* @returns `true` if attribute is a boolean attribute
|
|
32
|
+
*/
|
|
33
|
+
export function isBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Is the attribute a boolean attribute?
|
|
37
|
+
* @param name Attribute name
|
|
38
|
+
* @returns `true` if attribute is a boolean attribute
|
|
39
|
+
*/
|
|
40
|
+
export function isBooleanAttribute(name: string): boolean;
|
|
41
|
+
|
|
42
|
+
export function isBooleanAttribute(first: unknown): boolean {
|
|
43
|
+
return _isBooleanAttribute(first, true);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Is the attribute empty and not a boolean attribute?
|
|
48
|
+
* @param attribute Attribute to check
|
|
49
|
+
* @returns `true` if attribute is empty and not a boolean attribute
|
|
50
|
+
*/
|
|
51
|
+
export function isEmptyNonBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Is the attribute empty and not a boolean attribute?
|
|
55
|
+
* @param name Attribute name
|
|
56
|
+
* @param value Attribute value
|
|
57
|
+
* @returns `true` if attribute is empty and not a boolean attribute
|
|
58
|
+
*/
|
|
59
|
+
export function isEmptyNonBooleanAttribute(name: string, value: string): boolean;
|
|
60
|
+
|
|
61
|
+
export function isEmptyNonBooleanAttribute(first: unknown, second?: unknown): boolean {
|
|
62
|
+
return _isEmptyNonBooleanAttribute(first, second, true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Is the attribute an invalid boolean attribute?
|
|
67
|
+
*
|
|
68
|
+
* _(I.e., its value is not empty or the same as its name)_
|
|
69
|
+
* @param attribute Attribute to check
|
|
70
|
+
* @returns `true` if attribute is an invalid boolean attribute
|
|
71
|
+
*/
|
|
72
|
+
export function isInvalidBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Is the attribute an invalid boolean attribute?
|
|
76
|
+
*
|
|
77
|
+
* _(I.e., its value is not empty or the same as its name)_
|
|
78
|
+
* @param name Attribute name
|
|
79
|
+
* @param value Attribute value
|
|
80
|
+
* @returns `true` if attribute is an invalid boolean attribute
|
|
81
|
+
*/
|
|
82
|
+
export function isInvalidBooleanAttribute(name: string, value: string): boolean;
|
|
83
|
+
|
|
84
|
+
export function isInvalidBooleanAttribute(first: unknown, second?: unknown): boolean {
|
|
85
|
+
return _isInvalidBooleanAttribute(first, second, true);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {booleanAttributes} from '../internal/attribute';
|
|
8
89
|
export * from './get';
|
|
9
90
|
export * from './set';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
-
import {sanitizeNodes} from './
|
|
2
|
+
import {sanitizeNodes} from './sanitize';
|
|
3
3
|
|
|
4
4
|
//
|
|
5
5
|
|
|
@@ -34,9 +34,9 @@ type Html = {
|
|
|
34
34
|
|
|
35
35
|
type HtmlOptions = {
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Cache template element for the HTML string? _(defaults to `true`)_
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
cache?: boolean;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
type Options = Required<HtmlOptions>;
|
|
@@ -51,7 +51,7 @@ function createHtml(value: string | HTMLTemplateElement): string {
|
|
|
51
51
|
|
|
52
52
|
html.body.normalize();
|
|
53
53
|
|
|
54
|
-
sanitizeNodes([html.body]);
|
|
54
|
+
sanitizeNodes([html.body], 0);
|
|
55
55
|
|
|
56
56
|
return html.body.innerHTML;
|
|
57
57
|
}
|
|
@@ -64,7 +64,7 @@ function createTemplate(
|
|
|
64
64
|
|
|
65
65
|
template.innerHTML = createHtml(value);
|
|
66
66
|
|
|
67
|
-
if (typeof value === 'string' &&
|
|
67
|
+
if (typeof value === 'string' && options.cache) {
|
|
68
68
|
templates[value] = template;
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -84,7 +84,7 @@ function getNodes(value: string | HTMLTemplateElement, options: Options): Node[]
|
|
|
84
84
|
function getOptions(input?: HtmlOptions): Options {
|
|
85
85
|
const options = isPlainObject(input) ? input : {};
|
|
86
86
|
|
|
87
|
-
options.
|
|
87
|
+
options.cache = typeof options.cache === 'boolean' ? options.cache : true;
|
|
88
88
|
|
|
89
89
|
return options as Options;
|
|
90
90
|
}
|
|
@@ -156,7 +156,7 @@ html.remove = (template: string): void => {
|
|
|
156
156
|
* @returns Sanitized nodes
|
|
157
157
|
*/
|
|
158
158
|
export function sanitize(value: Node | Node[]): Node[] {
|
|
159
|
-
return sanitizeNodes(Array.isArray(value) ? value : [value]);
|
|
159
|
+
return sanitizeNodes(Array.isArray(value) ? value : [value], 0);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
//
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBadAttribute,
|
|
3
|
+
isEmptyNonBooleanAttribute,
|
|
4
|
+
isInvalidBooleanAttribute,
|
|
5
|
+
} from '../internal/attribute';
|
|
6
|
+
|
|
7
|
+
function handleElement(element: Element, depth: number): boolean {
|
|
8
|
+
if (isClobbered(element)) {
|
|
9
|
+
element.remove();
|
|
10
|
+
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (depth === 0) {
|
|
15
|
+
const scripts = element.querySelectorAll('script');
|
|
16
|
+
|
|
17
|
+
for (const script of scripts) {
|
|
18
|
+
script.remove();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
sanitizeAttributes(element, [...element.attributes]);
|
|
23
|
+
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Is the element clobbered?
|
|
29
|
+
*
|
|
30
|
+
* Thanks, DOMPurify _(https://github.com/cure53/DOMPurify)_
|
|
31
|
+
*/
|
|
32
|
+
function isClobbered(element: Element): boolean {
|
|
33
|
+
return (
|
|
34
|
+
element instanceof HTMLFormElement &&
|
|
35
|
+
(typeof element.nodeName !== 'string' ||
|
|
36
|
+
typeof element.textContent !== 'string' ||
|
|
37
|
+
typeof element.removeChild !== 'function' ||
|
|
38
|
+
!(element.attributes instanceof NamedNodeMap) ||
|
|
39
|
+
typeof element.removeAttribute !== 'function' ||
|
|
40
|
+
typeof element.setAttribute !== 'function' ||
|
|
41
|
+
typeof element.namespaceURI !== 'string' ||
|
|
42
|
+
typeof element.insertBefore !== 'function' ||
|
|
43
|
+
typeof element.hasChildNodes !== 'function')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function sanitizeAttributes(element: Element, attributes: Attr[]): void {
|
|
48
|
+
const {length} = attributes;
|
|
49
|
+
|
|
50
|
+
for (let index = 0; index < length; index += 1) {
|
|
51
|
+
const {name, value} = attributes[index];
|
|
52
|
+
|
|
53
|
+
if (isBadAttribute(name, value, false) || isEmptyNonBooleanAttribute(name, value, false)) {
|
|
54
|
+
element.removeAttribute(name);
|
|
55
|
+
} else if (isInvalidBooleanAttribute(name, value, false)) {
|
|
56
|
+
element.setAttribute(name, '');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function sanitizeNodes(nodes: Node[], depth: number): Node[] {
|
|
62
|
+
const actual = nodes.filter(node => node instanceof Node);
|
|
63
|
+
let {length} = nodes;
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < length; index += 1) {
|
|
66
|
+
const node = actual[index];
|
|
67
|
+
|
|
68
|
+
if (node instanceof Element && handleElement(node, depth)) {
|
|
69
|
+
actual.splice(index, 1);
|
|
70
|
+
|
|
71
|
+
length -= 1;
|
|
72
|
+
index -= 1;
|
|
73
|
+
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (node.hasChildNodes()) {
|
|
78
|
+
sanitizeNodes([...node.childNodes], depth + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return nodes;
|
|
83
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import supportsTouch from './touch';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export {
|
|
4
|
+
isBadAttribute,
|
|
5
|
+
isBooleanAttribute,
|
|
6
|
+
isEmptyNonBooleanAttribute,
|
|
7
|
+
isInvalidBooleanAttribute,
|
|
8
|
+
} from './attribute';
|
|
4
9
|
export * from './data';
|
|
5
10
|
export * from './event/index';
|
|
6
11
|
export * from './find/index';
|
|
7
12
|
export * from './focusable';
|
|
8
|
-
export * from './html';
|
|
13
|
+
export * from './html/index';
|
|
9
14
|
export * from './is';
|
|
10
15
|
export * from './models';
|
|
11
16
|
export * from './style';
|
|
@@ -4,6 +4,75 @@ import {getString} from '@oscarpalmer/atoms/string';
|
|
|
4
4
|
import type {Attribute, HTMLOrSVGElement, Property} from '../models';
|
|
5
5
|
import {isHTMLOrSVGElement} from './is';
|
|
6
6
|
|
|
7
|
+
function badAttributeHandler(name?: string, value?: string): boolean {
|
|
8
|
+
if (name == null || value == null) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
(EXPRESSION_CLOBBERED_NAME.test(name) && (value in document || value in formElement)) ||
|
|
14
|
+
EXPRESSION_EVENT_NAME.test(name)
|
|
15
|
+
) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
EXPRESSION_SKIP_NAME.test(name) ||
|
|
21
|
+
EXPRESSION_URI_VALUE.test(value) ||
|
|
22
|
+
isValidSourceAttribute(name, value)
|
|
23
|
+
) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return EXPRESSION_DATA_OR_SCRIPT.test(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function booleanAttributeHandler(name?: string, value?: string): boolean {
|
|
31
|
+
if (name == null || value == null) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!booleanAttributesSet.has(name)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const normalized = value.toLowerCase().trim();
|
|
40
|
+
|
|
41
|
+
return !(normalized.length === 0 || normalized === name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function decodeAttribute(value: string): string {
|
|
45
|
+
textArea ??= document.createElement('textarea');
|
|
46
|
+
|
|
47
|
+
textArea.innerHTML = value;
|
|
48
|
+
|
|
49
|
+
return decodeURIComponent(textArea.value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleAttribute(
|
|
53
|
+
callback: (name?: string, value?: string) => boolean,
|
|
54
|
+
decode: boolean,
|
|
55
|
+
first: unknown,
|
|
56
|
+
second?: unknown,
|
|
57
|
+
): boolean {
|
|
58
|
+
let name: string | undefined;
|
|
59
|
+
let value: string | undefined;
|
|
60
|
+
|
|
61
|
+
if (isAttribute(first)) {
|
|
62
|
+
name = first.name;
|
|
63
|
+
value = String(first.value);
|
|
64
|
+
} else if (typeof first === 'string' && typeof second === 'string') {
|
|
65
|
+
name = first;
|
|
66
|
+
value = second;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (decode && value != null) {
|
|
70
|
+
value = decodeAttribute(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return callback(name, value?.replace(EXPRESSION_WHITESPACE, ''));
|
|
74
|
+
}
|
|
75
|
+
|
|
7
76
|
function isAttribute(value: unknown): value is Attr | Attribute {
|
|
8
77
|
return (
|
|
9
78
|
value instanceof Attr ||
|
|
@@ -13,148 +82,51 @@ function isAttribute(value: unknown): value is Attr | Attribute {
|
|
|
13
82
|
);
|
|
14
83
|
}
|
|
15
84
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @param attribute Attribute to check
|
|
19
|
-
* @returns `true` if attribute is considered bad
|
|
20
|
-
*/
|
|
21
|
-
export function isBadAttribute(attribute: Attr | Attribute): boolean;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Is the attribute considered bad and potentially harmful?
|
|
25
|
-
* @param name Attribute name
|
|
26
|
-
* @param value Attribute value
|
|
27
|
-
* @returns `true` if attribute is considered bad
|
|
28
|
-
*/
|
|
29
|
-
export function isBadAttribute(name: string, value: string): boolean;
|
|
30
|
-
|
|
31
|
-
export function isBadAttribute(first: string | Attr | Attribute, second?: string): boolean {
|
|
32
|
-
return isValidAttribute(
|
|
33
|
-
attribute =>
|
|
34
|
-
attribute == null ||
|
|
35
|
-
EXPRESSION_ON_PREFIX.test(attribute.name) ||
|
|
36
|
-
(EXPRESSION_SOURCE_PREFIX.test(attribute.name) &&
|
|
37
|
-
EXPRESSION_VALUE_PREFIX.test(String(attribute.value))),
|
|
38
|
-
first,
|
|
39
|
-
second,
|
|
40
|
-
);
|
|
85
|
+
export function isBadAttribute(first: unknown, second: unknown, decode: boolean): boolean {
|
|
86
|
+
return handleAttribute(badAttributeHandler, decode, first, second);
|
|
41
87
|
}
|
|
42
88
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
export function isBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Is the attribute a boolean attribute?
|
|
52
|
-
* @param name Attribute name
|
|
53
|
-
* @returns `true` if attribute is a boolean attribute
|
|
54
|
-
*/
|
|
55
|
-
export function isBooleanAttribute(name: string): boolean;
|
|
56
|
-
|
|
57
|
-
export function isBooleanAttribute(value: string | Attr | Attribute): boolean {
|
|
58
|
-
return isValidAttribute(
|
|
59
|
-
attribute => attribute != null && booleanAttributes.includes(attribute.name.toLowerCase()),
|
|
60
|
-
value,
|
|
89
|
+
export function isBooleanAttribute(first: unknown, decode: boolean): boolean {
|
|
90
|
+
return handleAttribute(
|
|
91
|
+
name => booleanAttributesSet.has(name?.toLowerCase() as string),
|
|
92
|
+
decode,
|
|
93
|
+
first,
|
|
61
94
|
'',
|
|
62
95
|
);
|
|
63
96
|
}
|
|
64
97
|
|
|
65
|
-
/**
|
|
66
|
-
* Is the attribute empty and not a boolean attribute?
|
|
67
|
-
* @param attribute Attribute to check
|
|
68
|
-
* @returns `true` if attribute is empty and not a boolean attribute
|
|
69
|
-
*/
|
|
70
|
-
export function isEmptyNonBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Is the attribute empty and not a boolean attribute?
|
|
74
|
-
* @param name Attribute name
|
|
75
|
-
* @param value Attribute value
|
|
76
|
-
* @returns `true` if attribute is empty and not a boolean attribute
|
|
77
|
-
*/
|
|
78
|
-
export function isEmptyNonBooleanAttribute(name: string, value: string): boolean;
|
|
79
|
-
|
|
80
98
|
export function isEmptyNonBooleanAttribute(
|
|
81
|
-
first:
|
|
82
|
-
second
|
|
99
|
+
first: unknown,
|
|
100
|
+
second: unknown,
|
|
101
|
+
decode: boolean,
|
|
83
102
|
): boolean {
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
String(attribute.value).trim().length === 0,
|
|
103
|
+
return handleAttribute(
|
|
104
|
+
(name, value) =>
|
|
105
|
+
name != null && value != null && !booleanAttributesSet.has(name) && value.trim().length === 0,
|
|
106
|
+
decode,
|
|
89
107
|
first,
|
|
90
108
|
second,
|
|
91
109
|
);
|
|
92
110
|
}
|
|
93
111
|
|
|
94
|
-
/**
|
|
95
|
-
* Is the attribute an invalid boolean attribute?
|
|
96
|
-
*
|
|
97
|
-
* _(I.e., its value is not empty or the same as its name)_
|
|
98
|
-
* @param attribute Attribute to check
|
|
99
|
-
* @returns `true` if attribute is an invalid boolean attribute
|
|
100
|
-
*/
|
|
101
|
-
export function isInvalidBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Is the attribute an invalid boolean attribute?
|
|
105
|
-
*
|
|
106
|
-
* _(I.e., its value is not empty or the same as its name)_
|
|
107
|
-
* @param name Attribute name
|
|
108
|
-
* @param value Attribute value
|
|
109
|
-
* @returns `true` if attribute is an invalid boolean attribute
|
|
110
|
-
*/
|
|
111
|
-
export function isInvalidBooleanAttribute(name: string, value: string): boolean;
|
|
112
|
-
|
|
113
112
|
export function isInvalidBooleanAttribute(
|
|
114
|
-
first:
|
|
115
|
-
second
|
|
113
|
+
first: unknown,
|
|
114
|
+
second: unknown,
|
|
115
|
+
decode: boolean,
|
|
116
116
|
): boolean {
|
|
117
|
-
return
|
|
118
|
-
attribute => {
|
|
119
|
-
if (attribute == null) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (!booleanAttributes.includes(attribute.name)) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const normalized = String(attribute.value).toLowerCase().trim();
|
|
128
|
-
|
|
129
|
-
return !(normalized.length === 0 || normalized === attribute.name);
|
|
130
|
-
},
|
|
131
|
-
first,
|
|
132
|
-
second,
|
|
133
|
-
);
|
|
117
|
+
return handleAttribute(booleanAttributeHandler, decode, first, second);
|
|
134
118
|
}
|
|
135
119
|
|
|
136
120
|
export function isProperty(value: unknown): value is Property {
|
|
137
121
|
return isPlainObject(value) && typeof (value as PlainObject).name === 'string';
|
|
138
122
|
}
|
|
139
123
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
first: string | Attr | Attribute,
|
|
143
|
-
second?: string,
|
|
144
|
-
): boolean {
|
|
145
|
-
let attribute: Attribute | undefined;
|
|
146
|
-
|
|
147
|
-
if (isAttribute(first)) {
|
|
148
|
-
attribute = first;
|
|
149
|
-
} else if (typeof first === 'string' && typeof second === 'string') {
|
|
150
|
-
attribute = {name: first, value: second};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return callback(attribute);
|
|
124
|
+
function isValidSourceAttribute(name: string, value: string): boolean {
|
|
125
|
+
return EXPRESSION_SOURCE_NAME.test(name) && EXPRESSION_SOURCE_VALUE.test(value);
|
|
154
126
|
}
|
|
155
127
|
|
|
156
128
|
function updateAttribute(element: HTMLOrSVGElement, name: string, value: unknown): void {
|
|
157
|
-
const isBoolean =
|
|
129
|
+
const isBoolean = booleanAttributesSet.has(name.toLowerCase());
|
|
158
130
|
|
|
159
131
|
if (isBoolean) {
|
|
160
132
|
updateProperty(element, name, value);
|
|
@@ -211,11 +183,23 @@ export function updateValues(
|
|
|
211
183
|
|
|
212
184
|
//
|
|
213
185
|
|
|
214
|
-
const
|
|
186
|
+
const EXPRESSION_CLOBBERED_NAME = /^(id|name)$/i;
|
|
187
|
+
|
|
188
|
+
const EXPRESSION_DATA_OR_SCRIPT = /^(?:data|\w+script):/i;
|
|
189
|
+
|
|
190
|
+
const EXPRESSION_EVENT_NAME = /^on/i;
|
|
191
|
+
|
|
192
|
+
const EXPRESSION_SKIP_NAME = /^(aria-[-\w]+|data-[-\w.\u00B7-\uFFFF]+)$/i;
|
|
193
|
+
|
|
194
|
+
const EXPRESSION_SOURCE_NAME = /^src$/i;
|
|
215
195
|
|
|
216
|
-
const
|
|
196
|
+
const EXPRESSION_SOURCE_VALUE = /^data:/i;
|
|
217
197
|
|
|
218
|
-
const
|
|
198
|
+
const EXPRESSION_URI_VALUE =
|
|
199
|
+
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
|
|
200
|
+
|
|
201
|
+
// oxlint-disable-next-line no-control-regex
|
|
202
|
+
const EXPRESSION_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
219
203
|
|
|
220
204
|
/**
|
|
221
205
|
* List of boolean attributes
|
|
@@ -246,3 +230,9 @@ export const booleanAttributes: readonly string[] = Object.freeze([
|
|
|
246
230
|
'reversed',
|
|
247
231
|
'selected',
|
|
248
232
|
]);
|
|
233
|
+
|
|
234
|
+
const booleanAttributesSet = new Set(booleanAttributes);
|
|
235
|
+
|
|
236
|
+
const formElement = document.createElement('form');
|
|
237
|
+
|
|
238
|
+
let textArea: HTMLTextAreaElement;
|
|
@@ -1,3 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Attribute } from '../models';
|
|
2
|
+
/**
|
|
3
|
+
* Is the attribute considered bad and potentially harmful?
|
|
4
|
+
* @param attribute Attribute to check
|
|
5
|
+
* @returns `true` if attribute is considered bad
|
|
6
|
+
*/
|
|
7
|
+
export declare function isBadAttribute(attribute: Attr | Attribute): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Is the attribute considered bad and potentially harmful?
|
|
10
|
+
* @param name Attribute name
|
|
11
|
+
* @param value Attribute value
|
|
12
|
+
* @returns `true` if attribute is considered bad
|
|
13
|
+
*/
|
|
14
|
+
export declare function isBadAttribute(name: string, value: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Is the attribute a boolean attribute?
|
|
17
|
+
* @param name Attribute to check
|
|
18
|
+
* @returns `true` if attribute is a boolean attribute
|
|
19
|
+
*/
|
|
20
|
+
export declare function isBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Is the attribute a boolean attribute?
|
|
23
|
+
* @param name Attribute name
|
|
24
|
+
* @returns `true` if attribute is a boolean attribute
|
|
25
|
+
*/
|
|
26
|
+
export declare function isBooleanAttribute(name: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Is the attribute empty and not a boolean attribute?
|
|
29
|
+
* @param attribute Attribute to check
|
|
30
|
+
* @returns `true` if attribute is empty and not a boolean attribute
|
|
31
|
+
*/
|
|
32
|
+
export declare function isEmptyNonBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Is the attribute empty and not a boolean attribute?
|
|
35
|
+
* @param name Attribute name
|
|
36
|
+
* @param value Attribute value
|
|
37
|
+
* @returns `true` if attribute is empty and not a boolean attribute
|
|
38
|
+
*/
|
|
39
|
+
export declare function isEmptyNonBooleanAttribute(name: string, value: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Is the attribute an invalid boolean attribute?
|
|
42
|
+
*
|
|
43
|
+
* _(I.e., its value is not empty or the same as its name)_
|
|
44
|
+
* @param attribute Attribute to check
|
|
45
|
+
* @returns `true` if attribute is an invalid boolean attribute
|
|
46
|
+
*/
|
|
47
|
+
export declare function isInvalidBooleanAttribute(attribute: Attr | Attribute): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Is the attribute an invalid boolean attribute?
|
|
50
|
+
*
|
|
51
|
+
* _(I.e., its value is not empty or the same as its name)_
|
|
52
|
+
* @param name Attribute name
|
|
53
|
+
* @param value Attribute value
|
|
54
|
+
* @returns `true` if attribute is an invalid boolean attribute
|
|
55
|
+
*/
|
|
56
|
+
export declare function isInvalidBooleanAttribute(name: string, value: string): boolean;
|
|
57
|
+
export { booleanAttributes } from '../internal/attribute';
|
|
2
58
|
export * from './get';
|
|
3
59
|
export * from './set';
|
|
@@ -25,9 +25,9 @@ type Html = {
|
|
|
25
25
|
};
|
|
26
26
|
type HtmlOptions = {
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Cache template element for the HTML string? _(defaults to `true`)_
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
cache?: boolean;
|
|
31
31
|
};
|
|
32
32
|
declare const html: Html;
|
|
33
33
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function sanitizeAttributes(element: Element, attributes: Attr[]): void;
|
|
2
|
-
export declare function sanitizeNodes(nodes: Node[]): Node[];
|
|
2
|
+
export declare function sanitizeNodes(nodes: Node[], depth: number): Node[];
|
package/types/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import supportsTouch from './touch';
|
|
2
|
-
export
|
|
2
|
+
export { isBadAttribute, isBooleanAttribute, isEmptyNonBooleanAttribute, isInvalidBooleanAttribute, } from './attribute';
|
|
3
3
|
export * from './data';
|
|
4
4
|
export * from './event/index';
|
|
5
5
|
export * from './find/index';
|
|
6
6
|
export * from './focusable';
|
|
7
|
-
export * from './html';
|
|
7
|
+
export * from './html/index';
|
|
8
8
|
export * from './is';
|
|
9
9
|
export * from './models';
|
|
10
10
|
export * from './style';
|